diff --git a/.github/workflows/Typos.yml b/.github/workflows/Typos.yml new file mode 100644 index 0000000000000..da5a6a550abe8 --- /dev/null +++ b/.github/workflows/Typos.yml @@ -0,0 +1,70 @@ +name: Typos + +permissions: {} + +on: [pull_request] + +jobs: + typos-check: + name: Check for new typos + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout the JuliaLang/julia repository + uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Check spelling with typos + #uses: crate-ci/typos@master + env: + GH_TOKEN: "${{ github.token }}" + run: | + git fetch --depth=1 origin ${{ github.base_ref }} + OLD_FILES=$(git diff-index --name-only --diff-filter=ad FETCH_HEAD) + NEW_FILES=$(git diff-index --name-only --diff-filter=d FETCH_HEAD) + + # This is necessary because the typos command interprets the + # empty string as "check all files" rather than "check no files". + if [ -z "$NEW_FILES" ]; then + echo "All edited files were deleted. Skipping typos check." + exit 0 + fi + + mkdir -p "${{ runner.temp }}/typos" + RELEASE_ASSET_URL="$( + gh api /repos/crate-ci/typos/releases/latest \ + --jq '."assets"[] | select(."name" | test("^typos-.+-x86_64-unknown-linux-musl\\.tar\\.gz$")) | ."browser_download_url"' + )" + wget --secure-protocol=TLSv1_3 --max-redirect=1 --retry-on-host-error --retry-connrefused --tries=3 \ + --quiet --output-document=- "${RELEASE_ASSET_URL}" \ + | tar -xz -C "${{ runner.temp }}/typos" ./typos + "${{ runner.temp }}/typos/typos" --version + + echo -n $NEW_FILES | xargs "${{ runner.temp }}/typos/typos" --format json >> ${{ runner.temp }}/new_typos.jsonl || true + git checkout FETCH_HEAD -- $OLD_FILES + if [ -z "$OLD_FILES" ]; then + touch "${{ runner.temp }}/old_typos.jsonl" # No old files, so no old typos. + else + echo -n $OLD_FILES | xargs "${{ runner.temp }}/typos/typos" --format json >> ${{ runner.temp }}/old_typos.jsonl || true + fi + + + python -c ' + import sys, json + old = set() + with open(sys.argv[1]) as old_file: + for line in old_file: + j = json.loads(line) + if j["type"] == "typo": + old.add(j["typo"]) + clean = True + with open(sys.argv[2]) as new_file: + for line in new_file: + new = json.loads(line) + if new["type"] == "typo" and new["typo"] not in old: + if len(new["typo"]) > 6: # Short typos might be false positives. Long are probably real. + clean = False + print("::warning file={},line={},col={}::perhaps \"{}\" should be \"{}\".".format( + new["path"], new["line_num"], new["byte_offset"], + new["typo"], " or ".join(new["corrections"]))) + sys.exit(1 if not clean else 0)' "${{ runner.temp }}/old_typos.jsonl" "${{ runner.temp }}/new_typos.jsonl" diff --git a/.github/workflows/cffconvert.yml b/.github/workflows/cffconvert.yml new file mode 100644 index 0000000000000..751476865ae4c --- /dev/null +++ b/.github/workflows/cffconvert.yml @@ -0,0 +1,33 @@ +name: cffconvert + +on: + push: + branches: + - 'master' + - 'release-*' + paths: + - CITATION.cff + pull_request: + branches: + - 'master' + - 'release-*' + paths: + - CITATION.cff + +permissions: + contents: read + +jobs: + validate: + name: "validate" + runs-on: ubuntu-latest + steps: + - name: Check out a copy of the repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Check whether the citation metadata from CITATION.cff is valid + uses: citation-file-format/cffconvert-github-action@2.0.0 + with: + args: "--validate" diff --git a/.gitignore b/.gitignore index f0072fec9c91e..524a12d066c4d 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,9 @@ # Buildkite: Ignore the entire .buildkite directory /.buildkite +# Builtkite: json test data +/test/results.json + # Buildkite: Ignore the unencrypted repo_key repo_key diff --git a/CITATION.cff b/CITATION.cff index c88727bcfa311..878ab94a4d86a 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,3 +1,4 @@ +# Official format description at https://citation-file-format.github.io cff-version: 1.2.0 message: "Cite this paper whenever you use Julia" authors: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0131dcbc4a278..e028cf486c0f2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -347,7 +347,7 @@ please remove the `backport-X.Y` tag from the originating pull request for the c ### Git Recommendations For Pull Requests - Avoid working from the `master` branch of your fork, creating a new branch will make it easier if Julia's `master` changes and you need to update your pull request. - - Try to [squash](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) together small commits that make repeated changes to the same section of code so your pull request is easier to review. A reasonable number of separate well-factored commits is fine, especially for larger changes. + - Try to [squash](https://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) together small commits that make repeated changes to the same section of code so your pull request is easier to review. A reasonable number of separate well-factored commits is fine, especially for larger changes. - If any conflicts arise due to changes in Julia's `master`, prefer updating your pull request branch with `git rebase` versus `git merge` or `git pull`, since the latter will introduce merge commits that clutter the git history with noise that makes your changes more difficult to review. - Descriptive commit messages are good. - Using `git add -p` or `git add -i` can be useful to avoid accidentally committing unrelated changes. diff --git a/HISTORY.md b/HISTORY.md index 6a4373cdbf337..55bbfa1335a0e 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,280 @@ +Julia v1.11 Release Notes +======================== + +New language features +--------------------- +* `public` is a new keyword. Symbols marked with `public` are considered public + API. Symbols marked with `export` are now also treated as public API. The + difference between `public` and `export` is that `public` names do not become + available when `using` a package/module ([#50105]). +* `ScopedValue` implements dynamic scope with inheritance across tasks ([#50958]). +* The new macro `Base.Cartesian.@ncallkw` is analogous to `Base.Cartesian.@ncall`, + but allows to add keyword arguments to the function call ([#51501]). +* Support for Unicode 15.1 ([#51799]). +* Three new types around the idea of text with "annotations" (`Pair{Symbol, Any}` + entries, e.g. `:lang => "en"` or `:face => :magenta`). These annotations + are preserved across operations (e.g. string concatenation with `*`) when + possible. + * `AnnotatedString` is a new `AbstractString` type. It wraps an underlying + string and allows for annotations to be attached to regions of the string. + This type is used extensively in the new `StyledStrings` standard library to + hold styling information. + * `AnnotatedChar` is a new `AbstractChar` type. It wraps another char and + holds a list of annotations that apply to it. + * `AnnotatedIOBuffer` is a new `IO` type that mimics an `IOBuffer`, but has + specialised `read`/`write` methods for annotated content. This can be + thought of both as a "string builder" of sorts and also as glue between + annotated and unannotated content. +* `Manifest.toml` files can now be renamed in the format `Manifest-v{major}.{minor}.toml` + to be preferentially picked up by the given julia version. i.e. in the same folder, + a `Manifest-v1.11.toml` would be used by v1.11 and `Manifest.toml` by every other julia + version. This makes managing environments for multiple julia versions at the same time + easier ([#43845]). +* `@time` now reports a count of any lock conflicts where a `ReentrantLock` had to wait, plus a new macro + `@lock_conflicts` which returns that count ([#52883]). + +Language changes +---------------- +* During precompilation, the `atexit` hooks now run before saving the output file. This + allows users to safely tear down background state (such as closing Timers and sending + disconnect notifications to heartbeat tasks) and cleanup other resources when the program + wants to begin exiting. +* Code coverage and malloc tracking is no longer generated during the package precompilation stage. + Further, during these modes pkgimage caches are now used for packages that are not being tracked. + This means that coverage testing (the default for `julia-actions/julia-runtest`) will by default use + pkgimage caches for all other packages than the package being tested, likely meaning faster test + execution. ([#52123]) + +* Specifying a path in `JULIA_DEPOT_PATH` now results in the expansion of empty strings to + omit the default user depot ([#51448]). + +Compiler/Runtime improvements +----------------------------- +* Updated GC heuristics to count allocated pages instead of individual objects ([#50144]). +* A new `LazyLibrary` type is exported from `Libdl` for use in building chained lazy library + loads, primarily to be used within JLLs ([#50074]). +* Added support for annotating `Base.@assume_effects` on code blocks ([#52400]). +* The libuv library has been updated from a base of v1.44.2 to v1.48.0 ([#49937]). + +Command-line option changes +--------------------------- + +* The entry point for Julia has been standardized to `Main.main(ARGS)`. This must be explicitly opted into using the `@main` macro +(see the docstring for further details). When opted-in, and julia is invoked to run a script or expression +(i.e. using `julia script.jl` or `julia -e expr`), julia will subsequently run the `Main.main` function automatically. +This is intended to unify script and compilation workflows, where code loading may happen +in the compiler and execution of `Main.main` may happen in the resulting executable. For interactive use, there is no semantic +difference between defining a `main` function and executing the code directly at the end of the script ([50974]). +* The `--compiled-modules` and `--pkgimages` flags can now be set to `existing`, which will + cause Julia to consider loading existing cache files, but not to create new ones ([#50586] + and [#52573]). + +Multi-threading changes +----------------------- + +* `Threads.@threads` now supports the `:greedy` scheduler, intended for non-uniform workloads ([#52096]). +* A new exported struct `Lockable{T, L<:AbstractLock}` makes it easy to bundle a resource and its lock together ([#52898]). + +Build system changes +-------------------- + +New library functions +--------------------- + +* `in!(x, s::AbstractSet)` will return whether `x` is in `s`, and insert `x` in `s` if not. +* The new `Libc.mkfifo` function wraps the `mkfifo` C function on Unix platforms ([#34587]). +* `copyuntil(out, io, delim)` and `copyline(out, io)` copy data into an `out::IO` stream ([#48273]). +* `eachrsplit(string, pattern)` iterates split substrings right to left. +* `Sys.username()` can be used to return the current user's username ([#51897]). +* `GC.logging_enabled()` can be used to test whether GC logging has been enabled via `GC.enable_logging` ([#51647]). +* `IdSet` is now exported from Base and considered public ([#53262]). + +New library features +-------------------- + +* `invmod(n, T)` where `T` is a native integer type now computes the modular inverse of `n` in the modular integer ring that `T` defines ([#52180]). +* `invmod(n)` is an abbreviation for `invmod(n, typeof(n))` for native integer types ([#52180]). +* `replace(string, pattern...)` now supports an optional `IO` argument to + write the output to a stream rather than returning a string ([#48625]). +* New methods `allequal(f, itr)` and `allunique(f, itr)` taking a predicate function ([#47679]). +* `sizehint!(s, n)` now supports an optional `shrink` argument to disable shrinking ([#51929]). +* New function `Docs.hasdoc(module, symbol)` tells whether a name has a docstring ([#52139]). +* New function `Docs.undocumented_names(module)` returns a module's undocumented public names ([#52413]). +* Passing an `IOBuffer` as a stdout argument for `Process` spawn now works as + expected, synchronized with `wait` or `success`, so a `Base.BufferStream` is + no longer required there for correctness to avoid data races ([#52461]). +* After a process exits, `closewrite` will no longer be automatically called on + the stream passed to it. Call `wait` on the process instead to ensure the + content is fully written, then call `closewrite` manually to avoid + data-races. Or use the callback form of `open` to have all that handled + automatically. +* `@timed` now additionally returns the elapsed compilation and recompilation time ([#52889]) +* `filter` can now act on a `NamedTuple` ([#50795]). +* `Iterators.cycle(iter, n)` runs over `iter` a fixed number of times, instead of forever ([#47354]) +* `zero(::AbstractArray)` now applies recursively, so `zero([[1,2],[3,4,5]])` now produces the additive identity `[[0,0],[0,0,0]]` rather than erroring ([#38064]). + +Standard library changes +------------------------ + +#### StyledStrings + +* A new standard library for handling styling in a more comprehensive and structured way ([#49586]). +* The new `Faces` struct serves as a container for text styling information + (think typeface, as well as color and decoration), and comes with a framework + to provide a convenient, extensible (via `addface!`), and customisable (with a + user's `Faces.toml` and `loadfaces!`) approach to + styled content ([#49586]). +* The new `@styled_str` string macro provides a convenient way of creating a + `AnnotatedString` with various faces or other attributes applied ([#49586]). + +#### JuliaSyntaxHighlighting + +* A new standard library for applying syntax highlighting to Julia code, this + uses `JuliaSyntax` and `StyledStrings` to implement a `highlight` function + that creates an `AnnotatedString` with syntax highlighting applied. + +#### Package Manager + +#### LinearAlgebra +* `cbrt(::AbstractMatrix{<:Real})` is now defined and returns real-valued matrix cube roots of real-valued matrices ([#50661]). +* `eigvals/eigen(A, bunchkaufman(B))` and `eigvals/eigen(A, lu(B))`, which utilize the Bunchkaufman (LDL) and LU decomposition of `B`, + respectively, now efficiently compute the generalized eigenvalues (`eigen`: and eigenvectors) of `A` and `B`. Note: The second + argument is the output of `bunchkaufman` or `lu` ([#50471]). +* There is now a specialized dispatch for `eigvals/eigen(::Hermitian{<:Tridiagonal})` which performs a similarity transformation to create a real symmetrix triagonal matrix, and solve that using the LAPACK routines ([#49546]). +* Structured matrices now retain either the axes of the parent (for `Symmetric`/`Hermitian`/`AbstractTriangular`/`UpperHessenberg`), or that of the principal diagonal (for banded matrices) ([#52480]). +* `bunchkaufman` and `bunchkaufman!` now work for any `AbstractFloat`, `Rational` and their complex variants. `bunchkaufman` now supports `Integer` types, by making an internal conversion to `Rational{BigInt}`. Added new function `inertia` that computes the inertia of the diagonal factor given by the `BunchKaufman` factorization object of a real symmetric or Hermitian matrix. For complex symmetric matrices, `inertia` only computes the number of zero eigenvalues of the diagonal factor ([#51487]). +* Packages that specialize matrix-matrix `mul!` with a method signature of the form `mul!(::AbstractMatrix, ::MyMatrix, ::AbstractMatrix, ::Number, ::Number)` no longer encounter method ambiguities when interacting with `LinearAlgebra`. Previously, ambiguities used to arise when multiplying a `MyMatrix` with a structured matrix type provided by LinearAlgebra, such as `AbstractTriangular`, which used to necessitate additional methods to resolve such ambiguities. Similar sources of ambiguities have also been removed for matrix-vector `mul!` operations ([#52837]). +* `lu` and `issuccess(::LU)` now accept an `allowsingular` keyword argument. When set to `true`, a valid factorization with rank-deficient U factor will be treated as success instead of throwing an error. Such factorizations are now shown by printing the factors together with a "rank-deficient" note rather than printing a "Failed Factorization" message ([#52957]). + +#### Logging + +#### Printf + +#### Profile + +#### Random +* `rand` now supports sampling over `Tuple` types ([#35856], [#50251]). +* `rand` now supports sampling over `Pair` types ([#28705]). +* When seeding RNGs provided by `Random`, negative integer seeds can now be used ([#51416]). +* Seedable random number generators from `Random` can now be seeded by a string, e.g. + `seed!(rng, "a random seed")` ([#51527]). + +#### REPL + +* Tab complete hints now show in lighter text while typing in the repl. To disable + set `Base.active_repl.options.hint_tab_completes = false` interactively, or in startup.jl: + ``` + if VERSION >= v"1.11.0-0" + atreplinit() do repl + repl.options.hint_tab_completes = false + end + end + ``` ([#51229]). +* Meta-M with an empty prompt now toggles the contextual module between the previous non-Main + contextual module and Main so that switching back and forth is simple. ([#51616], [#52670]) + +#### SuiteSparse + + +#### SparseArrays + +#### Test + +#### Dates + +The undocumented function `adjust` is no longer exported but is now documented + +#### Statistics + +* Statistics is now an upgradeable standard library ([#46501]). + +#### Distributed + +* `pmap` now defaults to using a `CachingPool` ([#33892]). + +#### Unicode + + +#### DelimitedFiles + + +#### InteractiveUtils + +Deprecated or removed +--------------------- + +* `Base.map`, `Iterators.map`, and `foreach` lost their single-argument methods ([#52631]). + + +External dependencies +--------------------- +* `tput` is no longer called to check terminal capabilities, it has been replaced with a pure-Julia terminfo parser ([#50797]). + +Tooling Improvements +-------------------- + +* CI now performs limited automatic typo detection on all PRs. If you merge a PR with a + failing typo CI check, then the reported typos will be automatically ignored in future CI + runs on PRs that edit those same files ([#51704]). + + +[#28705]: https://github.com/JuliaLang/julia/issues/28705 +[#33892]: https://github.com/JuliaLang/julia/issues/33892 +[#34587]: https://github.com/JuliaLang/julia/issues/34587 +[#35856]: https://github.com/JuliaLang/julia/issues/35856 +[#38064]: https://github.com/JuliaLang/julia/issues/38064 +[#43845]: https://github.com/JuliaLang/julia/issues/43845 +[#46501]: https://github.com/JuliaLang/julia/issues/46501 +[#47354]: https://github.com/JuliaLang/julia/issues/47354 +[#47679]: https://github.com/JuliaLang/julia/issues/47679 +[#48273]: https://github.com/JuliaLang/julia/issues/48273 +[#48625]: https://github.com/JuliaLang/julia/issues/48625 +[#49546]: https://github.com/JuliaLang/julia/issues/49546 +[#49586]: https://github.com/JuliaLang/julia/issues/49586 +[#49937]: https://github.com/JuliaLang/julia/issues/49937 +[#50074]: https://github.com/JuliaLang/julia/issues/50074 +[#50105]: https://github.com/JuliaLang/julia/issues/50105 +[#50144]: https://github.com/JuliaLang/julia/issues/50144 +[#50251]: https://github.com/JuliaLang/julia/issues/50251 +[#50471]: https://github.com/JuliaLang/julia/issues/50471 +[#50586]: https://github.com/JuliaLang/julia/issues/50586 +[#50661]: https://github.com/JuliaLang/julia/issues/50661 +[#50795]: https://github.com/JuliaLang/julia/issues/50795 +[#50797]: https://github.com/JuliaLang/julia/issues/50797 +[#50958]: https://github.com/JuliaLang/julia/issues/50958 +[#51229]: https://github.com/JuliaLang/julia/issues/51229 +[#51416]: https://github.com/JuliaLang/julia/issues/51416 +[#51448]: https://github.com/JuliaLang/julia/issues/51448 +[#51487]: https://github.com/JuliaLang/julia/issues/51487 +[#51501]: https://github.com/JuliaLang/julia/issues/51501 +[#51527]: https://github.com/JuliaLang/julia/issues/51527 +[#51616]: https://github.com/JuliaLang/julia/issues/51616 +[#51647]: https://github.com/JuliaLang/julia/issues/51647 +[#51704]: https://github.com/JuliaLang/julia/issues/51704 +[#51799]: https://github.com/JuliaLang/julia/issues/51799 +[#51897]: https://github.com/JuliaLang/julia/issues/51897 +[#51929]: https://github.com/JuliaLang/julia/issues/51929 +[#52049]: https://github.com/JuliaLang/julia/issues/52049 +[#52096]: https://github.com/JuliaLang/julia/issues/52096 +[#52123]: https://github.com/JuliaLang/julia/issues/52123 +[#52139]: https://github.com/JuliaLang/julia/issues/52139 +[#52180]: https://github.com/JuliaLang/julia/issues/52180 +[#52196]: https://github.com/JuliaLang/julia/issues/52196 +[#52400]: https://github.com/JuliaLang/julia/issues/52400 +[#52413]: https://github.com/JuliaLang/julia/issues/52413 +[#52461]: https://github.com/JuliaLang/julia/issues/52461 +[#52480]: https://github.com/JuliaLang/julia/issues/52480 +[#52573]: https://github.com/JuliaLang/julia/issues/52573 +[#52631]: https://github.com/JuliaLang/julia/issues/52631 +[#52670]: https://github.com/JuliaLang/julia/issues/52670 +[#52837]: https://github.com/JuliaLang/julia/issues/52837 +[#52883]: https://github.com/JuliaLang/julia/issues/52883 +[#52889]: https://github.com/JuliaLang/julia/issues/52889 +[#52898]: https://github.com/JuliaLang/julia/issues/52898 +[#52957]: https://github.com/JuliaLang/julia/issues/52957 +[#53262]: https://github.com/JuliaLang/julia/issues/53262 + + Julia v1.10 Release Notes ========================= @@ -98,7 +375,7 @@ Standard library changes ([#46196]). * Adjoints and transposes of `Factorization` objects are no longer wrapped in `Adjoint` and `Transpose` wrappers, respectively. Instead, they are wrapped in - `AdjointFactorization` and `TranposeFactorization` types, which themselves subtype + `AdjointFactorization` and `TransposeFactorization` types, which themselves subtype `Factorization` ([#46874]). * New functions `hermitianpart` and `hermitianpart!` for extracting the Hermitian (real symmetric) part of a matrix ([#31836]). @@ -5291,7 +5568,7 @@ Language tooling improvements talk](https://www.youtube.com/watch?v=e6-hcOHO0tc&list=PLP8iPy9hna6SQPwZUDtAM59-wPzCPyD_S&index=5) on Gallium shows off various features of the debugger. - * The [Juno IDE](http://junolab.org) has matured significantly, and now + * The [Juno IDE](https://junolab.org) has matured significantly, and now also includes support for plotting and debugging. * [Cxx.jl](https://github.com/Keno/Cxx.jl) provides a convenient FFI for @@ -5689,7 +5966,7 @@ Library improvements * Other improvements - * You can now tab-complete emoji via their [short names](http://www.emoji-cheat-sheet.com/), using `\:name:` ([#10709]). + * You can now tab-complete emoji via their [short names](https://www.emoji-cheat-sheet.com/), using `\:name:` ([#10709]). * `gc_enable` subsumes `gc_disable`, and also returns the previous GC state. diff --git a/LICENSE.md b/LICENSE.md index d4125f4fba221..da8b6920491cc 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2009-2023: Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors: https://github.com/JuliaLang/julia/contributors +Copyright (c) 2009-2024: 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 diff --git a/Make.inc b/Make.inc index 0f170734127ed..56558e17bb2f4 100644 --- a/Make.inc +++ b/Make.inc @@ -359,6 +359,10 @@ USE_MLIR := 0 # Options to use RegionVectorizer USE_RV := 0 +# Use `ccache` for speeding up recompilation of the C/C++ part of Julia. +# Requires the `ccache` executable to be in the `PATH` environment variable. +USECCACHE := 0 + # Cross-compile #XC_HOST := i686-w64-mingw32 #XC_HOST := x86_64-w64-mingw32 @@ -497,7 +501,7 @@ JCPPFLAGS_COMMON := -fasynchronous-unwind-tables JCPPFLAGS_CLANG := $(JCPPFLAGS_COMMON) -mllvm -enable-tail-merge=0 JCPPFLAGS_GCC := $(JCPPFLAGS_COMMON) -fno-tree-tail-merge -JCXXFLAGS_COMMON := -pipe $(fPIC) -fno-rtti -std=c++14 +JCXXFLAGS_COMMON := -pipe $(fPIC) -fno-rtti -std=c++17 JCXXFLAGS_CLANG := $(JCXXFLAGS_COMMON) -pedantic JCXXFLAGS_GCC := $(JCXXFLAGS_COMMON) -fno-gnu-unique @@ -567,10 +571,10 @@ CXX_BASE := ccache FC_BASE := ccache ifeq ($(USECLANG),1) # ccache and Clang don't do well together -# http://petereisentraut.blogspot.be/2011/05/ccache-and-clang.html +# https://petereisentraut.blogspot.be/2011/05/ccache-and-clang.html CC += -Qunused-arguments CXX += -Qunused-arguments -# http://petereisentraut.blogspot.be/2011/09/ccache-and-clang-part-2.html +# https://petereisentraut.blogspot.be/2011/09/ccache-and-clang-part-2.html export CCACHE_CPP2 := yes endif else #USECCACHE @@ -595,10 +599,13 @@ CPP_STDOUT := $(CPP) -P # file extensions ifeq ($(OS), WINNT) SHLIB_EXT := dll + PATHSEP := ; else ifeq ($(OS), Darwin) SHLIB_EXT := dylib + PATHSEP := : else SHLIB_EXT := so + PATHSEP := : endif ifeq ($(OS),WINNT) @@ -663,7 +670,7 @@ JL_MAJOR_SHLIB_EXT := $(SHLIB_EXT).$(SOMAJOR) endif endif -ifeq ($(OS), FreeBSD) +ifneq ($(findstring $(OS),FreeBSD OpenBSD),) LOCALBASE ?= /usr/local else LOCALBASE ?= /usr @@ -719,7 +726,7 @@ SANITIZE_LDFLAGS := ifeq ($(SANITIZE_MEMORY),1) SANITIZE_OPTS += -fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer SANITIZE_LDFLAGS += $(SANITIZE_OPTS) -ifneq ($(findstring $(OS),Linux FreeBSD),) +ifneq ($(findstring $(OS),Linux FreeBSD OpenBSD),) SANITIZE_LDFLAGS += -Wl,--warn-unresolved-symbols endif # OS Linux or FreeBSD endif # SANITIZE_MEMORY=1 @@ -752,6 +759,12 @@ JCXXFLAGS += -DGC_VERIFY JCFLAGS += -DGC_VERIFY endif +ifneq ($(JL_STACK_SIZE),) +JCXXFLAGS += -DJL_STACK_SIZE=$(JL_STACK_SIZE) +JCFLAGS += -DJL_STACK_SIZE=$(JL_STACK_SIZE) +endif + + ifeq ($(WITH_GC_DEBUG_ENV), 1) JCXXFLAGS += -DGC_DEBUG_ENV JCFLAGS += -DGC_DEBUG_ENV @@ -1056,7 +1069,7 @@ JCFLAGS+=-DSYSTEM_LIBUNWIND JCPPFLAGS+=-DSYSTEM_LIBUNWIND endif else -ifeq ($(OS),Darwin) +ifneq ($(findstring $(OS),Darwin OpenBSD),) LIBUNWIND:=-lunwind JCPPFLAGS+=-DLLVMLIBUNWIND else @@ -1248,7 +1261,7 @@ LIBGFORTRAN_VERSION := $(subst libgfortran,,$(filter libgfortran%,$(subst -,$(SP # shipped with CSL. Although we do not depend on any of the symbols, it is entirely # possible that a user might choose to install a library which depends on symbols provided # by a newer libstdc++. Without runtime detection, those libraries would break. -CSL_NEXT_GLIBCXX_VERSION=GLIBCXX_3\.4\.31|GLIBCXX_3\.5\.|GLIBCXX_4\. +CSL_NEXT_GLIBCXX_VERSION=GLIBCXX_3\.4\.33|GLIBCXX_3\.5\.|GLIBCXX_4\. # This is the set of projects that BinaryBuilder dependencies are hooked up for. @@ -1367,6 +1380,19 @@ OSLIBS += -Wl,--export-dynamic -Wl,--version-script=$(BUILDROOT)/src/julia.expma $(NO_WHOLE_ARCHIVE) endif +ifeq ($(OS), OpenBSD) +JLDFLAGS += -Wl,--Bdynamic +ifneq ($(SANITIZE),1) +JLDFLAGS += -Wl,-no-undefined +endif + +JLIBLDFLAGS += -Wl,-Bsymbolic-functions + +OSLIBS += -Wl,--no-as-needed -lpthread -lm -lc++abi -lc +OSLIBS += -Wl,--whole-archive -lcompiler_rt -Wl,--no-whole-archive +OSLIBS += -Wl,--export-dynamic,--as-needed,--version-script=$(BUILDROOT)/src/julia.expmap +endif + ifeq ($(OS), Darwin) SHLIB_EXT := dylib OSLIBS += -framework CoreFoundation @@ -1379,7 +1405,7 @@ endif ifeq ($(OS), WINNT) HAVE_SSP := 1 OSLIBS += -Wl,--export-all-symbols -Wl,--version-script=$(BUILDROOT)/src/julia.expmap \ - $(NO_WHOLE_ARCHIVE) -lpsapi -lkernel32 -lws2_32 -liphlpapi -lwinmm -ldbghelp -luserenv -lsecur32 -latomic + $(NO_WHOLE_ARCHIVE) -lpsapi -lkernel32 -lws2_32 -liphlpapi -lwinmm -ldbghelp -luserenv -lsecur32 -latomic -lole32 JLDFLAGS += -Wl,--stack,8388608 ifeq ($(ARCH),i686) JLDFLAGS += -Wl,--large-address-aware @@ -1474,7 +1500,7 @@ endif CLANGSA_FLAGS := CLANGSA_CXXFLAGS := ifeq ($(OS), Darwin) # on new XCode, the files are hidden -CLANGSA_FLAGS += -isysroot $(shell xcode-select -p)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk + CLANGSA_FLAGS += -isysroot $(shell xcrun --show-sdk-path -sdk macosx) endif ifeq ($(USEGCC),1) # try to help clang find the c++ files for CC by guessing the value for --prefix diff --git a/Makefile b/Makefile index d205a4cf8deb8..6fbbe194aa615 100644 --- a/Makefile +++ b/Makefile @@ -61,6 +61,10 @@ $(foreach link,base $(JULIAHOME)/test,$(eval $(call symlink_target,$(link),$$(bu julia_flisp.boot.inc.phony: julia-deps @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT)/src julia_flisp.boot.inc.phony +# Build the HTML docs (skipped if already exists, notably in tarballs) +$(BUILDROOT)/doc/_build/html/en/index.html: $(shell find $(BUILDROOT)/base $(BUILDROOT)/doc \( -path $(BUILDROOT)/doc/_build -o -path $(BUILDROOT)/doc/deps -o -name *_constants.jl -o -name *_h.jl -o -name version_git.jl \) -prune -o -type f -print) + @$(MAKE) docs + julia-symlink: julia-cli-$(JULIA_BUILD_MODE) ifeq ($(OS),WINNT) echo '@"%~dp0/'"$$(echo '$(call rel_path,$(BUILDROOT),$(JULIA_EXECUTABLE))')"'" %*' | tr / '\\' > $(BUILDROOT)/julia.bat @@ -112,7 +116,7 @@ julia-debug julia-release : julia-% : julia-sysimg-% julia-src-% julia-symlink j julia-libccalllazyfoo julia-libccalllazybar julia-libllvmcalltest julia-base-cache stdlibs-cache-release stdlibs-cache-debug : stdlibs-cache-% : julia-% - @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT) -f pkgimage.mk all-$* + @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT) -f pkgimage.mk $* debug release : % : julia-% stdlibs-cache-% @@ -160,7 +164,7 @@ release-candidate: release testall @echo 8. Replace github release tarball with tarballs created from make light-source-dist and make full-source-dist with USE_BINARYBUILDER=0 @echo 9. Check that 'make && make install && make test' succeed with unpacked tarballs even without Internet access. @echo 10. Follow packaging instructions in doc/src/devdocs/build/distributing.md to create binary packages for all platforms - @echo 11. Upload to AWS, update https://julialang.org/downloads and http://status.julialang.org/stable links + @echo 11. Upload to AWS, update https://julialang.org/downloads and https://status.julialang.org/stable links @echo 12. Update checksums on AWS for tarball and packaged binaries @echo 13. Update versions.json. Wait at least 60 minutes before proceeding to step 14. @echo 14. Push to Juliaup (https://github.com/JuliaLang/juliaup/wiki/Adding-a-Julia-version) @@ -185,7 +189,7 @@ $(build_depsbindir)/stringreplace: $(JULIAHOME)/contrib/stringreplace.c | $(buil julia-base-cache: julia-sysimg-$(JULIA_BUILD_MODE) | $(DIRS) $(build_datarootdir)/julia @JULIA_BINDIR=$(call cygpath_w,$(build_bindir)) JULIA_FALLBACK_REPL=1 WINEPATH="$(call cygpath_w,$(build_bindir));$$WINEPATH" \ - $(call spawn, $(JULIA_EXECUTABLE) --startup-file=no $(call cygpath_w,$(JULIAHOME)/etc/write_base_cache.jl) \ + $(call spawn, $(JULIA_EXECUTABLE) --startup-file=no $(call cygpath_w,$(JULIAHOME)/contrib/write_base_cache.jl) \ $(call cygpath_w,$(build_datarootdir)/julia/base.cache)) # public libraries, that are installed in $(prefix)/lib @@ -203,7 +207,7 @@ else ifeq ($(JULIA_BUILD_MODE),debug) JL_PRIVATE_LIBS-0 += libjulia-internal-debug libjulia-codegen-debug endif ifeq ($(USE_GPL_LIBS), 1) -JL_PRIVATE_LIBS-$(USE_SYSTEM_LIBSUITESPARSE) += libamd libbtf libcamd libccolamd libcholmod libcholmod_cuda libcolamd libklu libldl librbio libspqr libspqr_cuda libsuitesparseconfig libumfpack +JL_PRIVATE_LIBS-$(USE_SYSTEM_LIBSUITESPARSE) += libamd libbtf libcamd libccolamd libcholmod libcolamd libklu libldl librbio libspqr libsuitesparseconfig libumfpack endif JL_PRIVATE_LIBS-$(USE_SYSTEM_LIBBLASTRAMPOLINE) += libblastrampoline JL_PRIVATE_LIBS-$(USE_SYSTEM_PCRE) += libpcre2-8 @@ -272,7 +276,7 @@ define stringreplace endef -install: $(build_depsbindir)/stringreplace docs +install: $(build_depsbindir)/stringreplace $(BUILDROOT)/doc/_build/html/en/index.html @$(MAKE) $(QUIET_MAKE) $(JULIA_BUILD_MODE) @for subdir in $(bindir) $(datarootdir)/julia/stdlib/$(VERSDIR) $(docdir) $(man1dir) $(includedir)/julia $(libdir) $(private_libdir) $(sysconfdir) $(private_libexecdir); do \ mkdir -p $(DESTDIR)$$subdir; \ @@ -288,16 +292,14 @@ else ifeq ($(JULIA_BUILD_MODE),debug) -$(INSTALL_M) $(build_libdir)/libjulia-debug.dll.a $(DESTDIR)$(libdir)/ -$(INSTALL_M) $(build_libdir)/libjulia-internal-debug.dll.a $(DESTDIR)$(libdir)/ endif + -$(INSTALL_M) $(wildcard $(build_private_libdir)/*.a) $(DESTDIR)$(private_libdir)/ + -rm -f $(DESTDIR)$(private_libdir)/sys-o.a - # We have a single exception; we want 7z.dll to live in private_libexecdir, not bindir, so that 7z.exe can find it. + # We have a single exception; we want 7z.dll to live in private_libexecdir, + # not bindir, so that 7z.exe can find it. -mv $(DESTDIR)$(bindir)/7z.dll $(DESTDIR)$(private_libexecdir)/ -$(INSTALL_M) $(build_bindir)/libopenlibm.dll.a $(DESTDIR)$(libdir)/ -$(INSTALL_M) $(build_libdir)/libssp.dll.a $(DESTDIR)$(libdir)/ - # The rest are compiler dependencies, as an example memcpy is exported by msvcrt - # These are files from mingw32 and required for creating shared libraries like our caches. - -$(INSTALL_M) $(build_libdir)/libgcc_s.a $(DESTDIR)$(libdir)/ - -$(INSTALL_M) $(build_libdir)/libgcc.a $(DESTDIR)$(libdir)/ - -$(INSTALL_M) $(build_libdir)/libmsvcrt.a $(DESTDIR)$(libdir)/ else # Copy over .dSYM directories directly for Darwin @@ -532,7 +534,7 @@ app: darwinframework: $(MAKE) -C $(JULIAHOME)/contrib/mac/framework -light-source-dist.tmp: docs +light-source-dist.tmp: $(BUILDROOT)/doc/_build/html/en/index.html ifneq ($(BUILDROOT),$(JULIAHOME)) $(error make light-source-dist does not work in out-of-tree builds) endif @@ -590,6 +592,7 @@ clean: | $(CLEAN_TARGETS) @-$(MAKE) -C $(BUILDROOT)/cli clean @-$(MAKE) -C $(BUILDROOT)/test clean @-$(MAKE) -C $(BUILDROOT)/stdlib clean + @-$(MAKE) -C $(BUILDROOT) -f pkgimage.mk clean -rm -f $(BUILDROOT)/julia -rm -f $(BUILDROOT)/*.tar.gz -rm -f $(build_depsbindir)/stringreplace \ @@ -654,7 +657,7 @@ win-extras: ifeq ($(USE_SYSTEM_LLVM), 1) LLVM_SIZE := llvm-size$(EXE) else -LLVM_SIZE := $(build_depsbindir)/llvm-size$(EXE) +LLVM_SIZE := PATH=$(build_bindir):$$PATH; $(build_depsbindir)/llvm-size$(EXE) endif build-stats: ifeq ($(USE_BINARYBUILDER_LLVM),1) diff --git a/NEWS.md b/NEWS.md index c8ed757ef3fd7..72b4629fe4174 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,32 +1,41 @@ -Julia v1.11 Release Notes +Julia v1.12 Release Notes ======================== New language features --------------------- -* `public` is a new keyword. Symbols marked with `public` are considered public - API. Symbols marked with `export` are now also treated as public API. The - difference between `public` and `export` is that `public` names do not become - available when `using` a package/module. ([#50105]) -* `ScopedValue` implement dynamic scope with inheritance across tasks ([#50958]). Language changes ---------------- + - When methods are replaced with exactly equivalent ones, the old method is no + longer deleted implicitly simultaneously, although the new method does take + priority and become more specific than the old method. Thus if the new + method is deleted later, the old method will resume operating. This can be + useful to mocking frameworks (such as in SparseArrays, Pluto, and Mocking, + among others), as they do not need to explicitly restore the old method. + While inference and compilation still must be repeated with this, it also + may pave the way for inference to be able to intelligently re-use the old + results, once the new method is deleted. ([#53415]) + + - Macro expansion will no longer eargerly recurse into into `Expr(:toplevel)` + expressions returned from macros. Instead, macro expansion of `:toplevel` + expressions will be delayed until evaluation time. This allows a later + expression within a given `:toplevel` expression to make use of macros + defined earlier in the same `:toplevel` expression. ([#53515]) + Compiler/Runtime improvements ----------------------------- -* Updated GC heuristics to count allocated pages instead of individual objects ([#50144]). -* A new `LazyLibrary` type is exported from `Libdl` for use in building chained lazy library - loads, primarily to be used within JLLs ([#50074]). + +- Generated LLVM IR now uses actual pointer types instead of passing pointers as integers. + This affects `llvmcall`: Inline LLVM IR should be updated to use `i8*` or `ptr` instead of + `i32` or `i64`, and remove unneeded `ptrtoint`/`inttoptr` conversions. For compatibility, + IR with integer pointers is still supported, but generates a deprecation warning. ([#53687]) Command-line option changes --------------------------- -* The entry point for Julia has been standardized to `Main.main(ARGS)`. This must be explicitly opted into using the `@main` macro -(see the docstring for futher details). When opted-in, and julia is invoked to run a script or expression -(i.e. using `julia script.jl` or `julia -e expr`), julia will subsequently run the `Main.main` function automatically. -This is intended to unify script and compilation workflows, where code loading may happen -in the compiler and execution of `Main.main` may happen in the resulting executable. For interactive use, there is no semantic -difference between defining a `main` function and executing the code directly at the end of the script. ([50974]) +* The `-m/--module` flag can be passed to run the `main` function inside a package with a set of arguments. + This `main` function should be declared using `@main` to indicate that it is an entry point. Multi-threading changes ----------------------- @@ -37,43 +46,57 @@ Build system changes New library functions --------------------- -* The new `Libc.mkfifo` function wraps the `mkfifo` C function on Unix platforms ([#34587]). -* `hardlink(src, dst)` can be used to create hard links. ([#41639]) -* `diskstat(path=pwd())` can be used to return statistics about the disk. ([#42248]) -* `copyuntil(out, io, delim)` and `copyline(out, io)` copy data into an `out::IO` stream ([#48273]). -* `eachrsplit(string, pattern)` iterates split substrings right to left. +* `logrange(start, stop; length)` makes a range of constant ratio, instead of constant step ([#39071]) +* The new `isfull(c::Channel)` function can be used to check if `put!(c, some_value)` will block. ([#53159]) +* `waitany(tasks; throw=false)` and `waitall(tasks; failfast=false, throw=false)` which wait multiple tasks at once ([#53341]). New library features -------------------- + +* `invmod(n, T)` where `T` is a native integer type now computes the modular inverse of `n` in the modular integer ring that `T` defines ([#52180]). +* `invmod(n)` is an abbreviation for `invmod(n, typeof(n))` for native integer types ([#52180]). * `replace(string, pattern...)` now supports an optional `IO` argument to write the output to a stream rather than returning a string ([#48625]). +* `sizehint!(s, n)` now supports an optional `shrink` argument to disable shrinking ([#51929]). +* New function `Docs.hasdoc(module, symbol)` tells whether a name has a docstring ([#52139]). +* New function `Docs.undocumented_names(module)` returns a module's undocumented public names ([#52413]). +* Passing an `IOBuffer` as a stdout argument for `Process` spawn now works as + expected, synchronized with `wait` or `success`, so a `Base.BufferStream` is + no longer required there for correctness to avoid data races ([#52461]). +* After a process exits, `closewrite` will no longer be automatically called on + the stream passed to it. Call `wait` on the process instead to ensure the + content is fully written, then call `closewrite` manually to avoid + data-races. Or use the callback form of `open` to have all that handled + automatically. +* `@timed` now additionally returns the elapsed compilation and recompilation time ([#52889]) +* `filter` can now act on a `NamedTuple` ([#50795]). +* `tempname` can now take a suffix string to allow the file name to include a suffix and include that suffix in + the uniquing checking ([#53474]) +* `RegexMatch` objects can now be used to construct `NamedTuple`s and `Dict`s ([#50988]) Standard library changes ------------------------ +#### StyledStrings + +#### JuliaSyntaxHighlighting + #### Package Manager #### LinearAlgebra +#### Logging + #### Printf #### Profile #### Random -* `rand` now supports sampling over `Tuple` types ([#35856], [#50251]). -* `rand` now supports sampling over `Pair` types ([#28705]). -* When seeding RNGs provided by `Random`, negative integer seeds can now be used ([#51416]). -* Seedable random number generators from `Random` can now be seeded by a string, e.g. - `seed!(rng, "a random seed")` ([#51527]). #### REPL -* Tab complete hints now show in lighter text while typing in the repl. To disable - set `Base.active_repl.options.hint_tab_completes = false` ([#51229]) - #### SuiteSparse - #### SparseArrays #### Test @@ -82,30 +105,21 @@ Standard library changes #### Statistics -* Statistics is now an upgradeable standard library.([#46501]) - #### Distributed -* `pmap` now defaults to using a `CachingPool` ([#33892]). - #### Unicode - #### DelimitedFiles - #### InteractiveUtils Deprecated or removed --------------------- - External dependencies --------------------- -* `tput` is no longer called to check terminal capabilities, it has been replaced with a pure-Julia terminfo parser. Tooling Improvements -------------------- - diff --git a/README.md b/README.md index e904b3b8d6770..9ca71313f5483 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ and installing Julia, below. - **Documentation:** - **Packages:** - **Discussion forum:** +- **Zulip:** - **Slack:** (get an invite from ) - **YouTube:** - **Code coverage:** @@ -76,7 +77,7 @@ interactive prompt into which you can enter expressions for evaluation. You can read about [getting started](https://docs.julialang.org/en/v1/manual/getting-started/) in the manual. -**Note**: Although some system package managers provide Julia, such +**Note**: Although some OS package managers provide Julia, such installations are neither maintained nor endorsed by the Julia project. They may be outdated, broken and/or unmaintained. We recommend you use the official Julia binaries instead. @@ -89,11 +90,11 @@ Then, acquire the source code by cloning the git repository: git clone https://github.com/JuliaLang/julia.git -and then use the command prompt to change into the resulting julia directory. By default you will be building the latest unstable version of +and then use the command prompt to change into the resulting julia directory. By default, you will be building the latest unstable version of Julia. However, most users should use the [most recent stable version](https://github.com/JuliaLang/julia/releases) of Julia. You can get this version by running: - git checkout v1.9.3 + git checkout v1.10.2 To build the `julia` executable, run `make` from within the julia directory. @@ -116,7 +117,7 @@ started](https://docs.julialang.org/en/v1/manual/getting-started/) in the manual. Detailed build instructions, should they be necessary, -are included in the [build documentation](https://github.com/JuliaLang/julia/blob/master/doc/src/devdocs/build/). +are included in the [build documentation](https://github.com/JuliaLang/julia/blob/master/doc/src/devdocs/build/build.md). ### Uninstalling Julia @@ -131,14 +132,14 @@ The Julia source code is organized as follows: | Directory | Contents | | - | - | | `base/` | source code for the Base module (part of Julia's standard library) | -| `stdlib/` | source code for other standard library packages | | `cli/` | source for the command line interface/REPL | | `contrib/` | miscellaneous scripts | | `deps/` | external dependencies | | `doc/src/` | source for the user manual | +| `etc/` | contains `startup.jl` | | `src/` | source for Julia language core | +| `stdlib/` | source code for other standard library packages | | `test/` | test suites | -| `usr/` | binaries and shared libraries loaded by Julia's standard libraries | ## Terminal, Editors and IDEs @@ -146,7 +147,7 @@ The Julia REPL is quite powerful. See the section in the manual on [the Julia REPL](https://docs.julialang.org/en/v1/stdlib/REPL/) for more details. -On Windows we highly recommend running Julia in a modern terminal, +On Windows, we highly recommend running Julia in a modern terminal, such as [Windows Terminal from the Microsoft Store](https://aka.ms/terminal). Support for editing Julia is available for many @@ -157,7 +158,7 @@ Support for editing Julia is available for many others. For users who prefer IDEs, we recommend using VS Code with the -[julia-vscode](https://www.julia-vscode.org/) plugin. +[julia-vscode](https://www.julia-vscode.org/) plugin.\ For notebook users, [Jupyter](https://jupyter.org/) notebook support is available through the [IJulia](https://github.com/JuliaLang/IJulia.jl) package, and the [Pluto.jl](https://github.com/fonsp/Pluto.jl) package provides Pluto notebooks. diff --git a/THIRDPARTY.md b/THIRDPARTY.md index 51950d9e2c6a1..89d1ce3de3d97 100644 --- a/THIRDPARTY.md +++ b/THIRDPARTY.md @@ -1,5 +1,5 @@ The Julia language is licensed under the MIT License (see [LICENSE.md](./LICENSE.md) ). The "language" consists -of the compiler (the contents of src/), most of the standard library (base/), +of the compiler (the contents of `src/`), most of the standard library (`base/` and `stdlib/`), and some utilities (most of the rest of the files in this repository). See below for exceptions. @@ -26,7 +26,8 @@ own licenses: and optionally: -- [ITTAPI](https://github.com/intel/ittapi/blob/master/LICENSES/BSD-3-Clause.txt) [BSD-3] +- [LibTracyClient](https://github.com/wolfpld/tracy/blob/master/LICENSE) [BSD-3] +- [ITTAPI](https://github.com/intel/ittapi/tree/master/LICENSES) [BSD-3 AND GPL2] Julia's `stdlib` uses the following external libraries, which have their own licenses: @@ -47,8 +48,8 @@ Julia's `stdlib` uses the following external libraries, which have their own lic Julia's build process uses the following external tools: -- [PATCHELF](https://nixos.org/patchelf.html) -- [OBJCONV](https://www.agner.org/optimize/#objconv) +- [PATCHELF](https://github.com/NixOS/patchelf/blob/master/COPYING) [GPL3] +- [OBJCONV](https://www.agner.org/optimize/#objconv) [GPL3] - [LIBWHICH](https://github.com/vtjnash/libwhich/blob/master/LICENSE) [MIT] Julia bundles the following external programs and libraries: diff --git a/VERSION b/VERSION index 0bc25cfcab2c1..f6c25a3020b69 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.11.0-DEV +1.12.0-DEV diff --git a/base/Base.jl b/base/Base.jl index b440fda78a90a..1f0dfceb26492 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -6,10 +6,19 @@ using Core.Intrinsics, Core.IR # to start, we're going to use a very simple definition of `include` # that doesn't require any function (except what we can get from the `Core` top-module) -const _included_files = Array{Tuple{Module,String},1}(Core.undef, 1) +# start this big so that we don't have to resize before we have defined how to grow an array +const _included_files = Array{Tuple{Module,String},1}(Core.undef, 400) +setfield!(_included_files, :size, (1,)) function include(mod::Module, path::String) - ccall(:jl_array_grow_end, Cvoid, (Any, UInt), _included_files, UInt(1)) - Core.arrayset(true, _included_files, (mod, ccall(:jl_prepend_cwd, Any, (Any,), path)), arraylen(_included_files)) + len = getfield(_included_files.size, 1) + memlen = _included_files.ref.mem.length + lenp1 = Core.add_int(len, 1) + if len === memlen # by the time this is true we hopefully will have defined _growend! + _growend!(_included_files, UInt(1)) + else + setfield!(_included_files, :size, (lenp1,)) + end + Core.memoryrefset!(Core.memoryref(_included_files.ref, lenp1), (mod, ccall(:jl_prepend_cwd, Any, (Any,), path)), :not_atomic, true) Core.println(path) ccall(:jl_uv_flush, Nothing, (Ptr{Nothing},), Core.io_pointer(Core.stdout)) Core.include(mod, path) @@ -25,12 +34,15 @@ ccall(:jl_set_istopmod, Cvoid, (Any, Bool), Base, is_primary_base_module) macro inline() Expr(:meta, :inline) end macro noinline() Expr(:meta, :noinline) end +macro _boundscheck() Expr(:boundscheck) end + # Try to help prevent users from shooting them-selves in the foot # with ambiguities by defining a few common and critical operations # (and these don't need the extra convert code) getproperty(x::Module, f::Symbol) = (@inline; getglobal(x, f)) getproperty(x::Type, f::Symbol) = (@inline; getfield(x, f)) setproperty!(x::Type, f::Symbol, v) = error("setfield! fields of Types should not be changed") +setproperty!(x::Array, f::Symbol, v) = error("setfield! fields of Array should not be changed") getproperty(x::Tuple, f::Int) = (@inline; getfield(x, f)) setproperty!(x::Tuple, f::Int, v) = setfield!(x, f, v) # to get a decent error @@ -79,6 +91,36 @@ function replaceproperty!(x, f::Symbol, expected, desired, success_order::Symbol val = desired isa ty ? desired : convert(ty, desired) return Core.replacefield!(x, f, expected, val, success_order, fail_order) end +function setpropertyonce!(x, f::Symbol, desired, success_order::Symbol=:not_atomic, fail_order::Symbol=success_order) + @inline + ty = fieldtype(typeof(x), f) + val = desired isa ty ? desired : convert(ty, desired) + return Core.setfieldonce!(x, f, val, success_order, fail_order) +end + +function swapproperty!(x::Module, f::Symbol, v, order::Symbol=:not_atomic) + @inline + ty = Core.get_binding_type(x, f) + val = v isa ty ? v : convert(ty, v) + return Core.swapglobal!(x, f, val, order) +end +function modifyproperty!(x::Module, f::Symbol, op, v, order::Symbol=:not_atomic) + @inline + return Core.modifyglobal!(x, f, op, v, order) +end +function replaceproperty!(x::Module, f::Symbol, expected, desired, success_order::Symbol=:not_atomic, fail_order::Symbol=success_order) + @inline + ty = Core.get_binding_type(x, f) + val = desired isa ty ? desired : convert(ty, desired) + return Core.replaceglobal!(x, f, expected, val, success_order, fail_order) +end +function setpropertyonce!(x::Module, f::Symbol, desired, success_order::Symbol=:not_atomic, fail_order::Symbol=success_order) + @inline + ty = Core.get_binding_type(x, f) + val = desired isa ty ? desired : convert(ty, desired) + return Core.setglobalonce!(x, f, val, success_order, fail_order) +end + convert(::Type{Any}, Core.@nospecialize x) = x convert(::Type{T}, x::T) where {T} = x @@ -95,6 +137,7 @@ if isdefined(Core, :Compiler) end include("exports.jl") +include("public.jl") if false # simple print definitions for debugging. enable these if something @@ -115,6 +158,12 @@ time_ns() = ccall(:jl_hrtime, UInt64, ()) start_base_include = time_ns() +# A warning to be interpolated in the docstring of every dangerous mutating function in Base, see PR #50824 +const _DOCS_ALIASING_WARNING = """ +!!! warning + Behavior can be unexpected when any mutated argument shares memory with any other argument. +""" + ## Load essential files and libraries include("essentials.jl") include("ctypes.jl") @@ -186,23 +235,22 @@ include("strings/lazy.jl") # array structures include("indices.jl") +include("genericmemory.jl") include("array.jl") include("abstractarray.jl") include("subarray.jl") include("views.jl") include("baseext.jl") +include("c.jl") include("ntuple.jl") - include("abstractdict.jl") include("iddict.jl") include("idset.jl") - include("iterators.jl") using .Iterators: zip, enumerate, only using .Iterators: Flatten, Filter, product # for generators using .Iterators: Stateful # compat (was formerly used in reinterpretarray.jl) - include("namedtuple.jl") # For OS specific stuff @@ -218,6 +266,17 @@ function strcat(x::String, y::String) end include(strcat((length(Core.ARGS)>=2 ? Core.ARGS[2] : ""), "build_h.jl")) # include($BUILDROOT/base/build_h.jl) include(strcat((length(Core.ARGS)>=2 ? Core.ARGS[2] : ""), "version_git.jl")) # include($BUILDROOT/base/version_git.jl) +# Initialize DL_LOAD_PATH as early as possible. We are defining things here in +# a slightly more verbose fashion than usual, because we're running so early. +const DL_LOAD_PATH = String[] +let os = ccall(:jl_get_UNAME, Any, ()) + if os === :Darwin || os === :Apple + if Base.DARWIN_FRAMEWORK + push!(DL_LOAD_PATH, "@loader_path/Frameworks") + end + push!(DL_LOAD_PATH, "@loader_path") + end +end # numeric operations include("hashing.jl") @@ -265,24 +324,24 @@ include("set.jl") # Strings include("char.jl") +function array_new_memory(mem::Memory{UInt8}, newlen::Int) + # add an optimization to array_new_memory for StringVector + if (@assume_effects :total @ccall jl_genericmemory_owner(mem::Any,)::Any) isa String + # If data is in a String, keep it that way. + # When implemented, this could use jl_gc_expand_string(oldstr, newlen) as an optimization + str = _string_n(newlen) + return (@assume_effects :total !:consistent @ccall jl_string_to_genericmemory(str::Any,)::Memory{UInt8}) + else + # TODO: when implemented, this should use a memory growing call + return typeof(mem)(undef, newlen) + end +end include("strings/basic.jl") include("strings/string.jl") include("strings/substring.jl") - -# Initialize DL_LOAD_PATH as early as possible. We are defining things here in -# a slightly more verbose fashion than usual, because we're running so early. -const DL_LOAD_PATH = String[] -let os = ccall(:jl_get_UNAME, Any, ()) - if os === :Darwin || os === :Apple - if Base.DARWIN_FRAMEWORK - push!(DL_LOAD_PATH, "@loader_path/Frameworks") - end - push!(DL_LOAD_PATH, "@loader_path") - end -end +include("strings/cstring.jl") include("osutils.jl") -include("c.jl") # Core I/O include("io.jl") @@ -453,6 +512,7 @@ include("summarysize.jl") include("errorshow.jl") include("initdefs.jl") +Filesystem.__postinit__() # worker threads include("threadcall.jl") @@ -483,6 +543,8 @@ if isdefined(Core, :Compiler) && is_primary_base_module Docs.loaddocs(Core.Compiler.CoreDocs.DOCS) end +include("precompilation.jl") + # finally, now make `include` point to the full version for m in methods(include) delete_method(m) @@ -506,74 +568,16 @@ end_base_include = time_ns() const _sysimage_modules = PkgId[] in_sysimage(pkgid::PkgId) = pkgid in _sysimage_modules -# Precompiles for Revise and other packages -# TODO: move these to contrib/generate_precompile.jl -# The problem is they don't work there -for match = _methods(+, (Int, Int), -1, get_world_counter()) - m = match.method - delete!(push!(Set{Method}(), m), m) - copy(Core.Compiler.retrieve_code_info(Core.Compiler.specialize_method(match), typemax(UInt))) - - empty!(Set()) - push!(push!(Set{Union{GlobalRef,Symbol}}(), :two), GlobalRef(Base, :two)) - (setindex!(Dict{String,Base.PkgId}(), Base.PkgId(Base), "file.jl"))["file.jl"] - (setindex!(Dict{Symbol,Vector{Int}}(), [1], :two))[:two] - (setindex!(Dict{Base.PkgId,String}(), "file.jl", Base.PkgId(Base)))[Base.PkgId(Base)] - (setindex!(Dict{Union{GlobalRef,Symbol}, Vector{Int}}(), [1], :two))[:two] - (setindex!(IdDict{Type, Union{Missing, Vector{Tuple{LineNumberNode, Expr}}}}(), missing, Int))[Int] - Dict{Symbol, Union{Nothing, Bool, Symbol}}(:one => false)[:one] - Dict(Base => [:(1+1)])[Base] - Dict(:one => [1])[:one] - Dict("abc" => Set())["abc"] - pushfirst!([], sum) - get(Base.pkgorigins, Base.PkgId(Base), nothing) - sort!([1,2,3]) - unique!([1,2,3]) - cumsum([1,2,3]) - append!(Int[], BitSet()) - isempty(BitSet()) - delete!(BitSet([1,2]), 3) - deleteat!(Int32[1,2,3], [1,3]) - deleteat!(Any[1,2,3], [1,3]) - Core.svec(1, 2) == Core.svec(3, 4) - any(t->t[1].line > 1, [(LineNumberNode(2,:none), :(1+1))]) - - # Code loading uses this - sortperm(mtime.(readdir(".")), rev=true) - # JLLWrappers uses these - Dict{UUID,Set{String}}()[UUID("692b3bcd-3c85-4b1f-b108-f13ce0eb3210")] = Set{String}() - get!(Set{String}, Dict{UUID,Set{String}}(), UUID("692b3bcd-3c85-4b1f-b108-f13ce0eb3210")) - eachindex(IndexLinear(), Expr[]) - push!(Expr[], Expr(:return, false)) - vcat(String[], String[]) - k, v = (:hello => nothing) - precompile(indexed_iterate, (Pair{Symbol, Union{Nothing, String}}, Int)) - precompile(indexed_iterate, (Pair{Symbol, Union{Nothing, String}}, Int, Int)) - # Preferences uses these - precompile(get_preferences, (UUID,)) - precompile(record_compiletime_preference, (UUID, String)) - get(Dict{String,Any}(), "missing", nothing) - delete!(Dict{String,Any}(), "missing") - for (k, v) in Dict{String,Any}() - println(k) - end - - break # only actually need to do this once -end - if is_primary_base_module # Profiling helper # triggers printing the report and (optionally) saving a heap snapshot after a SIGINFO/SIGUSR1 profile request # Needs to be in Base because Profile is no longer loaded on boot -const PROFILE_PRINT_COND = Ref{Base.AsyncCondition}() -function profile_printing_listener() +function profile_printing_listener(cond::Base.AsyncCondition) profile = nothing try - while true - wait(PROFILE_PRINT_COND[]) - profile = @something(profile, require(PkgId(UUID("9abbd945-dff8-562f-b5e8-e1ebf5ef1b79"), "Profile"))) - + while _trywait(cond) + profile = @something(profile, require_stdlib(PkgId(UUID("9abbd945-dff8-562f-b5e8-e1ebf5ef1b79"), "Profile")))::Module invokelatest(profile.peek_report[]) if Base.get_bool_env("JULIA_PROFILE_PEEK_HEAP_SNAPSHOT", false) === true println(stderr, "Saving heap snapshot...") @@ -586,10 +590,13 @@ function profile_printing_listener() @error "Profile printing listener crashed" exception=ex,catch_backtrace() end end + nothing end function __init__() # Base library init + global _atexit_hooks_finished = false + Filesystem.__postinit__() reinit_stdio() Multimedia.reinit_displays() # since Multimedia.displays uses stdout as fallback # initialize loading @@ -597,6 +604,7 @@ function __init__() init_load_path() init_active_project() append!(empty!(_sysimage_modules), keys(loaded_modules)) + empty!(explicit_loaded_modules) if haskey(ENV, "JULIA_MAX_NUM_PRECOMPILE_FILES") MAX_NUM_PRECOMPILE_FILES[] = parse(Int, ENV["JULIA_MAX_NUM_PRECOMPILE_FILES"]) end @@ -605,9 +613,20 @@ function __init__() # triggering a profile via signals is not implemented on windows cond = Base.AsyncCondition() Base.uv_unref(cond.handle) - PROFILE_PRINT_COND[] = cond - ccall(:jl_set_peek_cond, Cvoid, (Ptr{Cvoid},), PROFILE_PRINT_COND[].handle) - errormonitor(Threads.@spawn(profile_printing_listener())) + t = errormonitor(Threads.@spawn(profile_printing_listener(cond))) + atexit() do + # destroy this callback when exiting + ccall(:jl_set_peek_cond, Cvoid, (Ptr{Cvoid},), C_NULL) + # this will prompt any ongoing or pending event to flush also + close(cond) + # error-propagation is not needed, since the errormonitor will handle printing that better + _wait(t) + end + finalizer(cond) do c + # if something goes south, still make sure we aren't keeping a reference in C to this + ccall(:jl_set_peek_cond, Cvoid, (Ptr{Cvoid},), C_NULL) + end + ccall(:jl_set_peek_cond, Cvoid, (Ptr{Cvoid},), cond.handle) end _require_world_age[] = get_world_counter() # Prevent spawned Julia process from getting stuck waiting on Tracy to connect. diff --git a/base/Makefile b/base/Makefile index ad2bb6a63ccc0..9a6c0d0d03833 100644 --- a/base/Makefile +++ b/base/Makefile @@ -203,19 +203,25 @@ endif $(build_bindir)/7z$(EXE): [ -e "$(7Z_PATH)" ] && \ rm -f "$@" && \ - ln -svf "$(7Z_PATH)" "$@" + ln -sf "$(7Z_PATH)" "$@" -symlink_lld: $(build_bindir)/lld$(EXE) +symlink_llvm_utils: $(build_depsbindir)/lld$(EXE) $(build_depsbindir)/dsymutil$(EXE) ifneq ($(USE_SYSTEM_LLD),0) -SYMLINK_SYSTEM_LIBRARIES += symlink_lld +SYMLINK_SYSTEM_LIBRARIES += symlink_llvm_utils LLD_PATH := $(shell which lld$(EXE)) +DSYMUTIL_PATH := $(shell which dsymutil$(EXE)) endif -$(build_bindir)/lld$(EXE): +$(build_depsbindir)/lld$(EXE): [ -e "$(LLD_PATH)" ] && \ rm -f "$@" && \ - ln -svf "$(LLD_PATH)" "$@" + ln -sf "$(LLD_PATH)" "$@" + +$(build_depsbindir)/dsymutil$(EXE): + [ -e "$(DSYMUTIL_PATH)" ] && \ + rm -f "$@" && \ + ln -sf "$(DSYMUTIL_PATH)" "$@" # the following excludes: libuv.a, libutf8proc.a @@ -269,11 +275,9 @@ $(eval $(call symlink_system_library,LIBSUITESPARSE,libamd)) $(eval $(call symlink_system_library,LIBSUITESPARSE,libcamd)) $(eval $(call symlink_system_library,LIBSUITESPARSE,libccolamd)) $(eval $(call symlink_system_library,LIBSUITESPARSE,libcholmod)) -$(eval $(call symlink_system_library,LIBSUITESPARSE,libcholmod_cuda)) $(eval $(call symlink_system_library,LIBSUITESPARSE,libcolamd)) $(eval $(call symlink_system_library,LIBSUITESPARSE,libumfpack)) $(eval $(call symlink_system_library,LIBSUITESPARSE,libspqr)) -$(eval $(call symlink_system_library,LIBSUITESPARSE,libspqr_cuda)) $(eval $(call symlink_system_library,LIBSUITESPARSE,libsuitesparseconfig)) # EXCLUDED LIBRARIES (installed/used, but not vendored for use with dlopen): # libunwind diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 0171c38bc9c0b..1de59d9d2ac3b 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -103,17 +103,20 @@ end has_offset_axes(A, B, ...) Return `true` if the indices of `A` start with something other than 1 along any axis. -If multiple arguments are passed, equivalent to `has_offset_axes(A) | has_offset_axes(B) | ...`. +If multiple arguments are passed, equivalent to `has_offset_axes(A) || has_offset_axes(B) || ...`. See also [`require_one_based_indexing`](@ref). """ +has_offset_axes() = false has_offset_axes(A) = _any_tuple(x->Int(first(x))::Int != 1, false, axes(A)...) has_offset_axes(A::AbstractVector) = Int(firstindex(A))::Int != 1 # improve performance of a common case (ranges) -# Use `_any_tuple` to avoid unneeded invoke. -# note: this could call `any` directly if the compiler can infer it -has_offset_axes(As...) = _any_tuple(has_offset_axes, false, As...) has_offset_axes(::Colon) = false has_offset_axes(::Array) = false +# note: this could call `any` directly if the compiler can infer it. We don't use _any_tuple +# here because it stops full elision in some cases (#49332) and we don't need handling of +# `missing` (has_offset_axes(A) always returns a Bool) +has_offset_axes(A, As...) = has_offset_axes(A) || has_offset_axes(As...) + """ require_one_based_indexing(A::AbstractArray) @@ -268,8 +271,8 @@ julia> ndims(A) 3 ``` """ -ndims(::AbstractArray{T,N}) where {T,N} = N -ndims(::Type{<:AbstractArray{<:Any,N}}) where {N} = N +ndims(::AbstractArray{T,N}) where {T,N} = N::Int +ndims(::Type{<:AbstractArray{<:Any,N}}) where {N} = N::Int ndims(::Type{Union{}}, slurp...) = throw(ArgumentError("Union{} does not have elements")) """ @@ -446,7 +449,7 @@ julia> firstindex(rand(3,4,5), 2) firstindex(a::AbstractArray) = (@inline; first(eachindex(IndexLinear(), a))) firstindex(a, d) = (@inline; first(axes(a, d))) -first(a::AbstractArray) = a[first(eachindex(a))] +@propagate_inbounds first(a::AbstractArray) = a[first(eachindex(a))] """ first(coll) @@ -678,15 +681,12 @@ function checkbounds(::Type{Bool}, A::AbstractArray, I...) checkbounds_indices(Bool, axes(A), I) end -# Linear indexing is explicitly allowed when there is only one (non-cartesian) index +# Linear indexing is explicitly allowed when there is only one (non-cartesian) index; +# indices that do not allow linear indexing (e.g., logical arrays, cartesian indices, etc) +# must add specialized methods to implement their restrictions function checkbounds(::Type{Bool}, A::AbstractArray, i) @inline - checkindex(Bool, eachindex(IndexLinear(), A), i) -end -# As a special extension, allow using logical arrays that match the source array exactly -function checkbounds(::Type{Bool}, A::AbstractArray{<:Any,N}, I::AbstractArray{Bool,N}) where N - @inline - axes(A) == axes(I) + return checkindex(Bool, eachindex(IndexLinear(), A), i) end """ @@ -720,18 +720,13 @@ of `IA`. See also [`checkbounds`](@ref). """ -function checkbounds_indices(::Type{Bool}, IA::Tuple, I::Tuple) +function checkbounds_indices(::Type{Bool}, inds::Tuple, I::Tuple{Any, Vararg}) @inline - checkindex(Bool, IA[1], I[1])::Bool & checkbounds_indices(Bool, tail(IA), tail(I)) + return checkindex(Bool, get(inds, 1, OneTo(1)), I[1])::Bool & + checkbounds_indices(Bool, safe_tail(inds), tail(I)) end -function checkbounds_indices(::Type{Bool}, ::Tuple{}, I::Tuple) - @inline - checkindex(Bool, OneTo(1), I[1])::Bool & checkbounds_indices(Bool, (), tail(I)) -end -checkbounds_indices(::Type{Bool}, IA::Tuple, ::Tuple{}) = (@inline; all(x->length(x)==1, IA)) -checkbounds_indices(::Type{Bool}, ::Tuple{}, ::Tuple{}) = true -throw_boundserror(A, I) = (@noinline; throw(BoundsError(A, I))) +checkbounds_indices(::Type{Bool}, inds::Tuple, ::Tuple{}) = (@inline; all(x->length(x)==1, inds)) # check along a single dimension """ @@ -753,20 +748,19 @@ julia> checkindex(Bool, 1:20, 21) false ``` """ -checkindex(::Type{Bool}, inds::AbstractUnitRange, i) = - throw(ArgumentError("unable to check bounds for indices of type $(typeof(i))")) +checkindex(::Type{Bool}, inds, i) = throw(ArgumentError("unable to check bounds for indices of type $(typeof(i))")) checkindex(::Type{Bool}, inds::AbstractUnitRange, i::Real) = (first(inds) <= i) & (i <= last(inds)) checkindex(::Type{Bool}, inds::IdentityUnitRange, i::Real) = checkindex(Bool, inds.indices, i) checkindex(::Type{Bool}, inds::OneTo{T}, i::T) where {T<:BitInteger} = unsigned(i - one(i)) < unsigned(last(inds)) checkindex(::Type{Bool}, inds::AbstractUnitRange, ::Colon) = true checkindex(::Type{Bool}, inds::AbstractUnitRange, ::Slice) = true -function checkindex(::Type{Bool}, inds::AbstractUnitRange, r::AbstractRange) - @_propagate_inbounds_meta - isempty(r) | (checkindex(Bool, inds, first(r)) & checkindex(Bool, inds, last(r))) -end -checkindex(::Type{Bool}, indx::AbstractUnitRange, I::AbstractVector{Bool}) = indx == axes1(I) -checkindex(::Type{Bool}, indx::AbstractUnitRange, I::AbstractArray{Bool}) = false -function checkindex(::Type{Bool}, inds::AbstractUnitRange, I::AbstractArray) +checkindex(::Type{Bool}, inds::AbstractUnitRange, i::AbstractRange) = + isempty(i) | (checkindex(Bool, inds, first(i)) & checkindex(Bool, inds, last(i))) +# range like indices with cheap `extrema` +checkindex(::Type{Bool}, inds::AbstractUnitRange, i::LinearIndices) = + isempty(i) | (checkindex(Bool, inds, first(i)) & checkindex(Bool, inds, last(i))) + +function checkindex(::Type{Bool}, inds, I::AbstractArray) @inline b = true for i in I @@ -890,7 +884,7 @@ julia> empty([1.0, 2.0, 3.0], String) String[] ``` """ -empty(a::AbstractVector{T}, ::Type{U}=T) where {T,U} = Vector{U}() +empty(a::AbstractVector{T}, ::Type{U}=T) where {T,U} = similar(a, U, 0) # like empty, but should return a mutable collection, a Vector by default emptymutable(a::AbstractVector{T}, ::Type{U}=T) where {T,U} = Vector{U}() @@ -905,6 +899,8 @@ If `dst` and `src` are of the same type, `dst == src` should hold after the call. If `dst` and `src` are multidimensional arrays, they must have equal [`axes`](@ref). +$(_DOCS_ALIASING_WARNING) + See also [`copyto!`](@ref). !!! compat "Julia 1.1" @@ -1034,6 +1030,8 @@ the other elements are left untouched. See also [`copy!`](@ref Base.copy!), [`copy`](@ref). +$(_DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> x = [1., 0., 3., 0., 5.]; @@ -1080,28 +1078,45 @@ function copyto_unaliased!(deststyle::IndexStyle, dest::AbstractArray, srcstyle: if srcstyle isa IndexLinear # Single-index implementation @inbounds for i in srcinds - dest[i + Δi] = src[i] + if isassigned(src, i) + dest[i + Δi] = src[i] + else + _unsetindex!(dest, i + Δi) + end end else # Dual-index implementation i = idf - 1 - @inbounds for a in src - dest[i+=1] = a + @inbounds for a in eachindex(src) + i += 1 + if isassigned(src, a) + dest[i] = src[a] + else + _unsetindex!(dest, i) + end end end else iterdest, itersrc = eachindex(dest), eachindex(src) if iterdest == itersrc # Shared-iterator implementation - for I in iterdest - @inbounds dest[I] = src[I] + @inbounds for I in iterdest + if isassigned(src, I) + dest[I] = src[I] + else + _unsetindex!(dest, I) + end end else # Dual-iterator implementation ret = iterate(iterdest) - @inbounds for a in src + @inbounds for a in itersrc idx, state = ret::NTuple{2,Any} - dest[idx] = a + if isassigned(src, a) + dest[idx] = src[a] + else + _unsetindex!(dest, idx) + end ret = iterate(iterdest, state) end end @@ -1120,8 +1135,8 @@ function copyto!(dest::AbstractArray, dstart::Integer, src::AbstractArray, sstar end function copyto!(dest::AbstractArray, dstart::Integer, - src::AbstractArray, sstart::Integer, - n::Integer) + src::AbstractArray, sstart::Integer, + n::Integer) n == 0 && return dest n < 0 && throw(ArgumentError(LazyString("tried to copy n=", n," elements, but n should be non-negative"))) @@ -1130,7 +1145,11 @@ function copyto!(dest::AbstractArray, dstart::Integer, (checkbounds(Bool, srcinds, sstart) && checkbounds(Bool, srcinds, sstart+n-1)) || throw(BoundsError(src, sstart:sstart+n-1)) src′ = unalias(dest, src) @inbounds for i = 0:n-1 - dest[dstart+i] = src′[sstart+i] + if isassigned(src′, sstart+i) + dest[dstart+i] = src′[sstart+i] + else + _unsetindex!(dest, dstart+i) + end end return dest end @@ -1141,7 +1160,7 @@ function copy(a::AbstractArray) end function copyto!(B::AbstractVecOrMat{R}, ir_dest::AbstractRange{Int}, jr_dest::AbstractRange{Int}, - A::AbstractVecOrMat{S}, ir_src::AbstractRange{Int}, jr_src::AbstractRange{Int}) where {R,S} + A::AbstractVecOrMat{S}, ir_src::AbstractRange{Int}, jr_src::AbstractRange{Int}) where {R,S} if length(ir_dest) != length(ir_src) throw(ArgumentError(LazyString("source and destination must have same size (got ", length(ir_src)," and ",length(ir_dest),")"))) @@ -1198,7 +1217,26 @@ function copymutable(a::AbstractArray) end copymutable(itr) = collect(itr) -zero(x::AbstractArray{T}) where {T} = fill!(similar(x, typeof(zero(T))), zero(T)) +zero(x::AbstractArray{T}) where {T<:Number} = fill!(similar(x, typeof(zero(T))), zero(T)) +zero(x::AbstractArray{S}) where {S<:Union{Missing, Number}} = fill!(similar(x, typeof(zero(S))), zero(S)) +zero(x::AbstractArray) = map(zero, x) + +function _one(unit::T, mat::AbstractMatrix) where {T} + (rows, cols) = axes(mat) + (length(rows) == length(cols)) || + throw(DimensionMismatch("multiplicative identity defined only for square matrices")) + zer = zero(unit)::T + require_one_based_indexing(mat) + I = similar(mat, T) + fill!(I, zer) + for i ∈ rows + I[i, i] = unit + end + I +end + +one(x::AbstractMatrix{T}) where {T} = _one(one(T), x) +oneunit(x::AbstractMatrix{T}) where {T} = _one(oneunit(T), x) ## iteration support for arrays by iterating over `eachindex` in the array ## # Allows fast iteration by default for both IndexLinear and IndexCartesian arrays @@ -1229,10 +1267,10 @@ end # note: the following type definitions don't mean any AbstractArray is convertible to # a data Ref. they just map the array element type to the pointer type for # convenience in cases that work. -pointer(x::AbstractArray{T}) where {T} = unsafe_convert(Ptr{T}, x) +pointer(x::AbstractArray{T}) where {T} = unsafe_convert(Ptr{T}, cconvert(Ptr{T}, x)) function pointer(x::AbstractArray{T}, i::Integer) where T @inline - unsafe_convert(Ptr{T}, x) + Int(_memory_offset(x, i))::Int + pointer(x) + Int(_memory_offset(x, i))::Int end # The distance from pointer(x) to the element at x[I...] in bytes @@ -1254,8 +1292,11 @@ end """ getindex(A, inds...) -Return a subset of array `A` as specified by `inds`, where each `ind` may be, -for example, an `Int`, an [`AbstractRange`](@ref), or a [`Vector`](@ref). +Return a subset of array `A` as selected by the indices `inds`. + +Each index may be any [supported index type](@ref man-supported-index-types), such +as an [`Integer`](@ref), [`CartesianIndex`](@ref), [range](@ref Base.AbstractRange), or [array](@ref man-multi-dim-arrays) of supported indices. +A [:](@ref Base.Colon) may be used to select all elements along a specific dimension, and a boolean array (e.g. an `Array{Bool}` or a [`BitArray`](@ref)) may be used to filter for elements where the corresponding index is `true`. When `inds` selects multiple elements, this function returns a newly allocated array. To index multiple elements without making a copy, @@ -1283,6 +1324,27 @@ julia> getindex(A, 2:4) 3 2 4 + +julia> getindex(A, 2, 1) +3 + +julia> getindex(A, CartesianIndex(2, 1)) +3 + +julia> getindex(A, :, 2) +2-element Vector{Int64}: + 2 + 4 + +julia> getindex(A, 2, :) +2-element Vector{Int64}: + 3 + 4 + +julia> getindex(A, A .> 2) +2-element Vector{Int64}: + 3 + 4 ``` """ function getindex(A::AbstractArray, I...) @@ -1370,6 +1432,8 @@ _unsafe_ind2sub(sz, i) = (@inline; _ind2sub(sz, i)) Store values from array `X` within some subset of `A` as specified by `inds`. The syntax `A[inds...] = X` is equivalent to `(setindex!(A, X, inds...); X)`. +$(_DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> A = zeros(2,2); @@ -1426,6 +1490,21 @@ function _setindex!(::IndexCartesian, A::AbstractArray, v, I::Vararg{Int,M}) whe r end +function _unsetindex!(A::AbstractArray, i::Integer...) + @_propagate_inbounds_meta + _unsetindex!(A, map(to_index, i)...) +end + +function _unsetindex!(A::AbstractArray{T}, i::Int...) where T + # this provides a fallback method which is a no-op if the element is already unassigned + # such that copying into an uninitialized object generally always will work, + # even if the specific custom array type has not implemented `_unsetindex!` + @inline + @boundscheck checkbounds(A, i...) + allocatedinline(T) || @inbounds(!isassigned(A, i...)) || throw(MethodError(_unsetindex!, (A, i...))) + return A +end + """ parent(A) @@ -1507,7 +1586,7 @@ Perform a conservative test to check if arrays `A` and `B` might share the same By default, this simply checks if either of the arrays reference the same memory regions, as identified by their [`Base.dataids`](@ref). """ -mightalias(A::AbstractArray, B::AbstractArray) = !isbits(A) && !isbits(B) && !_isdisjoint(dataids(A), dataids(B)) +mightalias(A::AbstractArray, B::AbstractArray) = !isbits(A) && !isbits(B) && !isempty(A) && !isempty(B) && !_isdisjoint(dataids(A), dataids(B)) mightalias(x, y) = false _isdisjoint(as::Tuple{}, bs::Tuple{}) = true @@ -1531,7 +1610,8 @@ their component parts. A typical definition for an array that wraps a parent is `Base.dataids(C::CustomArray) = dataids(C.parent)`. """ dataids(A::AbstractArray) = (UInt(objectid(A)),) -dataids(A::Array) = (UInt(pointer(A)),) +dataids(A::Memory) = (B = ccall(:jl_genericmemory_owner, Any, (Any,), A); (UInt(pointer(B isa typeof(A) ? B : A)),)) +dataids(A::Array) = dataids(A.ref.mem) dataids(::AbstractRange) = () dataids(x) = () @@ -1587,13 +1667,15 @@ eltypeof(x::AbstractArray) = eltype(x) promote_eltypeof() = error() promote_eltypeof(v1) = eltypeof(v1) -promote_eltypeof(v1, vs...) = promote_type(eltypeof(v1), promote_eltypeof(vs...)) +promote_eltypeof(v1, v2) = promote_type(eltypeof(v1), eltypeof(v2)) +promote_eltypeof(v1, v2, vs...) = (@inline; afoldl(((::Type{T}, y) where {T}) -> promote_type(T, eltypeof(y)), promote_eltypeof(v1, v2), vs...)) promote_eltypeof(v1::T, vs::T...) where {T} = eltypeof(v1) promote_eltypeof(v1::AbstractArray{T}, vs::AbstractArray{T}...) where {T} = T promote_eltype() = error() promote_eltype(v1) = eltype(v1) -promote_eltype(v1, vs...) = promote_type(eltype(v1), promote_eltype(vs...)) +promote_eltype(v1, v2) = promote_type(eltype(v1), eltype(v2)) +promote_eltype(v1, v2, vs...) = (@inline; afoldl(((::Type{T}, y) where {T}) -> promote_type(T, eltype(y)), promote_eltype(v1, v2), vs...)) promote_eltype(v1::T, vs::T...) where {T} = eltype(T) promote_eltype(v1::AbstractArray{T}, vs::AbstractArray{T}...) where {T} = T @@ -1965,24 +2047,91 @@ The keyword also accepts `Val(dims)`. For multiple dimensions `dims = Val(::Tuple)` was added in Julia 1.8. # Examples + +Concatenate two arrays in different dimensions: +```jldoctest +julia> a = [1 2 3] +1×3 Matrix{Int64}: + 1 2 3 + +julia> b = [4 5 6] +1×3 Matrix{Int64}: + 4 5 6 + +julia> cat(a, b; dims=1) +2×3 Matrix{Int64}: + 1 2 3 + 4 5 6 + +julia> cat(a, b; dims=2) +1×6 Matrix{Int64}: + 1 2 3 4 5 6 + +julia> cat(a, b; dims=(1, 2)) +2×6 Matrix{Int64}: + 1 2 3 0 0 0 + 0 0 0 4 5 6 +``` + +# Extended Help + +Concatenate 3D arrays: +```jldoctest +julia> a = ones(2, 2, 3); + +julia> b = ones(2, 2, 4); + +julia> c = cat(a, b; dims=3); + +julia> size(c) == (2, 2, 7) +true +``` + +Concatenate arrays of different sizes: ```jldoctest julia> cat([1 2; 3 4], [pi, pi], fill(10, 2,3,1); dims=2) # same as hcat 2×6×1 Array{Float64, 3}: [:, :, 1] = 1.0 2.0 3.14159 10.0 10.0 10.0 3.0 4.0 3.14159 10.0 10.0 10.0 +``` +Construct a block diagonal matrix: +``` julia> cat(true, trues(2,2), trues(4)', dims=(1,2)) # block-diagonal 4×7 Matrix{Bool}: 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 1 0 0 0 0 0 0 0 1 1 1 1 +``` +``` julia> cat(1, [2], [3;;]; dims=Val(2)) 1×3 Matrix{Int64}: 1 2 3 ``` + +!!! note + `cat` does not join two strings, you may want to use `*`. + +```jldoctest +julia> a = "aaa"; + +julia> b = "bbb"; + +julia> cat(a, b; dims=1) +2-element Vector{String}: + "aaa" + "bbb" + +julia> cat(a, b; dims=2) +1×2 Matrix{String}: + "aaa" "bbb" + +julia> a * b +"aaabbb" +``` """ @inline cat(A...; dims) = _cat(dims, A...) # `@constprop :aggressive` allows `catdims` to be propagated as constant improving return type inference @@ -3087,9 +3236,8 @@ julia> foreach((x, y) -> println(x, " with ", y), tri, 'a':'z') 7 with c ``` """ -foreach(f) = (f(); nothing) foreach(f, itr) = (for x in itr; f(x); end; nothing) -foreach(f, itrs...) = (for z in zip(itrs...); f(z...); end; nothing) +foreach(f, itr, itrs...) = (for z in zip(itr, itrs...); f(z...); end; nothing) ## map over arrays ## @@ -3261,10 +3409,6 @@ end concatenate_setindex!(R, v, I...) = (R[I...] .= (v,); R) concatenate_setindex!(R, X::AbstractArray, I...) = (R[I...] = X) -## 0 arguments - -map(f) = f() - ## 1 argument function map!(f::F, dest::AbstractArray, A::AbstractArray) where F @@ -3344,6 +3488,8 @@ end Like [`map`](@ref), but stores the result in `destination` rather than a new collection. `destination` must be at least as large as the smallest collection. +$(_DOCS_ALIASING_WARNING) + See also: [`map`](@ref), [`foreach`](@ref), [`zip`](@ref), [`copyto!`](@ref). # Examples @@ -3398,7 +3544,7 @@ julia> map(+, [1 2; 3 4], [1,10,100,1000], zeros(3,1)) # iterates until 3rd is 102.0 ``` """ -map(f, iters...) = collect(Generator(f, iters...)) +map(f, it, iters...) = collect(Generator(f, it, iters...)) # multi-item push!, pushfirst! (built on top of type-specific 1-item version) # (note: must not cause a dispatch loop when 1-item case is not defined) @@ -3530,9 +3676,9 @@ end ## 1-d circshift ## function circshift!(a::AbstractVector, shift::Integer) n = length(a) - n == 0 && return + n == 0 && return a shift = mod(shift, n) - shift == 0 && return + shift == 0 && return a l = lastindex(a) reverse!(a, firstindex(a), l-shift) reverse!(a, l-shift+1, lastindex(a)) diff --git a/base/abstractarraymath.jl b/base/abstractarraymath.jl index 70c304d9060c1..a9efc2b87bee4 100644 --- a/base/abstractarraymath.jl +++ b/base/abstractarraymath.jl @@ -119,6 +119,7 @@ julia> A """ conj!(A::AbstractArray{<:Number}) = (@inbounds broadcast!(conj, A, A); A) conj!(x::AbstractArray{<:Real}) = x +conj!(A::AbstractArray) = (foreach(conj!, A); A) """ conj(A::AbstractArray) @@ -264,10 +265,13 @@ circshift(a::AbstractArray, shiftamt::DimsInteger) = circshift!(similar(a), a, s """ circshift(A, shifts) -Circularly shift, i.e. rotate, the data in an array. The second argument is a tuple or +Circularly shift, i.e. rotate, the data in `A`. The second argument is a tuple or vector giving the amount to shift in each dimension, or an integer to shift only in the first dimension. +The generated code is most efficient when the shift amounts are known at compile-time, i.e., +compile-time constants. + See also: [`circshift!`](@ref), [`circcopy!`](@ref), [`bitrotate`](@ref), [`<<`](@ref). # Examples @@ -316,6 +320,18 @@ julia> circshift(a, -1) 0 1 1 + +julia> x = (1, 2, 3, 4, 5) +(1, 2, 3, 4, 5) + +julia> circshift(x, 4) +(2, 3, 4, 5, 1) + +julia> z = (1, 'a', -7.0, 3) +(1, 'a', -7.0, 3) + +julia> circshift(z, -1) +('a', -7.0, 3, 1) ``` """ function circshift(a::AbstractArray, shiftamt) @@ -353,7 +369,7 @@ julia> repeat([1, 2, 3], 2, 3) ``` """ function repeat(A::AbstractArray, counts...) - return _RepeatInnerOuter.repeat(A, outer=counts) + return repeat(A, outer=counts) end """ diff --git a/base/abstractdict.jl b/base/abstractdict.jl index faeab78afaa53..62a5b3ee9e1b0 100644 --- a/base/abstractdict.jl +++ b/base/abstractdict.jl @@ -12,6 +12,8 @@ struct KeyError <: Exception key end +KeyTypeError(K, key) = TypeError(:var"dict key", K, key) + const secret_table_token = :__c782dbf1cf4d6a2e5e3865d7e95634f2e09b5902__ haskey(d::AbstractDict, k) = in(k, keys(d)) @@ -218,8 +220,7 @@ Dict{Int64, Int64} with 3 entries: function merge!(d::AbstractDict, others::AbstractDict...) for other in others if haslength(d) && haslength(other) - # TODO - do not shrink - sizehint!(d, length(d) + length(other)) + sizehint!(d, length(d) + length(other); shrink = false) end for (k,v) in other d[k] = v @@ -300,7 +301,7 @@ julia> keytype(Dict(Int32(1) => "foo")) Int32 ``` """ -keytype(::Type{<:AbstractDict{K,V}}) where {K,V} = K +keytype(::Type{<:AbstractDict{K}}) where {K} = K keytype(a::AbstractDict) = keytype(typeof(a)) """ @@ -314,7 +315,7 @@ julia> valtype(Dict(Int32(1) => "foo")) String ``` """ -valtype(::Type{<:AbstractDict{K,V}}) where {K,V} = V +valtype(::Type{<:AbstractDict{<:Any,V}}) where {V} = V valtype(a::AbstractDict) = valtype(typeof(a)) """ @@ -415,7 +416,7 @@ end Update `d`, removing elements for which `f` is `false`. The function `f` is passed `key=>value` pairs. -# Example +# Examples ```jldoctest julia> d = Dict(1=>"a", 2=>"b", 3=>"c") Dict{Int64, String} with 3 entries: @@ -578,6 +579,55 @@ _tablesz(x::T) where T <: Integer = x < 16 ? T(16) : one(T)<<(top_set_bit(x-one( TP{K,V} = Union{Type{Tuple{K,V}},Type{Pair{K,V}}} +# This error is thrown if `grow_to!` cannot validate the contents of the iterator argument to it, which it does by testing the iteration protocol (isiterable) on it each time it is about to start iteration on it +_throw_dict_kv_error() = throw(ArgumentError("AbstractDict(kv): kv needs to be an iterator of 2-tuples or pairs")) + +function grow_to!(dest::AbstractDict, itr) + applicable(iterate, itr) || _throw_dict_kv_error() + y = iterate(itr) + y === nothing && return dest + kv, st = y + applicable(iterate, kv) || _throw_dict_kv_error() + k = iterate(kv) + k === nothing && _throw_dict_kv_error() + k, kvst = k + v = iterate(kv, kvst) + v === nothing && _throw_dict_kv_error() + v, kvst = v + iterate(kv, kvst) === nothing || _throw_dict_kv_error() + if !(dest isa AbstractDict{typeof(k), typeof(v)}) + dest = empty(dest, typeof(k), typeof(v)) + end + dest[k] = v + return grow_to!(dest, itr, st) +end + +function grow_to!(dest::AbstractDict{K,V}, itr, st) where {K, V} + y = iterate(itr, st) + while y !== nothing + kv, st = y + applicable(iterate, kv) || _throw_dict_kv_error() + kst = iterate(kv) + kst === nothing && _throw_dict_kv_error() + k, kvst = kst + vst = iterate(kv, kvst) + vst === nothing && _throw_dict_kv_error() + v, kvst = vst + iterate(kv, kvst) === nothing || _throw_dict_kv_error() + if isa(k, K) && isa(v, V) + dest[k] = v + else + new = empty(dest, promote_typejoin(K, typeof(k)), promote_typejoin(V, typeof(v))) + merge!(new, dest) + new[k] = v + return grow_to!(new, itr, st) + end + y = iterate(itr, st) + end + return dest +end + + dict_with_eltype(DT_apply, kv, ::TP{K,V}) where {K,V} = DT_apply(K, V)(kv) dict_with_eltype(DT_apply, kv::Generator, ::TP{K,V}) where {K,V} = DT_apply(K, V)(kv) dict_with_eltype(DT_apply, ::Type{Pair{K,V}}) where {K,V} = DT_apply(K, V)() diff --git a/base/abstractset.jl b/base/abstractset.jl index 45801eac1209d..b38cb2799740b 100644 --- a/base/abstractset.jl +++ b/base/abstractset.jl @@ -65,6 +65,8 @@ const ∪ = union Construct the [`union`](@ref) of passed in sets and overwrite `s` with the result. Maintain order with arrays. +$(_DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> a = Set([3, 4, 5]); @@ -99,7 +101,7 @@ max_values(::Type{Bool}) = 2 max_values(::Type{Nothing}) = 1 function union!(s::AbstractSet{T}, itr) where T - haslength(itr) && _sizehint!(s, length(s) + Int(length(itr))::Int; shrink = false) + haslength(itr) && sizehint!(s, length(s) + Int(length(itr))::Int; shrink = false) for x in itr push!(s, x) length(s) == max_values(T) && break @@ -182,6 +184,8 @@ const ∩ = intersect Intersect all passed in sets and overwrite `s` with the result. Maintain order with arrays. + +$(_DOCS_ALIASING_WARNING) """ function intersect!(s::AbstractSet, itrs...) for x in itrs @@ -218,6 +222,8 @@ setdiff(s) = union(s) Remove from set `s` (in-place) each element of each iterable from `itrs`. Maintain order with arrays. +$(_DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> a = Set([1, 3, 4, 5]); @@ -272,6 +278,8 @@ symdiff(s) = symdiff!(copy(s)) Construct the symmetric difference of the passed in sets, and overwrite `s` with the result. When `s` is an array, the order is maintained. Note that in this case the multiplicity of elements matters. + +$(_DOCS_ALIASING_WARNING) """ function symdiff!(s::AbstractSet, itrs...) for x in itrs @@ -356,6 +364,31 @@ hasfastin(x) = hasfastin(typeof(x)) ⊇(a, b) = b ⊆ a +""" + issubset(x) + +Create a function that compares its argument to `x` using [`issubset`](@ref), i.e. +a function equivalent to `y -> issubset(y, x)`. +The returned function is of type `Base.Fix2{typeof(issubset)}`, which can be +used to implement specialized methods. + +!!! compat "Julia 1.11" + This functionality requires at least Julia 1.11. +""" +issubset(a) = Fix2(issubset, a) + +""" + ⊇(x) + +Create a function that compares its argument to `x` using [`⊇`](@ref), i.e. +a function equivalent to `y -> y ⊇ x`. +The returned function is of type `Base.Fix2{typeof(⊇)}`, which can be +used to implement specialized methods. + +!!! compat "Julia 1.11" + This functionality requires at least Julia 1.11. +""" +⊇(a) = Fix2(⊇, a) ## strict subset comparison function ⊊ end @@ -385,6 +418,31 @@ false ⊊(a, b) = Set(a) ⊊ Set(b) ⊋(a, b) = b ⊊ a +""" + ⊋(x) + +Create a function that compares its argument to `x` using [`⊋`](@ref), i.e. +a function equivalent to `y -> y ⊋ x`. +The returned function is of type `Base.Fix2{typeof(⊋)}`, which can be +used to implement specialized methods. + +!!! compat "Julia 1.11" + This functionality requires at least Julia 1.11. +""" +⊋(a) = Fix2(⊋, a) +""" + ⊊(x) + +Create a function that compares its argument to `x` using [`⊊`](@ref), i.e. +a function equivalent to `y -> y ⊊ x`. +The returned function is of type `Base.Fix2{typeof(⊊)}`, which can be +used to implement specialized methods. + +!!! compat "Julia 1.11" + This functionality requires at least Julia 1.11. +""" +⊊(a) = Fix2(⊊, a) + function ⊈ end function ⊉ end """ @@ -409,6 +467,32 @@ false ⊈(a, b) = !⊆(a, b) ⊉(a, b) = b ⊈ a +""" + ⊉(x) + +Create a function that compares its argument to `x` using [`⊉`](@ref), i.e. +a function equivalent to `y -> y ⊉ x`. +The returned function is of type `Base.Fix2{typeof(⊉)}`, which can be +used to implement specialized methods. + +!!! compat "Julia 1.11" + This functionality requires at least Julia 1.11. +""" +⊉(a) = Fix2(⊉, a) + +""" + ⊈(x) + +Create a function that compares its argument to `x` using [`⊈`](@ref), i.e. +a function equivalent to `y -> y ⊈ x`. +The returned function is of type `Base.Fix2{typeof(⊈)}`, which can be +used to implement specialized methods. + +!!! compat "Julia 1.11" + This functionality requires at least Julia 1.11. +""" +⊈(a) = Fix2(⊈, a) + ## set equality comparison """ @@ -445,6 +529,19 @@ function issetequal(a, b) return issetequal(Set(a), Set(b)) end +""" + issetequal(x) + +Create a function that compares its argument to `x` using [`issetequal`](@ref), i.e. +a function equivalent to `y -> issetequal(y, x)`. +The returned function is of type `Base.Fix2{typeof(issetequal)}`, which can be +used to implement specialized methods. + +!!! compat "Julia 1.11" + This functionality requires at least Julia 1.11. +""" +issetequal(a) = Fix2(issetequal, a) + ## set disjoint comparison """ isdisjoint(a, b) -> Bool @@ -491,6 +588,19 @@ function isdisjoint(a::AbstractRange{T}, b::AbstractRange{T}) where T end end +""" + isdisjoint(x) + +Create a function that compares its argument to `x` using [`isdisjoint`](@ref), i.e. +a function equivalent to `y -> isdisjoint(y, x)`. +The returned function is of type `Base.Fix2{typeof(isdisjoint)}`, which can be +used to implement specialized methods. + +!!! compat "Julia 1.11" + This functionality requires at least Julia 1.11. +""" +isdisjoint(a) = Fix2(isdisjoint, a) + _overlapping_range_isdisjoint(a::AbstractRange{T}, b::AbstractRange{T}) where T = invoke(isdisjoint, Tuple{Any,Any}, a, b) function _overlapping_range_isdisjoint(a::AbstractRange{T}, b::AbstractRange{T}) where T<:Integer diff --git a/base/accumulate.jl b/base/accumulate.jl index eeb9759e125c7..2748a4da481fa 100644 --- a/base/accumulate.jl +++ b/base/accumulate.jl @@ -33,7 +33,7 @@ function accumulate_pairwise!(op::Op, result::AbstractVector, v::AbstractVector) end function accumulate_pairwise(op, v::AbstractVector{T}) where T - out = similar(v, promote_op(op, T, T)) + out = similar(v, _accumulate_promote_op(op, v)) return accumulate_pairwise!(op, out, v) end @@ -42,6 +42,8 @@ end cumsum!(B, A; dims::Integer) Cumulative sum of `A` along the dimension `dims`, storing the result in `B`. See also [`cumsum`](@ref). + +$(_DOCS_ALIASING_WARNING) """ cumsum!(B::AbstractArray{T}, A; dims::Integer) where {T} = accumulate!(add_sum, B, A, dims=dims) @@ -109,8 +111,8 @@ julia> cumsum(a, dims=2) widening happens and integer overflow results in `Int8[100, -128]`. """ function cumsum(A::AbstractArray{T}; dims::Integer) where T - out = similar(A, promote_op(add_sum, T, T)) - cumsum!(out, A, dims=dims) + out = similar(A, _accumulate_promote_op(add_sum, A)) + return cumsum!(out, A, dims=dims) end """ @@ -150,6 +152,8 @@ cumsum(itr) = accumulate(add_sum, itr) Cumulative product of `A` along the dimension `dims`, storing the result in `B`. See also [`cumprod`](@ref). + +$(_DOCS_ALIASING_WARNING) """ cumprod!(B::AbstractArray{T}, A; dims::Integer) where {T} = accumulate!(mul_prod, B, A, dims=dims) @@ -159,6 +163,8 @@ cumprod!(B::AbstractArray{T}, A; dims::Integer) where {T} = Cumulative product of a vector `x`, storing the result in `y`. See also [`cumprod`](@ref). + +$(_DOCS_ALIASING_WARNING) """ cumprod!(y::AbstractVector, x::AbstractVector) = cumprod!(y, x, dims=1) @@ -274,14 +280,13 @@ function accumulate(op, A; dims::Union{Nothing,Integer}=nothing, kw...) # This branch takes care of the cases not handled by `_accumulate!`. return collect(Iterators.accumulate(op, A; kw...)) end + nt = values(kw) - if isempty(kw) - out = similar(A, promote_op(op, eltype(A), eltype(A))) - elseif keys(nt) === (:init,) - out = similar(A, promote_op(op, typeof(nt.init), eltype(A))) - else + if !(isempty(kw) || keys(nt) === (:init,)) throw(ArgumentError("accumulate does not support the keyword arguments $(setdiff(keys(nt), (:init,)))")) end + + out = similar(A, _accumulate_promote_op(op, A; kw...)) accumulate!(op, out, A; dims=dims, kw...) end @@ -301,6 +306,8 @@ Cumulative operation `op` on `A` along the dimension `dims`, storing the result Providing `dims` is optional for vectors. If the keyword argument `init` is given, its value is used to instantiate the accumulation. +$(_DOCS_ALIASING_WARNING) + See also [`accumulate`](@ref), [`cumsum!`](@ref), [`cumprod!`](@ref). # Examples @@ -434,3 +441,42 @@ function _accumulate1!(op, B, v1, A::AbstractVector, dim::Integer) end return B end + +# Internal function used to identify the widest possible eltype required for accumulate results +function _accumulate_promote_op(op, v; init=nothing) + # Nested mock functions used to infer the widest necessary eltype + # NOTE: We are just passing this to promote_op for inference and should never be run. + + # Initialization function used to identify initial type of `r` + # NOTE: reduce_first may have a different return type than calling `op` + function f(op, v, init) + val = first(something(iterate(v))) + return isnothing(init) ? Base.reduce_first(op, val) : op(init, val) + end + + # Infer iteration type independent of the initialization type + # If `op` fails then this will return `Union{}` as `k` will be undefined. + # Returning `Union{}` is desirable as it won't break the `promote_type` call in the + # outer scope below + function g(op, v, r) + local k + for val in v + k = op(r, val) + end + return k + end + + # Finally loop again with the two types promoted together + # If the `op` fails and reduce_first was used then then this will still just + # return the initial type, allowing the `op` to error during execution. + function h(op, v, r) + for val in v + r = op(r, val) + end + return r + end + + R = Base.promote_op(f, typeof(op), typeof(v), typeof(init)) + K = Base.promote_op(g, typeof(op), typeof(v), R) + return Base.promote_op(h, typeof(op), typeof(v), Base.promote_type(R, K)) +end diff --git a/base/array.jl b/base/array.jl index b0e9d99f8a46d..49cf0e9d03e7b 100644 --- a/base/array.jl +++ b/base/array.jl @@ -120,39 +120,37 @@ const DenseVecOrMat{T} = Union{DenseVector{T}, DenseMatrix{T}} ## Basic functions ## -using Core: arraysize, arrayset, const_arrayref - """ @_safeindex This internal macro converts: -- `getindex(xs::Tuple, )` -> `__inbounds_getindex(args...)` -- `setindex!(xs::Vector, args...)` -> `__inbounds_setindex!(xs, args...)` +- `getindex(xs::Tuple, i::Int)` -> `__safe_getindex(xs, i)` +- `setindex!(xs::Vector{T}, x, i::Int)` -> `__safe_setindex!(xs, x, i)` to tell the compiler that indexing operations within the applied expression are always inbounds and do not need to taint `:consistent` and `:nothrow`. """ macro _safeindex(ex) - return esc(_safeindex(__module__, ex)) + return esc(_safeindex(ex)) end -function _safeindex(__module__, ex) +function _safeindex(ex) isa(ex, Expr) || return ex if ex.head === :(=) - lhs = arrayref(true, ex.args, 1) + lhs = ex.args[1] if isa(lhs, Expr) && lhs.head === :ref # xs[i] = x - rhs = arrayref(true, ex.args, 2) - xs = arrayref(true, lhs.args, 1) + rhs = ex.args[2] + xs = lhs.args[1] args = Vector{Any}(undef, length(lhs.args)-1) for i = 2:length(lhs.args) - arrayset(true, args, _safeindex(__module__, arrayref(true, lhs.args, i)), i-1) + args[i-1] = _safeindex(lhs.args[i]) end - return Expr(:call, GlobalRef(__module__, :__inbounds_setindex!), xs, _safeindex(__module__, rhs), args...) + return Expr(:call, GlobalRef(@__MODULE__, :__safe_setindex!), xs, _safeindex(rhs), args...) end elseif ex.head === :ref # xs[i] - return Expr(:call, GlobalRef(__module__, :__inbounds_getindex), ex.args...) + return Expr(:call, GlobalRef(@__MODULE__, :__safe_getindex), ex.args...) end args = Vector{Any}(undef, length(ex.args)) for i = 1:length(ex.args) - arrayset(true, args, _safeindex(__module__, arrayref(true, ex.args, i)), i) + args[i] = _safeindex(ex.args[i]) end return Expr(ex.head, args...) end @@ -187,12 +185,15 @@ function vect(X...) return T[X...] end -size(a::Array, d::Integer) = arraysize(a, d isa Int ? d : convert(Int, d)) -size(a::Vector) = (arraysize(a,1),) -size(a::Matrix) = (arraysize(a,1), arraysize(a,2)) -size(a::Array{<:Any,N}) where {N} = (@inline; ntuple(M -> size(a, M), Val(N))::Dims) +size(a::Array, d::Integer) = size(a, Int(d)::Int) +function size(a::Array, d::Int) + d < 1 && error("arraysize: dimension out of range") + sz = getfield(a, :size) + return d > length(sz) ? 1 : getfield(sz, d, false) # @inbounds +end +size(a::Array) = getfield(a, :size) -asize_from(a::Array, n) = n > ndims(a) ? () : (arraysize(a,n), asize_from(a, n+1)...) +asize_from(a::Array, n) = n > ndims(a) ? () : (size(a,n), asize_from(a, n+1)...) allocatedinline(@nospecialize T::Type) = (@_total_meta; ccall(:jl_stored_inline, Cint, (Any,), T) != Cint(0)) @@ -210,49 +211,23 @@ julia> Base.isbitsunion(Union{Float64, String}) false ``` """ -isbitsunion(u::Union) = allocatedinline(u) -isbitsunion(x) = false +isbitsunion(u::Type) = u isa Union && allocatedinline(u) -function _unsetindex!(A::Array{T}, i::Int) where {T} +function _unsetindex!(A::Array, i::Int) @inline @boundscheck checkbounds(A, i) - t = @_gc_preserve_begin A - p = Ptr{Ptr{Cvoid}}(pointer(A, i)) - if !allocatedinline(T) - Intrinsics.atomic_pointerset(p, C_NULL, :monotonic) - elseif T isa DataType - if !datatype_pointerfree(T) - for j = 1:Core.sizeof(Ptr{Cvoid}):Core.sizeof(T) - Intrinsics.atomic_pointerset(p + j - 1, C_NULL, :monotonic) - end - end - end - @_gc_preserve_end t + @inbounds _unsetindex!(GenericMemoryRef(A.ref, i)) return A end - - -""" - Base.bitsunionsize(U::Union) -> Int - -For a `Union` of [`isbitstype`](@ref) types, return the size of the largest type; assumes `Base.isbitsunion(U) == true`. - -# Examples -```jldoctest -julia> Base.bitsunionsize(Union{Float64, UInt8}) -8 - -julia> Base.bitsunionsize(Union{Float64, UInt8, Int128}) -16 -``` -""" -function bitsunionsize(u::Union) - isinline, sz, _ = uniontype_layout(u) - @assert isinline - return sz +function _unsetindex!(A::Array, i::Int...) + @inline + @boundscheck checkbounds(A, i...) + @inbounds _unsetindex!(A, _to_linear_index(A, i...)) + return A end -elsize(@nospecialize _::Type{A}) where {T,A<:Array{T}} = aligned_sizeof(T) +# TODO: deprecate this (aligned_sizeof and/or elsize and/or sizeof(Some{T}) are more correct) +elsize(::Type{A}) where {T,A<:Array{T}} = aligned_sizeof(T) function elsize(::Type{Ptr{T}}) where T # this only must return something valid for values which satisfy is_valid_intrinsic_elptr(T), # which includes Any and most concrete datatypes @@ -261,15 +236,25 @@ function elsize(::Type{Ptr{T}}) where T return LLT_ALIGN(Core.sizeof(T), datatype_alignment(T)) end elsize(::Type{Union{}}, slurp...) = 0 -sizeof(a::Array) = Core.sizeof(a) + +sizeof(a::Array) = length(a) * elsize(typeof(a)) # n.b. this ignores bitsunion bytes, as a historical fact function isassigned(a::Array, i::Int...) @inline + @_noub_if_noinbounds_meta @boundscheck checkbounds(Bool, a, i...) || return false - ii = (_sub2ind(size(a), i...) % UInt) - 1 - ccall(:jl_array_isassigned, Cint, (Any, UInt), a, ii) == 1 + ii = _sub2ind(size(a), i...) + return @inbounds isassigned(memoryref(a.ref, ii, false)) end +function isassigned(a::Vector, i::Int) # slight compiler simplification for the most common case + @inline + @_noub_if_noinbounds_meta + @boundscheck checkbounds(Bool, a, i) || return false + return @inbounds isassigned(memoryref(a.ref, i, false)) +end + + ## copy ## """ @@ -289,92 +274,48 @@ function unsafe_copyto!(dest::Ptr{T}, src::Ptr{T}, n) where T return dest end - -function _unsafe_copyto!(dest, doffs, src, soffs, n) - destp = pointer(dest, doffs) - srcp = pointer(src, soffs) - @inbounds if destp < srcp || destp > srcp + n - for i = 1:n - if isassigned(src, soffs + i - 1) - dest[doffs + i - 1] = src[soffs + i - 1] - else - _unsetindex!(dest, doffs + i - 1) - end - end - else - for i = n:-1:1 - if isassigned(src, soffs + i - 1) - dest[doffs + i - 1] = src[soffs + i - 1] - else - _unsetindex!(dest, doffs + i - 1) - end - end - end - return dest -end - """ - unsafe_copyto!(dest::Array, do, src::Array, so, N) + unsafe_copyto!(dest::Array, doffs, src::Array, soffs, n) -Copy `N` elements from a source array to a destination, starting at the linear index `so` in the -source and `do` in the destination (1-indexed). +Copy `n` elements from a source array to a destination, starting at the linear index `soffs` in the +source and `doffs` in the destination (1-indexed). The `unsafe` prefix on this function indicates that no validation is performed to ensure -that N is inbounds on either array. Incorrect usage may corrupt or segfault your program, in +that n is inbounds on either array. Incorrect usage may corrupt or segfault your program, in the same manner as C. """ -function unsafe_copyto!(dest::Array{T}, doffs, src::Array{T}, soffs, n) where T - t1 = @_gc_preserve_begin dest - t2 = @_gc_preserve_begin src - destp = pointer(dest, doffs) - srcp = pointer(src, soffs) - if !allocatedinline(T) - ccall(:jl_array_ptr_copy, Cvoid, (Any, Ptr{Cvoid}, Any, Ptr{Cvoid}, Int), - dest, destp, src, srcp, n) - elseif isbitstype(T) - memmove(destp, srcp, n * aligned_sizeof(T)) - elseif isbitsunion(T) - memmove(destp, srcp, n * aligned_sizeof(T)) - # copy selector bytes - memmove( - ccall(:jl_array_typetagdata, Ptr{UInt8}, (Any,), dest) + doffs - 1, - ccall(:jl_array_typetagdata, Ptr{UInt8}, (Any,), src) + soffs - 1, - n) - else - _unsafe_copyto!(dest, doffs, src, soffs, n) - end - @_gc_preserve_end t2 - @_gc_preserve_end t1 +function unsafe_copyto!(dest::Array, doffs, src::Array, soffs, n) + n == 0 && return dest + unsafe_copyto!(GenericMemoryRef(dest.ref, doffs), GenericMemoryRef(src.ref, soffs), n) return dest end -unsafe_copyto!(dest::Array, doffs, src::Array, soffs, n) = - _unsafe_copyto!(dest, doffs, src, soffs, n) - """ - copyto!(dest, do, src, so, N) + copyto!(dest, doffs, src, soffs, n) -Copy `N` elements from collection `src` starting at the linear index `so`, to array `dest` starting at -the index `do`. Return `dest`. +Copy `n` elements from collection `src` starting at the linear index `soffs`, to array `dest` starting at +the index `doffs`. Return `dest`. """ -function copyto!(dest::Array, doffs::Integer, src::Array, soffs::Integer, n::Integer) - return _copyto_impl!(dest, doffs, src, soffs, n) -end +copyto!(dest::Array, doffs::Integer, src::Array, soffs::Integer, n::Integer) = _copyto_impl!(dest, doffs, src, soffs, n) +copyto!(dest::Array, doffs::Integer, src::Memory, soffs::Integer, n::Integer) = _copyto_impl!(dest, doffs, src, soffs, n) +copyto!(dest::Memory, doffs::Integer, src::Array, soffs::Integer, n::Integer) = _copyto_impl!(dest, doffs, src, soffs, n) # this is only needed to avoid possible ambiguities with methods added in some packages -function copyto!(dest::Array{T}, doffs::Integer, src::Array{T}, soffs::Integer, n::Integer) where T - return _copyto_impl!(dest, doffs, src, soffs, n) -end +copyto!(dest::Array{T}, doffs::Integer, src::Array{T}, soffs::Integer, n::Integer) where {T} = _copyto_impl!(dest, doffs, src, soffs, n) -function _copyto_impl!(dest::Array, doffs::Integer, src::Array, soffs::Integer, n::Integer) +function _copyto_impl!(dest::Union{Array,Memory}, doffs::Integer, src::Union{Array,Memory}, soffs::Integer, n::Integer) n == 0 && return dest n > 0 || _throw_argerror("Number of elements to copy must be non-negative.") @boundscheck checkbounds(dest, doffs:doffs+n-1) @boundscheck checkbounds(src, soffs:soffs+n-1) - unsafe_copyto!(dest, doffs, src, soffs, n) + @inbounds let dest = GenericMemoryRef(dest isa Array ? getfield(dest, :ref) : dest, doffs) + src = GenericMemoryRef(src isa Array ? getfield(src, :ref) : src, soffs) + unsafe_copyto!(dest, src, n) + end return dest end + # Outlining this because otherwise a catastrophic inference slowdown # occurs, see discussion in #27874. # It is also mitigated by using a constant string. @@ -406,7 +347,14 @@ See also [`copy!`](@ref Base.copy!), [`copyto!`](@ref), [`deepcopy`](@ref). """ copy -copy(a::T) where {T<:Array} = ccall(:jl_array_copy, Ref{T}, (Any,), a) +@eval function copy(a::Array{T}) where {T} + # `jl_genericmemory_copy_slice` only throws when the size exceeds the max allocation + # size, but since we're copying an existing array, we're guaranteed that this will not happen. + @_nothrow_meta + ref = a.ref + newmem = ccall(:jl_genericmemory_copy_slice, Ref{Memory{T}}, (Any, Ptr{Cvoid}, Int), ref.mem, ref.ptr_or_offset, length(a)) + return $(Expr(:new, :(typeof(a)), :(Core.memoryref(newmem)), :(a.size))) +end ## Constructors ## @@ -469,8 +417,9 @@ end getindex(::Type{Any}) = Vector{Any}() function fill!(a::Union{Array{UInt8}, Array{Int8}}, x::Integer) - t = @_gc_preserve_begin a - p = unsafe_convert(Ptr{Cvoid}, a) + ref = a.ref + t = @_gc_preserve_begin ref + p = unsafe_convert(Ptr{Cvoid}, ref) memset(p, x isa eltype(a) ? x : convert(eltype(a), x), length(a)) @_gc_preserve_end t return a @@ -580,6 +529,7 @@ function fill end fill(v, dims::DimOrInd...) = fill(v, dims) fill(v, dims::NTuple{N, Union{Integer, OneTo}}) where {N} = fill(v, map(to_dim, dims)) fill(v, dims::NTuple{N, Integer}) where {N} = (a=Array{typeof(v),N}(undef, dims); fill!(a, v); a) +fill(v, dims::NTuple{N, DimOrInd}) where {N} = (a=similar(Array{typeof(v),N}, dims); fill!(a, v); a) fill(v, dims::Tuple{}) = (a=Array{typeof(v),0}(undef, dims); fill!(a, v); a) """ @@ -640,24 +590,14 @@ for (fname, felt) in ((:zeros, :zero), (:ones, :one)) fill!(a, $felt(T)) return a end + function $fname(::Type{T}, dims::NTuple{N, DimOrInd}) where {T,N} + a = similar(Array{T,N}, dims) + fill!(a, $felt(T)) + return a + end end end -function _one(unit::T, x::AbstractMatrix) where T - require_one_based_indexing(x) - m,n = size(x) - m==n || throw(DimensionMismatch("multiplicative identity defined only for square matrices")) - # Matrix{T}(I, m, m) - I = zeros(T, m, m) - for i in 1:m - I[i,i] = unit - end - I -end - -one(x::AbstractMatrix{T}) where {T} = _one(one(T), x) -oneunit(x::AbstractMatrix{T}) where {T} = _one(oneunit(T), x) - ## Conversions ## convert(::Type{T}, a::AbstractArray) where {T<:Array} = a isa T ? a : T(a)::T @@ -728,30 +668,34 @@ _array_for(::Type{T}, itr, isz) where {T} = _array_for(T, isz, _similar_shape(it collect(collection) Return an `Array` of all items in a collection or iterator. For dictionaries, returns -`Vector{Pair{KeyType, ValType}}`. If the argument is array-like or is an iterator with the -[`HasShape`](@ref IteratorSize) trait, the result will have the same shape +a `Vector` of `key=>value` [Pair](@ref Pair)s. If the argument is array-like or is an iterator +with the [`HasShape`](@ref IteratorSize) trait, the result will have the same shape and number of dimensions as the argument. -Used by comprehensions to turn a generator into an `Array`. +Used by [comprehensions](@ref man-comprehensions) to turn a [generator expression](@ref man-generators) +into an `Array`. Thus, *on generators*, the square-brackets notation may be used instead of calling `collect`, +see second example. # Examples + +Collect items from a `UnitRange{Int64}` collection: + ```jldoctest -julia> collect(1:2:13) -7-element Vector{Int64}: - 1 - 3 - 5 - 7 - 9 - 11 - 13 +julia> collect(1:3) +3-element Vector{Int64}: + 1 + 2 + 3 +``` -julia> [x^2 for x in 1:8 if isodd(x)] -4-element Vector{Int64}: - 1 - 9 - 25 - 49 +Collect items from a generator (same output as `[x^2 for x in 1:3]`): + +```jldoctest +julia> collect(x^2 for x in 1:3) +3-element Vector{Int64}: + 1 + 4 + 9 ``` """ collect(itr) = _collect(1:1 #= Array =#, itr, IteratorEltype(itr), IteratorSize(itr)) @@ -940,7 +884,7 @@ end ## Iteration ## -iterate(A::Array, i=1) = (@inline; (i % UInt) - 1 < length(A) ? (@inbounds A[i], i + 1) : nothing) +iterate(A::Array, i=1) = (@inline; (i - 1)%UInt < length(A)%UInt ? (@inbounds A[i], i + 1) : nothing) ## Indexing: getindex ## @@ -965,6 +909,12 @@ julia> getindex(A, "a") """ function getindex end +function getindex(A::Array, i1::Int, i2::Int, I::Int...) + @inline + @boundscheck checkbounds(A, i1, i2, I...) # generally _to_linear_index requires bounds checking + return @inbounds A[_to_linear_index(A, i1, i2, I...)] +end + # Faster contiguous indexing using copyto! for AbstractUnitRange and Colon function getindex(A::Array, I::AbstractUnitRange{<:Integer}) @inline @@ -1016,27 +966,38 @@ Dict{String, Int64} with 2 entries: """ function setindex! end -@eval setindex!(A::Array{T}, x, i1::Int) where {T} = - arrayset($(Expr(:boundscheck)), A, x isa T ? x : convert(T,x)::T, i1) -@eval setindex!(A::Array{T}, x, i1::Int, i2::Int, I::Int...) where {T} = - (@inline; arrayset($(Expr(:boundscheck)), A, x isa T ? x : convert(T,x)::T, i1, i2, I...)) +function setindex!(A::Array{T}, x, i::Int) where {T} + @_noub_if_noinbounds_meta + @boundscheck (i - 1)%UInt < length(A)%UInt || throw_boundserror(A, (i,)) + memoryrefset!(memoryref(A.ref, i, false), x isa T ? x : convert(T,x)::T, :not_atomic, false) + return A +end +function setindex!(A::Array{T}, x, i1::Int, i2::Int, I::Int...) where {T} + @inline + @_noub_if_noinbounds_meta + @boundscheck checkbounds(A, i1, i2, I...) # generally _to_linear_index requires bounds checking + memoryrefset!(memoryref(A.ref, _to_linear_index(A, i1, i2, I...), false), x isa T ? x : convert(T,x)::T, :not_atomic, false) + return A +end -__inbounds_setindex!(A::Array{T}, x, i1::Int) where {T} = - arrayset(false, A, convert(T,x)::T, i1) -__inbounds_setindex!(A::Array{T}, x, i1::Int, i2::Int, I::Int...) where {T} = - (@inline; arrayset(false, A, convert(T,x)::T, i1, i2, I...)) +__safe_setindex!(A::Vector{Any}, @nospecialize(x), i::Int) = (@inline; @_nothrow_noub_meta; + memoryrefset!(memoryref(A.ref, i, false), x, :not_atomic, false); return A) +__safe_setindex!(A::Vector{T}, x::T, i::Int) where {T} = (@inline; @_nothrow_noub_meta; + memoryrefset!(memoryref(A.ref, i, false), x, :not_atomic, false); return A) +__safe_setindex!(A::Vector{T}, x, i::Int) where {T} = (@inline; + __safe_setindex!(A, convert(T, x)::T, i)) # This is redundant with the abstract fallbacks but needed and helpful for bootstrap function setindex!(A::Array, X::AbstractArray, I::AbstractVector{Int}) @_propagate_inbounds_meta @boundscheck setindex_shape_check(X, length(I)) + @boundscheck checkbounds(A, I) require_one_based_indexing(X) X′ = unalias(A, X) I′ = unalias(A, I) count = 1 for i in I′ - @inbounds x = X′[count] - A[i] = x + @inbounds A[i] = X′[count] count += 1 end return A @@ -1063,24 +1024,200 @@ function setindex!(A::Array{T}, X::Array{T}, c::Colon) where T return A end -# efficiently grow an array +# Pick new memory size for efficiently growing an array +# TODO: This should know about the size of our GC pools +# Specifically we are wasting ~10% of memory for small arrays +# by not picking memory sizes that max out a GC pool +function overallocation(maxsize) + maxsize < 8 && return 8; + # compute maxsize = maxsize + 4*maxsize^(7/8) + maxsize/8 + # for small n, we grow faster than O(n) + # for large n, we grow at O(n/8) + # and as we reach O(memory) for memory>>1MB, + # this means we end by adding about 10% of memory each time + exp2 = sizeof(maxsize) * 8 - Core.Intrinsics.ctlz_int(maxsize) + maxsize += (1 << div(exp2 * 7, 8)) * 4 + div(maxsize, 8) + return maxsize +end + +array_new_memory(mem::Memory, newlen::Int) = typeof(mem)(undef, newlen) # when implemented, this should attempt to first expand mem + +function _growbeg!(a::Vector, delta::Integer) + @_noub_meta + delta = Int(delta) + delta == 0 && return # avoid attempting to index off the end + delta >= 0 || throw(ArgumentError("grow requires delta >= 0")) + ref = a.ref + mem = ref.mem + len = length(a) + offset = memoryrefoffset(ref) + newlen = len + delta + setfield!(a, :size, (newlen,)) + # if offset is far enough advanced to fit data in existing memory without copying + if delta <= offset - 1 + setfield!(a, :ref, @inbounds GenericMemoryRef(ref, 1 - delta)) + else + @noinline (function() + memlen = length(mem) + if offset + len - 1 > memlen || offset < 1 + throw(ConcurrencyViolationError("Vector has invalid state. Don't modify internal fields incorrectly, or resize without correct locks")) + end + # since we will allocate the array in the middle of the memory we need at least 2*delta extra space + # the +1 is because I didn't want to have an off by 1 error. + newmemlen = max(overallocation(memlen), len + 2 * delta + 1) + newoffset = div(newmemlen - newlen, 2) + 1 + # If there is extra data after the end of the array we can use that space so long as there is enough + # space at the end that there won't be quadratic behavior with a mix of growth from both ends. + # Specifically, we want to ensure that we will only do this operation once before + # increasing the size of the array, and that we leave enough space at both the beginning and the end. + if newoffset + newlen < memlen + newoffset = div(memlen - newlen, 2) + 1 + newmem = mem + else + newmem = array_new_memory(mem, newmemlen) + end + unsafe_copyto!(newmem, newoffset + delta, mem, offset, len) + if ref !== a.ref + @noinline throw(ConcurrencyViolationError("Vector can not be resized concurrently")) + end + setfield!(a, :ref, @inbounds GenericMemoryRef(newmem, newoffset)) + end)() + end + return +end -_growbeg!(a::Vector, delta::Integer) = - ccall(:jl_array_grow_beg, Cvoid, (Any, UInt), a, delta) -_growend!(a::Vector, delta::Integer) = - ccall(:jl_array_grow_end, Cvoid, (Any, UInt), a, delta) -_growat!(a::Vector, i::Integer, delta::Integer) = - ccall(:jl_array_grow_at, Cvoid, (Any, Int, UInt), a, i - 1, delta) +function _growend!(a::Vector, delta::Integer) + @_noub_meta + delta = Int(delta) + delta >= 0 || throw(ArgumentError("grow requires delta >= 0")) + ref = a.ref + mem = ref.mem + memlen = length(mem) + len = length(a) + newlen = len + delta + offset = memoryrefoffset(ref) + setfield!(a, :size, (newlen,)) + newmemlen = offset + newlen - 1 + if memlen < newmemlen + @noinline (function() + if offset + len - 1 > memlen || offset < 1 + throw(ConcurrencyViolationError("Vector has invalid state. Don't modify internal fields incorrectly, or resize without correct locks")) + end -# efficiently delete part of an array + if offset - 1 > div(5 * newlen, 4) + # If the offset is far enough that we can copy without resizing + # while maintaining proportional spacing on both ends of the array + # note that this branch prevents infinite growth when doing combinations + # of push! and popfirst! (i.e. when using a Vector as a queue) + newmem = mem + newoffset = div(newlen, 8) + 1 + else + # grow either by our computed overallocation factor + # or exactly the requested size, whichever is larger + # TODO we should possibly increase the offset if the current offset is nonzero. + newmemlen2 = max(overallocation(memlen), newmemlen) + newmem = array_new_memory(mem, newmemlen2) + newoffset = offset + end + newref = @inbounds GenericMemoryRef(newmem, newoffset) + unsafe_copyto!(newref, ref, len) + if ref !== a.ref + @noinline throw(ConcurrencyViolationError("Vector can not be resized concurrently")) + end + setfield!(a, :ref, newref) + end)() + end + return +end -_deletebeg!(a::Vector, delta::Integer) = - ccall(:jl_array_del_beg, Cvoid, (Any, UInt), a, delta) -_deleteend!(a::Vector, delta::Integer) = - ccall(:jl_array_del_end, Cvoid, (Any, UInt), a, delta) -_deleteat!(a::Vector, i::Integer, delta::Integer) = - ccall(:jl_array_del_at, Cvoid, (Any, Int, UInt), a, i - 1, delta) +function _growat!(a::Vector, i::Integer, delta::Integer) + @_terminates_globally_noub_meta + delta = Int(delta) + i = Int(i) + i == 1 && return _growbeg!(a, delta) + len = length(a) + i == len + 1 && return _growend!(a, delta) + delta >= 0 || throw(ArgumentError("grow requires delta >= 0")) + 1 < i <= len || throw(BoundsError(a, i)) + ref = a.ref + mem = ref.mem + memlen = length(mem) + newlen = len + delta + offset = memoryrefoffset(ref) + setfield!(a, :size, (newlen,)) + newmemlen = offset + newlen - 1 + + # which side would we rather grow into? + prefer_start = i <= div(len, 2) + # if offset is far enough advanced to fit data in beginning of the memory + if prefer_start && delta <= offset - 1 + newref = @inbounds GenericMemoryRef(mem, offset - delta) + unsafe_copyto!(newref, ref, i) + setfield!(a, :ref, newref) + for j in i:i+delta-1 + @inbounds _unsetindex!(a, j) + end + elseif !prefer_start && memlen >= newmemlen + unsafe_copyto!(mem, offset - 1 + delta + i, mem, offset - 1 + i, len - i + 1) + for j in i:i+delta-1 + @inbounds _unsetindex!(a, j) + end + else + # since we will allocate the array in the middle of the memory we need at least 2*delta extra space + # the +1 is because I didn't want to have an off by 1 error. + newmemlen = max(overallocation(memlen), len+2*delta+1) + newoffset = (newmemlen - newlen) ÷ 2 + 1 + newmem = array_new_memory(mem, newmemlen) + newref = @inbounds GenericMemoryRef(newmem, newoffset) + unsafe_copyto!(newref, ref, i-1) + unsafe_copyto!(newmem, newoffset + delta + i - 1, mem, offset + i - 1, len - i + 1) + setfield!(a, :ref, newref) + end +end +# efficiently delete part of an array +function _deletebeg!(a::Vector, delta::Integer) + delta = Int(delta) + len = length(a) + 0 <= delta <= len || throw(ArgumentError("_deletebeg! requires delta in 0:length(a)")) + for i in 1:delta + @inbounds _unsetindex!(a, i) + end + newlen = len - delta + if newlen != 0 # if newlen==0 we could accidentally index past the memory + newref = @inbounds GenericMemoryRef(a.ref, delta + 1) + setfield!(a, :ref, newref) + end + setfield!(a, :size, (newlen,)) + return +end +function _deleteend!(a::Vector, delta::Integer) + delta = Int(delta) + len = length(a) + 0 <= delta <= len || throw(ArgumentError("_deleteend! requires delta in 0:length(a)")) + newlen = len - delta + for i in newlen+1:len + @inbounds _unsetindex!(a, i) + end + setfield!(a, :size, (newlen,)) + return +end +function _deleteat!(a::Vector, i::Integer, delta::Integer) + i = Int(i) + len = length(a) + 0 <= delta || throw(ArgumentError("_deleteat! requires delta >= 0")) + 1 <= i <= len || throw(BoundsError(a, i)) + i + delta <= len + 1 || throw(BoundsError(a, i + delta - 1)) + newa = a + if 2*i + delta <= len + unsafe_copyto!(newa, 1 + delta, a, 1, i - 1) + _deletebeg!(a, delta) + else + unsafe_copyto!(newa, i, a, i + delta, len + 1 - delta - i) + _deleteend!(a, delta) + end + return +end ## Dequeue functionality ## """ @@ -1174,11 +1311,11 @@ and [`prepend!`](@ref) and [`pushfirst!`](@ref) for the opposite order. """ function append! end -function append!(a::Vector, items::AbstractVector) - itemindices = eachindex(items) - n = length(itemindices) +function append!(a::Vector{T}, items::Union{AbstractVector{<:T},Tuple}) where T + items isa Tuple && (items = map(x -> convert(T, x), items)) + n = length(items) _growend!(a, n) - copyto!(a, length(a)-n+1, items, first(itemindices), n) + copyto!(a, length(a)-n+1, items, firstindex(items), n) return a end @@ -1188,16 +1325,11 @@ push!(a::AbstractVector, iter...) = append!(a, iter) append!(a::AbstractVector, iter...) = foldl(append!, iter, init=a) function _append!(a::AbstractVector, ::Union{HasLength,HasShape}, iter) - @_terminates_locally_meta - n = length(a) + n = Int(length(iter))::Int i = lastindex(a) - resize!(a, n+Int(length(iter))::Int) - for (i, item) in zip(i+1:lastindex(a), iter) - if isa(a, Vector) # give better effects for builtin vectors - @_safeindex a[i] = item - else - a[i] = item - end + sizehint!(a, length(a) + n; shrink=false) + for item in iter + push!(a, item) end a end @@ -1239,15 +1371,13 @@ julia> prepend!([6], [1, 2], [3, 4, 5]) """ function prepend! end -function prepend!(a::Vector, items::AbstractVector) - itemindices = eachindex(items) - n = length(itemindices) +function prepend!(a::Vector{T}, items::Union{AbstractVector{<:T},Tuple}) where T + items isa Tuple && (items = map(x -> convert(T, x), items)) + n = length(items) _growbeg!(a, n) - if a === items - copyto!(a, 1, items, n+1, n) - else - copyto!(a, 1, items, first(itemindices), n) - end + # in case of aliasing, the _growbeg might have shifted our data, so copy + # just the last n elements instead of all of them from the first + copyto!(a, 1, items, lastindex(items)-n+1, n) return a end @@ -1259,12 +1389,14 @@ prepend!(a::AbstractVector, iter...) = foldr((v, a) -> prepend!(a, v), iter, ini function _prepend!(a::Vector, ::Union{HasLength,HasShape}, iter) @_terminates_locally_meta require_one_based_indexing(a) - n = length(iter) - _growbeg!(a, n) - i = 0 + n = Int(length(iter))::Int + sizehint!(a, length(a) + n; first=true, shrink=false) + n = 0 for item in iter - @_safeindex a[i += 1] = item + n += 1 + pushfirst!(a, item) end + reverse!(a, 1, n) a end function _prepend!(a::Vector, ::IteratorSize, iter) @@ -1321,13 +1453,21 @@ function resize!(a::Vector, nl::Integer) end """ - sizehint!(s, n) -> s + sizehint!(s, n; first::Bool=false, shrink::Bool=true) -> s Suggest that collection `s` reserve capacity for at least `n` elements. That is, if you expect that you're going to have to push a lot of values onto `s`, you can avoid the cost of incremental reallocation by doing it once up front; this can improve performance. +If `first` is `true`, then any additional space is reserved before the start of the collection. +This way, subsequent calls to `pushfirst!` (instead of `push!`) may become faster. +Supplying this keyword may result in an error if the collection is not ordered +or if `pushfirst!` is not supported for this collection. + +If `shrink=true` (the default), the collection's capacity may be reduced if its current +capacity is greater than `n`. + See also [`resize!`](@ref). # Notes on the performance model @@ -1342,16 +1482,53 @@ For types that support `sizehint!`, `Base`. 3. `empty!` is nearly costless (and O(1)) for types that support this kind of preallocation. + +!!! compat "Julia 1.11" + The `shrink` and `first` arguments were added in Julia 1.11. """ function sizehint! end -function sizehint!(a::Vector, sz::Integer) - ccall(:jl_array_sizehint, Cvoid, (Any, UInt), a, sz) +function sizehint!(a::Vector, sz::Integer; first::Bool=false, shrink::Bool=true) + len = length(a) + ref = a.ref + mem = ref.mem + memlen = length(mem) + sz = max(Int(sz), len) + inc = sz - len + if sz <= memlen + # if we don't save at least 1/8th memlen then its not worth it to shrink + if !shrink || memlen - sz <= div(memlen, 8) + return a + end + newmem = array_new_memory(mem, sz) + if first + newref = GenericMemoryRef(newmem, inc + 1) + else + newref = GenericMemoryRef(newmem) + end + unsafe_copyto!(newref, ref, len) + setfield!(a, :ref, newref) + elseif first + _growbeg!(a, inc) + newref = getfield(a, :ref) + newref = GenericMemoryRef(newref, inc + 1) + setfield!(a, :size, (len,)) # undo the size change from _growbeg! + setfield!(a, :ref, newref) # undo the offset change from _growbeg! + else # last + _growend!(a, inc) + setfield!(a, :size, (len,)) # undo the size change from _growend! + end a end # Fall-back implementation for non-shrinkable collections -_sizehint!(a, sz; shrink) = sizehint!(a, sz) +# avoid defining this the normal way to avoid avoid infinite recursion +function Core.kwcall(kwargs::NamedTuple{names}, ::typeof(sizehint!), a, sz) where names + get(kwargs, :first, false)::Bool + get(kwargs, :shrink, true)::Bool + isempty(diff_names(names, (:first, :shrink))) || kwerr(kwargs, sizehint!, a, sz) + sizehint!(a, sz) +end """ pop!(collection) -> item @@ -1557,10 +1734,11 @@ julia> insert!(Any[1:6;], 3, "here") ``` """ function insert!(a::Array{T,1}, i::Integer, item) where T + @_noub_meta # Throw convert error before changing the shape of the array _item = item isa T ? item : convert(T, item)::T _growat!(a, i, 1) - # _growat! already did bound check + # :noub, because _growat! already did bound check @inbounds a[i] = _item return a end @@ -1638,17 +1816,19 @@ struct Nowhere; end push!(::Nowhere, _) = nothing _growend!(::Nowhere, _) = nothing -@inline function _push_deleted!(dltd, a::Vector, ind) - if @inbounds isassigned(a, ind) - push!(dltd, @inbounds a[ind]) +function _push_deleted!(dltd, a::Vector, ind) + @_propagate_inbounds_meta + if isassigned(a, ind) + push!(dltd, a[ind]) else _growend!(dltd, 1) end end -@inline function _copy_item!(a::Vector, p, q) - if @inbounds isassigned(a, q) - @inbounds a[p] = a[q] +function _copy_item!(a::Vector, p, q) + @_propagate_inbounds_meta + if isassigned(a, q) + a[p] = a[q] else _unsetindex!(a, p) end @@ -1660,7 +1840,7 @@ function _deleteat!(a::Vector, inds, dltd=Nowhere()) y === nothing && return a (p, s) = y checkbounds(a, p) - _push_deleted!(dltd, a, p) + @inbounds _push_deleted!(dltd, a, p) q = p+1 while true y = iterate(inds, s) @@ -1674,14 +1854,14 @@ function _deleteat!(a::Vector, inds, dltd=Nowhere()) end end while q < i - _copy_item!(a, p, q) + @inbounds _copy_item!(a, p, q) p += 1; q += 1 end - _push_deleted!(dltd, a, i) + @inbounds _push_deleted!(dltd, a, i) q = i+1 end while q <= n - _copy_item!(a, p, q) + @inbounds _copy_item!(a, p, q) p += 1; q += 1 end _deleteend!(a, n-p+1) @@ -1694,7 +1874,7 @@ function deleteat!(a::Vector, inds::AbstractVector{Bool}) length(inds) == n || throw(BoundsError(a, inds)) p = 1 for (q, i) in enumerate(inds) - _copy_item!(a, p, q) + @inbounds _copy_item!(a, p, q) p += !i end _deleteend!(a, n-p+1) @@ -1784,6 +1964,8 @@ place of the removed items; in this case, `indices` must be a `AbstractUnitRange To insert `replacement` before an index `n` without removing any items, use `splice!(collection, n:n-1, replacement)`. +$(_DOCS_ALIASING_WARNING) + !!! compat "Julia 1.5" Prior to Julia 1.5, `indices` must always be a `UnitRange`. @@ -1844,10 +2026,12 @@ end # use memcmp for cmp on byte arrays function cmp(a::Array{UInt8,1}, b::Array{UInt8,1}) - ta = @_gc_preserve_begin a - tb = @_gc_preserve_begin b - pa = unsafe_convert(Ptr{Cvoid}, a) - pb = unsafe_convert(Ptr{Cvoid}, b) + aref = a.ref + bref = b.ref + ta = @_gc_preserve_begin aref + tb = @_gc_preserve_begin bref + pa = unsafe_convert(Ptr{Cvoid}, aref) + pb = unsafe_convert(Ptr{Cvoid}, bref) c = memcmp(pa, pb, min(length(a),length(b))) @_gc_preserve_end ta @_gc_preserve_end tb @@ -1858,10 +2042,12 @@ const BitIntegerArray{N} = Union{map(T->Array{T,N}, BitInteger_types)...} where # use memcmp for == on bit integer types function ==(a::Arr, b::Arr) where {Arr <: BitIntegerArray} if size(a) == size(b) - ta = @_gc_preserve_begin a - tb = @_gc_preserve_begin b - pa = unsafe_convert(Ptr{Cvoid}, a) - pb = unsafe_convert(Ptr{Cvoid}, b) + aref = a.ref + bref = b.ref + ta = @_gc_preserve_begin aref + tb = @_gc_preserve_begin bref + pa = unsafe_convert(Ptr{Cvoid}, aref) + pb = unsafe_convert(Ptr{Cvoid}, bref) c = memcmp(pa, pb, sizeof(eltype(Arr)) * length(a)) @_gc_preserve_end ta @_gc_preserve_end tb @@ -1874,11 +2060,13 @@ end function ==(a::Arr, b::Arr) where Arr <: BitIntegerArray{1} len = length(a) if len == length(b) - ta = @_gc_preserve_begin a - tb = @_gc_preserve_begin b + aref = a.ref + bref = b.ref + ta = @_gc_preserve_begin aref + tb = @_gc_preserve_begin bref T = eltype(Arr) - pa = unsafe_convert(Ptr{T}, a) - pb = unsafe_convert(Ptr{T}, b) + pa = unsafe_convert(Ptr{T}, aref) + pb = unsafe_convert(Ptr{T}, bref) c = memcmp(pa, pb, sizeof(T) * len) @_gc_preserve_end ta @_gc_preserve_end tb @@ -2126,7 +2314,9 @@ findfirst(A::AbstractArray) = findnext(A, first(keys(A))) findnext(predicate::Function, A, i) Find the next index after or including `i` of an element of `A` -for which `predicate` returns `true`, or `nothing` if not found. +for which `predicate` returns `true`, or `nothing` if not found. This works for +Arrays, Strings, and most other collections that support [`getindex`](@ref), +[`keys(A)`](@ref), and [`nextind`](@ref). Indices are of the same type as those returned by [`keys(A)`](@ref) and [`pairs(A)`](@ref). @@ -2144,6 +2334,9 @@ julia> A = [1 4; 2 2]; julia> findnext(isodd, A, CartesianIndex(1, 1)) CartesianIndex(1, 1) + +julia> findnext(isspace, "a b c", 3) +4 ``` """ function findnext(testf::Function, A, start) @@ -2300,7 +2493,9 @@ findlast(A::AbstractArray) = findprev(A, last(keys(A))) findprev(predicate::Function, A, i) Find the previous index before or including `i` of an element of `A` -for which `predicate` returns `true`, or `nothing` if not found. +for which `predicate` returns `true`, or `nothing` if not found. This works for +Arrays, Strings, and most other collections that support [`getindex`](@ref), +[`keys(A)`](@ref), and [`nextind`](@ref). Indices are of the same type as those returned by [`keys(A)`](@ref) and [`pairs(A)`](@ref). @@ -2326,6 +2521,9 @@ julia> A = [4 6; 1 2] julia> findprev(isodd, A, CartesianIndex(1, 2)) CartesianIndex(2, 1) + +julia> findprev(isspace, "a b c", 3) +2 ``` """ function findprev(testf::Function, A, start) @@ -2428,7 +2626,7 @@ Dict{Symbol, Int64} with 3 entries: :B => -1 :C => 0 -julia> findall(x -> x >= 0, d) +julia> findall(≥(0), d) 2-element Vector{Symbol}: :A :C @@ -2762,6 +2960,8 @@ Remove the items at all the indices which are not given by `inds`, and return the modified `a`. Items which are kept are shifted to fill the resulting gaps. +$(_DOCS_ALIASING_WARNING) + `inds` must be an iterator of sorted and unique integer indices. See also [`deleteat!`](@ref). diff --git a/base/arrayshow.jl b/base/arrayshow.jl index a05a8d4dac51c..164a9257d8412 100644 --- a/base/arrayshow.jl +++ b/base/arrayshow.jl @@ -364,13 +364,13 @@ function show(io::IO, ::MIME"text/plain", X::AbstractArray) if isempty(X) && (get(io, :compact, false)::Bool || X isa Vector) return show(io, X) end - # 0) show summary before setting :compact + # 1) show summary before setting :compact summary(io, X) isempty(X) && return print(io, ":") show_circular(io, X) && return - # 1) compute new IOContext + # 2) compute new IOContext if !haskey(io, :compact) && length(axes(X, 2)) > 1 io = IOContext(io, :compact => true) end @@ -385,7 +385,7 @@ function show(io::IO, ::MIME"text/plain", X::AbstractArray) println(io) end - # 2) update typeinfo + # 3) update typeinfo # # it must come after printing the summary, which can exploit :typeinfo itself # (e.g. views) @@ -394,7 +394,7 @@ function show(io::IO, ::MIME"text/plain", X::AbstractArray) # checking for current :typeinfo (this could be changed in the future) io = IOContext(io, :typeinfo => eltype(X)) - # 2) show actual content + # 4) show actual content recur_io = IOContext(io, :SHOWN_SET => X) print_array(recur_io, X) end @@ -554,7 +554,7 @@ function typeinfo_implicit(@nospecialize(T)) end return isconcretetype(T) && ((T <: Array && typeinfo_implicit(eltype(T))) || - ((T <: Tuple || T <: Pair) && all(typeinfo_implicit, fieldtypes(T))) || + ((T <: Tuple || T <: NamedTuple || T <: Pair) && all(typeinfo_implicit, fieldtypes(T))) || (T <: AbstractDict && typeinfo_implicit(keytype(T)) && typeinfo_implicit(valtype(T)))) end diff --git a/base/asyncevent.jl b/base/asyncevent.jl index 498fb054ecd02..3c782be10e194 100644 --- a/base/asyncevent.jl +++ b/base/asyncevent.jl @@ -118,14 +118,18 @@ end unsafe_convert(::Type{Ptr{Cvoid}}, t::Timer) = t.handle unsafe_convert(::Type{Ptr{Cvoid}}, async::AsyncCondition) = async.handle +# if this returns true, the object has been signaled +# if this returns false, the object is closed function _trywait(t::Union{Timer, AsyncCondition}) set = t.set if set # full barrier now for AsyncCondition t isa Timer || Core.Intrinsics.atomic_fence(:acquire_release) else - t.isopen || return false - t.handle == C_NULL && return false + if !isopen(t) + close(t) # wait for the close to complete + return false + end iolock_begin() set = t.set if !set @@ -133,7 +137,7 @@ function _trywait(t::Union{Timer, AsyncCondition}) lock(t.cond) try set = t.set - if !set && t.isopen && t.handle != C_NULL + if !set && t.handle != C_NULL # wait for set or handle, but not the isopen flag iolock_end() set = wait(t.cond) unlock(t.cond) @@ -160,10 +164,28 @@ end isopen(t::Union{Timer, AsyncCondition}) = t.isopen && t.handle != C_NULL function close(t::Union{Timer, AsyncCondition}) + t.handle == C_NULL && return # short-circuit path iolock_begin() - if isopen(t) - @atomic :monotonic t.isopen = false - ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), t) + if t.handle != C_NULL + if t.isopen + @atomic :monotonic t.isopen = false + ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), t) + end + # implement _trywait here without the auto-reset function, just waiting for the final close signal + preserve_handle(t) + lock(t.cond) + try + while t.handle != C_NULL + iolock_end() + wait(t.cond) + unlock(t.cond) + iolock_begin() + lock(t.cond) + end + finally + unlock(t.cond) + unpreserve_handle(t) + end end iolock_end() nothing @@ -220,7 +242,10 @@ function uv_timercb(handle::Ptr{Cvoid}) @atomic :monotonic t.set = true if ccall(:uv_timer_get_repeat, UInt64, (Ptr{Cvoid},), t) == 0 # timer is stopped now - close(t) + if t.isopen + @atomic :monotonic t.isopen = false + ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), t) + end end notify(t.cond, true) finally @@ -302,11 +327,24 @@ end """ timedwait(testcb, timeout::Real; pollint::Real=0.1) -Waits until `testcb()` returns `true` or `timeout` seconds have passed, whichever is earlier. +Wait until `testcb()` returns `true` or `timeout` seconds have passed, whichever is earlier. The test function is polled every `pollint` seconds. The minimum value for `pollint` is 0.001 seconds, that is, 1 millisecond. Return `:ok` or `:timed_out`. + +# Examples +```jldoctest +julia> cb() = (sleep(5); return); + +julia> t = @async cb(); + +julia> timedwait(()->istaskdone(t), 1) +:timed_out + +julia> timedwait(()->istaskdone(t), 6.5) +:ok +``` """ function timedwait(testcb, timeout::Real; pollint::Real=0.1) pollint >= 1e-3 || throw(ArgumentError("pollint must be ≥ 1 millisecond")) diff --git a/base/asyncmap.jl b/base/asyncmap.jl index be16ba1b27610..c81afbb7e9115 100644 --- a/base/asyncmap.jl +++ b/base/asyncmap.jl @@ -394,6 +394,8 @@ length(itr::AsyncGenerator) = length(itr.collector.enumerator) Like [`asyncmap`](@ref), but stores output in `results` rather than returning a collection. + +$(_DOCS_ALIASING_WARNING) """ function asyncmap!(f, r, c1, c...; ntasks=0, batch_size=nothing) foreach(identity, AsyncCollector(f, r, c1, c...; ntasks=ntasks, batch_size=batch_size)) diff --git a/base/atomics.jl b/base/atomics.jl index 7312206c19896..e6f3a5654cbf7 100644 --- a/base/atomics.jl +++ b/base/atomics.jl @@ -80,6 +80,13 @@ end Atomic() = Atomic{Int}() +const LOCK_PROFILING = Atomic{Int}(0) +lock_profiling(state::Bool) = state ? atomic_add!(LOCK_PROFILING, 1) : atomic_sub!(LOCK_PROFILING, 1) +lock_profiling() = LOCK_PROFILING[] > 0 + +const LOCK_CONFLICT_COUNT = Atomic{Int}(0); +inc_lock_conflict_count() = atomic_add!(LOCK_CONFLICT_COUNT, 1) + """ Threads.atomic_cas!(x::Atomic{T}, cmp::T, newval::T) where T @@ -357,13 +364,13 @@ for typ in atomictypes irt = "$ilt, $ilt*" @eval getindex(x::Atomic{$typ}) = GC.@preserve x llvmcall($""" - %ptr = inttoptr i$WORD_SIZE %0 to $lt* + %ptr = bitcast i8* %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) = GC.@preserve x llvmcall($""" - %ptr = inttoptr i$WORD_SIZE %0 to $lt* + %ptr = bitcast i8* %0 to $lt* store atomic $lt %1, $lt* %ptr release, align $(gc_alignment(typ)) ret void """, Cvoid, Tuple{Ptr{$typ}, $typ}, unsafe_convert(Ptr{$typ}, x), v) @@ -372,7 +379,7 @@ for typ in atomictypes if typ <: Integer @eval atomic_cas!(x::Atomic{$typ}, cmp::$typ, new::$typ) = GC.@preserve x llvmcall($""" - %ptr = inttoptr i$WORD_SIZE %0 to $lt* + %ptr = bitcast i8* %0 to $lt* %rs = cmpxchg $lt* %ptr, $lt %1, $lt %2 acq_rel acquire %rv = extractvalue { $lt, i1 } %rs, 0 ret $lt %rv @@ -381,7 +388,7 @@ for typ in atomictypes else @eval atomic_cas!(x::Atomic{$typ}, cmp::$typ, new::$typ) = GC.@preserve x llvmcall($""" - %iptr = inttoptr i$WORD_SIZE %0 to $ilt* + %iptr = bitcast i8* %0 to $ilt* %icmp = bitcast $lt %1 to $ilt %inew = bitcast $lt %2 to $ilt %irs = cmpxchg $ilt* %iptr, $ilt %icmp, $ilt %inew acq_rel acquire @@ -404,7 +411,7 @@ for typ in atomictypes if typ <: Integer @eval $fn(x::Atomic{$typ}, v::$typ) = GC.@preserve x llvmcall($""" - %ptr = inttoptr i$WORD_SIZE %0 to $lt* + %ptr = bitcast i8* %0 to $lt* %rv = atomicrmw $rmw $lt* %ptr, $lt %1 acq_rel ret $lt %rv """, $typ, Tuple{Ptr{$typ}, $typ}, unsafe_convert(Ptr{$typ}, x), v) @@ -412,7 +419,7 @@ for typ in atomictypes rmwop === :xchg || continue @eval $fn(x::Atomic{$typ}, v::$typ) = GC.@preserve x llvmcall($""" - %iptr = inttoptr i$WORD_SIZE %0 to $ilt* + %iptr = bitcast i8* %0 to $ilt* %ival = bitcast $lt %1 to $ilt %irv = atomicrmw $rmw $ilt* %iptr, $ilt %ival acq_rel %rv = bitcast $ilt %irv to $lt diff --git a/base/binaryplatforms.jl b/base/binaryplatforms.jl index b374d57ce9731..8c6fa2a5f4a03 100644 --- a/base/binaryplatforms.jl +++ b/base/binaryplatforms.jl @@ -198,7 +198,7 @@ function validate_tags(tags::Dict) throw_invalid_key("arch") end # Validate `os` - if tags["os"] ∉ ("linux", "macos", "freebsd", "windows") + if tags["os"] ∉ ("linux", "macos", "freebsd", "openbsd", "windows") throw_invalid_key("os") end # Validate `os`/`arch` combination @@ -306,7 +306,7 @@ function compare_version_cap(a::String, b::String, a_requested::Bool, b_requeste return a == b end - # Otherwise, do the comparison between the the single version cap and the single version: + # Otherwise, do the comparison between the single version cap and the single version: if a_requested return b <= a else @@ -375,8 +375,10 @@ function os() return "windows" elseif Sys.isapple() return "macos" - elseif Sys.isbsd() + elseif Sys.isfreebsd() return "freebsd" + elseif Sys.isopenbsd() + return "openbsd" else return "linux" end @@ -422,6 +424,7 @@ const platform_names = Dict( "macos" => "macOS", "windows" => "Windows", "freebsd" => "FreeBSD", + "openbsd" => "OpenBSD", nothing => "Unknown", ) @@ -556,6 +559,8 @@ function os_str(p::AbstractPlatform) else return "-unknown-freebsd" end + elseif os(p) == "openbsd" + return "-unknown-openbsd" else return "-unknown" end @@ -581,7 +586,8 @@ Sys.isapple(p::AbstractPlatform) = os(p) == "macos" Sys.islinux(p::AbstractPlatform) = os(p) == "linux" Sys.iswindows(p::AbstractPlatform) = os(p) == "windows" Sys.isfreebsd(p::AbstractPlatform) = os(p) == "freebsd" -Sys.isbsd(p::AbstractPlatform) = os(p) ∈ ("freebsd", "macos") +Sys.isopenbsd(p::AbstractPlatform) = os(p) == "openbsd" +Sys.isbsd(p::AbstractPlatform) = os(p) ∈ ("freebsd", "openbsd", "macos") Sys.isunix(p::AbstractPlatform) = Sys.isbsd(p) || Sys.islinux(p) const arch_mapping = Dict( @@ -632,6 +638,7 @@ end const os_mapping = Dict( "macos" => "-apple-darwin[\\d\\.]*", "freebsd" => "-(.*-)?freebsd[\\d\\.]*", + "openbsd" => "-(.*-)?openbsd[\\d\\.]*", "windows" => "-w64-mingw32", "linux" => "-(.*-)?linux", ) @@ -661,18 +668,12 @@ const libstdcxx_version_mapping = Dict{String,String}( "libstdcxx" => "-libstdcxx\\d+", ) -""" - parse(::Type{Platform}, triplet::AbstractString) - -Parses a string platform triplet back into a `Platform` object. -""" -function Base.parse(::Type{Platform}, triplet::String; validate_strict::Bool = false) +const triplet_regex = let # Helper function to collapse dictionary of mappings down into a regex of # named capture groups joined by "|" operators c(mapping) = string("(",join(["(?<$k>$v)" for (k, v) in mapping], "|"), ")") - # We're going to build a mondo regex here to parse everything: - triplet_regex = Regex(string( + Regex(string( "^", # First, the core triplet; arch/os/libc/call_abi c(arch_mapping), @@ -687,7 +688,14 @@ function Base.parse(::Type{Platform}, triplet::String; validate_strict::Bool = f "(?(?:-[^-]+\\+[^-]+)*)?", "\$", )) +end +""" + parse(::Type{Platform}, triplet::AbstractString) + +Parses a string platform triplet back into a `Platform` object. +""" +function Base.parse(::Type{Platform}, triplet::String; validate_strict::Bool = false) m = match(triplet_regex, triplet) if m !== nothing # Helper function to find the single named field within the giant regex @@ -744,6 +752,9 @@ function Base.parse(::Type{Platform}, triplet::String; validate_strict::Bool = f if os == "freebsd" os_version = extract_os_version("freebsd", r".*freebsd([\d.]+)"sa) end + if os == "openbsd" + os_version = extract_os_version("openbsd", r".*openbsd([\d.]+)"sa) + end tags["os_version"] = os_version return Platform(arch, os, tags; validate_strict) @@ -801,7 +812,7 @@ function parse_dl_name_version(path::String, os::String) # On OSX, libraries look like `libnettle.6.3.dylib` dlregex = r"^(.*?)((?:\.[\d]+)*)\.dylib$"sa else - # On Linux and FreeBSD, libraries look like `libnettle.so.6.3.0` + # On Linux and others BSD, libraries look like `libnettle.so.6.3.0` dlregex = r"^(.*?)\.so((?:\.[\d]+)*)$"sa end diff --git a/base/bitarray.jl b/base/bitarray.jl index 746dce493f4c0..f7eeafbb62231 100644 --- a/base/bitarray.jl +++ b/base/bitarray.jl @@ -404,6 +404,7 @@ falses(dims::DimOrInd...) = falses(dims) falses(dims::NTuple{N, Union{Integer, OneTo}}) where {N} = falses(map(to_dim, dims)) falses(dims::NTuple{N, Integer}) where {N} = fill!(BitArray(undef, dims), false) falses(dims::Tuple{}) = fill!(BitArray(undef, dims), false) +falses(dims::NTuple{N, DimOrInd}) where {N} = fill!(similar(BitArray, dims), false) """ trues(dims) @@ -422,6 +423,7 @@ trues(dims::DimOrInd...) = trues(dims) trues(dims::NTuple{N, Union{Integer, OneTo}}) where {N} = trues(map(to_dim, dims)) trues(dims::NTuple{N, Integer}) where {N} = fill!(BitArray(undef, dims), true) trues(dims::Tuple{}) = fill!(BitArray(undef, dims), true) +trues(dims::NTuple{N, DimOrInd}) where {N} = fill!(similar(BitArray, dims), true) function one(x::BitMatrix) m, n = size(x) @@ -482,7 +484,7 @@ end reshape(B::BitArray, dims::Tuple{Vararg{Int}}) = _bitreshape(B, dims) function _bitreshape(B::BitArray, dims::NTuple{N,Int}) where N prod(dims) == length(B) || - throw(DimensionMismatch("new dimensions $(dims) must be consistent with array size $(length(B))")) + throw(DimensionMismatch("new dimensions $(dims) must be consistent with array length $(length(B))")) Br = BitArray{N}(undef, ntuple(i->0,Val(N))...) Br.chunks = B.chunks Br.len = prod(dims) @@ -807,7 +809,7 @@ prepend!(B::BitVector, items) = prepend!(B, BitArray(items)) prepend!(A::Vector{Bool}, items::BitVector) = prepend!(A, Array(items)) function sizehint!(B::BitVector, sz::Integer) - ccall(:jl_array_sizehint, Cvoid, (Any, UInt), B.chunks, num_bit_chunks(sz)) + sizehint!(B.chunks, num_bit_chunks(sz)) return B end diff --git a/base/bitset.jl b/base/bitset.jl index 240be822fa263..78d8fc8769de1 100644 --- a/base/bitset.jl +++ b/base/bitset.jl @@ -15,7 +15,11 @@ mutable struct BitSet <: AbstractSet{Int} # 1st stored Int equals 64*offset offset::Int - BitSet() = new(resize!(Vector{UInt64}(undef, 4), 0), NO_OFFSET) + function BitSet() + a = Vector{UInt64}(undef, 4) # start with some initial space for holding 0:255 without additional allocations later + setfield!(a, :size, (0,)) # aka `empty!(a)` inlined + return new(a, NO_OFFSET) + end end """ @@ -51,7 +55,10 @@ function copy!(dest::BitSet, src::BitSet) dest end -sizehint!(s::BitSet, n::Integer) = (sizehint!(s.bits, (n+63) >> 6); s) +function sizehint!(s::BitSet, n::Integer; first::Bool=false, shrink::Bool=true) + sizehint!(s.bits, (n+63) >> 6; first, shrink) + s +end function _bits_getindex(b::Bits, n::Int, offset::Int) ci = _div64(n) - offset + 1 diff --git a/base/boot.jl b/base/boot.jl index 7f7f4cf02422d..a3c5c03bdf721 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -52,8 +52,26 @@ #abstract type AbstractArray{T,N} end #abstract type DenseArray{T,N} <: AbstractArray{T,N} end +#primitive type AddrSpace{Backend::Module} 8 end +#const CPU = bitcast(AddrSpace{Core}, 0x00) + +#struct GenericMemory{kind::Symbol, T, AS::AddrSpace} +# length::Int +# const data::Ptr{Cvoid} # make this GenericPtr{addrspace, Cvoid} +# Union{ # hidden data +# elements :: NTuple{length, T} +# owner :: Any +# } +#end + +#struct GenericMemoryRef{kind::Symbol, T, AS::AddrSpace} +# mem::GenericMemory{kind, T, AS} +# data::Ptr{Cvoid} # make this GenericPtr{addrspace, Cvoid} +#end + #mutable struct Array{T,N} <: DenseArray{T,N} -## opaque +# ref::MemoryRef{T} +# size::NTuple{N,Int} #end #mutable struct Module @@ -107,12 +125,13 @@ # file::Union{Symbol,Nothing} #end -#struct LineInfoNode -# module::Module -# method::Any (Union{Symbol, Method, MethodInstance}) -# file::Symbol -# line::Int32 -# inlined_at::Int32 +#struct LegacyLineInfoNode end # only used internally during lowering + +#struct DebugInfo +# def::Any # (Union{Symbol, Method, MethodInstance}) +# linetable::Any # (Union{Nothing,DebugInfo}) +# edges::SimpleVector # Vector{DebugInfo} +# codelocs::String # compressed Vector{UInt8} #end #struct GotoNode @@ -173,8 +192,8 @@ export Tuple, Type, UnionAll, TypeVar, Union, Nothing, Cvoid, AbstractArray, DenseArray, NamedTuple, Pair, # special objects - Function, Method, - Module, Symbol, Task, Array, UndefInitializer, undef, WeakRef, VecElement, + Function, Method, Module, Symbol, Task, UndefInitializer, undef, WeakRef, VecElement, + Array, Memory, MemoryRef, AtomicMemory, AtomicMemoryRef, GenericMemory, GenericMemoryRef, # numeric types Number, Real, Integer, Bool, Ref, Ptr, AbstractFloat, Float16, Float32, Float64, @@ -191,10 +210,10 @@ export # AST representation Expr, QuoteNode, LineNumberNode, GlobalRef, # object model functions - fieldtype, getfield, setfield!, swapfield!, modifyfield!, replacefield!, + fieldtype, getfield, setfield!, swapfield!, modifyfield!, replacefield!, setfieldonce!, nfields, throw, tuple, ===, isdefined, eval, # access to globals - getglobal, setglobal!, + getglobal, setglobal!, swapglobal!, modifyglobal!, replaceglobal!, setglobalonce!, # ifelse, sizeof # not exported, to avoid conflicting with Base # type reflection <:, typeof, isa, typeassert, @@ -250,11 +269,12 @@ ccall(:jl_toplevel_eval_in, Any, (Any, Any), macro nospecialize(x) _expr(:meta, :nospecialize, x) end +Expr(@nospecialize args...) = _expr(args...) _is_internal(__module__) = __module__ === Core # can be used in place of `@assume_effects :foldable` (supposed to be used for bootstrapping) macro _foldable_meta() - return _is_internal(__module__) && _expr(:meta, _expr(:purity, + return _is_internal(__module__) && Expr(:meta, Expr(:purity, #=:consistent=#true, #=:effect_free=#true, #=:nothrow=#false, @@ -262,15 +282,24 @@ macro _foldable_meta() #=:terminates_locally=#false, #=:notaskstate=#true, #=:inaccessiblememonly=#true, - #=:noub=#true)) + #=:noub=#true, + #=:noub_if_noinbounds=#false)) end +macro inline() Expr(:meta, :inline) end +macro noinline() Expr(:meta, :noinline) end + +macro _boundscheck() Expr(:boundscheck) end + # n.b. the effects and model of these is refined in inference abstractinterpretation.jl TypeVar(@nospecialize(n)) = _typevar(n::Symbol, Union{}, Any) TypeVar(@nospecialize(n), @nospecialize(ub)) = _typevar(n::Symbol, Union{}, ub) TypeVar(@nospecialize(n), @nospecialize(lb), @nospecialize(ub)) = _typevar(n::Symbol, lb, ub) UnionAll(@nospecialize(v), @nospecialize(t)) = ccall(:jl_type_unionall, Any, (Any, Any), v::TypeVar, t) +const Memory{T} = GenericMemory{:not_atomic, T, CPU} +const MemoryRef{T} = GenericMemoryRef{:not_atomic, T, CPU} + # simple convert for use by constructors of types in Core # note that there is no actual conversion defined here, # so the methods and ccall's in Core aren't permitted to use convert @@ -291,16 +320,11 @@ kwftype(@nospecialize(t)) = typeof(kwcall) Union{}(a...) = throw(ArgumentError("cannot construct a value of type Union{} for return result")) kwcall(kwargs, ::Type{Union{}}, a...) = Union{}(a...) -Expr(@nospecialize args...) = _expr(args...) - abstract type Exception end struct ErrorException <: Exception msg::AbstractString end -macro inline() Expr(:meta, :inline) end -macro noinline() Expr(:meta, :noinline) end - struct BoundsError <: Exception a::Any i::Any @@ -316,10 +340,16 @@ struct StackOverflowError <: Exception end struct UndefRefError <: Exception end struct UndefVarError <: Exception var::Symbol + scope # a Module or Symbol or other object describing the context where this variable was looked for (e.g. Main or :local or :static_parameter) + UndefVarError(var::Symbol) = new(var) + UndefVarError(var::Symbol, @nospecialize scope) = new(var, scope) end struct ConcurrencyViolationError <: Exception msg::AbstractString end +struct MissingCodeError <: Exception + mi::MethodInstance +end struct InterruptException <: Exception end struct DomainError <: Exception val @@ -358,13 +388,15 @@ struct UndefKeywordError <: Exception var::Symbol end +const typemax_UInt = Intrinsics.sext_int(UInt, 0xFF) +const typemax_Int = Core.Intrinsics.udiv_int(Core.Intrinsics.sext_int(Int, 0xFF), 2) + struct MethodError <: Exception f args world::UInt MethodError(@nospecialize(f), @nospecialize(args), world::UInt) = new(f, args, world) end -const typemax_UInt = ccall(:jl_typemax_uint, Any, (Any,), UInt) MethodError(@nospecialize(f), @nospecialize(args)) = MethodError(f, args, typemax_UInt) struct AssertionError <: Exception @@ -385,6 +417,8 @@ struct InitError <: WrappedException error end +struct PrecompilableError <: Exception end + String(s::String) = s # no constructor yet const Cvoid = Nothing @@ -429,13 +463,17 @@ eval(Core, quote ReturnNode(@nospecialize val) = $(Expr(:new, :ReturnNode, :val)) ReturnNode() = $(Expr(:new, :ReturnNode)) # unassigned val indicates unreachable GotoIfNot(@nospecialize(cond), dest::Int) = $(Expr(:new, :GotoIfNot, :cond, :dest)) + EnterNode(dest::Int) = $(Expr(:new, :EnterNode, :dest)) + EnterNode(dest::Int, @nospecialize(scope)) = $(Expr(:new, :EnterNode, :dest, :scope)) LineNumberNode(l::Int) = $(Expr(:new, :LineNumberNode, :l, nothing)) function LineNumberNode(l::Int, @nospecialize(f)) isa(f, String) && (f = Symbol(f)) return $(Expr(:new, :LineNumberNode, :l, :f)) end - LineInfoNode(mod::Module, @nospecialize(method), file::Symbol, line::Int32, inlined_at::Int32) = - $(Expr(:new, :LineInfoNode, :mod, :method, :file, :line, :inlined_at)) + DebugInfo(def::Union{Method,MethodInstance,Symbol}, linetable::Union{Nothing,DebugInfo}, edges::SimpleVector, codelocs::String) = + $(Expr(:new, :DebugInfo, :def, :linetable, :edges, :codelocs)) + DebugInfo(def::Union{Method,MethodInstance,Symbol}) = + $(Expr(:new, :DebugInfo, :def, nothing, Core.svec(), "")) SlotNumber(n::Int) = $(Expr(:new, :SlotNumber, :n)) PhiNode(edges::Array{Int32, 1}, values::Array{Any, 1}) = $(Expr(:new, :PhiNode, :edges, :values)) PiNode(@nospecialize(val), @nospecialize(typ)) = $(Expr(:new, :PiNode, :val, :typ)) @@ -450,16 +488,25 @@ eval(Core, quote MethodMatch(@nospecialize(spec_types), sparams::SimpleVector, method::Method, fully_covers::Bool) = $(Expr(:new, :MethodMatch, :spec_types, :sparams, :method, :fully_covers)) end) +struct LineInfoNode # legacy support for aiding Serializer.deserialize of old IR + mod::Module + method + file::Symbol + line::Int32 + inlined_at::Int32 + LineInfoNode(mod::Module, @nospecialize(method), file::Symbol, line::Int32, inlined_at::Int32) = new(mod, method, file, line, inlined_at) +end + + function CodeInstance( - mi::MethodInstance, @nospecialize(rettype), @nospecialize(inferred_const), + mi::MethodInstance, owner, @nospecialize(rettype), @nospecialize(exctype), @nospecialize(inferred_const), @nospecialize(inferred), const_flags::Int32, min_world::UInt, max_world::UInt, - ipo_effects::UInt32, effects::UInt32, @nospecialize(argescapes#=::Union{Nothing,Vector{ArgEscapeInfo}}=#), - relocatability::UInt8) + ipo_effects::UInt32, effects::UInt32, @nospecialize(analysis_results), + relocatability::UInt8, edges::DebugInfo) return ccall(:jl_new_codeinst, Ref{CodeInstance}, - (Any, Any, Any, Any, Int32, UInt, UInt, UInt32, UInt32, Any, UInt8), - mi, rettype, inferred_const, inferred, const_flags, min_world, max_world, - ipo_effects, effects, argescapes, - relocatability) + (Any, Any, Any, Any, Any, Any, Int32, UInt, UInt, UInt32, UInt32, Any, UInt8, Any), + mi, owner, rettype, exctype, inferred_const, inferred, const_flags, min_world, max_world, + ipo_effects, effects, analysis_results, relocatability, edges) end GlobalRef(m::Module, s::Symbol) = ccall(:jl_module_globalref, Ref{GlobalRef}, (Any, Any), m, s) Module(name::Symbol=:anonymous, std_imports::Bool=true, default_names::Bool=true) = ccall(:jl_f_new_module, Ref{Module}, (Any, Bool, Bool), name, std_imports, default_names) @@ -473,34 +520,105 @@ const NTuple{N,T} = Tuple{Vararg{T,N}} ## primitive Array constructors struct UndefInitializer end const undef = UndefInitializer() + +# type and dimensionality specified +(self::Type{GenericMemory{kind,T,addrspace}})(::UndefInitializer, m::Int) where {T,addrspace,kind} = + if isdefined(self, :instance) && m === 0 + self.instance + else + ccall(:jl_alloc_genericmemory, Ref{GenericMemory{kind,T,addrspace}}, (Any, Int), self, m) + end +(self::Type{GenericMemory{kind,T,addrspace}})(::UndefInitializer, d::NTuple{1,Int}) where {T,kind,addrspace} = self(undef, getfield(d,1)) +# empty vector constructor +(self::Type{GenericMemory{kind,T,addrspace}})() where {T,kind,addrspace} = self(undef, 0) + +GenericMemoryRef(mem::GenericMemory) = memoryref(mem) +GenericMemoryRef(ref::GenericMemoryRef, i::Integer) = memoryref(ref, Int(i), @_boundscheck) +GenericMemoryRef(mem::GenericMemory, i::Integer) = memoryref(memoryref(mem), Int(i), @_boundscheck) +GenericMemoryRef{kind,<:Any,AS}(mem::GenericMemory{kind,<:Any,AS}) where {kind,AS} = memoryref(mem) +GenericMemoryRef{kind,<:Any,AS}(ref::GenericMemoryRef{kind,<:Any,AS}, i::Integer) where {kind,AS} = memoryref(ref, Int(i), @_boundscheck) +GenericMemoryRef{kind,<:Any,AS}(mem::GenericMemory{kind,<:Any,AS}, i::Integer) where {kind,AS} = memoryref(memoryref(mem), Int(i), @_boundscheck) +GenericMemoryRef{kind,T,AS}(mem::GenericMemory{kind,T,AS}) where {kind,T,AS} = memoryref(mem) +GenericMemoryRef{kind,T,AS}(ref::GenericMemoryRef{kind,T,AS}, i::Integer) where {kind,T,AS} = memoryref(ref, Int(i), @_boundscheck) +GenericMemoryRef{kind,T,AS}(mem::GenericMemory{kind,T,AS}, i::Integer) where {kind,T,AS} = memoryref(memoryref(mem), Int(i), @_boundscheck) + +const Memory{T} = GenericMemory{:not_atomic, T, CPU} +const MemoryRef{T} = GenericMemoryRef{:not_atomic, T, CPU} +const AtomicMemory{T} = GenericMemory{:atomic, T, CPU} +const AtomicMemoryRef{T} = GenericMemoryRef{:atomic, T, CPU} + +# construction helpers for Array +new_as_memoryref(self::Type{GenericMemoryRef{kind,T,addrspace}}, m::Int) where {T,kind,addrspace} = memoryref(fieldtype(self, :mem)(undef, m)) + +# checked-multiply intrinsic function for dimensions +_checked_mul_dims() = 1, false +_checked_mul_dims(m::Int) = m, Intrinsics.ule_int(typemax_Int, m) # equivalently: (m + 1) < 1 +function _checked_mul_dims(m::Int, n::Int) + b = Intrinsics.checked_smul_int(m, n) + a = getfield(b, 1) + ovflw = getfield(b, 2) + ovflw = Intrinsics.or_int(ovflw, Intrinsics.ule_int(typemax_Int, m)) + ovflw = Intrinsics.or_int(ovflw, Intrinsics.ule_int(typemax_Int, n)) + return a, ovflw +end +function _checked_mul_dims(m::Int, d::Int...) + @_foldable_meta # the compiler needs to know this loop terminates + a = m + i = 1 + ovflw = false + while Intrinsics.sle_int(i, nfields(d)) + di = getfield(d, i) + b = Intrinsics.checked_smul_int(a, di) + ovflw = Intrinsics.or_int(ovflw, getfield(b, 2)) + ovflw = Intrinsics.or_int(ovflw, Intrinsics.ule_int(typemax_Int, di)) + a = getfield(b, 1) + i = Intrinsics.add_int(i, 1) + end + return a, ovflw +end + +# convert a set of dims to a length, with overflow checking +checked_dims() = 1 +checked_dims(m::Int) = m # defer this check to Memory constructor instead +function checked_dims(d::Int...) + b = _checked_mul_dims(d...) + getfield(b, 2) && throw(ArgumentError("invalid Array dimensions")) + return getfield(b, 1) +end + # type and dimensionality specified, accepting dims as series of Ints -Array{T,1}(::UndefInitializer, m::Int) where {T} = - ccall(:jl_alloc_array_1d, Array{T,1}, (Any, Int), Array{T,1}, m) -Array{T,2}(::UndefInitializer, m::Int, n::Int) where {T} = - ccall(:jl_alloc_array_2d, Array{T,2}, (Any, Int, Int), Array{T,2}, m, n) -Array{T,3}(::UndefInitializer, m::Int, n::Int, o::Int) where {T} = - ccall(:jl_alloc_array_3d, Array{T,3}, (Any, Int, Int, Int), Array{T,3}, m, n, o) -Array{T,N}(::UndefInitializer, d::Vararg{Int,N}) where {T,N} = - ccall(:jl_new_array, Array{T,N}, (Any, Any), Array{T,N}, d) +eval(Core, :(function (self::Type{Array{T,1}})(::UndefInitializer, m::Int) where {T} + mem = fieldtype(fieldtype(self, :ref), :mem)(undef, m) + return $(Expr(:new, :self, :(memoryref(mem)), :((m,)))) +end)) +eval(Core, :(function (self::Type{Array{T,2}})(::UndefInitializer, m::Int, n::Int) where {T} + return $(Expr(:new, :self, :(new_as_memoryref(fieldtype(self, :ref), checked_dims(m, n))), :((m, n)))) +end)) +eval(Core, :(function (self::Type{Array{T,3}})(::UndefInitializer, m::Int, n::Int, o::Int) where {T} + return $(Expr(:new, :self, :(new_as_memoryref(fieldtype(self, :ref), checked_dims(m, n, o))), :((m, n, o)))) +end)) +eval(Core, :(function (self::Type{Array{T, N}})(::UndefInitializer, d::Vararg{Int, N}) where {T, N} + return $(Expr(:new, :self, :(new_as_memoryref(fieldtype(self, :ref), checked_dims(d...))), :d)) +end)) # type and dimensionality specified, accepting dims as tuples of Ints -Array{T,1}(::UndefInitializer, d::NTuple{1,Int}) where {T} = Array{T,1}(undef, getfield(d,1)) -Array{T,2}(::UndefInitializer, d::NTuple{2,Int}) where {T} = Array{T,2}(undef, getfield(d,1), getfield(d,2)) -Array{T,3}(::UndefInitializer, d::NTuple{3,Int}) where {T} = Array{T,3}(undef, getfield(d,1), getfield(d,2), getfield(d,3)) -Array{T,N}(::UndefInitializer, d::NTuple{N,Int}) where {T,N} = ccall(:jl_new_array, Array{T,N}, (Any, Any), Array{T,N}, d) +(self::Type{Array{T,1}})(::UndefInitializer, d::NTuple{1, Int}) where {T} = self(undef, getfield(d, 1)) +(self::Type{Array{T,2}})(::UndefInitializer, d::NTuple{2, Int}) where {T} = self(undef, getfield(d, 1), getfield(d, 2)) +(self::Type{Array{T,3}})(::UndefInitializer, d::NTuple{3, Int}) where {T} = self(undef, getfield(d, 1), getfield(d, 2), getfield(d, 3)) +(self::Type{Array{T,N}})(::UndefInitializer, d::NTuple{N, Int}) where {T, N} = self(undef, d...) # type but not dimensionality specified -Array{T}(::UndefInitializer, m::Int) where {T} = Array{T,1}(undef, m) -Array{T}(::UndefInitializer, m::Int, n::Int) where {T} = Array{T,2}(undef, m, n) -Array{T}(::UndefInitializer, m::Int, n::Int, o::Int) where {T} = Array{T,3}(undef, m, n, o) -Array{T}(::UndefInitializer, d::NTuple{N,Int}) where {T,N} = Array{T,N}(undef, d) +Array{T}(::UndefInitializer, m::Int) where {T} = Array{T, 1}(undef, m) +Array{T}(::UndefInitializer, m::Int, n::Int) where {T} = Array{T, 2}(undef, m, n) +Array{T}(::UndefInitializer, m::Int, n::Int, o::Int) where {T} = Array{T, 3}(undef, m, n, o) +Array{T}(::UndefInitializer, d::NTuple{N, Int}) where {T, N} = Array{T, N}(undef, d) # empty vector constructor -Array{T,1}() where {T} = Array{T,1}(undef, 0) +(self::Type{Array{T, 1}})() where {T} = self(undef, 0) -(Array{T,N} where T)(x::AbstractArray{S,N}) where {S,N} = Array{S,N}(x) +(Array{T, N} where T)(x::AbstractArray{S, N}) where {S, N} = Array{S, N}(x) -Array(A::AbstractArray{T,N}) where {T,N} = Array{T,N}(A) -Array{T}(A::AbstractArray{S,N}) where {T,N,S} = Array{T,N}(A) +Array(A::AbstractArray{T, N}) where {T, N} = Array{T, N}(A) +Array{T}(A::AbstractArray{S, N}) where {T, N, S} = Array{T, N}(A) -AbstractArray{T}(A::AbstractArray{S,N}) where {T,S,N} = AbstractArray{T,N}(A) +AbstractArray{T}(A::AbstractArray{S, N}) where {T, S, N} = AbstractArray{T, N}(A) # primitive Symbol constructors @@ -515,9 +633,9 @@ function Symbol(s::String) @noinline return _Symbol(ccall(:jl_string_ptr, Ptr{UInt8}, (Any,), s), sizeof(s), s) end -function Symbol(a::Array{UInt8,1}) +function Symbol(a::Array{UInt8, 1}) @noinline - return _Symbol(ccall(:jl_array_ptr, Ptr{UInt8}, (Any,), a), Intrinsics.arraylen(a), a) + return _Symbol(bitcast(Ptr{UInt8}, a.ref.ptr_or_offset), getfield(a.size, 1), a.ref.mem) end Symbol(s::Symbol) = s @@ -526,13 +644,13 @@ module IR export CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, ReturnNode, NewvarNode, SSAValue, SlotNumber, Argument, - PiNode, PhiNode, PhiCNode, UpsilonNode, LineInfoNode, - Const, PartialStruct, InterConditional + PiNode, PhiNode, PhiCNode, UpsilonNode, DebugInfo, + Const, PartialStruct, InterConditional, EnterNode using Core: CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, ReturnNode, NewvarNode, SSAValue, SlotNumber, Argument, - PiNode, PhiNode, PhiCNode, UpsilonNode, LineInfoNode, - Const, PartialStruct, InterConditional + PiNode, PhiNode, PhiCNode, UpsilonNode, DebugInfo, + Const, PartialStruct, InterConditional, EnterNode end # module IR @@ -545,8 +663,17 @@ end macro __doc__(x) return Expr(:escape, Expr(:block, Expr(:meta, :doc), x)) end -atdoc = (source, mod, str, expr) -> Expr(:escape, expr) -atdoc!(λ) = global atdoc = λ + +isbasicdoc(@nospecialize x) = (isa(x, Expr) && x.head === :.) || isa(x, Union{QuoteNode, Symbol}) +iscallexpr(ex::Expr) = (isa(ex, Expr) && ex.head === :where) ? iscallexpr(ex.args[1]) : (isa(ex, Expr) && ex.head === :call) +iscallexpr(ex) = false +function ignoredoc(source, mod, str, expr) + (isbasicdoc(expr) || iscallexpr(expr)) && return Expr(:escape, nothing) + Expr(:escape, expr) +end + +global atdoc = ignoredoc +atdoc!(λ) = global atdoc = λ # macros for big integer syntax macro int128_str end @@ -624,8 +751,8 @@ eval(Core, :(NamedTuple{names}(args::Tuple) where {names} = using .Intrinsics: sle_int, add_int -eval(Core, :(NamedTuple{names,T}(args::T) where {names, T <: Tuple} = - $(Expr(:splatnew, :(NamedTuple{names,T}), :args)))) +eval(Core, :((NT::Type{NamedTuple{names,T}})(args::T) where {names, T <: Tuple} = + $(Expr(:splatnew, :NT, :args)))) # constructors for built-in types @@ -641,12 +768,14 @@ function is_top_bit_set(x::Union{Int8,UInt8}) eq_int(lshr_int(x, 7), trunc_int(typeof(x), 1)) end -#TODO delete this function (but see #48097): -throw_inexacterror(args...) = throw(InexactError(args...)) +# n.b. This function exists for CUDA to overload to configure error behavior (see #48097) +throw_inexacterror(func::Symbol, to, val) = throw(InexactError(func, to, val)) -function check_top_bit(::Type{To}, x) where {To} +function check_sign_bit(::Type{To}, x) where {To} @inline - is_top_bit_set(x) && throw_inexacterror(:check_top_bit, To, x) + # the top bit is the sign bit of x but "sign bit" sounds better in stacktraces + # n.b. if x is signed, then sizeof(x) === sizeof(To), otherwise sizeof(x) >= sizeof(To) + is_top_bit_set(x) && throw_inexacterror(sizeof(x) === sizeof(To) ? :convert : :trunc, To, x) x end @@ -671,11 +800,11 @@ toInt8(x::Int16) = checked_trunc_sint(Int8, x) toInt8(x::Int32) = checked_trunc_sint(Int8, x) toInt8(x::Int64) = checked_trunc_sint(Int8, x) toInt8(x::Int128) = checked_trunc_sint(Int8, x) -toInt8(x::UInt8) = bitcast(Int8, check_top_bit(Int8, x)) -toInt8(x::UInt16) = checked_trunc_sint(Int8, check_top_bit(Int8, x)) -toInt8(x::UInt32) = checked_trunc_sint(Int8, check_top_bit(Int8, x)) -toInt8(x::UInt64) = checked_trunc_sint(Int8, check_top_bit(Int8, x)) -toInt8(x::UInt128) = checked_trunc_sint(Int8, check_top_bit(Int8, x)) +toInt8(x::UInt8) = bitcast(Int8, check_sign_bit(Int8, x)) +toInt8(x::UInt16) = checked_trunc_sint(Int8, check_sign_bit(Int8, x)) +toInt8(x::UInt32) = checked_trunc_sint(Int8, check_sign_bit(Int8, x)) +toInt8(x::UInt64) = checked_trunc_sint(Int8, check_sign_bit(Int8, x)) +toInt8(x::UInt128) = checked_trunc_sint(Int8, check_sign_bit(Int8, x)) toInt8(x::Bool) = and_int(bitcast(Int8, x), Int8(1)) toInt16(x::Int8) = sext_int(Int16, x) toInt16(x::Int16) = x @@ -683,10 +812,10 @@ toInt16(x::Int32) = checked_trunc_sint(Int16, x) toInt16(x::Int64) = checked_trunc_sint(Int16, x) toInt16(x::Int128) = checked_trunc_sint(Int16, x) toInt16(x::UInt8) = zext_int(Int16, x) -toInt16(x::UInt16) = bitcast(Int16, check_top_bit(Int16, x)) -toInt16(x::UInt32) = checked_trunc_sint(Int16, check_top_bit(Int16, x)) -toInt16(x::UInt64) = checked_trunc_sint(Int16, check_top_bit(Int16, x)) -toInt16(x::UInt128) = checked_trunc_sint(Int16, check_top_bit(Int16, x)) +toInt16(x::UInt16) = bitcast(Int16, check_sign_bit(Int16, x)) +toInt16(x::UInt32) = checked_trunc_sint(Int16, check_sign_bit(Int16, x)) +toInt16(x::UInt64) = checked_trunc_sint(Int16, check_sign_bit(Int16, x)) +toInt16(x::UInt128) = checked_trunc_sint(Int16, check_sign_bit(Int16, x)) toInt16(x::Bool) = and_int(zext_int(Int16, x), Int16(1)) toInt32(x::Int8) = sext_int(Int32, x) toInt32(x::Int16) = sext_int(Int32, x) @@ -695,9 +824,9 @@ toInt32(x::Int64) = checked_trunc_sint(Int32, x) toInt32(x::Int128) = checked_trunc_sint(Int32, x) toInt32(x::UInt8) = zext_int(Int32, x) toInt32(x::UInt16) = zext_int(Int32, x) -toInt32(x::UInt32) = bitcast(Int32, check_top_bit(Int32, x)) -toInt32(x::UInt64) = checked_trunc_sint(Int32, check_top_bit(Int32, x)) -toInt32(x::UInt128) = checked_trunc_sint(Int32, check_top_bit(Int32, x)) +toInt32(x::UInt32) = bitcast(Int32, check_sign_bit(Int32, x)) +toInt32(x::UInt64) = checked_trunc_sint(Int32, check_sign_bit(Int32, x)) +toInt32(x::UInt128) = checked_trunc_sint(Int32, check_sign_bit(Int32, x)) toInt32(x::Bool) = and_int(zext_int(Int32, x), Int32(1)) toInt64(x::Int8) = sext_int(Int64, x) toInt64(x::Int16) = sext_int(Int64, x) @@ -707,8 +836,8 @@ toInt64(x::Int128) = checked_trunc_sint(Int64, x) toInt64(x::UInt8) = zext_int(Int64, x) toInt64(x::UInt16) = zext_int(Int64, x) toInt64(x::UInt32) = zext_int(Int64, x) -toInt64(x::UInt64) = bitcast(Int64, check_top_bit(Int64, x)) -toInt64(x::UInt128) = checked_trunc_sint(Int64, check_top_bit(Int64, x)) +toInt64(x::UInt64) = bitcast(Int64, check_sign_bit(Int64, x)) +toInt64(x::UInt128) = checked_trunc_sint(Int64, check_sign_bit(Int64, x)) toInt64(x::Bool) = and_int(zext_int(Int64, x), Int64(1)) toInt128(x::Int8) = sext_int(Int128, x) toInt128(x::Int16) = sext_int(Int128, x) @@ -719,9 +848,9 @@ toInt128(x::UInt8) = zext_int(Int128, x) toInt128(x::UInt16) = zext_int(Int128, x) toInt128(x::UInt32) = zext_int(Int128, x) toInt128(x::UInt64) = zext_int(Int128, x) -toInt128(x::UInt128) = bitcast(Int128, check_top_bit(Int128, x)) +toInt128(x::UInt128) = bitcast(Int128, check_sign_bit(Int128, x)) toInt128(x::Bool) = and_int(zext_int(Int128, x), Int128(1)) -toUInt8(x::Int8) = bitcast(UInt8, check_top_bit(UInt8, x)) +toUInt8(x::Int8) = bitcast(UInt8, check_sign_bit(UInt8, x)) toUInt8(x::Int16) = checked_trunc_uint(UInt8, x) toUInt8(x::Int32) = checked_trunc_uint(UInt8, x) toUInt8(x::Int64) = checked_trunc_uint(UInt8, x) @@ -732,8 +861,8 @@ toUInt8(x::UInt32) = checked_trunc_uint(UInt8, x) toUInt8(x::UInt64) = checked_trunc_uint(UInt8, x) toUInt8(x::UInt128) = checked_trunc_uint(UInt8, x) toUInt8(x::Bool) = and_int(bitcast(UInt8, x), UInt8(1)) -toUInt16(x::Int8) = sext_int(UInt16, check_top_bit(UInt16, x)) -toUInt16(x::Int16) = bitcast(UInt16, check_top_bit(UInt16, x)) +toUInt16(x::Int8) = sext_int(UInt16, check_sign_bit(UInt16, x)) +toUInt16(x::Int16) = bitcast(UInt16, check_sign_bit(UInt16, x)) toUInt16(x::Int32) = checked_trunc_uint(UInt16, x) toUInt16(x::Int64) = checked_trunc_uint(UInt16, x) toUInt16(x::Int128) = checked_trunc_uint(UInt16, x) @@ -743,9 +872,9 @@ toUInt16(x::UInt32) = checked_trunc_uint(UInt16, x) toUInt16(x::UInt64) = checked_trunc_uint(UInt16, x) toUInt16(x::UInt128) = checked_trunc_uint(UInt16, x) toUInt16(x::Bool) = and_int(zext_int(UInt16, x), UInt16(1)) -toUInt32(x::Int8) = sext_int(UInt32, check_top_bit(UInt32, x)) -toUInt32(x::Int16) = sext_int(UInt32, check_top_bit(UInt32, x)) -toUInt32(x::Int32) = bitcast(UInt32, check_top_bit(UInt32, x)) +toUInt32(x::Int8) = sext_int(UInt32, check_sign_bit(UInt32, x)) +toUInt32(x::Int16) = sext_int(UInt32, check_sign_bit(UInt32, x)) +toUInt32(x::Int32) = bitcast(UInt32, check_sign_bit(UInt32, x)) toUInt32(x::Int64) = checked_trunc_uint(UInt32, x) toUInt32(x::Int128) = checked_trunc_uint(UInt32, x) toUInt32(x::UInt8) = zext_int(UInt32, x) @@ -754,10 +883,10 @@ toUInt32(x::UInt32) = x toUInt32(x::UInt64) = checked_trunc_uint(UInt32, x) toUInt32(x::UInt128) = checked_trunc_uint(UInt32, x) toUInt32(x::Bool) = and_int(zext_int(UInt32, x), UInt32(1)) -toUInt64(x::Int8) = sext_int(UInt64, check_top_bit(UInt64, x)) -toUInt64(x::Int16) = sext_int(UInt64, check_top_bit(UInt64, x)) -toUInt64(x::Int32) = sext_int(UInt64, check_top_bit(UInt64, x)) -toUInt64(x::Int64) = bitcast(UInt64, check_top_bit(UInt64, x)) +toUInt64(x::Int8) = sext_int(UInt64, check_sign_bit(UInt64, x)) +toUInt64(x::Int16) = sext_int(UInt64, check_sign_bit(UInt64, x)) +toUInt64(x::Int32) = sext_int(UInt64, check_sign_bit(UInt64, x)) +toUInt64(x::Int64) = bitcast(UInt64, check_sign_bit(UInt64, x)) toUInt64(x::Int128) = checked_trunc_uint(UInt64, x) toUInt64(x::UInt8) = zext_int(UInt64, x) toUInt64(x::UInt16) = zext_int(UInt64, x) @@ -765,11 +894,11 @@ toUInt64(x::UInt32) = zext_int(UInt64, x) toUInt64(x::UInt64) = x toUInt64(x::UInt128) = checked_trunc_uint(UInt64, x) toUInt64(x::Bool) = and_int(zext_int(UInt64, x), UInt64(1)) -toUInt128(x::Int8) = sext_int(UInt128, check_top_bit(UInt128, x)) -toUInt128(x::Int16) = sext_int(UInt128, check_top_bit(UInt128, x)) -toUInt128(x::Int32) = sext_int(UInt128, check_top_bit(UInt128, x)) -toUInt128(x::Int64) = sext_int(UInt128, check_top_bit(UInt128, x)) -toUInt128(x::Int128) = bitcast(UInt128, check_top_bit(UInt128, x)) +toUInt128(x::Int8) = sext_int(UInt128, check_sign_bit(UInt128, x)) +toUInt128(x::Int16) = sext_int(UInt128, check_sign_bit(UInt128, x)) +toUInt128(x::Int32) = sext_int(UInt128, check_sign_bit(UInt128, x)) +toUInt128(x::Int64) = sext_int(UInt128, check_sign_bit(UInt128, x)) +toUInt128(x::Int128) = bitcast(UInt128, check_sign_bit(UInt128, x)) toUInt128(x::UInt8) = zext_int(UInt128, x) toUInt128(x::UInt16) = zext_int(UInt128, x) toUInt128(x::UInt32) = zext_int(UInt128, x) @@ -798,8 +927,8 @@ if Int === Int32 Int64(x::Ptr) = Int64(UInt32(x)) UInt64(x::Ptr) = UInt64(UInt32(x)) end -Ptr{T}(x::Union{Int,UInt,Ptr}) where {T} = bitcast(Ptr{T}, x) -Ptr{T}() where {T} = Ptr{T}(0) +(PT::Type{Ptr{T}} where T)(x::Union{Int,UInt,Ptr}=0) = bitcast(PT, x) +(AS::Type{AddrSpace{Backend}} where Backend)(x::UInt8) = bitcast(AS, x) Signed(x::UInt8) = Int8(x) Unsigned(x::Int8) = UInt8(x) @@ -853,8 +982,23 @@ struct Pair{A, B} end function _hasmethod(@nospecialize(tt)) # this function has a special tfunc - world = ccall(:jl_get_tls_world_age, UInt, ()) + world = ccall(:jl_get_tls_world_age, UInt, ()) # tls_world_age() return Intrinsics.not_int(ccall(:jl_gf_invoke_lookup, Any, (Any, Any, UInt), tt, nothing, world) === nothing) end +# for backward compat +arrayref(inbounds::Bool, A::Array, i::Int...) = Main.Base.getindex(A, i...) +const_arrayref(inbounds::Bool, A::Array, i::Int...) = Main.Base.getindex(A, i...) +arrayset(inbounds::Bool, A::Array{T}, x::Any, i::Int...) where {T} = Main.Base.setindex!(A, x::T, i...) +arraysize(a::Array) = a.size +arraysize(a::Array, i::Int) = sle_int(i, nfields(a.size)) ? getfield(a.size, i) : 1 +export arrayref, arrayset, arraysize, const_arrayref +const check_top_bit = check_sign_bit + +# For convenience +EnterNode(old::EnterNode, new_dest::Int) = isdefined(old, :scope) ? + EnterNode(new_dest, old.scope) : EnterNode(new_dest) + +include(Core, "optimized_generics.jl") + ccall(:jl_set_istopmod, Cvoid, (Any, Bool), Core, true) diff --git a/base/broadcast.jl b/base/broadcast.jl index 43044f9b7d6ed..1928535770f92 100644 --- a/base/broadcast.jl +++ b/base/broadcast.jl @@ -196,14 +196,18 @@ const andand = AndAnd() broadcasted(::AndAnd, a, b) = broadcasted((a, b) -> a && b, a, b) function broadcasted(::AndAnd, a, bc::Broadcasted) bcf = flatten(bc) - broadcasted((a, args...) -> a && bcf.f(args...), a, bcf.args...) + # Vararg type signature to specialize on args count. This is necessary for performance + # and innexpensive because this should only ever get called with 1+N = length(bc.args) + broadcasted(((a, args::Vararg{Any, N}) where {N}) -> a && bcf.f(args...), a, bcf.args...) end struct OrOr end const oror = OrOr() broadcasted(::OrOr, a, b) = broadcasted((a, b) -> a || b, a, b) function broadcasted(::OrOr, a, bc::Broadcasted) bcf = flatten(bc) - broadcasted((a, args...) -> a || bcf.f(args...), a, bcf.args...) + # Vararg type signature to specialize on args count. This is necessary for performance + # and innexpensive because this should only ever get called with 1+N = length(bc.args) + broadcasted(((a, args::Vararg{Any, N}) where {N}) -> a || bcf.f(args...), a, bcf.args...) end Base.convert(::Type{Broadcasted{NewStyle}}, bc::Broadcasted{<:Any,Axes,F,Args}) where {NewStyle,Axes,F,Args} = @@ -338,13 +342,14 @@ function flatten(bc::Broadcasted) # concatenate the nested arguments into {a, b, c, d} args = cat_nested(bc) # build a tuple of functions `makeargs`. Its elements take - # the whole "flat" argument list and and generate the appropriate + # the whole "flat" argument list and generate the appropriate # input arguments for the broadcasted function `f`, e.g., # makeargs[1] = ((w, x, y, z)) -> w # makeargs[2] = ((w, x, y, z)) -> g(x, y) # makeargs[3] = ((w, x, y, z)) -> z makeargs = make_makeargs(bc.args) f = Base.maybeconstructor(bc.f) + # TODO: consider specializing on args... if performance problems emerge: newf = (args...) -> (@inline; f(prepare_args(makeargs, args)...)) return Broadcasted(bc.style, newf, args, bc.axes) end @@ -379,7 +384,7 @@ make_makeargs(args::Tuple) = _make_makeargs(args, 1)[1] end _make_makeargs(::Tuple{}, n::Int) = (), n -# A help struct to store the flattened index staticly +# A help struct to store the flattened index statically struct Pick{N} <: Function end (::Pick{N})(@nospecialize(args::Tuple)) where {N} = args[N] @@ -419,6 +424,10 @@ function combine_styles end combine_styles() = DefaultArrayStyle{0}() combine_styles(c) = result_style(BroadcastStyle(typeof(c))) +function combine_styles(bc::Broadcasted) + bc.style isa Union{Nothing,Unknown} || return bc.style + throw(ArgumentError("Broadcasted{Unknown} wrappers do not have a style assigned")) +end combine_styles(c1, c2) = result_style(combine_styles(c1), combine_styles(c2)) @inline combine_styles(c1, c2, cs...) = result_style(combine_styles(c1), combine_styles(c2, cs...)) @@ -513,10 +522,10 @@ function _bcs(shape::Tuple, newshape::Tuple) return (_bcs1(shape[1], newshape[1]), _bcs(tail(shape), tail(newshape))...) end # _bcs1 handles the logic for a single dimension -_bcs1(a::Integer, b::Integer) = a == 1 ? b : (b == 1 ? a : (a == b ? a : throw(DimensionMismatch("arrays could not be broadcast to a common size; got a dimension with lengths $a and $b")))) -_bcs1(a::Integer, b) = a == 1 ? b : (first(b) == 1 && last(b) == a ? b : throw(DimensionMismatch("arrays could not be broadcast to a common size; got a dimension with lengths $a and $(length(b))"))) +_bcs1(a::Integer, b::Integer) = a == 1 ? b : (b == 1 ? a : (a == b ? a : throw(DimensionMismatch(LazyString("arrays could not be broadcast to a common size; got a dimension with lengths ", a, " and ", b))))) +_bcs1(a::Integer, b) = a == 1 ? b : (first(b) == 1 && last(b) == a ? b : throw(DimensionMismatch(LazyString("arrays could not be broadcast to a common size; got a dimension with lengths ", a, " and ", length(b))))) _bcs1(a, b::Integer) = _bcs1(b, a) -_bcs1(a, b) = _bcsm(b, a) ? axistype(b, a) : (_bcsm(a, b) ? axistype(a, b) : throw(DimensionMismatch("arrays could not be broadcast to a common size; got a dimension with lengths $(length(a)) and $(length(b))"))) +_bcs1(a, b) = _bcsm(b, a) ? axistype(b, a) : _bcsm(a, b) ? axistype(a, b) : throw(DimensionMismatch(LazyString("arrays could not be broadcast to a common size: a has axes ", a, " and b has axes ", b))) # _bcsm tests whether the second index is consistent with the first _bcsm(a, b) = a == b || length(b) == 1 _bcsm(a, b::Number) = b == 1 @@ -567,15 +576,15 @@ an `Int`. Any remaining indices in `I` beyond the length of the `keep` tuple are truncated. The `keep` and `default` tuples may be created by `newindexer(argument)`. """ -Base.@propagate_inbounds newindex(arg, I::CartesianIndex) = CartesianIndex(_newindex(axes(arg), I.I)) -Base.@propagate_inbounds newindex(arg, I::Integer) = CartesianIndex(_newindex(axes(arg), (I,))) +Base.@propagate_inbounds newindex(arg, I::CartesianIndex) = to_index(_newindex(axes(arg), I.I)) +Base.@propagate_inbounds newindex(arg, I::Integer) = to_index(_newindex(axes(arg), (I,))) Base.@propagate_inbounds _newindex(ax::Tuple, I::Tuple) = (ifelse(length(ax[1]) == 1, ax[1][1], I[1]), _newindex(tail(ax), tail(I))...) Base.@propagate_inbounds _newindex(ax::Tuple{}, I::Tuple) = () Base.@propagate_inbounds _newindex(ax::Tuple, I::Tuple{}) = (ax[1][1], _newindex(tail(ax), ())...) Base.@propagate_inbounds _newindex(ax::Tuple{}, I::Tuple{}) = () # If dot-broadcasting were already defined, this would be `ifelse.(keep, I, Idefault)`. -@inline newindex(I::CartesianIndex, keep, Idefault) = CartesianIndex(_newindex(I.I, keep, Idefault)) +@inline newindex(I::CartesianIndex, keep, Idefault) = to_index(_newindex(I.I, keep, Idefault)) @inline newindex(i::Integer, keep::Tuple, idefault) = ifelse(keep[1], i, idefault[1]) @inline newindex(i::Integer, keep::Tuple{}, idefault) = CartesianIndex(()) @inline _newindex(I, keep, Idefault) = @@ -595,18 +604,14 @@ Base.@propagate_inbounds _newindex(ax::Tuple{}, I::Tuple{}) = () (Base.length(ind1)::Integer != 1, keep...), (first(ind1), Idefault...) end -@inline function Base.getindex(bc::Broadcasted, I::Union{Integer,CartesianIndex}) +@inline function Base.getindex(bc::Broadcasted, Is::Vararg{Union{Integer,CartesianIndex},N}) where {N} + I = to_index(Base.IteratorsMD.flatten(Is)) @boundscheck checkbounds(bc, I) @inbounds _broadcast_getindex(bc, I) end -Base.@propagate_inbounds Base.getindex( - bc::Broadcasted, - i1::Union{Integer,CartesianIndex}, - i2::Union{Integer,CartesianIndex}, - I::Union{Integer,CartesianIndex}..., -) = - bc[CartesianIndex((i1, i2, I...))] -Base.@propagate_inbounds Base.getindex(bc::Broadcasted) = bc[CartesianIndex(())] +to_index(::Tuple{}) = CartesianIndex() +to_index(Is::Tuple{Any}) = Is[1] +to_index(Is::Tuple) = CartesianIndex(Is) @inline Base.checkbounds(bc::Broadcasted, I::Union{Integer,CartesianIndex}) = Base.checkbounds_indices(Bool, axes(bc), (I,)) || Base.throw_boundserror(bc, (I,)) @@ -971,26 +976,41 @@ preprocess(dest, x) = extrude(broadcast_unalias(dest, x)) end # Performance optimization: for BitArray outputs, we cache the result -# in a "small" Vector{Bool}, and then copy in chunks into the output +# in a 64-bit register before writing into memory (to bypass LSQ) @inline function copyto!(dest::BitArray, bc::Broadcasted{Nothing}) axes(dest) == axes(bc) || throwdm(axes(dest), axes(bc)) ischunkedbroadcast(dest, bc) && return chunkedcopyto!(dest, bc) - length(dest) < 256 && return invoke(copyto!, Tuple{AbstractArray, Broadcasted{Nothing}}, dest, bc) - tmp = Vector{Bool}(undef, bitcache_size) - destc = dest.chunks - cind = 1 + ndims(dest) == 0 && (dest[] = bc[]; return dest) bc′ = preprocess(dest, bc) - @inbounds for P in Iterators.partition(eachindex(bc′), bitcache_size) - ind = 1 - @simd for I in P - tmp[ind] = bc′[I] - ind += 1 + ax = axes(bc′) + ax1, out = ax[1], CartesianIndices(tail(ax)) + destc, indc = dest.chunks, 0 + bitst, remain = 0, UInt64(0) + for I in out + i = first(ax1) - 1 + if ndims(bc) == 1 || bitst >= 64 - length(ax1) + if ndims(bc) > 1 && bitst != 0 + @inbounds @simd for j = bitst:63 + remain |= UInt64(convert(Bool, bc′[i+=1, I])) << (j & 63) + end + @inbounds destc[indc+=1] = remain + bitst, remain = 0, UInt64(0) + end + while i <= last(ax1) - 64 + z = UInt64(0) + @inbounds @simd for j = 0:63 + z |= UInt64(convert(Bool, bc′[i+=1, I])) << (j & 63) + end + @inbounds destc[indc+=1] = z + end end - @simd for i in ind:bitcache_size - tmp[i] = false + @inbounds @simd for j = i+1:last(ax1) + remain |= UInt64(convert(Bool, bc′[j, I])) << (bitst & 63) + bitst += 1 end - dumpbitcache(destc, cind, tmp) - cind += bitcache_chunks + end + @inbounds if bitst != 0 + destc[indc+=1] = remain end return dest end @@ -1042,7 +1062,7 @@ end @noinline throwdm(axdest, axsrc) = - throw(DimensionMismatch("destination axes $axdest are not compatible with source axes $axsrc")) + throw(DimensionMismatch(LazyString("destination axes ", axdest, " are not compatible with source axes ", axsrc))) function restart_copyto_nonleaf!(newdest, dest, bc, val, I, iter, state, count) # Function barrier that makes the copying to newdest type stable diff --git a/base/c.jl b/base/c.jl index bdcb030d799e0..eb552d3507662 100644 --- a/base/c.jl +++ b/base/c.jl @@ -2,7 +2,7 @@ # definitions related to C interface -import Core.Intrinsics: cglobal, bitcast +import Core.Intrinsics: cglobal """ cglobal((symbol, library) [, type=Cvoid]) @@ -91,7 +91,7 @@ Equivalent to the native `char` c-type. Cchar # The ccall here is equivalent to Sys.iswindows(), but that's not defined yet -@static if ccall(:jl_get_UNAME, Any, ()) === :NT +if ccall(:jl_get_UNAME, Any, ()) === :NT const Clong = Int32 const Culong = UInt32 const Cwchar_t = UInt16 @@ -122,32 +122,7 @@ Equivalent to the native `wchar_t` c-type ([`Int32`](@ref)). """ Cwchar_t -""" - Cwstring - -A C-style string composed of the native wide character type -[`Cwchar_t`](@ref)s. `Cwstring`s are NUL-terminated. For -C-style strings composed of the native character -type, see [`Cstring`](@ref). For more information -about string interoperability with C, see the -[manual](@ref man-bits-types). - -""" -Cwstring - -""" - Cstring - -A C-style string composed of the native character type -[`Cchar`](@ref)s. `Cstring`s are NUL-terminated. For -C-style strings composed of the native wide character -type, see [`Cwstring`](@ref). For more information -about string interoperability with C, see the -[manual](@ref man-bits-types). -""" -Cstring - -@static if ccall(:jl_get_UNAME, Any, ()) !== :NT +if ccall(:jl_get_UNAME, Any, ()) !== :NT const sizeof_mode_t = ccall(:jl_sizeof_mode_t, Cint, ()) if sizeof_mode_t == 2 const Cmode_t = Int16 @@ -155,292 +130,11 @@ Cstring const Cmode_t = Int32 elseif sizeof_mode_t == 8 const Cmode_t = Int64 + else + error("invalid sizeof mode_t") end end -# construction from pointers -Cstring(p::Union{Ptr{Int8},Ptr{UInt8},Ptr{Cvoid}}) = bitcast(Cstring, p) -Cwstring(p::Union{Ptr{Cwchar_t},Ptr{Cvoid}}) = bitcast(Cwstring, p) -Ptr{T}(p::Cstring) where {T<:Union{Int8,UInt8,Cvoid}} = bitcast(Ptr{T}, p) -Ptr{T}(p::Cwstring) where {T<:Union{Cwchar_t,Cvoid}} = bitcast(Ptr{Cwchar_t}, p) - -convert(::Type{Cstring}, p::Union{Ptr{Int8},Ptr{UInt8},Ptr{Cvoid}}) = Cstring(p) -convert(::Type{Cwstring}, p::Union{Ptr{Cwchar_t},Ptr{Cvoid}}) = Cwstring(p) -convert(::Type{Ptr{T}}, p::Cstring) where {T<:Union{Int8,UInt8,Cvoid}} = Ptr{T}(p) -convert(::Type{Ptr{T}}, p::Cwstring) where {T<:Union{Cwchar_t,Cvoid}} = Ptr{T}(p) - -""" - pointer(array [, index]) - -Get the native address of an array or string, optionally at a given location `index`. - -This function is "unsafe". Be careful to ensure that a Julia reference to -`array` exists as long as this pointer will be used. The [`GC.@preserve`](@ref) -macro should be used to protect the `array` argument from garbage collection -within a given block of code. - -Calling [`Ref(array[, index])`](@ref Ref) is generally preferable to this function as it guarantees validity. -""" -function pointer end - -pointer(p::Cstring) = convert(Ptr{Cchar}, p) -pointer(p::Cwstring) = convert(Ptr{Cwchar_t}, p) - -# comparisons against pointers (mainly to support `cstr==C_NULL`) -==(x::Union{Cstring,Cwstring}, y::Ptr) = pointer(x) == y -==(x::Ptr, y::Union{Cstring,Cwstring}) = x == pointer(y) - -unsafe_string(s::Cstring) = unsafe_string(convert(Ptr{UInt8}, s)) - -# convert strings to String etc. to pass as pointers -cconvert(::Type{Cstring}, s::String) = s -cconvert(::Type{Cstring}, s::AbstractString) = - cconvert(Cstring, String(s)::String) - -function cconvert(::Type{Cwstring}, s::AbstractString) - v = transcode(Cwchar_t, String(s)) - push!(v, 0) - return v -end - -eltype(::Type{Cstring}) = Cchar -eltype(::Type{Cwstring}) = Cwchar_t - -containsnul(p::Ptr, len) = - C_NULL != ccall(:memchr, Ptr{Cchar}, (Ptr{Cchar}, Cint, Csize_t), p, 0, len) -containsnul(s::String) = containsnul(unsafe_convert(Ptr{Cchar}, s), sizeof(s)) -containsnul(s::AbstractString) = '\0' in s - -function unsafe_convert(::Type{Cstring}, s::String) - p = unsafe_convert(Ptr{Cchar}, s) - containsnul(p, sizeof(s)) && - throw(ArgumentError("embedded NULs are not allowed in C strings: $(repr(s))")) - return Cstring(p) -end - -function unsafe_convert(::Type{Cwstring}, v::Vector{Cwchar_t}) - for i = 1:length(v)-1 - v[i] == 0 && - throw(ArgumentError("embedded NULs are not allowed in C strings: $(repr(v))")) - end - v[end] == 0 || - throw(ArgumentError("C string data must be NUL terminated: $(repr(v))")) - p = unsafe_convert(Ptr{Cwchar_t}, v) - return Cwstring(p) -end - -# symbols are guaranteed not to contain embedded NUL -cconvert(::Type{Cstring}, s::Symbol) = s -unsafe_convert(::Type{Cstring}, s::Symbol) = Cstring(unsafe_convert(Ptr{Cchar}, s)) - -@static if ccall(:jl_get_UNAME, Any, ()) === :NT -""" - Base.cwstring(s) - -Converts a string `s` to a NUL-terminated `Vector{Cwchar_t}`, suitable for passing to C -functions expecting a `Ptr{Cwchar_t}`. The main advantage of using this over the implicit -conversion provided by [`Cwstring`](@ref) is if the function is called multiple times with the -same argument. - -This is only available on Windows. -""" -function cwstring(s::AbstractString) - bytes = codeunits(String(s)) - 0 in bytes && throw(ArgumentError("embedded NULs are not allowed in C strings: $(repr(s))")) - return push!(transcode(UInt16, bytes), 0) -end -end - -# transcoding between data in UTF-8 and UTF-16 for Windows APIs, -# and also UTF-32 for APIs using Cwchar_t on other platforms. - -""" - transcode(T, src) - -Convert string data between Unicode encodings. `src` is either a -`String` or a `Vector{UIntXX}` of UTF-XX code units, where -`XX` is 8, 16, or 32. `T` indicates the encoding of the return value: -`String` to return a (UTF-8 encoded) `String` or `UIntXX` -to return a `Vector{UIntXX}` of UTF-`XX` data. (The alias [`Cwchar_t`](@ref) -can also be used as the integer type, for converting `wchar_t*` strings -used by external C libraries.) - -The `transcode` function succeeds as long as the input data can be -reasonably represented in the target encoding; it always succeeds for -conversions between UTF-XX encodings, even for invalid Unicode data. - -Only conversion to/from UTF-8 is currently supported. - -# Examples -```jldoctest -julia> str = "αβγ" -"αβγ" - -julia> transcode(UInt16, str) -3-element Vector{UInt16}: - 0x03b1 - 0x03b2 - 0x03b3 - -julia> transcode(String, transcode(UInt16, str)) -"αβγ" -``` -""" -function transcode end - -transcode(::Type{T}, src::AbstractVector{T}) where {T<:Union{UInt8,UInt16,UInt32,Int32}} = src -transcode(::Type{T}, src::String) where {T<:Union{Int32,UInt32}} = T[T(c) for c in src] -transcode(::Type{T}, src::AbstractVector{UInt8}) where {T<:Union{Int32,UInt32}} = - transcode(T, String(Vector(src))) -transcode(::Type{T}, src::CodeUnits{UInt8,String}) where {T<:Union{Int32,UInt32}} = - transcode(T, String(src)) - -function transcode(::Type{UInt8}, src::Vector{<:Union{Int32,UInt32}}) - buf = IOBuffer() - for c in src - print(buf, Char(c)) - end - take!(buf) -end -transcode(::Type{String}, src::String) = src -transcode(T, src::String) = transcode(T, codeunits(src)) -transcode(::Type{String}, src) = String(transcode(UInt8, src)) - -function transcode(::Type{UInt16}, src::AbstractVector{UInt8}) - require_one_based_indexing(src) - dst = UInt16[] - i, n = 1, length(src) - n > 0 || return dst - sizehint!(dst, 2n) - a = src[1] - while true - if i < n && -64 <= a % Int8 <= -12 # multi-byte character - b = src[i += 1] - if -64 <= (b % Int8) || a == 0xf4 && 0x8f < b - # invalid UTF-8 (non-continuation or too-high code point) - push!(dst, a) - a = b; continue - elseif a < 0xe0 # 2-byte UTF-8 - push!(dst, xor(0x3080, UInt16(a) << 6, b)) - elseif i < n # 3/4-byte character - c = src[i += 1] - if -64 <= (c % Int8) # invalid UTF-8 (non-continuation) - push!(dst, a, b) - a = c; continue - elseif a < 0xf0 # 3-byte UTF-8 - push!(dst, xor(0x2080, UInt16(a) << 12, UInt16(b) << 6, c)) - elseif i < n - d = src[i += 1] - if -64 <= (d % Int8) # invalid UTF-8 (non-continuation) - push!(dst, a, b, c) - a = d; continue - elseif a == 0xf0 && b < 0x90 # overlong encoding - push!(dst, xor(0x2080, UInt16(b) << 12, UInt16(c) << 6, d)) - else # 4-byte UTF-8 - push!(dst, 0xe5b8 + (UInt16(a) << 8) + (UInt16(b) << 2) + (c >> 4), - xor(0xdc80, UInt16(c & 0xf) << 6, d)) - end - else # too short - push!(dst, a, b, c) - break - end - else # too short - push!(dst, a, b) - break - end - else # ASCII or invalid UTF-8 (continuation byte or too-high code point) - push!(dst, a) - end - i < n || break - a = src[i += 1] - end - return dst -end - -function transcode(::Type{UInt8}, src::AbstractVector{UInt16}) - require_one_based_indexing(src) - n = length(src) - n == 0 && return UInt8[] - - # Precompute m = sizeof(dst). This involves annoying duplication - # of the loop over the src array. However, this is not just an - # optimization: it is problematic for security reasons to grow - # dst dynamically, because Base.winprompt uses this function to - # convert passwords to UTF-8 and we don't want to make unintentional - # copies of the password data. - a = src[1] - i, m = 1, 0 - while true - if a < 0x80 - m += 1 - elseif a < 0x800 # 2-byte UTF-8 - m += 2 - elseif a & 0xfc00 == 0xd800 && i < length(src) - b = src[i += 1] - if (b & 0xfc00) == 0xdc00 # 2-unit UTF-16 sequence => 4-byte UTF-8 - m += 4 - else - m += 3 - a = b; continue - end - else - # 1-unit high UTF-16 or unpaired high surrogate - # either way, encode as 3-byte UTF-8 code point - m += 3 - end - i < n || break - a = src[i += 1] - end - - dst = StringVector(m) - a = src[1] - i, j = 1, 0 - while true - if a < 0x80 # ASCII - dst[j += 1] = a % UInt8 - elseif a < 0x800 # 2-byte UTF-8 - dst[j += 1] = 0xc0 | ((a >> 6) % UInt8) - dst[j += 1] = 0x80 | ((a % UInt8) & 0x3f) - elseif a & 0xfc00 == 0xd800 && i < n - b = src[i += 1] - if (b & 0xfc00) == 0xdc00 - # 2-unit UTF-16 sequence => 4-byte UTF-8 - a += 0x2840 - dst[j += 1] = 0xf0 | ((a >> 8) % UInt8) - dst[j += 1] = 0x80 | ((a % UInt8) >> 2) - dst[j += 1] = xor(0xf0, ((a % UInt8) << 4) & 0x3f, (b >> 6) % UInt8) - dst[j += 1] = 0x80 | ((b % UInt8) & 0x3f) - else - dst[j += 1] = 0xe0 | ((a >> 12) % UInt8) - dst[j += 1] = 0x80 | (((a >> 6) % UInt8) & 0x3f) - dst[j += 1] = 0x80 | ((a % UInt8) & 0x3f) - a = b; continue - end - else - # 1-unit high UTF-16 or unpaired high surrogate - # either way, encode as 3-byte UTF-8 code point - dst[j += 1] = 0xe0 | ((a >> 12) % UInt8) - dst[j += 1] = 0x80 | (((a >> 6) % UInt8) & 0x3f) - dst[j += 1] = 0x80 | ((a % UInt8) & 0x3f) - end - i < n || break - a = src[i += 1] - end - return dst -end - -function unsafe_string(p::Ptr{T}, length::Integer) where {T<:Union{UInt16,UInt32,Cwchar_t}} - transcode(String, unsafe_wrap(Array, p, length; own=false)) -end -function unsafe_string(cw::Cwstring) - p = convert(Ptr{Cwchar_t}, cw) - n = 1 - while unsafe_load(p, n) != 0 - n += 1 - end - return unsafe_string(p, n - 1) -end - # deferring (or un-deferring) ctrl-c handler for external C code that # is not interrupt safe (see also issue #2622). The sigatomic_begin/end # functions should always be called in matched pairs, ideally via: @@ -576,21 +270,21 @@ The above input outputs this: """ function ccall_macro_parse(expr::Expr) # setup and check for errors - if !Meta.isexpr(expr, :(::)) + if !isexpr(expr, :(::)) throw(ArgumentError("@ccall needs a function signature with a return type")) end rettype = expr.args[2] call = expr.args[1] - if !Meta.isexpr(call, :call) + if !isexpr(call, :call) throw(ArgumentError("@ccall has to take a function call")) end # get the function symbols func = let f = call.args[1] - if Meta.isexpr(f, :.) + if isexpr(f, :.) :(($(f.args[2]), $(f.args[1]))) - elseif Meta.isexpr(f, :$) + elseif isexpr(f, :$) f elseif f isa Symbol QuoteNode(f) @@ -603,7 +297,7 @@ function ccall_macro_parse(expr::Expr) varargs = nothing argstart = 2 callargs = call.args - if length(callargs) >= 2 && Meta.isexpr(callargs[2], :parameters) + if length(callargs) >= 2 && isexpr(callargs[2], :parameters) argstart = 3 varargs = callargs[2].args end @@ -613,7 +307,7 @@ function ccall_macro_parse(expr::Expr) types = [] function pusharg!(arg) - if !Meta.isexpr(arg, :(::)) + if !isexpr(arg, :(::)) throw(ArgumentError("args in @ccall need type annotations. '$arg' doesn't have one.")) end push!(args, arg.args[1]) @@ -643,7 +337,7 @@ function ccall_macro_lower(convention, func, rettype, types, args, nreq) statements = [] # if interpolation was used, ensure the value is a function pointer at runtime. - if Meta.isexpr(func, :$) + if isexpr(func, :$) push!(statements, Expr(:(=), :func, esc(func.args[1]))) name = QuoteNode(func.args[1]) func = :func @@ -715,6 +409,6 @@ macro ccall(expr) return ccall_macro_lower(:ccall, ccall_macro_parse(expr)...) end -macro ccall_effects(effects::UInt8, expr) +macro ccall_effects(effects::UInt16, expr) return ccall_macro_lower((:ccall, effects), ccall_macro_parse(expr)...) end diff --git a/base/cartesian.jl b/base/cartesian.jl index 5f96a2061880f..ca0fc0aac0cfc 100644 --- a/base/cartesian.jl +++ b/base/cartesian.jl @@ -2,7 +2,7 @@ module Cartesian -export @nloops, @nref, @ncall, @nexprs, @nextract, @nall, @nany, @ntuple, @nif +export @nloops, @nref, @ncall, @ncallkw, @nexprs, @nextract, @nall, @nany, @ntuple, @nif ### Cartesian-specific macros @@ -104,10 +104,38 @@ while `@ncall 2 func a b i->c[i]` yields macro ncall(N::Int, f, args...) pre = args[1:end-1] ex = args[end] - vars = Any[ inlineanonymous(ex,i) for i = 1:N ] + vars = (inlineanonymous(ex, i) for i = 1:N) Expr(:escape, Expr(:call, f, pre..., vars...)) end +""" + @ncallkw N f kw sym... + +Generate a function call expression with keyword arguments `kw...`. As +in the case of [`@ncall`](@ref), `sym` represents any number of function arguments, the +last of which may be an anonymous-function expression and is expanded into `N` arguments. + +# Examples +```jldoctest +julia> using Base.Cartesian + +julia> f(x...; a, b = 1, c = 2, d = 3) = +(x..., a, b, c, d); + +julia> x_1, x_2 = (-1, -2); b = 0; kw = (c = 0, d = 0); + +julia> @ncallkw 2 f (; a = 0, b, kw...) x +-3 + +``` +""" +macro ncallkw(N::Int, f, kw, args...) + pre = args[1:end-1] + ex = args[end] + vars = (inlineanonymous(ex, i) for i = 1:N) + param = Expr(:parameters, Expr(:(...), kw)) + Expr(:escape, Expr(:call, f, param, pre..., vars...)) +end + """ @nexprs N expr @@ -374,6 +402,8 @@ function exprresolve_conditional(ex::Expr) return true, exprresolve_cond_dict[callee](ex.args[2], ex.args[3]) end end + elseif Meta.isexpr(ex, :block, 2) && ex.args[1] isa LineNumberNode + return exprresolve_conditional(ex.args[2]) end false, false end @@ -402,10 +432,16 @@ function exprresolve(ex::Expr) return ex.args[1][ex.args[2:end]...] end # Resolve conditionals - if ex.head === :if + if ex.head === :if || ex.head === :elseif can_eval, tf = exprresolve_conditional(ex.args[1]) if can_eval - ex = tf ? ex.args[2] : ex.args[3] + if tf + return ex.args[2] + elseif length(ex.args) == 3 + return ex.args[3] + else + return nothing + end end end ex diff --git a/base/channels.jl b/base/channels.jl index 90dac37f41cb6..3acbf37246a58 100644 --- a/base/channels.jl +++ b/base/channels.jl @@ -211,7 +211,28 @@ function close(c::Channel, @nospecialize(excp::Exception)) end nothing end -isopen(c::Channel) = ((@atomic :monotonic c.state) === :open) + +# Use acquire here to pair with release store in `close`, so that subsequent `isready` calls +# are forced to see `isready == true` if they see `isopen == false`. This means users must +# call `isopen` before `isready` if you are using the race-y APIs (or call `iterate`, which +# does this right for you). +isopen(c::Channel) = ((@atomic :acquire c.state) === :open) + +""" + empty!(c::Channel) + +Empty a Channel `c` by calling `empty!` on the internal buffer. +Return the empty channel. +""" +function Base.empty!(c::Channel) + @lock c begin + ndrop = length(c.data) + empty!(c.data) + _increment_n_avail(c, -ndrop) + notify(c.cond_put) + end + return c +end """ bind(chnl::Channel, task::Task) @@ -498,7 +519,7 @@ end Determines whether a [`Channel`](@ref) has a value stored in it. Returns immediately, does not block. -For unbuffered channels returns `true` if there are tasks waiting on a [`put!`](@ref). +For unbuffered channels, return `true` if there are tasks waiting on a [`put!`](@ref). # Examples @@ -538,6 +559,47 @@ function n_avail(c::Channel) @atomic :monotonic c.n_avail_items end +""" + isfull(c::Channel) + +Determines if a [`Channel`](@ref) is full, in the sense +that calling `put!(c, some_value)` would have blocked. +Returns immediately, does not block. + +Note that it may frequently be the case that `put!` will +not block after this returns `true`. Users must take +precautions not to accidentally create live-lock bugs +in their code by calling this method, as these are +generally harder to debug than deadlocks. It is also +possible that `put!` will block after this call +returns `false`, if there are multiple producer +tasks calling `put!` in parallel. + +# Examples + +Buffered channel: +```jldoctest +julia> c = Channel(1); # capacity = 1 + +julia> isfull(c) +false + +julia> put!(c, 1); + +julia> isfull(c) +true +``` + +Unbuffered channel: +```jldoctest +julia> c = Channel(); # capacity = 0 + +julia> isfull(c) # unbuffered channel is always full +true +``` +""" +isfull(c::Channel) = n_avail(c) ≥ c.sz_max + lock(c::Channel) = lock(c.cond_take) lock(f, c::Channel) = lock(f, c.cond_take) unlock(c::Channel) = unlock(c.cond_take) diff --git a/base/char.jl b/base/char.jl index 08d661c41de56..bc68a672ce0ca 100644 --- a/base/char.jl +++ b/base/char.jl @@ -62,7 +62,14 @@ to an output stream, or `ncodeunits(string(c))` but computed efficiently. This method requires at least Julia 1.1. In Julia 1.0 consider using `ncodeunits(string(c))`. """ -ncodeunits(c::Char) = write(devnull, c) # this is surprisingly efficient +function ncodeunits(c::Char) + u = reinterpret(UInt32, c) + # We care about how many trailing bytes are all zero + # subtract that from the total number of bytes + n_nonzero_bytes = sizeof(UInt32) - div(trailing_zeros(u), 0x8) + # Take care of '\0', which has an all-zero bitpattern + n_nonzero_bytes + iszero(u) +end """ codepoint(c::AbstractChar) -> Integer diff --git a/base/checked.jl b/base/checked.jl index d5b4112397e84..b374d34830280 100644 --- a/base/checked.jl +++ b/base/checked.jl @@ -13,7 +13,7 @@ return both the unchecked results and a boolean value denoting the presence of a module Checked export checked_neg, checked_abs, checked_add, checked_sub, checked_mul, - checked_div, checked_rem, checked_fld, checked_mod, checked_cld, + checked_div, checked_rem, checked_fld, checked_mod, checked_cld, checked_pow, checked_length, add_with_overflow, sub_with_overflow, mul_with_overflow import Core.Intrinsics: @@ -358,6 +358,19 @@ The overflow protection may impose a perceptible performance penalty. """ checked_cld(x::T, y::T) where {T<:Integer} = cld(x, y) # Base.cld already checks +""" + Base.checked_pow(x, y) + +Calculates `^(x,y)`, checking for overflow errors where applicable. + +The overflow protection may impose a perceptible performance penalty. +""" +checked_pow(x::Integer, y::Integer) = checked_power_by_squaring(x, y) + +checked_power_by_squaring(x_, p::Integer) = Base.power_by_squaring(x_, p; mul = checked_mul) +# For Booleans, the default implementation covers all cases. +checked_power_by_squaring(x::Bool, p::Integer) = Base.power_by_squaring(x, p) + """ Base.checked_length(r) diff --git a/base/client.jl b/base/client.jl index 79541f2c106b1..3ca2d54745290 100644 --- a/base/client.jl +++ b/base/client.jl @@ -95,7 +95,7 @@ function scrub_repl_backtrace(bt) if bt !== nothing && !(bt isa Vector{Any}) # ignore our sentinel value types bt = bt isa Vector{StackFrame} ? copy(bt) : stacktrace(bt) # remove REPL-related frames from interactive printing - eval_ind = findlast(frame -> !frame.from_c && frame.func === :eval, bt) + eval_ind = findlast(frame -> !frame.from_c && startswith(String(frame.func), "__repl_entry"), bt) eval_ind === nothing || deleteat!(bt, eval_ind:length(bt)) end return bt @@ -240,15 +240,15 @@ function exec_options(opts) if cmd_suppresses_program(cmd) arg_is_program = false repl = false - elseif cmd == 'L' + elseif cmd == 'L' || cmd == 'm' # nothing elseif cmd == 'B' # --bug-report # If we're doing a bug report, don't load anything else. We will # spawn a child in which to execute these options. let InteractiveUtils = load_InteractiveUtils() - InteractiveUtils.report_bug(arg) + invokelatest(InteractiveUtils.report_bug, arg) end - return nothing + return false else @warn "Unexpected command -$cmd'$arg'" end @@ -260,9 +260,9 @@ function exec_options(opts) # Load Distributed module only if any of the Distributed options have been specified. distributed_mode = (opts.worker == 1) || (opts.nprocs > 0) || (opts.machine_file != C_NULL) if distributed_mode - let Distributed = require(PkgId(UUID((0x8ba89e20_285c_5b6f, 0x9357_94700520ee1b)), "Distributed")) - Core.eval(Main, :(const Distributed = $Distributed)) - Core.eval(Main, :(using .Distributed)) + let Distributed = require_stdlib(PkgId(UUID((0x8ba89e20_285c_5b6f, 0x9357_94700520ee1b)), "Distributed")) + Core.eval(MainInclude, :(const Distributed = $Distributed)) + Core.eval(Main, :(using Base.MainInclude.Distributed)) end invokelatest(Main.Distributed.process_opts, opts) @@ -271,6 +271,10 @@ function exec_options(opts) interactiveinput = (repl || is_interactive::Bool) && isa(stdin, TTY) is_interactive::Bool |= interactiveinput + # load terminfo in for styled printing + term_env = get(ENV, "TERM", @static Sys.iswindows() ? "" : "dumb") + global current_terminfo = load_terminfo(term_env) + # load ~/.julia/config/startup.jl file if startup try @@ -288,6 +292,13 @@ function exec_options(opts) elseif cmd == 'E' invokelatest(show, Core.eval(Main, parse_input_line(arg))) println() + elseif cmd == 'm' + @eval Main import $(Symbol(arg)).main + if !should_use_main_entrypoint() + error("`main` in `$arg` not declared as entry point (use `@main` to do so)") + end + return false + elseif cmd == 'L' # load file immediately on all processors if !distributed_mode @@ -380,25 +391,25 @@ _atreplinit(repl) = invokelatest(__atreplinit, repl) function load_InteractiveUtils(mod::Module=Main) # load interactive-only libraries - if !isdefined(mod, :InteractiveUtils) + if !isdefined(MainInclude, :InteractiveUtils) try - let InteractiveUtils = require(PkgId(UUID(0xb77e0a4c_d291_57a0_90e8_8db25a27a240), "InteractiveUtils")) - Core.eval(mod, :(const InteractiveUtils = $InteractiveUtils)) - Core.eval(mod, :(using .InteractiveUtils)) - return InteractiveUtils + # TODO: we have to use require_stdlib here because it is a dependency of REPL, but we would sort of prefer not to + let InteractiveUtils = require_stdlib(PkgId(UUID(0xb77e0a4c_d291_57a0_90e8_8db25a27a240), "InteractiveUtils")) + Core.eval(MainInclude, :(const InteractiveUtils = $InteractiveUtils)) end catch ex @warn "Failed to import InteractiveUtils into module $mod" exception=(ex, catch_backtrace()) + return nothing end - return nothing end - return getfield(mod, :InteractiveUtils) + Core.eval(mod, :(using Base.MainInclude.InteractiveUtils)) + return MainInclude.InteractiveUtils end function load_REPL() # load interactive-only libraries try - return Base.require(PkgId(UUID(0x3fa0cd96_eef1_5676_8a61_b3b8758bbffb), "REPL")) + return Base.require_stdlib(PkgId(UUID(0x3fa0cd96_eef1_5676_8a61_b3b8758bbffb), "REPL")) catch ex @warn "Failed to import REPL" exception=(ex, catch_backtrace()) end @@ -417,13 +428,11 @@ function run_main_repl(interactive::Bool, quiet::Bool, banner::Symbol, history_f end end # TODO cleanup REPL_MODULE_REF - if !fallback_repl && interactive && isassigned(REPL_MODULE_REF) invokelatest(REPL_MODULE_REF[]) do REPL term_env = get(ENV, "TERM", @static Sys.iswindows() ? "" : "dumb") - global current_terminfo = load_terminfo(term_env) term = REPL.Terminals.TTYTerminal(term_env, stdin, stdout, stderr) - banner == :no || Base.banner(term, short=banner==:short) + banner == :no || REPL.banner(term, short=banner==:short) if term.term_type == "dumb" repl = REPL.BasicREPL(term) quiet || @warn "Terminal not fully functional" @@ -443,7 +452,6 @@ function run_main_repl(interactive::Bool, quiet::Bool, banner::Symbol, history_f if !fallback_repl && interactive && !quiet @warn "REPL provider not available: using basic fallback" LOAD_PATH=join(Base.LOAD_PATH, Sys.iswindows() ? ';' : ':') end - banner == :no || Base.banner(short=banner==:short) let input = stdin if isa(input, File) || isa(input, IOStream) # for files, we can slurp in the whole thing at once @@ -485,17 +493,9 @@ function run_main_repl(interactive::Bool, quiet::Bool, banner::Symbol, history_f nothing end -# MainInclude exists to hide Main.include and eval from `names(Main)`. +# MainInclude exists to weakly add certain identifiers to Main baremodule MainInclude using ..Base -# These definitions calls Base._include rather than Base.include to get -# one-frame stacktraces for the common case of using include(fname) in Main. -include(mapexpr::Function, fname::AbstractString) = Base._include(mapexpr, Main, fname) -function include(fname::AbstractString) - isa(fname, String) || (fname = Base.convert(String, fname)::String) - Base._include(identity, Main, fname) -end -eval(x) = Core.eval(Main, x) """ ans @@ -514,18 +514,8 @@ global err = nothing # weakly exposes ans and err variables to Main export ans, err - end -""" - eval(expr) - -Evaluate an expression in the global scope of the containing module. -Every `Module` (except those defined with `baremodule`) has its own 1-argument -definition of `eval`, which evaluates expressions in that module. -""" -MainInclude.eval - function should_use_main_entrypoint() isdefined(Main, :main) || return false M_binding_owner = Base.binding_module(Main, :main) @@ -533,30 +523,6 @@ function should_use_main_entrypoint() return true end -""" - include([mapexpr::Function,] path::AbstractString) - -Evaluate the contents of the input source file in the global scope of the containing module. -Every module (except those defined with `baremodule`) has its own -definition of `include`, which evaluates the file in that module. -Returns the result of the last evaluated expression of the input file. During including, -a task-local include path is set to the directory containing the file. Nested calls to -`include` will search relative to that path. This function is typically used to load source -interactively, or to combine files in packages that are broken into multiple source files. -The argument `path` is normalized using [`normpath`](@ref) which will resolve -relative path tokens such as `..` and convert `/` to the appropriate path separator. - -The optional first argument `mapexpr` can be used to transform the included code before -it is evaluated: for each parsed expression `expr` in `path`, the `include` function -actually evaluates `mapexpr(expr)`. If it is omitted, `mapexpr` defaults to [`identity`](@ref). - -Use [`Base.include`](@ref) to evaluate a file into another module. - -!!! compat "Julia 1.5" - Julia 1.5 is required for passing the `mapexpr` argument. -""" -MainInclude.include - function _start() empty!(ARGS) append!(ARGS, Core.ARGS) @@ -613,7 +579,7 @@ In the `julia` driver, if `Main.main` is marked as an entrypoint, it will be aut the completion of script execution. The `@main` macro may be used standalone or as part of the function definition, though in the latter -case, parenthese are required. In particular, the following are equivalent: +case, parentheses are required. In particular, the following are equivalent: ``` function (@main)(ARGS) diff --git a/base/cmd.jl b/base/cmd.jl index da29c732c7f26..202527abdf644 100644 --- a/base/cmd.jl +++ b/base/cmd.jl @@ -3,9 +3,10 @@ abstract type AbstractCmd end # libuv process option flags -const UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS = UInt8(1 << 2) -const UV_PROCESS_DETACHED = UInt8(1 << 3) -const UV_PROCESS_WINDOWS_HIDE = UInt8(1 << 4) +const UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS = UInt32(1 << 2) +const UV_PROCESS_DETACHED = UInt32(1 << 3) +const UV_PROCESS_WINDOWS_HIDE = UInt32(1 << 4) +const UV_PROCESS_WINDOWS_DISABLE_EXACT_NAME = UInt32(1 << 7) struct Cmd <: AbstractCmd exec::Vector{String} @@ -14,7 +15,7 @@ struct Cmd <: AbstractCmd env::Union{Vector{String},Nothing} dir::String cpus::Union{Nothing,Vector{UInt16}} - Cmd(exec::Vector{String}) = + Cmd(exec::Vector{<:AbstractString}) = new(exec, false, 0x00, nothing, "", nothing) Cmd(cmd::Cmd, ignorestatus, flags, env, dir, cpus = nothing) = new(cmd.exec, ignorestatus, flags, env, diff --git a/base/cmem.jl b/base/cmem.jl index 8b0b99b3a6ebd..dd4cbc30585f2 100644 --- a/base/cmem.jl +++ b/base/cmem.jl @@ -10,6 +10,7 @@ Call `memcpy` from the C standard library. """ function memcpy(dst::Ptr, src::Ptr, n::Integer) + @_terminates_globally_meta ccall(:memcpy, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t), dst, src, n) end @@ -23,6 +24,7 @@ Call `memmove` from the C standard library. """ function memmove(dst::Ptr, src::Ptr, n::Integer) + @_terminates_globally_meta ccall(:memmove, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t), dst, src, n) end @@ -36,6 +38,7 @@ Call `memset` from the C standard library. """ function memset(p::Ptr, val, n::Integer) + @_terminates_globally_meta ccall(:memset, Ptr{Cvoid}, (Ptr{Cvoid}, Cint, Csize_t), p, val, n) end @@ -49,5 +52,6 @@ Call `memcmp` from the C standard library. """ function memcmp(a::Ptr, b::Ptr, n::Integer) + @_terminates_globally_meta ccall(:memcmp, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t), a, b, n % Csize_t) % Int end diff --git a/base/combinatorics.jl b/base/combinatorics.jl index 519c8f723e728..a169cfb9ecd77 100644 --- a/base/combinatorics.jl +++ b/base/combinatorics.jl @@ -136,6 +136,44 @@ function permutecols!!(a::AbstractMatrix, p::AbstractVector{<:Integer}) a end +# Row and column permutations for AbstractMatrix +permutecols!(a::AbstractMatrix, p::AbstractVector{<:Integer}) = + _permute!(a, p, Base.swapcols!) +permuterows!(a::AbstractMatrix, p::AbstractVector{<:Integer}) = + _permute!(a, p, Base.swaprows!) +@inline function _permute!(a::AbstractMatrix, p::AbstractVector{<:Integer}, swapfun!::F) where {F} + require_one_based_indexing(a, p) + p .= .-p + for i in 1:length(p) + p[i] > 0 && continue + j = i + in = p[j] = -p[j] + while p[in] < 0 + swapfun!(a, in, j) + j = in + in = p[in] = -p[in] + end + end + a +end +invpermutecols!(a::AbstractMatrix, p::AbstractVector{<:Integer}) = + _invpermute!(a, p, Base.swapcols!) +invpermuterows!(a::AbstractMatrix, p::AbstractVector{<:Integer}) = + _invpermute!(a, p, Base.swaprows!) +@inline function _invpermute!(a::AbstractMatrix, p::AbstractVector{<:Integer}, swapfun!::F) where {F} + require_one_based_indexing(a, p) + p .= .-p + for i in 1:length(p) + p[i] > 0 && continue + j = p[i] = -p[i] + while j != i + swapfun!(a, j, i) + j = p[j] = -p[j] + end + end + a +end + """ permute!(v, p) @@ -147,6 +185,8 @@ it is even faster to write into a pre-allocated output array with `u .= @view v[ (Even though `permute!` overwrites `v` in-place, it internally requires some allocation to keep track of which elements have been moved.) +$(_DOCS_ALIASING_WARNING) + See also [`invpermute!`](@ref). # Examples @@ -176,6 +216,8 @@ Note that if you have a pre-allocated output array (e.g. `u = similar(v)`), it is quicker to instead employ `u[p] = v`. (`invpermute!` internally allocates a copy of the data.) +$(_DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> A = [1, 1, 3, 4]; @@ -237,7 +279,7 @@ julia> B[invperm(v)] """ function invperm(a::AbstractVector) require_one_based_indexing(a) - b = zero(a) # similar vector of zeros + b = fill!(similar(a), zero(eltype(a))) # mutable vector of zeros n = length(a) @inbounds for (i, j) in enumerate(a) ((1 <= j <= n) && b[j] == 0) || diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index bfddc2d6927b3..76908136113cf 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -10,38 +10,36 @@ call_result_unused(si::StmtInfo) = !si.used function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), arginfo::ArgInfo, si::StmtInfo, @nospecialize(atype), sv::AbsIntState, max_methods::Int) - ⊑ₚ = ⊑(ipo_lattice(interp)) + 𝕃ₚ, 𝕃ᵢ = ipo_lattice(interp), typeinf_lattice(interp) + ⊑ₚ = ⊑(𝕃ₚ) if !should_infer_this_call(interp, sv) add_remark!(interp, sv, "Skipped call in throw block") # At this point we are guaranteed to end up throwing on this path, # which is all that's required for :consistent-cy. Of course, we don't # know anything else about this statement. effects = Effects(; consistent=ALWAYS_TRUE) - return CallMeta(Any, effects, NoCallInfo()) + return CallMeta(Any, Any, effects, NoCallInfo()) end argtypes = arginfo.argtypes - matches = find_matching_methods(typeinf_lattice(interp), argtypes, atype, method_table(interp), - InferenceParams(interp).max_union_splitting, max_methods) + matches = find_method_matches(interp, argtypes, atype; max_methods) if isa(matches, FailedMethodMatch) add_remark!(interp, sv, matches.reason) - return CallMeta(Any, Effects(), NoCallInfo()) + return CallMeta(Any, Any, Effects(), NoCallInfo()) end (; valid_worlds, applicable, info) = matches update_valid_age!(sv, valid_worlds) napplicable = length(applicable) - rettype = Bottom + rettype = excttype = Bottom edges = MethodInstance[] conditionals = nothing # keeps refinement information of call argument types when the return type is boolean seen = 0 # number of signatures actually inferred - any_const_result = false - const_results = Union{Nothing,ConstResult}[] + const_results = nothing # or const_results::Vector{Union{Nothing,ConstResult}} if any const results are available multiple_matches = napplicable > 1 fargs = arginfo.fargs all_effects = EFFECTS_TOTAL - 𝕃ₚ = ipo_lattice(interp) for i in 1:napplicable match = applicable[i]::MethodMatch method = match.method @@ -52,6 +50,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), break end this_rt = Bottom + this_exct = Bottom splitunions = false # TODO: this used to trigger a bug in inference recursion detection, and is unmaintained now # sigtuple = unwrap_unionall(sig)::DataType @@ -60,25 +59,38 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), splitsigs = switchtupleunion(sig) for sig_n in splitsigs result = abstract_call_method(interp, method, sig_n, svec(), multiple_matches, si, sv) - (; rt, edge, effects) = result + (; rt, exct, edge, effects, volatile_inf_result) = result this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i] this_arginfo = ArgInfo(fargs, this_argtypes) const_call_result = abstract_call_method_with_const_args(interp, result, f, this_arginfo, si, match, sv) - const_result = nothing + const_result = volatile_inf_result if const_call_result !== nothing if const_call_result.rt ⊑ₚ rt rt = const_call_result.rt (; effects, const_result, edge) = const_call_result + elseif is_better_effects(const_call_result.effects, effects) + (; effects, const_result, edge) = const_call_result else add_remark!(interp, sv, "[constprop] Discarded because the result was wider than inference") end + if !(exct ⊑ₚ const_call_result.exct) + exct = const_call_result.exct + (; const_result, edge) = const_call_result + else + add_remark!(interp, sv, "[constprop] Discarded exception type because result was wider than inference") + end end all_effects = merge_effects(all_effects, effects) - push!(const_results, const_result) - any_const_result |= const_result !== nothing + if const_result !== nothing + if const_results === nothing + const_results = fill!(Vector{Union{Nothing,ConstResult}}(undef, #=TODO=#napplicable), nothing) + end + const_results[i] = const_result + end edge === nothing || push!(edges, edge) this_rt = tmerge(this_rt, rt) + this_exct = tmerge(this_exct, exct) if bail_out_call(interp, this_rt, sv) break end @@ -87,46 +99,67 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), this_rt = widenwrappedconditional(this_rt) else result = abstract_call_method(interp, method, sig, match.sparams, multiple_matches, si, sv) - (; rt, edge, effects) = result + (; rt, exct, edge, effects, volatile_inf_result) = result this_conditional = ignorelimited(rt) this_rt = widenwrappedconditional(rt) + this_exct = exct # try constant propagation with argtypes for this match # this is in preparation for inlining, or improving the return result this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i] this_arginfo = ArgInfo(fargs, this_argtypes) const_call_result = abstract_call_method_with_const_args(interp, result, f, this_arginfo, si, match, sv) - const_result = nothing + const_result = volatile_inf_result if const_call_result !== nothing this_const_conditional = ignorelimited(const_call_result.rt) this_const_rt = widenwrappedconditional(const_call_result.rt) - # return type of const-prop' inference can be wider than that of non const-prop' inference - # e.g. in cases when there are cycles but cached result is still accurate if this_const_rt ⊑ₚ this_rt + # As long as the const-prop result we have is not *worse* than + # what we found out on types, we'd like to use it. Even if the + # end result is exactly equivalent, it is likely that the IR + # we produced while constproping is better than that with + # generic types. + # Return type of const-prop' inference can be wider than that of non const-prop' inference + # e.g. in cases when there are cycles but cached result is still accurate this_conditional = this_const_conditional this_rt = this_const_rt (; effects, const_result, edge) = const_call_result + elseif is_better_effects(const_call_result.effects, effects) + (; effects, const_result, edge) = const_call_result else add_remark!(interp, sv, "[constprop] Discarded because the result was wider than inference") end + # Treat the exception type separately. Currently, constprop often cannot determine the exception type + # because consistent-cy does not apply to exceptions. + if !(this_exct ⊑ₚ const_call_result.exct) + this_exct = const_call_result.exct + (; const_result, edge) = const_call_result + else + add_remark!(interp, sv, "[constprop] Discarded exception type because result was wider than inference") + end end all_effects = merge_effects(all_effects, effects) - push!(const_results, const_result) - any_const_result |= const_result !== nothing + if const_result !== nothing + if const_results === nothing + const_results = fill!(Vector{Union{Nothing,ConstResult}}(undef, napplicable), nothing) + end + const_results[i] = const_result + end edge === nothing || push!(edges, edge) end @assert !(this_conditional isa Conditional || this_rt isa MustAlias) "invalid lattice element returned from inter-procedural context" seen += 1 rettype = tmerge(𝕃ₚ, rettype, this_rt) + excttype = tmerge(𝕃ₚ, excttype, this_exct) if has_conditional(𝕃ₚ, sv) && this_conditional !== Bottom && is_lattice_bool(𝕃ₚ, rettype) && fargs !== nothing if conditionals === nothing conditionals = Any[Bottom for _ in 1:length(argtypes)], Any[Bottom for _ in 1:length(argtypes)] end for i = 1:length(argtypes) - cnd = conditional_argtype(this_conditional, sig, argtypes, i) - conditionals[1][i] = tmerge(conditionals[1][i], cnd.thentype) - conditionals[2][i] = tmerge(conditionals[2][i], cnd.elsetype) + cnd = conditional_argtype(𝕃ᵢ, this_conditional, sig, argtypes, i) + conditionals[1][i] = tmerge(𝕃ᵢ, conditionals[1][i], cnd.thentype) + conditionals[2][i] = tmerge(𝕃ᵢ, conditionals[2][i], cnd.elsetype) end end if bail_out_call(interp, InferenceLoopState(sig, rettype, all_effects), sv) @@ -135,19 +168,20 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), end end - if any_const_result && seen == napplicable + if const_results !== nothing @assert napplicable == nmatches(info) == length(const_results) info = ConstCallInfo(info, const_results) end if seen ≠ napplicable # there is unanalyzed candidate, widen type and effects to the top - rettype = Any + rettype = excttype = Any all_effects = Effects() elseif isa(matches, MethodMatches) ? (!matches.fullmatch || any_ambig(matches)) : (!all(matches.fullmatches) || any_ambig(matches)) # Account for the fact that we may encounter a MethodError with a non-covered or ambiguous signature. all_effects = Effects(all_effects; nothrow=false) + excttype = tmerge(𝕃ₚ, excttype, MethodError) end rettype = from_interprocedural!(interp, rettype, sv, arginfo, conditionals) @@ -192,7 +226,8 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), end end end - return CallMeta(rettype, all_effects, info) + + return CallMeta(rettype, excttype, all_effects, info) end struct FailedMethodMatch @@ -219,73 +254,79 @@ struct UnionSplitMethodMatches end any_ambig(m::UnionSplitMethodMatches) = any(any_ambig, m.info.matches) -function find_matching_methods(𝕃::AbstractLattice, - argtypes::Vector{Any}, @nospecialize(atype), method_table::MethodTableView, - max_union_splitting::Int, max_methods::Int) - # NOTE this is valid as far as any "constant" lattice element doesn't represent `Union` type - if 1 < unionsplitcost(𝕃, argtypes) <= max_union_splitting - split_argtypes = switchtupleunion(𝕃, argtypes) - infos = MethodMatchInfo[] - applicable = Any[] - applicable_argtypes = Vector{Any}[] # arrays like `argtypes`, including constants, for each match - valid_worlds = WorldRange() - mts = 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::MethodTable - matches = findall(sig_n, method_table; limit = max_methods) - if matches === nothing - 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::MethodMatch->match.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 +function find_method_matches(interp::AbstractInterpreter, argtypes::Vector{Any}, @nospecialize(atype); + max_union_splitting::Int = InferenceParams(interp).max_union_splitting, + max_methods::Int = InferenceParams(interp).max_methods) + if is_union_split_eligible(typeinf_lattice(interp), argtypes, max_union_splitting) + return find_union_split_method_matches(interp, argtypes, atype, max_methods) + end + return find_simple_method_matches(interp, atype, max_methods) +end + +# NOTE this is valid as far as any "constant" lattice element doesn't represent `Union` type +is_union_split_eligible(𝕃::AbstractLattice, argtypes::Vector{Any}, max_union_splitting::Int) = + 1 < unionsplitcost(𝕃, argtypes) <= max_union_splitting + +function find_union_split_method_matches(interp::AbstractInterpreter, argtypes::Vector{Any}, + @nospecialize(atype), max_methods::Int) + split_argtypes = switchtupleunion(typeinf_lattice(interp), argtypes) + infos = MethodMatchInfo[] + applicable = Any[] + applicable_argtypes = Vector{Any}[] # arrays like `argtypes`, including constants, for each match + valid_worlds = WorldRange() + mts = 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::MethodTable - matches = findall(atype, method_table; limit = max_methods) + matches = findall(sig_n, method_table(interp); limit = max_methods) if matches === nothing - # 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") + 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::MethodMatch->match.fully_covers, matches) + found = false + for (i, mt′) in enumerate(mts) + if mt′ === mt + fullmatches[i] &= thisfullmatch + found = true + break + end end - fullmatch = any(match::MethodMatch->match.fully_covers, matches) - return MethodMatches(matches.matches, - MethodMatchInfo(matches), - matches.valid_worlds, - mt, - fullmatch) + if !found + push!(mts, mt) + push!(fullmatches, thisfullmatch) + end + end + info = UnionSplitInfo(infos) + return UnionSplitMethodMatches( + applicable, applicable_argtypes, info, valid_worlds, mts, fullmatches) +end + +function find_simple_method_matches(interp::AbstractInterpreter, @nospecialize(atype), max_methods::Int) + mt = ccall(:jl_method_table_for, Any, (Any,), atype) + if mt === nothing + return FailedMethodMatch("Could not identify method table for call") end + mt = mt::MethodTable + matches = findall(atype, method_table(interp); limit = max_methods) + if matches === nothing + # 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 + info = MethodMatchInfo(matches) + fullmatch = any(match::MethodMatch->match.fully_covers, matches) + return MethodMatches( + matches.matches, info, matches.valid_worlds, mt, fullmatch) end """ @@ -313,7 +354,7 @@ function from_interprocedural!(interp::AbstractInterpreter, @nospecialize(rt), s arginfo::ArgInfo, @nospecialize(maybecondinfo)) rt = collect_limitations!(rt, sv) if isa(rt, InterMustAlias) - rt = from_intermustalias(rt, arginfo) + rt = from_intermustalias(rt, arginfo, sv) elseif is_lattice_bool(ipo_lattice(interp), rt) if maybecondinfo === nothing rt = widenconditional(rt) @@ -333,10 +374,10 @@ function collect_limitations!(@nospecialize(typ), sv::InferenceState) return typ end -function from_intermustalias(rt::InterMustAlias, arginfo::ArgInfo) +function from_intermustalias(rt::InterMustAlias, arginfo::ArgInfo, sv::AbsIntState) fargs = arginfo.fargs if fargs !== nothing && 1 ≤ rt.slot ≤ length(fargs) - arg = fargs[rt.slot] + arg = ssa_def_slot(fargs[rt.slot], sv) if isa(arg, SlotNumber) argtyp = widenslotwrapper(arginfo.argtypes[rt.slot]) if rt.vartyp ⊑ argtyp @@ -387,7 +428,7 @@ function from_interconditional(𝕃ᵢ::AbstractLattice, @nospecialize(rt), sv:: new_elsetype = maybecondinfo[2][i] else # otherwise compute it on the fly - cnd = conditional_argtype(rt, maybecondinfo, argtypes, i) + cnd = conditional_argtype(𝕃ᵢ, rt, maybecondinfo, argtypes, i) new_thentype = cnd.thentype new_elsetype = cnd.elsetype end @@ -433,11 +474,12 @@ function from_interconditional(𝕃ᵢ::AbstractLattice, @nospecialize(rt), sv:: return widenconditional(rt) end -function conditional_argtype(@nospecialize(rt), @nospecialize(sig), argtypes::Vector{Any}, i::Int) +function conditional_argtype(𝕃ᵢ::AbstractLattice, @nospecialize(rt), @nospecialize(sig), + argtypes::Vector{Any}, i::Int) if isa(rt, InterConditional) && rt.slot == i return rt else - thentype = elsetype = tmeet(widenslotwrapper(argtypes[i]), fieldtype(sig, i)) + thentype = elsetype = tmeet(𝕃ᵢ, widenslotwrapper(argtypes[i]), fieldtype(sig, i)) condval = maybe_extract_const_bool(rt) condval === true && (elsetype = Bottom) condval === false && (thentype = Bottom) @@ -480,15 +522,11 @@ const RECURSION_MSG_HARDLIMIT = "Bounded recursion detected under hardlimit. Cal function abstract_call_method(interp::AbstractInterpreter, method::Method, @nospecialize(sig), sparams::SimpleVector, hardlimit::Bool, si::StmtInfo, sv::AbsIntState) - if method.name === :depwarn && isdefined(Main, :Base) && method.module === Main.Base - add_remark!(interp, sv, "Refusing to infer into `depwarn`") - return MethodCallResult(Any, false, false, nothing, Effects()) - end sigtuple = unwrap_unionall(sig) sigtuple isa DataType || - return MethodCallResult(Any, false, false, nothing, Effects()) + return MethodCallResult(Any, Any, false, false, nothing, Effects()) all(@nospecialize(x) -> valid_as_lattice(unwrapva(x), true), sigtuple.parameters) || - return MethodCallResult(Union{}, false, false, nothing, EFFECTS_THROWS) # catch bad type intersections early + return MethodCallResult(Union{}, Any, false, false, nothing, EFFECTS_THROWS) # catch bad type intersections early if is_nospecializeinfer(method) sig = get_nospecializeinfer_sig(method, sig, sparams) @@ -513,7 +551,7 @@ function abstract_call_method(interp::AbstractInterpreter, # we have a self-cycle in the call-graph, but not in the inference graph (typically): # break this edge now (before we record it) by returning early # (non-typically, this means that we lose the ability to detect a guaranteed StackOverflow in some cases) - return MethodCallResult(Any, true, true, nothing, Effects()) + return MethodCallResult(Any, Any, true, true, nothing, Effects()) end topmost = nothing edgecycle = true @@ -568,7 +606,7 @@ function abstract_call_method(interp::AbstractInterpreter, # since it's very unlikely that we'll try to inline this, # or want make an invoke edge to its calling convention return type. # (non-typically, this means that we lose the ability to detect a guaranteed StackOverflow in some cases) - return MethodCallResult(Any, true, true, nothing, Effects()) + return MethodCallResult(Any, Any, true, true, nothing, Effects()) end add_remark!(interp, sv, washardlimit ? RECURSION_MSG_HARDLIMIT : RECURSION_MSG) # TODO (#48913) implement a proper recursion handling for irinterp: @@ -614,7 +652,7 @@ function abstract_call_method(interp::AbstractInterpreter, sparams = recomputed[2]::SimpleVector end - (; rt, edge, effects) = typeinf_edge(interp, method, sig, sparams, sv) + (; rt, exct, edge, effects, volatile_inf_result) = typeinf_edge(interp, method, sig, sparams, sv) if edge === nothing edgecycle = edgelimited = true @@ -638,7 +676,7 @@ function abstract_call_method(interp::AbstractInterpreter, end end - return MethodCallResult(rt, edgecycle, edgelimited, edge, effects) + return MethodCallResult(rt, exct, edgecycle, edgelimited, edge, effects, volatile_inf_result) end function edge_matches_sv(interp::AbstractInterpreter, frame::AbsIntState, @@ -648,7 +686,7 @@ function edge_matches_sv(interp::AbstractInterpreter, frame::AbsIntState, # necessary in order to retrieve this field from the generated `CodeInfo`, if it exists. # The other `CodeInfo`s we inspect will already have this field inflated, so we just # access it directly instead (to avoid regeneration). - world = get_world_counter(interp) + world = get_inference_world(interp) callee_method2 = method_for_inference_heuristics(method, sig, sparams, world) # Union{Method, Nothing} inf_method2 = method_for_inference_limit_heuristics(frame) # limit only if user token match @@ -656,6 +694,10 @@ function edge_matches_sv(interp::AbstractInterpreter, frame::AbsIntState, if callee_method2 !== inf_method2 return false end + if isa(frame, InferenceState) && cache_owner(frame.interp) !== cache_owner(interp) + # Don't assume that frames in different interpreters are the same + return false + end if !hardlimit || InferenceParams(interp).ignore_recursion_hardlimit # if this is a soft limit, # also inspect the parent of this edge, @@ -737,16 +779,19 @@ end # backedge computation, and concrete evaluation or constant-propagation struct MethodCallResult rt + exct edgecycle::Bool edgelimited::Bool edge::Union{Nothing,MethodInstance} effects::Effects - function MethodCallResult(@nospecialize(rt), + volatile_inf_result::Union{Nothing,VolatileInferenceResult} + function MethodCallResult(@nospecialize(rt), @nospecialize(exct), edgecycle::Bool, edgelimited::Bool, edge::Union{Nothing,MethodInstance}, - effects::Effects) - return new(rt, edgecycle, edgelimited, edge, effects) + effects::Effects, + volatile_inf_result::Union{Nothing,VolatileInferenceResult}=nothing) + return new(rt, exct, edgecycle, edgelimited, edge, effects, volatile_inf_result) end end @@ -758,27 +803,23 @@ end struct ConstCallResults rt::Any + exct::Any const_result::ConstResult effects::Effects edge::MethodInstance function ConstCallResults( - @nospecialize(rt), + @nospecialize(rt), @nospecialize(exct), const_result::ConstResult, effects::Effects, edge::MethodInstance) - return new(rt, const_result, effects, edge) + return new(rt, exct, const_result, effects, edge) end end function abstract_call_method_with_const_args(interp::AbstractInterpreter, result::MethodCallResult, @nospecialize(f), arginfo::ArgInfo, si::StmtInfo, match::MethodMatch, sv::AbsIntState, invokecall::Union{Nothing,InvokeCall}=nothing) - - if !const_prop_enabled(interp, sv, match) - return nothing - end - if bail_out_const_call(interp, result, si) - add_remark!(interp, sv, "[constprop] No more information to be gained") + if bail_out_const_call(interp, result, si, match, sv) return nothing end eligibility = concrete_eval_eligible(interp, f, result, arginfo, sv) @@ -811,21 +852,33 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter, return const_prop_call(interp, mi, result, arginfo, sv, concrete_eval_result) end -function const_prop_enabled(interp::AbstractInterpreter, sv::AbsIntState, match::MethodMatch) +function bail_out_const_call(interp::AbstractInterpreter, result::MethodCallResult, + si::StmtInfo, match::MethodMatch, sv::AbsIntState) if !InferenceParams(interp).ipo_constant_propagation add_remark!(interp, sv, "[constprop] Disabled by parameter") - return false + return true end if is_no_constprop(match.method) add_remark!(interp, sv, "[constprop] Disabled by method parameter") - return false + return true end - return true -end - -function bail_out_const_call(interp::AbstractInterpreter, result::MethodCallResult, si::StmtInfo) if is_removable_if_unused(result.effects) - if isa(result.rt, Const) || call_result_unused(si) + if isa(result.rt, Const) + add_remark!(interp, sv, "[constprop] No more information to be gained (const)") + return true + elseif call_result_unused(si) + add_remark!(interp, sv, "[constprop] No more information to be gained (unused result)") + return true + end + end + if result.rt === Bottom + if is_terminates(result.effects) && is_effect_free(result.effects) + # In the future, we may want to add `&& isa(result.exct, Const)` to + # the list of conditions here, but currently, our effect system isn't + # precise enough to let us determine :consistency of `exct`, so we + # would have to force constprop just to determine this, which is too + # expensive. + add_remark!(interp, sv, "[constprop] No more information to be gained (bottom)") return true end end @@ -853,7 +906,12 @@ function concrete_eval_eligible(interp::AbstractInterpreter, add_remark!(interp, sv, "[constprop] Concrete eval disabled for overlayed methods") end if !any_conditional(arginfo) - return :semi_concrete_eval + if may_optimize(interp) + return :semi_concrete_eval + else + # disable irinterp if optimization is disabled, since it requires optimized IR + add_remark!(interp, sv, "[constprop] Semi-concrete interpretation disabled for non-optimizing interpreter") + end end end return :none @@ -862,12 +920,14 @@ end is_all_const_arg(arginfo::ArgInfo, start::Int) = is_all_const_arg(arginfo.argtypes, start::Int) function is_all_const_arg(argtypes::Vector{Any}, start::Int) for i = start:length(argtypes) - a = widenslotwrapper(argtypes[i]) - isa(a, Const) || isconstType(a) || issingletontype(a) || return false + argtype = widenslotwrapper(argtypes[i]) + is_const_argtype(argtype) || return false end return true end +is_const_argtype(@nospecialize argtype) = isa(argtype, Const) || isconstType(argtype) || issingletontype(argtype) + any_conditional(argtypes::Vector{Any}) = any(@nospecialize(x)->isa(x, Conditional), argtypes) any_conditional(arginfo::ArgInfo) = any_conditional(arginfo.argtypes) @@ -889,15 +949,16 @@ function concrete_eval_call(interp::AbstractInterpreter, pushfirst!(args, f, invokecall.types) f = invoke end - world = get_world_counter(interp) + world = get_inference_world(interp) edge = result.edge::MethodInstance value = try Core._call_in_world_total(world, f, args...) - catch - # The evaluation threw. By :consistent-cy, we're guaranteed this would have happened at runtime - return ConstCallResults(Bottom, ConcreteResult(edge, result.effects), result.effects, edge) + catch e + # The evaluation threw. By :consistent-cy, we're guaranteed this would have happened at runtime. + # Howevever, at present, :consistency does not mandate the type of the exception + return ConstCallResults(Bottom, Any, ConcreteResult(edge, result.effects), result.effects, edge) end - return ConstCallResults(Const(value), ConcreteResult(edge, EFFECTS_TOTAL, value), EFFECTS_TOTAL, edge) + return ConstCallResults(Const(value), Union{}, ConcreteResult(edge, EFFECTS_TOTAL, value), EFFECTS_TOTAL, edge) end # check if there is a cycle and duplicated inference of `mi` @@ -921,7 +982,10 @@ function maybe_get_const_prop_profitable(interp::AbstractInterpreter, match::MethodMatch, sv::AbsIntState) method = match.method force = force_const_prop(interp, f, method) - force || const_prop_entry_heuristic(interp, result, si, sv) || return nothing + if !const_prop_entry_heuristic(interp, result, si, sv, force) + # N.B. remarks are emitted within `const_prop_entry_heuristic` + return nothing + end nargs::Int = method.nargs method.isva && (nargs -= 1) length(arginfo.argtypes) < nargs && return nothing @@ -948,8 +1012,17 @@ function maybe_get_const_prop_profitable(interp::AbstractInterpreter, return mi end -function const_prop_entry_heuristic(interp::AbstractInterpreter, result::MethodCallResult, si::StmtInfo, sv::AbsIntState) - if call_result_unused(si) && result.edgecycle +function const_prop_entry_heuristic(interp::AbstractInterpreter, result::MethodCallResult, + si::StmtInfo, sv::AbsIntState, force::Bool) + if result.rt isa LimitedAccuracy + # optimizations like inlining are disabled for limited frames, + # thus there won't be much benefit in constant-prop' here + # N.B. don't allow forced constprop' for safety (xref #52763) + add_remark!(interp, sv, "[constprop] Disabled by entry heuristic (limited accuracy)") + return false + elseif force + return true + elseif call_result_unused(si) && result.edgecycle add_remark!(interp, sv, "[constprop] Disabled by entry heuristic (edgecycle with unused result)") return false end @@ -962,27 +1035,21 @@ function const_prop_entry_heuristic(interp::AbstractInterpreter, result::MethodC if rt === Bottom add_remark!(interp, sv, "[constprop] Disabled by entry heuristic (erroneous result)") return false - else - return true end + return true elseif isa(rt, PartialStruct) || isa(rt, InterConditional) || isa(rt, InterMustAlias) # could be improved to `Const` or a more precise wrapper return true - elseif isa(rt, LimitedAccuracy) - # optimizations like inlining are disabled for limited frames, - # thus there won't be much benefit in constant-prop' here - add_remark!(interp, sv, "[constprop] Disabled by entry heuristic (limited accuracy)") - return false - else - if isa(rt, Const) - if !is_nothrow(result.effects) - # Could still be improved to Bottom (or at least could see the effects improved) - return true - end + elseif isa(rt, Const) + if is_nothrow(result.effects) + add_remark!(interp, sv, "[constprop] Disabled by entry heuristic (nothrow const)") + return false end - add_remark!(interp, sv, "[constprop] Disabled by entry heuristic (unimprovable result)") - return false + # Could still be improved to Bottom (or at least could see the effects improved) + return true end + add_remark!(interp, sv, "[constprop] Disabled by entry heuristic (unimprovable result)") + return false end # determines heuristically whether if constant propagation can be worthwhile @@ -1060,12 +1127,12 @@ function const_prop_function_heuristic(interp::AbstractInterpreter, @nospecializ if !still_nothrow || ismutabletype(arrty) return false end - elseif ⊑(𝕃ᵢ, arrty, Array) + elseif ⊑(𝕃ᵢ, arrty, Array) || ⊑(𝕃ᵢ, arrty, GenericMemory) return false end elseif istopfunction(f, :iterate) itrty = argtypes[2] - if ⊑(𝕃ᵢ, itrty, Array) + if ⊑(𝕃ᵢ, itrty, Array) || ⊑(𝕃ᵢ, itrty, GenericMemory) return false end end @@ -1128,7 +1195,7 @@ function const_prop_methodinstance_heuristic(interp::AbstractInterpreter, if isa(code, CodeInstance) inferred = @atomic :monotonic code.inferred # TODO propagate a specific `CallInfo` that conveys information about this call - if inlining_policy(interp, inferred, NoCallInfo(), IR_FLAG_NULL, mi, arginfo.argtypes) !== nothing + if src_inlining_policy(interp, inferred, NoCallInfo(), IR_FLAG_NULL) return true end end @@ -1150,85 +1217,110 @@ function semi_concrete_eval_call(interp::AbstractInterpreter, if !(isa(rt, Type) && hasintersect(rt, Bool)) ir = irsv.ir # TODO (#48913) enable double inlining pass when there are any calls - # that are newly resovled by irinterp + # that are newly resolved by irinterp # state = InliningState(interp) # ir = ssa_inlining_pass!(irsv.ir, state, propagate_inbounds(irsv)) effects = result.effects - if !is_nothrow(effects) - effects = Effects(effects; nothrow) + if nothrow + effects = Effects(effects; nothrow=true) end if noub - effects = Effects(effects; noub = ALWAYS_TRUE) + effects = Effects(effects; noub=ALWAYS_TRUE) end - return ConstCallResults(rt, SemiConcreteResult(mi, ir, effects), effects, mi) + exct = refine_exception_type(result.exct, effects) + return ConstCallResults(rt, exct, SemiConcreteResult(mi, ir, effects), effects, mi) end end end return nothing end +const_prop_result(inf_result::InferenceResult) = + ConstCallResults(inf_result.result, inf_result.exc_result, ConstPropResult(inf_result), + inf_result.ipo_effects, inf_result.linfo) + +# return cached result of constant analysis +return_cached_result(::AbstractInterpreter, inf_result::InferenceResult, ::AbsIntState) = + const_prop_result(inf_result) + +function compute_forwarded_argtypes(interp::AbstractInterpreter, arginfo::ArgInfo, sv::AbsIntState) + 𝕃ᵢ = typeinf_lattice(interp) + return has_conditional(𝕃ᵢ, sv) ? ConditionalSimpleArgtypes(arginfo, sv) : SimpleArgtypes(arginfo.argtypes) +end + function const_prop_call(interp::AbstractInterpreter, mi::MethodInstance, result::MethodCallResult, arginfo::ArgInfo, sv::AbsIntState, - concrete_eval_result::Union{Nothing,ConstCallResults}=nothing) + concrete_eval_result::Union{Nothing, ConstCallResults}=nothing) inf_cache = get_inference_cache(interp) 𝕃ᵢ = typeinf_lattice(interp) - inf_result = cache_lookup(𝕃ᵢ, mi, arginfo.argtypes, inf_cache) - if inf_result === nothing - # fresh constant prop' - argtypes = has_conditional(𝕃ᵢ, sv) ? ConditionalArgtypes(arginfo, sv) : SimpleArgtypes(arginfo.argtypes) - inf_result = InferenceResult(mi, argtypes, typeinf_lattice(interp)) - 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=#:local, interp) - if frame === nothing - add_remark!(interp, sv, "[constprop] Could not retrieve the source") - return nothing # this is probably a bad generated function (unsound), but just ignore it - end - frame.parent = sv - if !typeinf(interp, frame) - add_remark!(interp, sv, "[constprop] Fresh constant inference hit a cycle") - return nothing - end - @assert inf_result.result !== nothing - if concrete_eval_result !== nothing - # override return type and effects with concrete evaluation result if available - inf_result.result = concrete_eval_result.rt - inf_result.ipo_effects = concrete_eval_result.effects - end + forwarded_argtypes = compute_forwarded_argtypes(interp, arginfo, sv) + # use `cache_argtypes` that has been constructed for fresh regular inference if available + volatile_inf_result = result.volatile_inf_result + if volatile_inf_result !== nothing + cache_argtypes = volatile_inf_result.inf_result.argtypes else + cache_argtypes = matching_cache_argtypes(𝕃ᵢ, mi) + end + argtypes = matching_cache_argtypes(𝕃ᵢ, mi, forwarded_argtypes, cache_argtypes) + inf_result = cache_lookup(𝕃ᵢ, mi, argtypes, inf_cache) + if inf_result !== nothing # found the cache for this constant prop' if inf_result.result === nothing add_remark!(interp, sv, "[constprop] Found cached constant inference in a cycle") return nothing end + @assert inf_result.linfo === mi "MethodInstance for cached inference result does not match" + return return_cached_result(interp, inf_result, sv) end - return ConstCallResults(inf_result.result, ConstPropResult(inf_result), inf_result.ipo_effects, mi) + overridden_by_const = falses(length(argtypes)) + for i = 1:length(argtypes) + if argtypes[i] !== cache_argtypes[i] + overridden_by_const[i] = true + end + end + if !any(overridden_by_const) + add_remark!(interp, sv, "[constprop] Could not handle constant info in matching_cache_argtypes") + return nothing + end + # perform fresh constant prop' + inf_result = InferenceResult(mi, argtypes, overridden_by_const) + frame = InferenceState(inf_result, #=cache_mode=#:local, interp) + if frame === nothing + add_remark!(interp, sv, "[constprop] Could not retrieve the source") + return nothing # this is probably a bad generated function (unsound), but just ignore it + end + frame.parent = sv + if !typeinf(interp, frame) + add_remark!(interp, sv, "[constprop] Fresh constant inference hit a cycle") + return nothing + end + @assert inf_result.result !== nothing + # ConditionalSimpleArgtypes is allowed, because the only case in which it modifies + # the argtypes is when one of the argtypes is a `Conditional`, which case + # concrete_eval_result will not be available. + if concrete_eval_result !== nothing && isa(forwarded_argtypes, Union{SimpleArgtypes, ConditionalSimpleArgtypes}) + # override return type and effects with concrete evaluation result if available + inf_result.result = concrete_eval_result.rt + inf_result.ipo_effects = concrete_eval_result.effects + end + return const_prop_result(inf_result) end # TODO implement MustAlias forwarding -struct ConditionalArgtypes <: ForwardableArgtypes +struct ConditionalSimpleArgtypes arginfo::ArgInfo sv::InferenceState end -""" - matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance, - conditional_argtypes::ConditionalArgtypes) - -The implementation is able to forward `Conditional` of `conditional_argtypes`, -as well as the other general extended lattice inforamtion. -""" -function matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance, - conditional_argtypes::ConditionalArgtypes) +function matching_cache_argtypes(𝕃::AbstractLattice, mi::MethodInstance, + conditional_argtypes::ConditionalSimpleArgtypes, + cache_argtypes::Vector{Any}) (; arginfo, sv) = conditional_argtypes (; fargs, argtypes) = arginfo given_argtypes = Vector{Any}(undef, length(argtypes)) - def = linfo.def::Method + def = mi.def::Method nargs = Int(def.nargs) - cache_argtypes, overridden_by_const = matching_cache_argtypes(𝕃, linfo) local condargs = nothing for i in 1:length(argtypes) argtype = argtypes[i] @@ -1259,7 +1351,7 @@ function matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance, end if condargs !== nothing given_argtypes = let condargs=condargs - va_process_argtypes(𝕃, given_argtypes, linfo) do isva_given_argtypes::Vector{Any}, last::Int + va_process_argtypes(𝕃, given_argtypes, mi) do isva_given_argtypes::Vector{Any}, last::Int # invalidate `Conditional` imposed on varargs for (slotid, i) in condargs if slotid ≥ last && (1 ≤ i ≤ length(isva_given_argtypes)) # `Conditional` is already widened to vararg-tuple otherwise @@ -1269,9 +1361,9 @@ function matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance, end end else - given_argtypes = va_process_argtypes(𝕃, given_argtypes, linfo) + given_argtypes = va_process_argtypes(𝕃, given_argtypes, mi) end - return pick_const_args!(𝕃, cache_argtypes, overridden_by_const, given_argtypes) + return pick_const_args!(𝕃, given_argtypes, cache_argtypes) end # This is only for use with `Conditional`. @@ -1325,6 +1417,9 @@ function ssa_def_slot(@nospecialize(arg), sv::InferenceState) return arg end +# No slots in irinterp +ssa_def_slot(@nospecialize(arg), sv::IRInterpretationState) = nothing + struct AbstractIterationResult cti::Vector{Any} info::MaybeAbstractIterationInfo @@ -1419,7 +1514,7 @@ function precise_container_type(interp::AbstractInterpreter, @nospecialize(itft) return AbstractIterationResult(Any[Vararg{Any}], nothing) elseif tti0 === Any return AbstractIterationResult(Any[Vararg{Any}], nothing, Effects()) - elseif tti0 <: Array + elseif tti0 <: Array || tti0 <: GenericMemory if eltype(tti0) === Union{} return AbstractIterationResult(Any[], nothing) end @@ -1443,8 +1538,8 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n # Return Bottom if this is not an iterator. # WARNING: Changes to the iteration protocol must be reflected here, # this is not just an optimization. - # TODO: this doesn't realize that Array, SimpleVector, Tuple, and NamedTuple do not use the iterate protocol - stateordonet === Bottom && return AbstractIterationResult(Any[Bottom], AbstractIterationInfo(CallMeta[CallMeta(Bottom, call.effects, info)], true)) + # TODO: this doesn't realize that Array, GenericMemory, SimpleVector, Tuple, and NamedTuple do not use the iterate protocol + stateordonet === Bottom && return AbstractIterationResult(Any[Bottom], AbstractIterationInfo(CallMeta[CallMeta(Bottom, Any, call.effects, info)], true)) valtype = statetype = Bottom ret = Any[] calls = CallMeta[call] @@ -1522,7 +1617,7 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: sv::AbsIntState, max_methods::Int=get_max_methods(interp, sv)) itft = argtype_by_index(argtypes, 2) aft = argtype_by_index(argtypes, 3) - (itft === Bottom || aft === Bottom) && return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + (itft === Bottom || aft === Bottom) && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) aargtypes = argtype_tail(argtypes, 4) aftw = widenconst(aft) if !isa(aft, Const) && !isa(aft, PartialOpaque) && (!isType(aftw) || has_free_typevars(aftw)) @@ -1530,7 +1625,7 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: add_remark!(interp, sv, "Core._apply_iterate called on a function of a non-concrete type") # bail now, since it seems unlikely that abstract_call will be able to do any better after splitting # this also ensures we don't call abstract_call_gf_by_type below on an IntrinsicFunction or Builtin - return CallMeta(Any, Effects(), NoCallInfo()) + return CallMeta(Any, Any, Effects(), NoCallInfo()) end end res = Union{} @@ -1589,6 +1684,7 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: retinfo = UnionSplitApplyCallInfo(retinfos) napplicable = length(ctypes) seen = 0 + exct = effects.nothrow ? Union{} : Any for i = 1:napplicable ct = ctypes[i] arginfo = infos[i] @@ -1606,6 +1702,7 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: seen += 1 push!(retinfos, ApplyCallInfo(call.info, arginfo)) res = tmerge(typeinf_lattice(interp), res, call.rt) + exct = tmerge(typeinf_lattice(interp), exct, call.exct) effects = merge_effects(effects, call.effects) if bail_out_apply(interp, InferenceLoopState(ct, res, effects), sv) add_remark!(interp, sv, "_apply_iterate inference reached maximally imprecise information. Bailing on.") @@ -1615,12 +1712,13 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: if seen ≠ napplicable # there is unanalyzed candidate, widen type and effects to the top res = Any + exct = Any effects = Effects() retinfo = NoCallInfo() # NOTE this is necessary to prevent the inlining processing end # TODO: Add a special info type to capture all the iteration info. # For now, only propagate info if we don't also union-split the iteration - return CallMeta(res, effects, retinfo) + return CallMeta(res, exct, effects, retinfo) end function argtype_by_index(argtypes::Vector{Any}, i::Int) @@ -1686,7 +1784,7 @@ end thentype = Bottom elseif rt === Const(true) elsetype = Bottom - elseif elsetype isa Type && isdefined(typeof(c.val), :instance) # can only widen a if it is a singleton + elseif elsetype isa Type && issingletontype(typeof(c.val)) # can only widen a if it is a singleton elsetype = typesubtract(elsetype, typeof(c.val), max_union_splitting) end return ConditionalTypes(thentype, elsetype) @@ -1729,12 +1827,14 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs end end end - rt = builtin_tfunction(interp, f, argtypes[2:end], sv) + ft = popfirst!(argtypes) + rt = builtin_tfunction(interp, f, argtypes, sv) + pushfirst!(argtypes, ft) if has_mustalias(𝕃ᵢ) && f === getfield && isa(fargs, Vector{Any}) && la ≥ 3 a3 = argtypes[3] if isa(a3, Const) if rt !== Bottom && !isalreadyconst(rt) - var = fargs[2] + var = ssa_def_slot(fargs[2], sv) if isa(var, SlotNumber) vartyp = widenslotwrapper(argtypes[2]) fldidx = maybe_const_fldidx(vartyp, a3.val) @@ -1869,9 +1969,9 @@ function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{An na = length(argtypes) if isvarargtype(argtypes[end]) if na ≤ 2 - return CallMeta(Any, EFFECTS_THROWS, call.info) + return CallMeta(Any, Any, EFFECTS_THROWS, call.info) elseif na > 4 - return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) end a2 = argtypes[2] a3 = unwrapva(argtypes[3]) @@ -1882,7 +1982,7 @@ function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{An ⊑ᵢ = ⊑(typeinf_lattice(interp)) nothrow = a2 ⊑ᵢ TypeVar && (a3 ⊑ᵢ Type || a3 ⊑ᵢ TypeVar) else - return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) end canconst = true if isa(a3, Const) @@ -1891,10 +1991,10 @@ function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{An body = a3.parameters[1] canconst = false else - return CallMeta(Any, Effects(EFFECTS_TOTAL; nothrow), call.info) + return CallMeta(Any, Any, Effects(EFFECTS_TOTAL; nothrow), call.info) end if !(isa(body, Type) || isa(body, TypeVar)) - return CallMeta(Any, EFFECTS_THROWS, call.info) + return CallMeta(Any, Any, EFFECTS_THROWS, call.info) end if has_free_typevars(body) if isa(a2, Const) @@ -1903,42 +2003,42 @@ function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{An tv = a2.tv canconst = false else - return CallMeta(Any, EFFECTS_THROWS, call.info) + return CallMeta(Any, Any, EFFECTS_THROWS, call.info) end - isa(tv, TypeVar) || return CallMeta(Any, EFFECTS_THROWS, call.info) + isa(tv, TypeVar) || return CallMeta(Any, Any, EFFECTS_THROWS, call.info) body = UnionAll(tv, body) end ret = canconst ? Const(body) : Type{body} - return CallMeta(ret, Effects(EFFECTS_TOTAL; nothrow), call.info) + return CallMeta(ret, Any, Effects(EFFECTS_TOTAL; nothrow), call.info) end function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgInfo, si::StmtInfo, sv::AbsIntState) ft′ = argtype_by_index(argtypes, 2) ft = widenconst(ft′) - ft === Bottom && return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + ft === Bottom && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) (types, isexact, isconcrete, istype) = instanceof_tfunc(argtype_by_index(argtypes, 3), false) - isexact || return CallMeta(Any, Effects(), NoCallInfo()) + isexact || return CallMeta(Any, Any, Effects(), NoCallInfo()) unwrapped = unwrap_unionall(types) if types === Bottom || !(unwrapped isa DataType) || unwrapped.name !== Tuple.name - return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) end argtype = argtypes_to_type(argtype_tail(argtypes, 4)) nargtype = typeintersect(types, argtype) - nargtype === Bottom && return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) - nargtype isa DataType || return CallMeta(Any, Effects(), NoCallInfo()) # other cases are not implemented below - isdispatchelem(ft) || return CallMeta(Any, Effects(), NoCallInfo()) # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below + nargtype === Bottom && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) + nargtype isa DataType || return CallMeta(Any, Any, Effects(), NoCallInfo()) # other cases are not implemented below + isdispatchelem(ft) || return CallMeta(Any, Any, Effects(), NoCallInfo()) # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below ft = ft::DataType lookupsig = rewrap_unionall(Tuple{ft, unwrapped.parameters...}, types)::Type nargtype = Tuple{ft, nargtype.parameters...} argtype = Tuple{ft, argtype.parameters...} match, valid_worlds = findsup(lookupsig, method_table(interp)) - match === nothing && return CallMeta(Any, Effects(), NoCallInfo()) + match === nothing && return CallMeta(Any, Any, Effects(), NoCallInfo()) update_valid_age!(sv, valid_worlds) method = match.method tienv = ccall(:jl_type_intersection_with_env, Any, (Any, Any), nargtype, method.sig)::SimpleVector ti = tienv[1]; env = tienv[2]::SimpleVector result = abstract_call_method(interp, method, ti, env, false, si, sv) - (; rt, edge, effects) = result + (; rt, edge, effects, volatile_inf_result) = result match = MethodMatch(ti, env, method, argtype <: method.sig) res = nothing sig = match.spec_types @@ -1955,7 +2055,7 @@ function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgIn invokecall = InvokeCall(types, lookupsig) const_call_result = abstract_call_method_with_const_args(interp, result, f, arginfo, si, match, sv, invokecall) - const_result = nothing + const_result = volatile_inf_result if const_call_result !== nothing if ⊑(𝕃ₚ, const_call_result.rt, rt) (; rt, effects, const_result, edge) = const_call_result @@ -1964,7 +2064,7 @@ function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgIn rt = from_interprocedural!(interp, rt, sv, arginfo, sig) info = InvokeCallInfo(match, const_result) edge !== nothing && add_invoke_backedge!(sv, lookupsig, edge) - return CallMeta(rt, effects, info) + return CallMeta(rt, Any, effects, info) end function invoke_rewrite(xs::Vector{Any}) @@ -1978,9 +2078,27 @@ function abstract_finalizer(interp::AbstractInterpreter, argtypes::Vector{Any}, if length(argtypes) == 3 finalizer_argvec = Any[argtypes[2], argtypes[3]] call = abstract_call(interp, ArgInfo(nothing, finalizer_argvec), StmtInfo(false), sv, #=max_methods=#1) - return CallMeta(Nothing, Effects(), FinalizerInfo(call.info, call.effects)) + return CallMeta(Nothing, Any, Effects(), FinalizerInfo(call.info, call.effects)) end - return CallMeta(Nothing, Effects(), NoCallInfo()) + return CallMeta(Nothing, Any, Effects(), NoCallInfo()) +end + +function abstract_throw(interp::AbstractInterpreter, argtypes::Vector{Any}, ::AbsIntState) + na = length(argtypes) + 𝕃ᵢ = typeinf_lattice(interp) + if na == 2 + argtype2 = argtypes[2] + if isvarargtype(argtype2) + exct = tmerge(𝕃ᵢ, unwrapva(argtype2), ArgumentError) + else + exct = argtype2 + end + elseif na == 3 && isvarargtype(argtypes[3]) + exct = tmerge(𝕃ᵢ, argtypes[2], ArgumentError) + else + exct = ArgumentError + end + return CallMeta(Union{}, exct, EFFECTS_THROWS, NoCallInfo()) end # call where the function is known exactly @@ -1989,30 +2107,38 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), max_methods::Int = get_max_methods(interp, f, sv)) (; fargs, argtypes) = arginfo la = length(argtypes) - 𝕃ᵢ = typeinf_lattice(interp) if isa(f, Builtin) if f === _apply_iterate return abstract_apply(interp, argtypes, si, sv, max_methods) elseif f === invoke return abstract_invoke(interp, arginfo, si, sv) - elseif f === modifyfield! - return abstract_modifyfield!(interp, argtypes, si, sv) + elseif f === modifyfield! || f === Core.modifyglobal! || f === Core.memoryrefmodify! || f === atomic_pointermodify + return abstract_modifyop!(interp, f, argtypes, si, sv) elseif f === Core.finalizer return abstract_finalizer(interp, argtypes, sv) elseif f === applicable return abstract_applicable(interp, argtypes, sv, max_methods) + elseif f === throw + return abstract_throw(interp, argtypes, sv) end rt = abstract_call_builtin(interp, f, arginfo, sv) - effects = builtin_effects(𝕃ᵢ, f, arginfo, rt) - return CallMeta(rt, effects, NoCallInfo()) + ft = popfirst!(argtypes) + effects = builtin_effects(𝕃ᵢ, f, argtypes, rt) + if effects.nothrow + exct = Union{} + else + exct = builtin_exct(𝕃ᵢ, f, argtypes, rt) + end + pushfirst!(argtypes, ft) + return CallMeta(rt, exct, effects, NoCallInfo()) elseif isa(f, Core.OpaqueClosure) # calling an OpaqueClosure about which we have no information returns no information - return CallMeta(typeof(f).parameters[2], Effects(), NoCallInfo()) - elseif f === TypeVar + return CallMeta(typeof(f).parameters[2], Any, Effects(), NoCallInfo()) + elseif f === TypeVar && !isvarargtype(argtypes[end]) # Manually look through the definition of TypeVar to # make sure to be able to get `PartialTypeVar`s out. - (la < 2 || la > 4) && return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + 2 ≤ la ≤ 4 || return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) n = argtypes[2] ub_var = Const(Any) lb_var = Const(Union{}) @@ -2030,9 +2156,14 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), abstract_call_gf_by_type(interp, f, ArgInfo(nothing, T), si, atype, sv, max_methods) end pT = typevar_tfunc(𝕃ᵢ, n, lb_var, ub_var) - effects = builtin_effects(𝕃ᵢ, Core._typevar, ArgInfo(nothing, - Any[Const(Core._typevar), n, lb_var, ub_var]), pT) - return CallMeta(pT, effects, call.info) + typevar_argtypes = Any[n, lb_var, ub_var] + effects = builtin_effects(𝕃ᵢ, Core._typevar, typevar_argtypes, pT) + if effects.nothrow + exct = Union{} + else + exct = builtin_exct(𝕃ᵢ, Core._typevar, typevar_argtypes, pT) + end + return CallMeta(pT, exct, effects, call.info) elseif f === UnionAll call = abstract_call_gf_by_type(interp, f, ArgInfo(nothing, Any[Const(UnionAll), Any, Any]), si, Tuple{Type{UnionAll}, Any, Any}, sv, max_methods) return abstract_call_unionall(interp, argtypes, call) @@ -2040,7 +2171,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), aty = argtypes[2] ty = isvarargtype(aty) ? unwrapva(aty) : widenconst(aty) if !isconcretetype(ty) - return CallMeta(Tuple, EFFECTS_UNKNOWN, NoCallInfo()) + return CallMeta(Tuple, Any, EFFECTS_UNKNOWN, NoCallInfo()) end elseif is_return_type(f) return return_type_tfunc(interp, argtypes, si, sv) @@ -2049,16 +2180,16 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), aty = argtypes[2] if isa(aty, Conditional) call = abstract_call_gf_by_type(interp, f, ArgInfo(fargs, Any[Const(f), Bool]), si, Tuple{typeof(f), Bool}, sv, max_methods) # make sure we've inferred `!(::Bool)` - return CallMeta(Conditional(aty.slot, aty.elsetype, aty.thentype), call.effects, call.info) + return CallMeta(Conditional(aty.slot, aty.elsetype, aty.thentype), Any, call.effects, call.info) end elseif la == 3 && istopfunction(f, :!==) # mark !== as exactly a negated call to === call = abstract_call_gf_by_type(interp, f, ArgInfo(fargs, Any[Const(f), Any, Any]), si, Tuple{typeof(f), Any, Any}, sv, max_methods) rty = abstract_call_known(interp, (===), arginfo, si, sv, max_methods).rt if isa(rty, Conditional) - return CallMeta(Conditional(rty.slot, rty.elsetype, rty.thentype), EFFECTS_TOTAL, NoCallInfo()) # swap if-else + return CallMeta(Conditional(rty.slot, rty.elsetype, rty.thentype), Bottom, EFFECTS_TOTAL, NoCallInfo()) # swap if-else elseif isa(rty, Const) - return CallMeta(Const(rty.val === false), EFFECTS_TOTAL, MethodResultPure()) + return CallMeta(Const(rty.val === false), Bottom, EFFECTS_TOTAL, MethodResultPure()) end return call elseif la == 3 && istopfunction(f, :(>:)) @@ -2072,7 +2203,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), argtypes = Any[typeof(<:), argtypes[3], argtypes[2]] return abstract_call_known(interp, <:, ArgInfo(fargs, argtypes), si, sv, max_methods) elseif la == 2 && istopfunction(f, :typename) - return CallMeta(typename_static(argtypes[2]), EFFECTS_TOTAL, MethodResultPure()) + return CallMeta(typename_static(argtypes[2]), Bottom, EFFECTS_TOTAL, MethodResultPure()) elseif f === Core._hasmethod return _hasmethod_tfunc(interp, argtypes, sv) end @@ -2084,13 +2215,13 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, closure::PartialOpaque, arginfo::ArgInfo, si::StmtInfo, sv::AbsIntState, check::Bool=true) sig = argtypes_to_type(arginfo.argtypes) result = abstract_call_method(interp, closure.source::Method, sig, Core.svec(), false, si, sv) - (; rt, edge, effects) = result + (; rt, edge, effects, volatile_inf_result) = result tt = closure.typ sigT = (unwrap_unionall(tt)::DataType).parameters[1] match = MethodMatch(sig, Core.svec(), closure.source, sig <: rewrap_unionall(sigT, tt)) 𝕃ₚ = ipo_lattice(interp) ⊑ₚ = ⊑(𝕃ₚ) - const_result = nothing + const_result = volatile_inf_result if !result.edgecycle const_call_result = abstract_call_method_with_const_args(interp, result, nothing, arginfo, si, match, sv) @@ -2111,7 +2242,7 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, rt = from_interprocedural!(interp, rt, sv, arginfo, match.spec_types) info = OpaqueClosureCallInfo(match, const_result) edge !== nothing && add_backedge!(sv, edge) - return CallMeta(rt, effects, info) + return CallMeta(rt, Any, effects, info) end function most_general_argtypes(closure::PartialOpaque) @@ -2136,13 +2267,13 @@ function abstract_call_unknown(interp::AbstractInterpreter, @nospecialize(ft), wft = widenconst(ft) if hasintersect(wft, Builtin) add_remark!(interp, sv, "Could not identify method table for call") - return CallMeta(Any, Effects(), NoCallInfo()) + return CallMeta(Any, Any, Effects(), NoCallInfo()) elseif hasintersect(wft, Core.OpaqueClosure) uft = unwrap_unionall(wft) if isa(uft, DataType) - return CallMeta(rewrap_unionall(uft.parameters[2], wft), Effects(), NoCallInfo()) + return CallMeta(rewrap_unionall(uft.parameters[2], wft), Any, Effects(), NoCallInfo()) end - return CallMeta(Any, Effects(), NoCallInfo()) + return CallMeta(Any, Any, Effects(), NoCallInfo()) end # non-constant function, but the number of arguments is known and the `f` is not a builtin or intrinsic atype = argtypes_to_type(arginfo.argtypes) @@ -2162,7 +2293,7 @@ function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, si::StmtIn return abstract_call_known(interp, f, arginfo, si, sv, max_methods) end -function sp_type_rewrap(@nospecialize(T), linfo::MethodInstance, isreturn::Bool) +function sp_type_rewrap(@nospecialize(T), mi::MethodInstance, isreturn::Bool) isref = false if unwrapva(T) === Bottom return Bottom @@ -2177,12 +2308,12 @@ function sp_type_rewrap(@nospecialize(T), linfo::MethodInstance, isreturn::Bool) else return Any end - if isa(linfo.def, Method) - spsig = linfo.def.sig + if isa(mi.def, Method) + spsig = mi.def.sig if isa(spsig, UnionAll) - if !isempty(linfo.sparam_vals) + if !isempty(mi.sparam_vals) sparam_vals = Any[isvarargtype(v) ? TypeVar(:N, Union{}, Any) : - v for v in linfo.sparam_vals] + v for v in mi.sparam_vals] T = ccall(:jl_instantiate_type_in_env, Any, (Any, Any, Ptr{Any}), T, spsig, sparam_vals) isref && isreturn && T === Any && return Bottom # catch invalid return Ref{T} where T = Any for v in sparam_vals @@ -2218,56 +2349,45 @@ function abstract_eval_cfunction(interp::AbstractInterpreter, e::Expr, vtypes::U # but some of the result is likely to be valid anyways # and that may help generate better codegen abstract_call(interp, ArgInfo(nothing, at), StmtInfo(false), sv) - nothing + rt = e.args[1] + isa(rt, Type) || (rt = Any) + return RTEffects(rt, Any, EFFECTS_UNKNOWN) end function abstract_eval_special_value(interp::AbstractInterpreter, @nospecialize(e), vtypes::Union{VarTable,Nothing}, sv::AbsIntState) - if isa(e, QuoteNode) - return Const(e.value) - elseif isa(e, SSAValue) - return abstract_eval_ssavalue(e, sv) + if isa(e, SSAValue) + return RTEffects(abstract_eval_ssavalue(e, sv), Union{}, EFFECTS_TOTAL) elseif isa(e, SlotNumber) if vtypes !== nothing vtyp = vtypes[slot_id(e)] - if vtyp.undef - merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; nothrow=false)) + if !vtyp.undef + return RTEffects(vtyp.typ, Union{}, EFFECTS_TOTAL) end - return vtyp.typ + return RTEffects(vtyp.typ, UndefVarError, EFFECTS_THROWS) end - merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; nothrow=false)) - return Any + return RTEffects(Any, UndefVarError, EFFECTS_THROWS) elseif isa(e, Argument) if vtypes !== nothing - return vtypes[slot_id(e)].typ + return RTEffects(vtypes[slot_id(e)].typ, Union{}, EFFECTS_TOTAL) else @assert isa(sv, IRInterpretationState) - return sv.ir.argtypes[e.n] # TODO frame_argtypes(sv)[e.n] and remove the assertion + return RTEffects(sv.ir.argtypes[e.n], Union{}, EFFECTS_TOTAL) # TODO frame_argtypes(sv)[e.n] and remove the assertion end elseif isa(e, GlobalRef) return abstract_eval_globalref(interp, e, sv) end - - return Const(e) + if isa(e, QuoteNode) + e = e.value + end + effects = Effects(EFFECTS_TOTAL; + inaccessiblememonly = is_mutation_free_argtype(typeof(e)) ? ALWAYS_TRUE : ALWAYS_FALSE) + return RTEffects(Const(e), Union{}, effects) end -function abstract_eval_value_expr(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) - head = e.head - if head === :static_parameter - n = e.args[1]::Int - nothrow = false - if 1 <= n <= length(sv.sptypes) - sp = sv.sptypes[n] - rt = sp.typ - nothrow = !sp.undef - else - rt = Any - end - merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; nothrow)) - return rt - elseif head === :call +function abstract_eval_value_expr(interp::AbstractInterpreter, e::Expr, sv::AbsIntState) + if e.head === :call && length(e.args) ≥ 1 # TODO: We still have non-linearized cglobal - @assert e.args[1] === Core.tuple || - e.args[1] === GlobalRef(Core, :tuple) + @assert e.args[1] === Core.tuple || e.args[1] === GlobalRef(Core, :tuple) else # Some of our tests expect us to handle invalid IR here and error later # - permit that for now. @@ -2279,10 +2399,11 @@ end function abstract_eval_value(interp::AbstractInterpreter, @nospecialize(e), vtypes::Union{VarTable,Nothing}, sv::AbsIntState) if isa(e, Expr) - return abstract_eval_value_expr(interp, e, vtypes, sv) + return abstract_eval_value_expr(interp, e, sv) else - typ = abstract_eval_special_value(interp, e, vtypes, sv) - return collect_limitations!(typ, sv) + (;rt, effects) = abstract_eval_special_value(interp, e, vtypes, sv) + merge_effects!(interp, sv, effects) + return collect_limitations!(rt, sv) end end @@ -2301,17 +2422,16 @@ end struct RTEffects rt + exct effects::Effects - RTEffects(@nospecialize(rt), effects::Effects) = new(rt, effects) + RTEffects(@nospecialize(rt), @nospecialize(exct), effects::Effects) = new(rt, exct, effects) end function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, sv::InferenceState) si = StmtInfo(!call_result_unused(sv, sv.currpc)) - (; rt, effects, info) = abstract_call(interp, arginfo, si, sv) - sv.stmt_info[sv.currpc] = info - # mark this call statement as DCE-elgible - # TODO better to do this in a single pass based on the `info` object at the end of abstractinterpret? - return RTEffects(rt, effects) + call = abstract_call(interp, arginfo, si, sv) + sv.stmt_info[sv.currpc] = call.info + return call end function abstract_eval_call(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, @@ -2319,249 +2439,279 @@ function abstract_eval_call(interp::AbstractInterpreter, e::Expr, vtypes::Union{ ea = e.args argtypes = collect_argtypes(interp, ea, vtypes, sv) if argtypes === nothing - return RTEffects(Bottom, Effects()) + return RTEffects(Bottom, Any, Effects()) end arginfo = ArgInfo(ea, argtypes) - return abstract_call(interp, arginfo, sv) + (; rt, exct, effects) = abstract_call(interp, arginfo, sv) + return RTEffects(rt, exct, effects) end -function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, - sv::AbsIntState) - effects = Effects() - ehead = e.head +function abstract_eval_new(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, + sv::AbsIntState) 𝕃ᵢ = typeinf_lattice(interp) - ⊑ᵢ = ⊑(𝕃ᵢ) - if ehead === :call - (; rt, effects) = abstract_eval_call(interp, e, vtypes, sv) - t = rt - elseif ehead === :new - t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv), true) - ut = unwrap_unionall(t) - consistent = noub = ALWAYS_FALSE - nothrow = false - if isa(ut, DataType) && !isabstracttype(ut) - ismutable = ismutabletype(ut) - fcount = datatype_fieldcount(ut) - nargs = length(e.args) - 1 - if (fcount === nothing || (fcount > nargs && (let t = t - any(i::Int -> !is_undefref_fieldtype(fieldtype(t, i)), (nargs+1):fcount) - end))) - # allocation with undefined field leads to undefined behavior and should taint `:noub` - elseif ismutable - # mutable object isn't `:consistent`, but we still have a chance that - # return type information later refines the `:consistent`-cy of the method - consistent = CONSISTENT_IF_NOTRETURNED - noub = ALWAYS_TRUE - else - consistent = ALWAYS_TRUE - noub = ALWAYS_TRUE - end - if isconcretedispatch(t) - nothrow = true - @assert fcount !== nothing && fcount ≥ nargs "malformed :new expression" # syntactically enforced by the front-end - ats = Vector{Any}(undef, nargs) - local anyrefine = false - local allconst = true - for i = 1:nargs - at = widenslotwrapper(abstract_eval_value(interp, e.args[i+1], vtypes, sv)) - ft = fieldtype(t, i) - nothrow && (nothrow = at ⊑ᵢ ft) - at = tmeet(𝕃ᵢ, at, ft) - at === Bottom && @goto always_throw - if ismutable && !isconst(t, i) - ats[i] = ft # can't constrain this field (as it may be modified later) - continue - end - allconst &= isa(at, Const) - if !anyrefine - anyrefine = has_nontrivial_extended_info(𝕃ᵢ, at) || # extended lattice information - ⋤(𝕃ᵢ, at, ft) # just a type-level information, but more precise than the declared type - end - ats[i] = at + rt, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv), true) + ut = unwrap_unionall(rt) + exct = Union{ErrorException,TypeError} + if isa(ut, DataType) && !isabstracttype(ut) + ismutable = ismutabletype(ut) + fcount = datatype_fieldcount(ut) + nargs = length(e.args) - 1 + has_any_uninitialized = (fcount === nothing || (fcount > nargs && (let t = rt + any(i::Int -> !is_undefref_fieldtype(fieldtype(t, i)), (nargs+1):fcount) + end))) + if has_any_uninitialized + # allocation with undefined field is inconsistent always + consistent = ALWAYS_FALSE + elseif ismutable + # mutable allocation isn't `:consistent`, but we still have a chance that + # return type information later refines the `:consistent`-cy of the method + consistent = CONSISTENT_IF_NOTRETURNED + else + consistent = ALWAYS_TRUE # immutable allocation is consistent + end + if isconcretedispatch(rt) + nothrow = true + @assert fcount !== nothing && fcount ≥ nargs "malformed :new expression" # syntactically enforced by the front-end + ats = Vector{Any}(undef, nargs) + local anyrefine = false + local allconst = true + for i = 1:nargs + at = widenslotwrapper(abstract_eval_value(interp, e.args[i+1], vtypes, sv)) + ft = fieldtype(rt, i) + nothrow && (nothrow = ⊑(𝕃ᵢ, at, ft)) + at = tmeet(𝕃ᵢ, at, ft) + at === Bottom && return RTEffects(Bottom, TypeError, EFFECTS_THROWS) + if ismutable && !isconst(rt, i) + ats[i] = ft # can't constrain this field (as it may be modified later) + continue end - # For now, don't allow: - # - Const/PartialStruct of mutables (but still allow PartialStruct of mutables - # with `const` fields if anything refined) - # - partially initialized Const/PartialStruct - if fcount == nargs - if consistent === ALWAYS_TRUE && allconst - argvals = Vector{Any}(undef, nargs) - for j in 1:nargs - argvals[j] = (ats[j]::Const).val - end - t = Const(ccall(:jl_new_structv, Any, (Any, Ptr{Cvoid}, UInt32), t, argvals, nargs)) - elseif anyrefine - t = PartialStruct(t, ats) + allconst &= isa(at, Const) + if !anyrefine + anyrefine = has_nontrivial_extended_info(𝕃ᵢ, at) || # extended lattice information + ⋤(𝕃ᵢ, at, ft) # just a type-level information, but more precise than the declared type + end + ats[i] = at + end + # For now, don't allow: + # - Const/PartialStruct of mutables (but still allow PartialStruct of mutables + # with `const` fields if anything refined) + # - partially initialized Const/PartialStruct + if fcount == nargs + if consistent === ALWAYS_TRUE && allconst + argvals = Vector{Any}(undef, nargs) + for j in 1:nargs + argvals[j] = (ats[j]::Const).val end + rt = Const(ccall(:jl_new_structv, Any, (Any, Ptr{Cvoid}, UInt32), rt, argvals, nargs)) + elseif anyrefine + rt = PartialStruct(rt, ats) end - else - t = refine_partial_type(t) end + else + rt = refine_partial_type(rt) + nothrow = false end - effects = Effects(EFFECTS_TOTAL; consistent, nothrow, noub) - elseif ehead === :splatnew - t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv), true) - nothrow = false # TODO: More precision - if length(e.args) == 2 && isconcretedispatch(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::Tuple) && - (let t = t, at = at - all(i::Int->getfield(at.val::Tuple, i) isa fieldtype(t, i), 1:n) + else + consistent = ALWAYS_FALSE + nothrow = false + end + nothrow && (exct = Union{}) + effects = Effects(EFFECTS_TOTAL; consistent, nothrow) + return RTEffects(rt, exct, effects) +end + +function abstract_eval_splatnew(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, + sv::AbsIntState) + 𝕃ᵢ = typeinf_lattice(interp) + rt, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv), true) + nothrow = false + if length(e.args) == 2 && isconcretedispatch(rt) && !ismutabletype(rt) + at = abstract_eval_value(interp, e.args[2], vtypes, sv) + n = fieldcount(rt) + if (isa(at, Const) && isa(at.val, Tuple) && n == length(at.val::Tuple) && + (let t = rt, at = at + all(i::Int -> getfield(at.val::Tuple, i) isa fieldtype(t, i), 1:n) + end)) + nothrow = isexact + rt = Const(ccall(:jl_new_structt, Any, (Any, Any), rt, at.val)) + elseif (isa(at, PartialStruct) && ⊑(𝕃ᵢ, at, Tuple) && n > 0 && + n == length(at.fields::Vector{Any}) && !isvarargtype(at.fields[end]) && + (let t = rt, at = at + all(i::Int -> ⊑(𝕃ᵢ, (at.fields::Vector{Any})[i], fieldtype(t, i)), 1:n) end)) - nothrow = isexact - t = Const(ccall(:jl_new_structt, Any, (Any, Any), t, at.val)) - elseif (isa(at, PartialStruct) && at ⊑ᵢ Tuple && n > 0 && n == length(at.fields::Vector{Any}) && !isvarargtype(at.fields[end]) && - (let t = t, at = at, ⊑ᵢ = ⊑ᵢ - all(i::Int->(at.fields::Vector{Any})[i] ⊑ᵢ fieldtype(t, i), 1:n) - end)) - nothrow = isexact - t = PartialStruct(t, at.fields::Vector{Any}) + nothrow = isexact + rt = PartialStruct(rt, at.fields::Vector{Any}) + end + else + rt = refine_partial_type(rt) + end + consistent = !ismutabletype(rt) ? ALWAYS_TRUE : CONSISTENT_IF_NOTRETURNED + effects = Effects(EFFECTS_TOTAL; consistent, nothrow) + return RTEffects(rt, Any, effects) +end + +function abstract_eval_new_opaque_closure(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, + sv::AbsIntState) + 𝕃ᵢ = typeinf_lattice(interp) + rt = Union{} + effects = Effects() # TODO + if length(e.args) >= 4 + ea = e.args + argtypes = collect_argtypes(interp, ea, vtypes, sv) + if argtypes === nothing + rt = Bottom + effects = EFFECTS_THROWS + else + mi = frame_instance(sv) + rt = opaque_closure_tfunc(𝕃ᵢ, argtypes[1], argtypes[2], argtypes[3], + argtypes[4], argtypes[5:end], mi) + if isa(rt, PartialOpaque) && isa(sv, InferenceState) && !call_result_unused(sv, sv.currpc) + # Infer this now so that the specialization is available to + # optimization. + argtypes = most_general_argtypes(rt) + pushfirst!(argtypes, rt.env) + callinfo = abstract_call_opaque_closure(interp, rt, + ArgInfo(nothing, argtypes), StmtInfo(true), sv, #=check=#false) + sv.stmt_info[sv.currpc] = OpaqueClosureCreateInfo(callinfo) end + end + end + return RTEffects(rt, Any, effects) +end + +function abstract_eval_copyast(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, + sv::AbsIntState) + effects = EFFECTS_UNKNOWN + rt = abstract_eval_value(interp, e.args[1], vtypes, sv) + if rt isa Const && rt.val isa Expr + # `copyast` makes copies of Exprs + rt = Expr + end + return RTEffects(rt, Any, effects) +end + +function abstract_eval_isdefined(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, + sv::AbsIntState) + sym = e.args[1] + rt = Bool + effects = EFFECTS_TOTAL + exct = Union{} + isa(sym, Symbol) && (sym = GlobalRef(frame_module(sv), sym)) + if isa(sym, SlotNumber) && vtypes !== nothing + vtyp = vtypes[slot_id(sym)] + if vtyp.typ === Bottom + rt = Const(false) # never assigned previously + elseif !vtyp.undef + rt = Const(true) # definitely assigned previously + end + elseif isa(sym, GlobalRef) + if InferenceParams(interp).assume_bindings_static + rt = Const(isdefined_globalref(sym)) + elseif isdefinedconst_globalref(sym) + rt = Const(true) else - t = refine_partial_type(t) + effects = Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE) end - consistent = !ismutabletype(t) ? ALWAYS_TRUE : CONSISTENT_IF_NOTRETURNED - effects = Effects(EFFECTS_TOTAL; consistent, nothrow) - elseif ehead === :new_opaque_closure - t = Union{} - effects = Effects() # TODO - merge_effects!(interp, sv, effects) - if length(e.args) >= 4 - ea = e.args - argtypes = collect_argtypes(interp, ea, vtypes, sv) - if argtypes === nothing - t = Bottom - else - mi = frame_instance(sv) - t = opaque_closure_tfunc(𝕃ᵢ, argtypes[1], argtypes[2], argtypes[3], - argtypes[4], argtypes[5:end], mi) - if isa(t, PartialOpaque) && isa(sv, InferenceState) && !call_result_unused(sv, sv.currpc) - # Infer this now so that the specialization is available to - # optimization. - argtypes = most_general_argtypes(t) - pushfirst!(argtypes, t.env) - callinfo = abstract_call_opaque_closure(interp, t, - ArgInfo(nothing, argtypes), StmtInfo(true), sv, #=check=#false) - sv.stmt_info[sv.currpc] = OpaqueClosureCreateInfo(callinfo) - end + elseif isexpr(sym, :static_parameter) + n = sym.args[1]::Int + if 1 <= n <= length(sv.sptypes) + sp = sv.sptypes[n] + if !sp.undef + rt = Const(true) + elseif sp.typ === Bottom + rt = Const(false) end end + else + effects = EFFECTS_UNKNOWN + end + return RTEffects(rt, exct, effects) +end + +function abstract_eval_throw_undef_if_not(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) + condt = abstract_eval_value(interp, e.args[2], vtypes, sv) + condval = maybe_extract_const_bool(condt) + rt = Nothing + exct = UndefVarError + effects = EFFECTS_THROWS + if condval isa Bool + if condval + effects = EFFECTS_TOTAL + exct = Union{} + else + rt = Union{} + end + elseif !hasintersect(widenconst(condt), Bool) + rt = Union{} + end + return RTEffects(rt, exct, effects) +end + +abstract_eval_the_exception(::AbstractInterpreter, sv::InferenceState) = + the_exception_info(sv.handlers[sv.handler_at[sv.currpc][2]].exct) +abstract_eval_the_exception(::AbstractInterpreter, ::IRInterpretationState) = the_exception_info(Any) +the_exception_info(@nospecialize t) = RTEffects(t, Union{}, Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE)) + +function abstract_eval_static_parameter(::AbstractInterpreter, e::Expr, sv::AbsIntState) + n = e.args[1]::Int + nothrow = false + if 1 <= n <= length(sv.sptypes) + sp = sv.sptypes[n] + rt = sp.typ + nothrow = !sp.undef + else + rt = Any + end + exct = nothrow ? Union{} : UndefVarError + effects = Effects(EFFECTS_TOTAL; nothrow) + return RTEffects(rt, exct, effects) +end + +function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, + sv::AbsIntState) + ehead = e.head + if ehead === :call + return abstract_eval_call(interp, e, vtypes, sv) + elseif ehead === :new + return abstract_eval_new(interp, e, vtypes, sv) + elseif ehead === :splatnew + return abstract_eval_splatnew(interp, e, vtypes, sv) + elseif ehead === :new_opaque_closure + return abstract_eval_new_opaque_closure(interp, e, vtypes, sv) elseif ehead === :foreigncall - (; rt, effects) = abstract_eval_foreigncall(interp, e, vtypes, sv) - t = rt + return abstract_eval_foreigncall(interp, e, vtypes, sv) elseif ehead === :cfunction - effects = EFFECTS_UNKNOWN - t = e.args[1] - isa(t, Type) || (t = Any) - abstract_eval_cfunction(interp, e, vtypes, sv) + return abstract_eval_cfunction(interp, e, vtypes, sv) elseif ehead === :method - t = (length(e.args) == 1) ? Any : Nothing - effects = EFFECTS_UNKNOWN + rt = (length(e.args) == 1) ? Any : Nothing + return RTEffects(rt, Any, EFFECTS_UNKNOWN) elseif ehead === :copyast - effects = EFFECTS_UNKNOWN - 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 + return abstract_eval_copyast(interp, e, vtypes, sv) elseif ehead === :invoke || ehead === :invoke_modify error("type inference data-flow error: tried to double infer a function") elseif ehead === :isdefined - sym = e.args[1] - t = Bool - effects = EFFECTS_TOTAL - if isa(sym, SlotNumber) && vtypes !== nothing - vtyp = vtypes[slot_id(sym)] - if vtyp.typ === Bottom - t = Const(false) # never assigned previously - elseif !vtyp.undef - t = Const(true) # definitely assigned previously - end - elseif isa(sym, Symbol) - if isdefined(frame_module(sv), sym) - t = Const(true) - elseif InferenceParams(interp).assume_bindings_static - t = Const(false) - else - effects = Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE) - end - elseif isa(sym, GlobalRef) - if isdefined(sym.mod, sym.name) - t = Const(true) - elseif InferenceParams(interp).assume_bindings_static - t = Const(false) - else - effects = Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE) - end - elseif isexpr(sym, :static_parameter) - n = sym.args[1]::Int - if 1 <= n <= length(sv.sptypes) - sp = sv.sptypes[n] - if !sp.undef - t = Const(true) - elseif sp.typ === Bottom - t = Const(false) - end - end - else - effects = EFFECTS_UNKNOWN - end + return abstract_eval_isdefined(interp, e, vtypes, sv) elseif ehead === :throw_undef_if_not - condt = argextype(stmt.args[2], ir) - condval = maybe_extract_const_bool(condt) - t = Nothing - effects = EFFECTS_THROWS - if condval isa Bool - if condval - effects = EFFECTS_TOTAL - else - t = Union{} - end - elseif !hasintersect(windenconst(condt), Bool) - t = Union{} - end + return abstract_eval_throw_undef_if_not(interp, e, vtypes, sv) elseif ehead === :boundscheck - t = Bool - effects = Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE) + return RTEffects(Bool, Union{}, Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE)) elseif ehead === :the_exception - t = Any - effects = Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE) + return abstract_eval_the_exception(interp, sv) elseif ehead === :static_parameter - n = e.args[1]::Int - nothrow = false - if 1 <= n <= length(sv.sptypes) - sp = sv.sptypes[n] - t = sp.typ - nothrow = !sp.undef - else - t = Any - end - effects = Effects(EFFECTS_TOTAL; nothrow) + return abstract_eval_static_parameter(interp, e, sv) elseif ehead === :gc_preserve_begin || ehead === :aliasscope - t = Any - effects = Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE, effect_free=EFFECT_FREE_GLOBALLY) - elseif ehead === :gc_preserve_end || ehead === :leave || ehead === :pop_exception || ehead === :global || ehead === :popaliasscope - t = Nothing - effects = Effects(EFFECTS_TOTAL; effect_free=EFFECT_FREE_GLOBALLY) - elseif ehead === :method - t = Method - effects = Effects(EFFECTS_TOTAL; effect_free=EFFECT_FREE_GLOBALLY) + return RTEffects(Any, Union{}, Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE, effect_free=EFFECT_FREE_GLOBALLY)) + elseif ehead === :gc_preserve_end || ehead === :leave || ehead === :pop_exception || + ehead === :global || ehead === :popaliasscope + return RTEffects(Nothing, Union{}, Effects(EFFECTS_TOTAL; effect_free=EFFECT_FREE_GLOBALLY)) elseif ehead === :thunk - t = Any - effects = EFFECTS_UNKNOWN - elseif false - @label always_throw - t = Bottom - effects = EFFECTS_THROWS - else - t = abstract_eval_value_expr(interp, e, vtypes, sv) - # N.B.: abstract_eval_value_expr can modify the global effects, but - # we move out any arguments with effects during SSA construction later - # and recompute the effects. - effects = EFFECTS_TOTAL + return RTEffects(Any, Any, EFFECTS_UNKNOWN) end - return RTEffects(t, effects) + # N.B.: abstract_eval_value_expr can modify the global effects, but + # we move out any arguments with effects during SSA construction later + # and recompute the effects. + rt = abstract_eval_value_expr(interp, e, sv) + return RTEffects(rt, Any, EFFECTS_TOTAL) end # refine the result of instantiation of partially-known type `t` if some invariant can be assumed @@ -2572,7 +2722,7 @@ function refine_partial_type(@nospecialize t) # if the first/second parameter of `NamedTuple` is known to be empty, # the second/first argument should also be empty tuple type, # so refine it here - return Const(NamedTuple()) + return Const((;)) end return t end @@ -2582,25 +2732,18 @@ function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, vtypes: t = sp_type_rewrap(e.args[2], mi, true) for i = 3:length(e.args) if abstract_eval_value(interp, e.args[i], vtypes, sv) === Bottom - return RTEffects(Bottom, EFFECTS_THROWS) + return RTEffects(Bottom, Any, EFFECTS_THROWS) end end effects = foreigncall_effects(e) do @nospecialize x abstract_eval_value(interp, x, vtypes, sv) end cconv = e.args[5] - if isa(cconv, QuoteNode) && (v = cconv.value; isa(v, Tuple{Symbol, UInt8})) + if isa(cconv, QuoteNode) && (v = cconv.value; isa(v, Tuple{Symbol, UInt16})) override = decode_effects_override(v[2]) - effects = Effects(effects; - consistent = override.consistent ? ALWAYS_TRUE : effects.consistent, - effect_free = override.effect_free ? ALWAYS_TRUE : effects.effect_free, - nothrow = override.nothrow ? true : effects.nothrow, - terminates = override.terminates_globally ? true : effects.terminates, - notaskstate = override.notaskstate ? true : effects.notaskstate, - inaccessiblememonly = override.inaccessiblememonly ? ALWAYS_TRUE : effects.inaccessiblememonly, - noub = override.noub ? ALWAYS_TRUE : effects.noub) + effects = override_effects(effects, override) end - return RTEffects(t, effects) + return RTEffects(t, Any, effects) end function abstract_eval_phi(interp::AbstractInterpreter, phi::PhiNode, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) @@ -2608,69 +2751,95 @@ function abstract_eval_phi(interp::AbstractInterpreter, phi::PhiNode, vtypes::Un for i in 1:length(phi.values) isassigned(phi.values, i) || continue val = phi.values[i] - rt = tmerge(typeinf_lattice(interp), rt, abstract_eval_special_value(interp, val, vtypes, sv)) + # N.B.: Phi arguments are restricted to not have effects, so we can drop + # them here safely. + thisval = abstract_eval_special_value(interp, val, vtypes, sv).rt + rt = tmerge(typeinf_lattice(interp), rt, thisval) end return rt end function stmt_taints_inbounds_consistency(sv::AbsIntState) propagate_inbounds(sv) && return true - return (get_curr_ssaflag(sv) & IR_FLAG_INBOUNDS) != 0 + return has_curr_ssaflag(sv, IR_FLAG_INBOUNDS) end function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), vtypes::VarTable, sv::InferenceState) if !isa(e, Expr) if isa(e, PhiNode) - add_curr_ssaflag!(sv, IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW) - return abstract_eval_phi(interp, e, vtypes, sv) - end - return abstract_eval_special_value(interp, e, vtypes, sv) - end - (; rt, effects) = abstract_eval_statement_expr(interp, e, vtypes, sv) - if effects.noub === NOUB_IF_NOINBOUNDS - if !iszero(get_curr_ssaflag(sv) & IR_FLAG_INBOUNDS) - effects = Effects(effects; noub=ALWAYS_FALSE) - elseif !propagate_inbounds(sv) - # The callee read our inbounds flag, but unless we propagate inbounds, - # we ourselves don't read our parent's inbounds. - effects = Effects(effects; noub=ALWAYS_TRUE) + add_curr_ssaflag!(sv, IR_FLAGS_REMOVABLE) + # Implement convergence for PhiNodes. In particular, PhiNodes need to tmerge over + # the incoming values from all iterations, but `abstract_eval_phi` will only tmerge + # over the first and last iterations. By tmerging in the current old_rt, we ensure that + # we will not lose an intermediate value. + rt = abstract_eval_phi(interp, e, vtypes, sv) + old_rt = sv.ssavaluetypes[sv.currpc] + rt = old_rt === NOT_FOUND ? rt : tmerge(typeinf_lattice(interp), old_rt, rt) + return RTEffects(rt, Union{}, EFFECTS_TOTAL) + end + (; rt, exct, effects) = abstract_eval_special_value(interp, e, vtypes, sv) + else + (; rt, exct, effects) = abstract_eval_statement_expr(interp, e, vtypes, sv) + if effects.noub === NOUB_IF_NOINBOUNDS + if has_curr_ssaflag(sv, IR_FLAG_INBOUNDS) + effects = Effects(effects; noub=ALWAYS_FALSE) + elseif !propagate_inbounds(sv) + # The callee read our inbounds flag, but unless we propagate inbounds, + # we ourselves don't read our parent's inbounds. + effects = Effects(effects; noub=ALWAYS_TRUE) + end + end + e = e::Expr + @assert !isa(rt, TypeVar) "unhandled TypeVar" + rt = maybe_singleton_const(rt) + if !isempty(sv.pclimitations) + if rt isa Const || rt === Union{} + empty!(sv.pclimitations) + else + rt = LimitedAccuracy(rt, sv.pclimitations) + sv.pclimitations = IdSet{InferenceState}() + end end end # N.B.: This only applies to the effects of the statement itself. # It is possible for arguments (GlobalRef/:static_parameter) to throw, # but these will be recomputed during SSA construction later. + override = decode_statement_effects_override(sv) + effects = override_effects(effects, override) set_curr_ssaflag!(sv, flags_for_effects(effects), IR_FLAGS_EFFECTS) merge_effects!(interp, sv, effects) - e = e::Expr - @assert !isa(rt, TypeVar) "unhandled TypeVar" - rt = maybe_singleton_const(rt) - if !isempty(sv.pclimitations) - if rt isa Const || rt === Union{} - empty!(sv.pclimitations) - else - rt = LimitedAccuracy(rt, sv.pclimitations) - sv.pclimitations = IdSet{InferenceState}() - end - end - return rt + + return RTEffects(rt, exct, effects) end -function isdefined_globalref(g::GlobalRef) - return ccall(:jl_globalref_boundp, Cint, (Any,), g) != 0 +function override_effects(effects::Effects, override::EffectsOverride) + return Effects(effects; + consistent = override.consistent ? ALWAYS_TRUE : effects.consistent, + effect_free = override.effect_free ? ALWAYS_TRUE : effects.effect_free, + nothrow = override.nothrow ? true : effects.nothrow, + terminates = override.terminates_globally ? true : effects.terminates, + notaskstate = override.notaskstate ? true : effects.notaskstate, + inaccessiblememonly = override.inaccessiblememonly ? ALWAYS_TRUE : effects.inaccessiblememonly, + noub = override.noub ? ALWAYS_TRUE : + override.noub_if_noinbounds && effects.noub !== ALWAYS_TRUE ? NOUB_IF_NOINBOUNDS : + effects.noub) end -function abstract_eval_globalref(g::GlobalRef) - if isdefined_globalref(g) && isconst(g) +isdefined_globalref(g::GlobalRef) = !iszero(ccall(:jl_globalref_boundp, Cint, (Any,), g)) +isdefinedconst_globalref(g::GlobalRef) = isconst(g) && isdefined_globalref(g) + +function abstract_eval_globalref_type(g::GlobalRef) + if isdefinedconst_globalref(g) return Const(ccall(:jl_get_globalref_value, Any, (Any,), g)) end ty = ccall(:jl_get_binding_type, Any, (Any, Any), g.mod, g.name) ty === nothing && return Any return ty end -abstract_eval_global(M::Module, s::Symbol) = abstract_eval_globalref(GlobalRef(M, s)) +abstract_eval_global(M::Module, s::Symbol) = abstract_eval_globalref_type(GlobalRef(M, s)) function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState) - rt = abstract_eval_globalref(g) + rt = abstract_eval_globalref_type(g) consistent = inaccessiblememonly = ALWAYS_FALSE nothrow = false if isa(rt, Const) @@ -2679,19 +2848,22 @@ function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, sv:: if is_mutation_free_argtype(rt) inaccessiblememonly = ALWAYS_TRUE end - elseif isdefined_globalref(g) - nothrow = true elseif InferenceParams(interp).assume_bindings_static consistent = inaccessiblememonly = ALWAYS_TRUE - rt = Union{} + if isdefined_globalref(g) + nothrow = true + else + rt = Union{} + end + elseif isdefinedconst_globalref(g) + nothrow = true end - merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly)) - return rt + return RTEffects(rt, nothrow ? Union{} : UndefVarError, Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly)) end function handle_global_assignment!(interp::AbstractInterpreter, frame::InferenceState, lhs::GlobalRef, @nospecialize(newty)) effect_free = ALWAYS_FALSE - nothrow = global_assignment_nothrow(lhs.mod, lhs.name, newty) + nothrow = global_assignment_nothrow(lhs.mod, lhs.name, ignorelimited(newty)) inaccessiblememonly = ALWAYS_FALSE if !nothrow sub_curr_ssaflag!(frame, IR_FLAG_NOTHROW) @@ -2880,50 +3052,50 @@ end struct BasicStmtChange changes::Union{Nothing,StateUpdate} - type::Any # ::Union{Type, Nothing} - `nothing` if this statement may not be used as an SSA Value + rt::Any # extended lattice element or `nothing` - `nothing` if this statement may not be used as an SSA Value + exct::Any # TODO effects::Effects - BasicStmtChange(changes::Union{Nothing,StateUpdate}, @nospecialize type) = new(changes, type) + BasicStmtChange(changes::Union{Nothing,StateUpdate}, @nospecialize(rt), @nospecialize(exct)) = new(changes, rt, exct) end @inline function abstract_eval_basic_statement(interp::AbstractInterpreter, @nospecialize(stmt), pc_vartable::VarTable, frame::InferenceState) if isa(stmt, NewvarNode) changes = StateUpdate(stmt.slot, VarState(Bottom, true), pc_vartable, false) - return BasicStmtChange(changes, nothing) + return BasicStmtChange(changes, nothing, Union{}) elseif !isa(stmt, Expr) - t = abstract_eval_statement(interp, stmt, pc_vartable, frame) - return BasicStmtChange(nothing, t) + (; rt, exct) = abstract_eval_statement(interp, stmt, pc_vartable, frame) + return BasicStmtChange(nothing, rt, exct) end changes = nothing - stmt = stmt::Expr hd = stmt.head if hd === :(=) - t = abstract_eval_statement(interp, stmt.args[2], pc_vartable, frame) - if t === Bottom - return BasicStmtChange(nothing, Bottom) + (; rt, exct) = abstract_eval_statement(interp, stmt.args[2], pc_vartable, frame) + if rt === Bottom + return BasicStmtChange(nothing, Bottom, exct) end lhs = stmt.args[1] if isa(lhs, SlotNumber) - changes = StateUpdate(lhs, VarState(t, false), pc_vartable, false) + changes = StateUpdate(lhs, VarState(rt, false), pc_vartable, false) elseif isa(lhs, GlobalRef) - handle_global_assignment!(interp, frame, lhs, t) + handle_global_assignment!(interp, frame, lhs, rt) elseif !isa(lhs, SSAValue) merge_effects!(interp, frame, EFFECTS_UNKNOWN) end - return BasicStmtChange(changes, t) + return BasicStmtChange(changes, rt, exct) elseif hd === :method fname = stmt.args[1] if isa(fname, SlotNumber) changes = StateUpdate(fname, VarState(Any, false), pc_vartable, false) end - return BasicStmtChange(changes, nothing) + return BasicStmtChange(changes, nothing, Union{}) elseif (hd === :code_coverage_effect || ( hd !== :boundscheck && # :boundscheck can be narrowed to Bool is_meta_expr(stmt))) - return BasicStmtChange(nothing, Nothing) + return BasicStmtChange(nothing, Nothing, Bottom) else - t = abstract_eval_statement(interp, stmt, pc_vartable, frame) - return BasicStmtChange(nothing, t) + (; rt, exct) = abstract_eval_statement(interp, stmt, pc_vartable, frame) + return BasicStmtChange(nothing, rt, exct) end end @@ -2981,6 +3153,51 @@ function update_bestguess!(interp::AbstractInterpreter, frame::InferenceState, end end +function update_exc_bestguess!(interp::AbstractInterpreter, @nospecialize(exct), frame::InferenceState) + 𝕃ₚ = ipo_lattice(interp) + cur_hand = frame.handler_at[frame.currpc][1] + if cur_hand == 0 + if !⊑(𝕃ₚ, exct, frame.exc_bestguess) + frame.exc_bestguess = tmerge(𝕃ₚ, frame.exc_bestguess, exct) + update_cycle_worklists!(frame) do caller::InferenceState, caller_pc::Int + caller_handler = caller.handler_at[caller_pc][1] + caller_exct = caller_handler == 0 ? + caller.exc_bestguess : caller.handlers[caller_handler].exct + return caller_exct !== Any + end + end + else + handler_frame = frame.handlers[cur_hand] + if !⊑(𝕃ₚ, exct, handler_frame.exct) + handler_frame.exct = tmerge(𝕃ₚ, handler_frame.exct, exct) + enter = frame.src.code[handler_frame.enter_idx]::EnterNode + exceptbb = block_for_inst(frame.cfg, enter.catch_dest) + push!(frame.ip, exceptbb) + end + end +end + +function propagate_to_error_handler!(currstate::VarTable, frame::InferenceState, 𝕃ᵢ::AbstractLattice) + # If this statement potentially threw, propagate the currstate to the + # exception handler, BEFORE applying any state changes. + cur_hand = frame.handler_at[frame.currpc][1] + if cur_hand != 0 + enter = frame.src.code[frame.handlers[cur_hand].enter_idx]::EnterNode + exceptbb = block_for_inst(frame.cfg, enter.catch_dest) + if update_bbstate!(𝕃ᵢ, frame, exceptbb, currstate) + push!(frame.ip, exceptbb) + end + end +end + +function update_cycle_worklists!(callback, frame::InferenceState) + for (caller, caller_pc) in frame.cycle_backedges + if callback(caller, caller_pc) + push!(caller.ip, block_for_inst(caller.cfg, caller_pc)) + end + end +end + # make as much progress on `frame` as possible (without handling cycles) function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) @assert !is_inferred(frame) @@ -3020,6 +3237,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) @goto branch elseif isa(stmt, GotoIfNot) condx = stmt.cond + condxslot = ssa_def_slot(condx, frame) condt = abstract_eval_value(interp, condx, currstate, frame) if condt === Bottom ssavaluetypes[currpc] = Bottom @@ -3027,16 +3245,18 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) @goto find_next_bb end orig_condt = condt - if !(isa(condt, Const) || isa(condt, Conditional)) && isa(condx, SlotNumber) + if !(isa(condt, Const) || isa(condt, Conditional)) && isa(condxslot, SlotNumber) # if this non-`Conditional` object is a slot, we form and propagate # the conditional constraint on it - condt = Conditional(condx, Const(true), Const(false)) + condt = Conditional(condxslot, Const(true), Const(false)) end condval = maybe_extract_const_bool(condt) nothrow = (condval !== nothing) || ⊑(𝕃ᵢ, orig_condt, Bool) if nothrow add_curr_ssaflag!(frame, IR_FLAG_NOTHROW) else + update_exc_bestguess!(interp, TypeError, frame) + propagate_to_error_handler!(currstate, frame, 𝕃ᵢ) merge_effects!(interp, frame, EFFECTS_THROWS) end @@ -3097,56 +3317,63 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) elseif isa(stmt, ReturnNode) rt = abstract_eval_value(interp, stmt.val, currstate, frame) if update_bestguess!(interp, frame, currstate, rt) - for (caller, caller_pc) in frame.cycle_backedges - if caller.ssavaluetypes[caller_pc] !== Any - # no reason to revisit if that call-site doesn't affect the final result - push!(caller.ip, block_for_inst(caller.cfg, caller_pc)) - end + update_cycle_worklists!(frame) do caller::InferenceState, caller_pc::Int + # no reason to revisit if that call-site doesn't affect the final result + return caller.ssavaluetypes[caller_pc] !== Any end end ssavaluetypes[frame.currpc] = Any @goto find_next_bb - elseif isexpr(stmt, :enter) - # Propagate entry info to exception handler - l = stmt.args[1]::Int - catchbb = block_for_inst(frame.cfg, l) - if update_bbstate!(𝕃ᵢ, frame, catchbb, currstate) - push!(W, catchbb) + elseif isa(stmt, EnterNode) + ssavaluetypes[currpc] = Any + add_curr_ssaflag!(frame, IR_FLAG_NOTHROW) + if isdefined(stmt, :scope) + scopet = abstract_eval_value(interp, stmt.scope, currstate, frame) + handler = frame.handlers[frame.handler_at[frame.currpc+1][1]] + @assert handler.scopet !== nothing + if !⊑(𝕃ᵢ, scopet, handler.scopet) + handler.scopet = tmerge(𝕃ᵢ, scopet, handler.scopet) + if isdefined(handler, :scope_uses) + for bb in handler.scope_uses + push!(W, bb) + end + end + end end + @goto fallthrough + elseif isexpr(stmt, :leave) ssavaluetypes[currpc] = Any @goto fallthrough end # Fall through terminator - treat as regular stmt end # Process non control-flow statements - (; changes, type) = abstract_eval_basic_statement(interp, + (; changes, rt, exct) = abstract_eval_basic_statement(interp, stmt, currstate, frame) - if type === Bottom + if !has_curr_ssaflag(frame, IR_FLAG_NOTHROW) + if exct !== Union{} + update_exc_bestguess!(interp, exct, frame) + # TODO: assert that these conditions match. For now, we assume the `nothrow` flag + # to be correct, but allow the exct to be an over-approximation. + end + propagate_to_error_handler!(currstate, frame, 𝕃ᵢ) + end + if rt === Bottom ssavaluetypes[currpc] = Bottom + # Special case: Bottom-typed PhiNodes do not error (but must also be unused) + if isa(stmt, PhiNode) + continue + end @goto find_next_bb end if changes !== nothing stoverwrite1!(currstate, changes) - let cur_hand = frame.handler_at[currpc], l, enter - while cur_hand != 0 - enter = frame.src.code[cur_hand]::Expr - l = enter.args[1]::Int - exceptbb = block_for_inst(frame.cfg, l) - # 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[exceptbb]::VarTable, changes) - push!(W, exceptbb) - end - cur_hand = frame.handler_at[cur_hand] - end - end end - if type === nothing + if rt === nothing ssavaluetypes[currpc] = Any continue end - record_ssa_assign!(𝕃ᵢ, currpc, type, frame) + record_ssa_assign!(𝕃ᵢ, currpc, rt, frame) end # for currpc in bbstart:bbend # Case 1: Fallthrough termination diff --git a/base/compiler/abstractlattice.jl b/base/compiler/abstractlattice.jl index c1229124d1cec..0102a59667c1e 100644 --- a/base/compiler/abstractlattice.jl +++ b/base/compiler/abstractlattice.jl @@ -260,7 +260,7 @@ end Appropriately converts inferred type of a return value `rt` to such a type that we know we can store in the cache and is valid and good inter-procedurally, E.g. if `rt isa Conditional` then `rt` should be converted to `InterConditional` -or the other cachable lattice element. +or the other cacheable lattice element. External lattice `𝕃ᵢ::ExternalLattice` may overload: - `widenreturn(𝕃ᵢ::ExternalLattice, @nospecialize(rt), info::BestguessInfo)` @@ -285,9 +285,12 @@ has_extended_unionsplit(::AnyMustAliasesLattice) = true has_extended_unionsplit(::JLTypeLattice) = false # Curried versions -⊑(lattice::AbstractLattice) = (@nospecialize(a), @nospecialize(b)) -> ⊑(lattice, a, b) -⊏(lattice::AbstractLattice) = (@nospecialize(a), @nospecialize(b)) -> ⊏(lattice, a, b) -⋤(lattice::AbstractLattice) = (@nospecialize(a), @nospecialize(b)) -> ⋤(lattice, a, b) +⊑(𝕃::AbstractLattice) = (@nospecialize(a), @nospecialize(b)) -> ⊑(𝕃, a, b) +⊏(𝕃::AbstractLattice) = (@nospecialize(a), @nospecialize(b)) -> ⊏(𝕃, a, b) +⋤(𝕃::AbstractLattice) = (@nospecialize(a), @nospecialize(b)) -> ⋤(𝕃, a, b) +partialorder(𝕃::AbstractLattice) = ⊑(𝕃) +strictpartialorder(𝕃::AbstractLattice) = ⊏(𝕃) +strictneqpartialorder(𝕃::AbstractLattice) = ⋤(𝕃) # Fallbacks for external packages using these methods const fallback_lattice = InferenceLattice(BaseInferenceLattice.instance) diff --git a/base/compiler/cicache.jl b/base/compiler/cicache.jl index 8332777e6d5bc..a6ed18fe5105f 100644 --- a/base/compiler/cicache.jl +++ b/base/compiler/cicache.jl @@ -7,15 +7,16 @@ Internally, each `MethodInstance` keep a unique global cache of code instances that have been created for the given method instance, stratified by world age ranges. This struct abstracts over access to this cache. """ -struct InternalCodeCache end +struct InternalCodeCache + owner::Any # `jl_egal` is used for comparison +end function setindex!(cache::InternalCodeCache, ci::CodeInstance, mi::MethodInstance) + @assert ci.owner === cache.owner ccall(:jl_mi_cache_insert, Cvoid, (Any, Any), mi, ci) return cache end -const GLOBAL_CI_CACHE = InternalCodeCache() - struct WorldRange min_world::UInt max_world::UInt @@ -49,11 +50,11 @@ WorldView(wvc::WorldView, wr::WorldRange) = WorldView(wvc.cache, wr) WorldView(wvc::WorldView, args...) = WorldView(wvc.cache, args...) function haskey(wvc::WorldView{InternalCodeCache}, mi::MethodInstance) - return ccall(:jl_rettype_inferred, Any, (Any, UInt, UInt), mi, first(wvc.worlds), last(wvc.worlds)) !== nothing + return ccall(:jl_rettype_inferred, Any, (Any, Any, UInt, UInt), wvc.cache.owner, mi, first(wvc.worlds), last(wvc.worlds)) !== nothing end function get(wvc::WorldView{InternalCodeCache}, mi::MethodInstance, default) - r = ccall(:jl_rettype_inferred, Any, (Any, UInt, UInt), mi, first(wvc.worlds), last(wvc.worlds)) + r = ccall(:jl_rettype_inferred, Any, (Any, Any, UInt, UInt), wvc.cache.owner, mi, first(wvc.worlds), last(wvc.worlds)) if r === nothing return default end @@ -70,3 +71,9 @@ function setindex!(wvc::WorldView{InternalCodeCache}, ci::CodeInstance, mi::Meth setindex!(wvc.cache, ci, mi) return wvc end + +function code_cache(interp::AbstractInterpreter) + cache = InternalCodeCache(cache_owner(interp)) + worlds = WorldRange(get_inference_world(interp)) + return WorldView(cache, worlds) +end diff --git a/base/compiler/compiler.jl b/base/compiler/compiler.jl index 31db9a5551c2b..12d6d5eb38764 100644 --- a/base/compiler/compiler.jl +++ b/base/compiler/compiler.jl @@ -28,12 +28,50 @@ include(mod, x) = Core.include(mod, x) macro inline() Expr(:meta, :inline) end macro noinline() Expr(:meta, :noinline) end +macro _boundscheck() Expr(:boundscheck) end + convert(::Type{Any}, Core.@nospecialize x) = x convert(::Type{T}, x::T) where {T} = x -# mostly used by compiler/methodtable.jl, but also by reflection.jl +# These types are used by reflection.jl and expr.jl too, so declare them here. +# Note that `@assume_effects` is available only after loading namedtuple.jl. abstract type MethodTableView end abstract type AbstractInterpreter end +struct EffectsOverride + consistent::Bool + effect_free::Bool + nothrow::Bool + terminates_globally::Bool + terminates_locally::Bool + notaskstate::Bool + inaccessiblememonly::Bool + noub::Bool + noub_if_noinbounds::Bool +end +function EffectsOverride( + override::EffectsOverride = + EffectsOverride(false, false, false, false, false, false, false, false, false); + consistent::Bool = override.consistent, + effect_free::Bool = override.effect_free, + nothrow::Bool = override.nothrow, + terminates_globally::Bool = override.terminates_globally, + terminates_locally::Bool = override.terminates_locally, + notaskstate::Bool = override.notaskstate, + inaccessiblememonly::Bool = override.inaccessiblememonly, + noub::Bool = override.noub, + noub_if_noinbounds::Bool = override.noub_if_noinbounds) + return EffectsOverride( + consistent, + effect_free, + nothrow, + terminates_globally, + terminates_locally, + notaskstate, + inaccessiblememonly, + noub, + noub_if_noinbounds) +end +const NUM_EFFECTS_OVERRIDES = 9 # sync with julia.h # essential files and libraries include("essentials.jl") @@ -104,6 +142,7 @@ include("strings/lazy.jl") # core array operations include("indices.jl") +include("genericmemory.jl") include("array.jl") include("abstractarray.jl") @@ -161,8 +200,10 @@ include("compiler/validation.jl") include("compiler/ssair/basicblock.jl") include("compiler/ssair/domtree.jl") include("compiler/ssair/ir.jl") +include("compiler/ssair/tarjan.jl") include("compiler/abstractlattice.jl") +include("compiler/stmtinfo.jl") include("compiler/inferenceresult.jl") include("compiler/inferencestate.jl") @@ -170,7 +211,6 @@ include("compiler/typeutils.jl") include("compiler/typelimits.jl") include("compiler/typelattice.jl") include("compiler/tfuncs.jl") -include("compiler/stmtinfo.jl") include("compiler/abstractinterpretation.jl") include("compiler/typeinfer.jl") diff --git a/base/compiler/effects.jl b/base/compiler/effects.jl index 485ba5e416665..a3d30baef9efa 100644 --- a/base/compiler/effects.jl +++ b/base/compiler/effects.jl @@ -24,6 +24,11 @@ following meanings: * `EFFECT_FREE_IF_INACCESSIBLEMEMONLY`: the `:effect-free`-ness of this method can later be refined to `ALWAYS_TRUE` in a case when `:inaccessiblememonly` is proven. - `nothrow::Bool`: this method is guaranteed to not throw an exception. + If the execution of this method may raise `MethodError`s and similar exceptions, then + the method is not considered as `:nothrow`. + However, note that environment-dependent errors like `StackOverflowError` or `InterruptException` + are not modeled by this effect and thus a method that may result in `StackOverflowError` + does not necessarily need to taint `:nothrow` (although it should usually taint `:terminates` too). - `terminates::Bool`: this method is guaranteed to terminate. - `notaskstate::Bool`: this method does not access any state bound to the current task and may thus be moved to a different task without changing observable @@ -38,15 +43,16 @@ following meanings: except that it may access or modify mutable memory pointed to by its call arguments. This may later be refined to `ALWAYS_TRUE` in a case when call arguments are known to be immutable. This state corresponds to LLVM's `inaccessiblemem_or_argmemonly` function attribute. -- `noub::Bool`: indicates that the method will not execute any undefined behavior (for any input). +- `noub::UInt8`: indicates that the method will not execute any undefined behavior (for any input). Note that undefined behavior may technically cause the method to violate any other effect assertions (such as `:consistent` or `:effect_free`) as well, but we do not model this, and they assume the absence of undefined behavior. + * `ALWAYS_TRUE`: this method is guaranteed to not execute any undefined behavior. + * `ALWAYS_FALSE`: this method may execute undefined behavior. + * `NOUB_IF_NOINBOUNDS`: this method is guaranteed to not execute any undefined behavior + if the caller does not set nor propagate the `@inbounds` context. - `nonoverlayed::Bool`: indicates that any methods that may be called within this method are not defined in an [overlayed method table](@ref OverlayMethodTable). -- `noinbounds::Bool`: If set, indicates that this method does not read the parent's `:inbounds` - state. In particular, it does not have any reached `:boundscheck` exprs, not propagates inbounds - to any children that do. Note that the representations above are just internal implementation details and thus likely to change in the future. See [`Base.@assume_effects`](@ref) for more detailed explanation @@ -87,9 +93,7 @@ The output represents the state of different effect properties in the following 7. `noub` (`u`): - `+u` (green): `true` - `-u` (red): `false` -8. `noinbounds` (`i`): - - `+i` (green): `true` - - `-i` (red): `false` + - `?u` (yellow): `NOUB_IF_NOINBOUNDS` Additionally, if the `nonoverlayed` property is false, a red prime symbol (′) is displayed after the tuple. """ @@ -171,6 +175,68 @@ function Effects(effects::Effects = _EFFECTS_UNKNOWN; nonoverlayed) end +function is_better_effects(new::Effects, old::Effects) + any_improved = false + if new.consistent == ALWAYS_TRUE + any_improved |= old.consistent != ALWAYS_TRUE + else + if !iszero(new.consistent & CONSISTENT_IF_NOTRETURNED) + old.consistent == ALWAYS_TRUE && return false + any_improved |= iszero(old.consistent & CONSISTENT_IF_NOTRETURNED) + elseif !iszero(new.consistent & CONSISTENT_IF_INACCESSIBLEMEMONLY) + old.consistent == ALWAYS_TRUE && return false + any_improved |= iszero(old.consistent & CONSISTENT_IF_INACCESSIBLEMEMONLY) + else + return false + end + end + if new.effect_free == ALWAYS_TRUE + any_improved |= old.consistent != ALWAYS_TRUE + elseif new.effect_free == EFFECT_FREE_IF_INACCESSIBLEMEMONLY + old.effect_free == ALWAYS_TRUE && return false + any_improved |= old.effect_free != EFFECT_FREE_IF_INACCESSIBLEMEMONLY + elseif new.effect_free != old.effect_free + return false + end + if new.nothrow + any_improved |= !old.nothrow + elseif new.nothrow != old.nothrow + return false + end + if new.terminates + any_improved |= !old.terminates + elseif new.terminates != old.terminates + return false + end + if new.notaskstate + any_improved |= !old.notaskstate + elseif new.notaskstate != old.notaskstate + return false + end + if new.inaccessiblememonly == ALWAYS_TRUE + any_improved |= old.inaccessiblememonly != ALWAYS_TRUE + elseif new.inaccessiblememonly == INACCESSIBLEMEM_OR_ARGMEMONLY + old.inaccessiblememonly == ALWAYS_TRUE && return false + any_improved |= old.inaccessiblememonly != INACCESSIBLEMEM_OR_ARGMEMONLY + elseif new.inaccessiblememonly != old.inaccessiblememonly + return false + end + if new.noub == ALWAYS_TRUE + any_improved |= old.noub != ALWAYS_TRUE + elseif new.noub == NOUB_IF_NOINBOUNDS + old.noub == ALWAYS_TRUE && return false + any_improved |= old.noub != NOUB_IF_NOINBOUNDS + elseif new.noub != old.noub + return false + end + if new.nonoverlayed + any_improved |= !old.nonoverlayed + elseif new.nonoverlayed != old.nonoverlayed + return false + end + return any_improved +end + function merge_effects(old::Effects, new::Effects) return Effects( merge_effectbits(old.consistent, new.consistent), @@ -197,15 +263,14 @@ is_nothrow(effects::Effects) = effects.nothrow is_terminates(effects::Effects) = effects.terminates is_notaskstate(effects::Effects) = effects.notaskstate is_inaccessiblememonly(effects::Effects) = effects.inaccessiblememonly === ALWAYS_TRUE +is_noub(effects::Effects) = effects.noub === ALWAYS_TRUE +is_noub_if_noinbounds(effects::Effects) = effects.noub === NOUB_IF_NOINBOUNDS is_nonoverlayed(effects::Effects) = effects.nonoverlayed -is_noub(effects::Effects, noinbounds::Bool=true) = - effects.noub === ALWAYS_TRUE || (noinbounds && effects.noub === NOUB_IF_NOINBOUNDS) - # implies `is_notaskstate` & `is_inaccessiblememonly`, but not explicitly checked here is_foldable(effects::Effects) = is_consistent(effects) && - is_noub(effects) && + (is_noub(effects) || is_noub_if_noinbounds(effects)) && is_effect_free(effects) && is_terminates(effects) @@ -253,38 +318,32 @@ function decode_effects(e::UInt32) _Bool((e >> 12) & 0x01)) end -struct EffectsOverride - consistent::Bool - effect_free::Bool - nothrow::Bool - terminates_globally::Bool - terminates_locally::Bool - notaskstate::Bool - inaccessiblememonly::Bool - noub::Bool -end - function encode_effects_override(eo::EffectsOverride) - e = 0x00 - eo.consistent && (e |= (0x01 << 0)) - eo.effect_free && (e |= (0x01 << 1)) - eo.nothrow && (e |= (0x01 << 2)) - eo.terminates_globally && (e |= (0x01 << 3)) - eo.terminates_locally && (e |= (0x01 << 4)) - eo.notaskstate && (e |= (0x01 << 5)) - eo.inaccessiblememonly && (e |= (0x01 << 6)) - eo.noub && (e |= (0x01 << 7)) + e = 0x0000 + eo.consistent && (e |= (0x0001 << 0)) + eo.effect_free && (e |= (0x0001 << 1)) + eo.nothrow && (e |= (0x0001 << 2)) + eo.terminates_globally && (e |= (0x0001 << 3)) + eo.terminates_locally && (e |= (0x0001 << 4)) + eo.notaskstate && (e |= (0x0001 << 5)) + eo.inaccessiblememonly && (e |= (0x0001 << 6)) + eo.noub && (e |= (0x0001 << 7)) + eo.noub_if_noinbounds && (e |= (0x0001 << 8)) return e end -function decode_effects_override(e::UInt8) +function decode_effects_override(e::UInt16) return EffectsOverride( - (e & (0x01 << 0)) != 0x00, - (e & (0x01 << 1)) != 0x00, - (e & (0x01 << 2)) != 0x00, - (e & (0x01 << 3)) != 0x00, - (e & (0x01 << 4)) != 0x00, - (e & (0x01 << 5)) != 0x00, - (e & (0x01 << 6)) != 0x00, - (e & (0x01 << 7)) != 0x00) + !iszero(e & (0x0001 << 0)), + !iszero(e & (0x0001 << 1)), + !iszero(e & (0x0001 << 2)), + !iszero(e & (0x0001 << 3)), + !iszero(e & (0x0001 << 4)), + !iszero(e & (0x0001 << 5)), + !iszero(e & (0x0001 << 6)), + !iszero(e & (0x0001 << 7)), + !iszero(e & (0x0001 << 8))) end + +decode_statement_effects_override(ssaflag::UInt32) = + decode_effects_override(UInt16((ssaflag >> NUM_IR_FLAGS) & (1 << NUM_EFFECTS_OVERRIDES - 1))) diff --git a/base/compiler/inferenceresult.jl b/base/compiler/inferenceresult.jl index 86eed13686ae9..2575429fbf924 100644 --- a/base/compiler/inferenceresult.jl +++ b/base/compiler/inferenceresult.jl @@ -1,77 +1,52 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -""" - matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance) -> - (cache_argtypes::Vector{Any}, overridden_by_const::BitVector) - -Returns argument types `cache_argtypes::Vector{Any}` for `linfo` that are in the native -Julia type domain. `overridden_by_const::BitVector` is all `false` meaning that -there is no additional extended lattice information there. - - matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance, argtypes::ForwardableArgtypes) -> - (cache_argtypes::Vector{Any}, overridden_by_const::BitVector) - -Returns cache-correct extended lattice argument types `cache_argtypes::Vector{Any}` -for `linfo` given some `argtypes` accompanied by `overridden_by_const::BitVector` -that marks which argument contains additional extended lattice information. - -In theory, there could be a `cache` containing a matching `InferenceResult` -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 end - -function matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance) - mthd = isa(linfo.def, Method) ? linfo.def::Method : nothing - cache_argtypes = most_general_argtypes(mthd, linfo.specTypes) - return cache_argtypes, falses(length(cache_argtypes)) +function matching_cache_argtypes(𝕃::AbstractLattice, mi::MethodInstance) + (; def, specTypes) = mi + return most_general_argtypes(isa(def, Method) ? def : nothing, specTypes) end -struct SimpleArgtypes <: ForwardableArgtypes +struct SimpleArgtypes argtypes::Vector{Any} end -""" - matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance, argtypes::SimpleArgtypes) +# Like `SimpleArgtypes`, but allows the argtypes to be wider than the current call. +# As a result, it is not legal to refine the cache result with information more +# precise than was it deducible from the `WidenedSimpleArgtypes`. +struct WidenedArgtypes + argtypes::Vector{Any} +end -The implementation for `argtypes` with general extended lattice information. -This is supposed to be used for debugging and testing or external `AbstractInterpreter` -usages and in general `matching_cache_argtypes(::MethodInstance, ::ConditionalArgtypes)` -is more preferred it can forward `Conditional` information. -""" -function matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance, simple_argtypes::SimpleArgtypes) +function matching_cache_argtypes(𝕃::AbstractLattice, mi::MethodInstance, + simple_argtypes::Union{SimpleArgtypes, WidenedArgtypes}, + cache_argtypes::Vector{Any}) (; argtypes) = simple_argtypes given_argtypes = Vector{Any}(undef, length(argtypes)) for i = 1:length(argtypes) given_argtypes[i] = widenslotwrapper(argtypes[i]) end - given_argtypes = va_process_argtypes(𝕃, given_argtypes, linfo) - return pick_const_args(𝕃, linfo, given_argtypes) + given_argtypes = va_process_argtypes(𝕃, given_argtypes, mi) + return pick_const_args!(𝕃, given_argtypes, cache_argtypes) end -function pick_const_args(𝕃::AbstractLattice, linfo::MethodInstance, given_argtypes::Vector{Any}) - cache_argtypes, overridden_by_const = matching_cache_argtypes(𝕃, linfo) - return pick_const_args!(𝕃, cache_argtypes, overridden_by_const, given_argtypes) -end - -function pick_const_args!(𝕃::AbstractLattice, cache_argtypes::Vector{Any}, overridden_by_const::BitVector, given_argtypes::Vector{Any}) - for i = 1:length(given_argtypes) +function pick_const_args!(𝕃::AbstractLattice, given_argtypes::Vector{Any}, cache_argtypes::Vector{Any}) + nargtypes = length(given_argtypes) + @assert nargtypes == length(cache_argtypes) #= == nargs =# "invalid `given_argtypes` for `mi`" + for i = 1:nargtypes given_argtype = given_argtypes[i] cache_argtype = cache_argtypes[i] if !is_argtype_match(𝕃, given_argtype, cache_argtype, false) - # prefer the argtype we were given over the one computed from `linfo` + # prefer the argtype we were given over the one computed from `mi` if (isa(given_argtype, PartialStruct) && isa(cache_argtype, Type) && !⊏(𝕃, given_argtype, cache_argtype)) # if the type information of this `PartialStruct` is less strict than # declared method signature, narrow it down using `tmeet` - given_argtype = tmeet(𝕃, given_argtype, cache_argtype) + given_argtypes[i] = tmeet(𝕃, given_argtype, cache_argtype) end - cache_argtypes[i] = given_argtype - overridden_by_const[i] = true + else + given_argtypes[i] = cache_argtype end end - return cache_argtypes, overridden_by_const + return given_argtypes end function is_argtype_match(𝕃::AbstractLattice, @@ -80,16 +55,17 @@ function is_argtype_match(𝕃::AbstractLattice, overridden_by_const::Bool) if is_forwardable_argtype(𝕃, given_argtype) return is_lattice_equal(𝕃, given_argtype, cache_argtype) + else + return !overridden_by_const end - return !overridden_by_const end va_process_argtypes(𝕃::AbstractLattice, given_argtypes::Vector{Any}, mi::MethodInstance) = va_process_argtypes(Returns(nothing), 𝕃, given_argtypes, mi) function va_process_argtypes(@specialize(va_handler!), 𝕃::AbstractLattice, given_argtypes::Vector{Any}, mi::MethodInstance) - def = mi.def - isva = isa(def, Method) ? def.isva : false - nargs = isa(def, Method) ? Int(def.nargs) : length(mi.specTypes.parameters) + def = mi.def::Method + isva = def.isva + nargs = Int(def.nargs) if isva || isvarargtype(given_argtypes[end]) isva_given_argtypes = Vector{Any}(undef, nargs) for i = 1:(nargs-isva) @@ -110,28 +86,25 @@ function va_process_argtypes(@specialize(va_handler!), 𝕃::AbstractLattice, gi return given_argtypes end -function most_general_argtypes(method::Union{Method, Nothing}, @nospecialize(specTypes), - withfirst::Bool = true) +function most_general_argtypes(method::Union{Method,Nothing}, @nospecialize(specTypes)) toplevel = method === nothing isva = !toplevel && method.isva - linfo_argtypes = Any[(unwrap_unionall(specTypes)::DataType).parameters...] + mi_argtypes = Any[(unwrap_unionall(specTypes)::DataType).parameters...] nargs::Int = toplevel ? 0 : method.nargs - # For opaque closure, the closure environment is processed elsewhere - withfirst || (nargs -= 1) cache_argtypes = Vector{Any}(undef, nargs) # First, if we're dealing with a varargs method, then we set the last element of `args` # to the appropriate `Tuple` type or `PartialStruct` instance. + mi_argtypes_length = length(mi_argtypes) if !toplevel && isva if specTypes::Type == Tuple - linfo_argtypes = Any[Any for i = 1:nargs] + mi_argtypes = Any[Any for i = 1:nargs] if nargs > 1 - linfo_argtypes[end] = Tuple + mi_argtypes[end] = Tuple end vargtype = Tuple else - linfo_argtypes_length = length(linfo_argtypes) - if nargs > linfo_argtypes_length - va = linfo_argtypes[linfo_argtypes_length] + if nargs > mi_argtypes_length + va = mi_argtypes[mi_argtypes_length] if isvarargtype(va) new_va = rewrap_unionall(unconstrain_vararg_length(va), specTypes) vargtype = Tuple{new_va} @@ -140,8 +113,8 @@ function most_general_argtypes(method::Union{Method, Nothing}, @nospecialize(spe end else vargtype_elements = Any[] - for i in nargs:linfo_argtypes_length - p = linfo_argtypes[i] + for i in nargs:mi_argtypes_length + p = mi_argtypes[i] p = unwraptv(isvarargtype(p) ? unconstrain_vararg_length(p) : p) push!(vargtype_elements, elim_free_typevars(rewrap_unionall(p, specTypes))) end @@ -160,18 +133,16 @@ function most_general_argtypes(method::Union{Method, Nothing}, @nospecialize(spe cache_argtypes[nargs] = vargtype nargs -= 1 end - # Now, we propagate type info from `linfo_argtypes` into `cache_argtypes`, improving some + # Now, we propagate type info from `mi_argtypes` into `cache_argtypes`, improving some # type info as we go (where possible). Note that if we're dealing with a varargs method, # we already handled the last element of `cache_argtypes` (and decremented `nargs` so that # we don't overwrite the result of that work here). - linfo_argtypes_length = length(linfo_argtypes) - if linfo_argtypes_length > 0 - n = linfo_argtypes_length > nargs ? nargs : linfo_argtypes_length - tail_index = n + if mi_argtypes_length > 0 + tail_index = nargtypes = min(mi_argtypes_length, nargs) local lastatype - for i = 1:n - atyp = linfo_argtypes[i] - if i == n && isvarargtype(atyp) + for i = 1:nargtypes + atyp = mi_argtypes[i] + if i == nargtypes && isvarargtype(atyp) atyp = unwrapva(atyp) tail_index -= 1 end @@ -184,16 +155,16 @@ function most_general_argtypes(method::Union{Method, Nothing}, @nospecialize(spe else atyp = elim_free_typevars(rewrap_unionall(atyp, specTypes)) end - i == n && (lastatype = atyp) + i == nargtypes && (lastatype = atyp) cache_argtypes[i] = atyp end - for i = (tail_index + 1):nargs + for i = (tail_index+1):nargs cache_argtypes[i] = lastatype end else @assert nargs == 0 "invalid specialization of method" # wrong number of arguments end - cache_argtypes + return cache_argtypes end # eliminate free `TypeVar`s in order to make the life much easier down the road: @@ -209,24 +180,18 @@ function elim_free_typevars(@nospecialize t) end end -function cache_lookup(𝕃::AbstractLattice, linfo::MethodInstance, given_argtypes::Vector{Any}, cache::Vector{InferenceResult}) - method = linfo.def::Method - nargs = Int(method.nargs) - method.isva && (nargs -= 1) - length(given_argtypes) ≥ nargs || return nothing +function cache_lookup(𝕃::AbstractLattice, mi::MethodInstance, given_argtypes::Vector{Any}, + cache::Vector{InferenceResult}) + method = mi.def::Method + nargtypes = length(given_argtypes) + @assert nargtypes == Int(method.nargs) "invalid `given_argtypes` for `mi`" for cached_result in cache - cached_result.linfo === linfo || continue + cached_result.linfo === mi || @goto next_cache cache_argtypes = cached_result.argtypes - cache_overridden_by_const = cached_result.overridden_by_const - for i in 1:nargs - if !is_argtype_match(𝕃, widenmustalias(given_argtypes[i]), - cache_argtypes[i], cache_overridden_by_const[i]) - @goto next_cache - end - end - if method.isva - if !is_argtype_match(𝕃, tuple_tfunc(𝕃, given_argtypes[(nargs + 1):end]), - cache_argtypes[end], cache_overridden_by_const[end]) + @assert length(cache_argtypes) == nargtypes "invalid `cache_argtypes` for `mi`" + cache_overridden_by_const = cached_result.overridden_by_const::BitVector + for i in 1:nargtypes + if !is_argtype_match(𝕃, given_argtypes[i], cache_argtypes[i], cache_overridden_by_const[i]) @goto next_cache end end diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index 9546d9ef79184..169d543f3249c 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -170,6 +170,17 @@ function getindex(tpdum::TwoPhaseDefUseMap, idx::Int) return TwoPhaseVectorView(tpdum.data, nelems, range) end +mutable struct LazyCFGReachability + ir::IRCode + reachability::CFGReachability + LazyCFGReachability(ir::IRCode) = new(ir) +end +function get!(x::LazyCFGReachability) + isdefined(x, :reachability) && return x.reachability + domtree = construct_domtree(x.ir) + return x.reachability = CFGReachability(x.ir.cfg, domtree) +end + mutable struct LazyGenericDomtree{IsPostDom} ir::IRCode domtree::GenericDomTree{IsPostDom} @@ -178,8 +189,8 @@ end function get!(x::LazyGenericDomtree{IsPostDom}) where {IsPostDom} isdefined(x, :domtree) && return x.domtree return @timeit "domtree 2" x.domtree = IsPostDom ? - construct_postdomtree(x.ir.cfg.blocks) : - construct_domtree(x.ir.cfg.blocks) + construct_postdomtree(x.ir) : + construct_domtree(x.ir) end const LazyDomtree = LazyGenericDomtree{false} @@ -198,6 +209,19 @@ to enable flow-sensitive analysis. """ const VarTable = Vector{VarState} +const CACHE_MODE_NULL = 0x00 # not cached, without optimization +const CACHE_MODE_GLOBAL = 0x01 << 0 # cached globally, optimization allowed +const CACHE_MODE_LOCAL = 0x01 << 1 # cached locally, optimization allowed +const CACHE_MODE_VOLATILE = 0x01 << 2 # not cached, optimization allowed + +mutable struct TryCatchFrame + exct + scopet + const enter_idx::Int + scope_uses::Vector{Int} + TryCatchFrame(@nospecialize(exct), @nospecialize(scopet), enter_idx::Int) = new(exct, scopet, enter_idx) +end + mutable struct InferenceState #= information about this method instance =# linfo::MethodInstance @@ -213,7 +237,8 @@ mutable struct InferenceState currbb::Int currpc::Int ip::BitSet#=TODO BoundedMinPrioritySet=# # current active instruction pointers - handler_at::Vector{Int} # current exception handler info + handlers::Vector{TryCatchFrame} + handler_at::Vector{Tuple{Int, Int}} # tuple of current (handler, exception stack) value at the pc ssavalue_uses::Vector{BitSet} # ssavalue sparsity and restart info # TODO: Could keep this sparsely by doing structural liveness analysis ahead of time. bb_vartables::Vector{Union{Nothing,VarTable}} # nothing if not analyzed yet @@ -234,13 +259,14 @@ mutable struct InferenceState unreachable::BitSet # statements that were found to be statically unreachable valid_worlds::WorldRange bestguess #::Type + exc_bestguess ipo_effects::Effects #= flags =# # Whether to restrict inference of abstract call sites to avoid excessive work # Set by default for toplevel frame. restrict_abstract_call_sites::Bool - cached::Bool # TODO move this to InferenceResult? + cache_mode::UInt8 # TODO move this to InferenceResult? insert_coverage::Bool # The interpreter that created this inference state. Not looked at by @@ -248,20 +274,23 @@ mutable struct InferenceState interp::AbstractInterpreter # src is assumed to be a newly-allocated CodeInfo, that can be modified in-place to contain intermediate results - function InferenceState(result::InferenceResult, src::CodeInfo, cache::Symbol, + function InferenceState(result::InferenceResult, src::CodeInfo, cache_mode::UInt8, interp::AbstractInterpreter) - linfo = result.linfo - world = get_world_counter(interp) - def = linfo.def + mi = result.linfo + world = get_inference_world(interp) + if world == typemax(UInt) + error("Entering inference from a generated function with an invalid world") + end + def = mi.def mod = isa(def, Method) ? def.module : def - sptypes = sptypes_from_meth_instance(linfo) + sptypes = sptypes_from_meth_instance(mi) code = src.code::Vector{Any} cfg = compute_basic_blocks(code) method_info = MethodInfo(src) currbb = currpc = 1 ip = BitSet(1) # TODO BitSetBoundedMinPrioritySet(1) - handler_at = compute_trycatch(code, BitSet()) + handler_at, handlers = compute_trycatch(code, BitSet()) nssavalues = src.ssavaluetypes::Int ssavalue_uses = find_ssavalue_uses(code, nssavalues) nstmts = length(code) @@ -289,11 +318,12 @@ mutable struct InferenceState dont_work_on_me = false parent = nothing - valid_worlds = WorldRange(src.min_world, src.max_world == typemax(UInt) ? get_world_counter() : src.max_world) + valid_worlds = WorldRange(1, get_world_counter()) bestguess = Bottom + exc_bestguess = Bottom ipo_effects = EFFECTS_TOTAL - insert_coverage = should_insert_coverage(mod, src) + insert_coverage = should_insert_coverage(mod, src.debuginfo) if insert_coverage ipo_effects = Effects(ipo_effects; effect_free = ALWAYS_FALSE) end @@ -303,20 +333,26 @@ mutable struct InferenceState end restrict_abstract_call_sites = isa(def, Module) - @assert cache === :no || cache === :local || cache === :global - cached = cache === :global # some more setups InferenceParams(interp).unoptimize_throw_blocks && mark_throw_blocks!(src, handler_at) - cache !== :no && push!(get_inference_cache(interp), result) + !iszero(cache_mode & CACHE_MODE_LOCAL) && push!(get_inference_cache(interp), result) - return new( - linfo, world, mod, sptypes, slottypes, src, cfg, method_info, - currbb, currpc, ip, handler_at, ssavalue_uses, bb_vartables, ssavaluetypes, stmt_edges, stmt_info, + this = new( + mi, world, mod, sptypes, slottypes, src, cfg, method_info, + currbb, currpc, ip, handlers, handler_at, ssavalue_uses, bb_vartables, ssavaluetypes, stmt_edges, stmt_info, pclimitations, limitations, cycle_backedges, callers_in_cycle, dont_work_on_me, parent, - result, unreachable, valid_worlds, bestguess, ipo_effects, - restrict_abstract_call_sites, cached, insert_coverage, + result, unreachable, valid_worlds, bestguess, exc_bestguess, ipo_effects, + restrict_abstract_call_sites, cache_mode, insert_coverage, interp) + + # Apply generated function restrictions + if src.min_world != 1 || src.max_world != typemax(UInt) + # From generated functions + this.valid_worlds = WorldRange(src.min_world, src.max_world) + end + + return this end end @@ -332,29 +368,49 @@ is_inferred(result::InferenceResult) = result.result !== nothing was_reached(sv::InferenceState, pc::Int) = sv.ssavaluetypes[pc] !== NOT_FOUND -function compute_trycatch(code::Vector{Any}, ip::BitSet) +compute_trycatch(ir::IRCode, ip::BitSet) = compute_trycatch(ir.stmts.stmt, ip, ir.cfg.blocks) + +""" + compute_trycatch(code, ip [, bbs]) -> (handler_at, handlers) + +Given the code of a function, compute, at every statement, the current +try/catch handler, and the current exception stack top. This function returns +a tuple of: + + 1. `handler_at`: A statement length vector of tuples `(catch_handler, exception_stack)`, + which are indices into `handlers` + + 2. `handlers`: A `TryCatchFrame` vector of handlers +""" +function compute_trycatch(code::Vector{Any}, ip::BitSet, bbs::Union{Vector{BasicBlock}, Nothing}=nothing) # 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 + # then we can find all `try`s by walking backwards from :enter statements, + # and all `catch`es 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) + handler_at = fill((0, 0), n) + handlers = TryCatchFrame[] # 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 + if isa(stmt, EnterNode) + l = stmt.catch_dest + (bbs !== nothing) && (l = first(bbs[l].stmts)) + push!(handlers, TryCatchFrame(Bottom, isdefined(stmt, :scope) ? Bottom : nothing, pc)) + handler_id = length(handlers) + handler_at[pc + 1] = (handler_id, 0) push!(ip, pc + 1) - handler_at[l] = pc - push!(ip, l) + if l != 0 + handler_at[l] = (0, handler_id) + push!(ip, l) + end end end @@ -366,26 +422,35 @@ function compute_trycatch(code::Vector{Any}, ip::BitSet) 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) delete!(ip, pc) - cur_hand = handler_at[pc] - @assert cur_hand != 0 "unbalanced try/catch" + cur_stacks = handler_at[pc] + @assert cur_stacks != (0, 0) "unbalanced try/catch" stmt = code[pc] if isa(stmt, GotoNode) pc´ = stmt.label + (bbs !== nothing) && (pc´ = first(bbs[pc´].stmts)) 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 + (bbs !== nothing) && (l = first(bbs[l].stmts)) + if handler_at[l] != cur_stacks + @assert handler_at[l][1] == 0 || handler_at[l][1] == cur_stacks[1] "unbalanced try/catch" + handler_at[l] = cur_stacks push!(ip, l) end elseif isa(stmt, ReturnNode) - @assert !isdefined(stmt, :val) "unbalanced try/catch" + @assert !isdefined(stmt, :val) || cur_stacks[1] == 0 "unbalanced try/catch" break + elseif isa(stmt, EnterNode) + l = stmt.catch_dest + (bbs !== nothing) && (l = first(bbs[l].stmts)) + # We assigned a handler number above. Here we just merge that + # with out current handler information. + if l != 0 + handler_at[l] = (cur_stacks[1], handler_at[l][2]) + end + cur_stacks = (handler_at[pc´][1], cur_stacks[2]) elseif isa(stmt, Expr) head = stmt.head - if head === :enter - cur_hand = pc - elseif head === :leave + if head === :leave l = 0 for j = 1:length(stmt.args) arg = stmt.args[j] @@ -396,23 +461,25 @@ function compute_trycatch(code::Vector{Any}, ip::BitSet) if enter_stmt === nothing continue end - @assert isexpr(enter_stmt, :enter) "malformed :leave" + @assert isa(enter_stmt, EnterNode) "malformed :leave" end l += 1 end + cur_hand = cur_stacks[1] for i = 1:l - cur_hand = handler_at[cur_hand] + cur_hand = handler_at[handlers[cur_hand].enter_idx][1] end - cur_hand == 0 && break + cur_stacks = (cur_hand, cur_stacks[2]) + cur_stacks == (0, 0) && break + elseif head === :pop_exception + cur_stacks = (cur_stacks[1], handler_at[(stmt.args[1]::SSAValue).id][2]) + cur_stacks == (0, 0) && break end end pc´ > n && break # can't proceed with the fast-path fall-through - if handler_at[pc´] != cur_hand - if handler_at[pc´] != 0 - @assert false "unbalanced try/catch" - end - handler_at[pc´] = cur_hand + if handler_at[pc´] != cur_stacks + handler_at[pc´] = cur_stacks elseif !in(pc´, ip) break # already visited end @@ -421,39 +488,52 @@ function compute_trycatch(code::Vector{Any}, ip::BitSet) end @assert first(ip) == n + 1 - return handler_at + return handler_at, handlers end # check if coverage mode is enabled -function should_insert_coverage(mod::Module, src::CodeInfo) +function should_insert_coverage(mod::Module, debuginfo::DebugInfo) coverage_enabled(mod) && return true JLOptions().code_coverage == 3 || return false # path-specific coverage mode: if any line falls in a tracked file enable coverage for all - linetable = src.linetable - if isa(linetable, Vector{Any}) - for line in linetable - line = line::LineInfoNode - if is_file_tracked(line.file) - return true - end - end - elseif isa(linetable, Vector{LineInfoNode}) - for line in linetable - if is_file_tracked(line.file) - return true - end - end - end + return _should_insert_coverage(debuginfo) +end + +_should_insert_coverage(mod::Symbol) = is_file_tracked(mod) +_should_insert_coverage(mod::Method) = _should_insert_coverage(mod.file) +_should_insert_coverage(mod::MethodInstance) = _should_insert_coverage(mod.def) +_should_insert_coverage(mod::Module) = false +function _should_insert_coverage(info::DebugInfo) + linetable = info.linetable + linetable === nothing || (_should_insert_coverage(linetable) && return true) + _should_insert_coverage(info.def) && return true return false end -function InferenceState(result::InferenceResult, cache::Symbol, interp::AbstractInterpreter) +function InferenceState(result::InferenceResult, cache_mode::UInt8, interp::AbstractInterpreter) # prepare an InferenceState object for inferring lambda - world = get_world_counter(interp) + world = get_inference_world(interp) src = retrieve_code_info(result.linfo, world) src === nothing && return nothing - validate_code_in_debug_mode(result.linfo, src, "lowered") - return InferenceState(result, src, cache, interp) + maybe_validate_code(result.linfo, src, "lowered") + return InferenceState(result, src, cache_mode, interp) +end +InferenceState(result::InferenceResult, cache_mode::Symbol, interp::AbstractInterpreter) = + InferenceState(result, convert_cache_mode(cache_mode), interp) +InferenceState(result::InferenceResult, src::CodeInfo, cache_mode::Symbol, interp::AbstractInterpreter) = + InferenceState(result, src, convert_cache_mode(cache_mode), interp) + +function convert_cache_mode(cache_mode::Symbol) + if cache_mode === :global + return CACHE_MODE_GLOBAL + elseif cache_mode === :local + return CACHE_MODE_LOCAL + elseif cache_mode === :volatile + return CACHE_MODE_VOLATILE + elseif cache_mode === :no + return CACHE_MODE_NULL + end + error("unexpected `cache_mode` is given") end """ @@ -520,13 +600,13 @@ end const EMPTY_SPTYPES = VarState[] -function sptypes_from_meth_instance(linfo::MethodInstance) - def = linfo.def +function sptypes_from_meth_instance(mi::MethodInstance) + def = mi.def isa(def, Method) || return EMPTY_SPTYPES # toplevel sig = def.sig - if isempty(linfo.sparam_vals) + if isempty(mi.sparam_vals) isa(sig, UnionAll) || return EMPTY_SPTYPES - # linfo is unspecialized + # mi is unspecialized spvals = Any[] sig′ = sig while isa(sig′, UnionAll) @@ -534,7 +614,7 @@ function sptypes_from_meth_instance(linfo::MethodInstance) sig′ = sig′.body end else - spvals = linfo.sparam_vals + spvals = mi.sparam_vals end nvals = length(spvals) sptypes = Vector{VarState}(undef, nvals) @@ -552,7 +632,7 @@ function sptypes_from_meth_instance(linfo::MethodInstance) if isType(sⱼ) && sⱼ.parameters[1] === vᵢ # if this parameter came from `arg::Type{T}`, # then `arg` is more precise than `Type{T} where lb<:T<:ub` - ty = fieldtype(linfo.specTypes, j) + ty = fieldtype(mi.specTypes, j) @goto ty_computed elseif (va = va_from_vatuple(sⱼ)) !== nothing # if this parameter came from `::Tuple{.., Vararg{T,vᵢ}}`, @@ -583,8 +663,8 @@ function sptypes_from_meth_instance(linfo::MethodInstance) # type variables, we can use it for a more accurate analysis of whether `v` # is constrained or not, otherwise we should use `def.sig` which always # doesn't contain any free type variables - if !has_free_typevars(linfo.specTypes) - sig = linfo.specTypes + if !has_free_typevars(mi.specTypes) + sig = mi.specTypes end @assert !has_free_typevars(sig) constrains_param(v, sig, #=covariant=#true) @@ -629,10 +709,7 @@ function record_ssa_assign!(𝕃ᵢ::AbstractLattice, ssa_id::Int, @nospecialize for r in frame.ssavalue_uses[ssa_id] if was_reached(frame, r) usebb = block_for_inst(frame.cfg, r) - # We're guaranteed to visit the statement if it's in the current - # basic block, since SSA values can only ever appear after their - # def. - if usebb != frame.currbb + if usebb != frame.currbb || r < ssa_id push!(W, usebb) end end @@ -665,16 +742,27 @@ function empty_backedges!(frame::InferenceState, currpc::Int=frame.currpc) end function print_callstack(sv::InferenceState) + print("=================== Callstack: ==================\n") + idx = 0 while sv !== nothing + print("[") + print(idx) + if !isa(sv.interp, NativeInterpreter) + print(", ") + print(typeof(sv.interp)) + end + print("] ") print(sv.linfo) - !sv.cached && print(" [uncached]") + is_cached(sv) || print(" [uncached]") println() for cycle in sv.callers_in_cycle print(' ', cycle.linfo) println() end sv = sv.parent + idx += 1 end + print("================= End callstack ==================\n") end function narguments(sv::InferenceState, include_va::Bool=true) @@ -700,7 +788,7 @@ mutable struct IRInterpretationState const sptypes::Vector{VarState} const tpdum::TwoPhaseDefUseMap const ssa_refined::BitSet - const lazydomtree::LazyDomtree + const lazyreachability::LazyCFGReachability valid_worlds::WorldRange const edges::Vector{Any} parent # ::Union{Nothing,AbsIntState} @@ -713,35 +801,39 @@ mutable struct IRInterpretationState for i = 1:length(given_argtypes) given_argtypes[i] = widenslotwrapper(argtypes[i]) end - given_argtypes = va_process_argtypes(optimizer_lattice(interp), given_argtypes, mi) - argtypes_refined = Bool[!⊑(optimizer_lattice(interp), ir.argtypes[i], given_argtypes[i]) - for i = 1:length(given_argtypes)] + if isa(mi.def, Method) + argtypes_refined = Bool[!⊑(optimizer_lattice(interp), ir.argtypes[i], given_argtypes[i]) + for i = 1:length(given_argtypes)] + else + argtypes_refined = Bool[false for i = 1:length(given_argtypes)] + end empty!(ir.argtypes) append!(ir.argtypes, given_argtypes) tpdum = TwoPhaseDefUseMap(length(ir.stmts)) ssa_refined = BitSet() - lazydomtree = LazyDomtree(ir) + lazyreachability = LazyCFGReachability(ir) valid_worlds = WorldRange(min_world, max_world == typemax(UInt) ? get_world_counter() : max_world) edges = Any[] parent = nothing return new(method_info, ir, mi, world, curridx, argtypes_refined, ir.sptypes, tpdum, - ssa_refined, lazydomtree, valid_worlds, edges, parent) + ssa_refined, lazyreachability, valid_worlds, edges, parent) end end function IRInterpretationState(interp::AbstractInterpreter, - code::CodeInstance, mi::MethodInstance, argtypes::Vector{Any}, world::UInt) - @assert code.def === mi - src = @atomic :monotonic code.inferred + codeinst::CodeInstance, mi::MethodInstance, argtypes::Vector{Any}, world::UInt) + @assert codeinst.def === mi "method instance is not synced with code instance" + src = @atomic :monotonic codeinst.inferred if isa(src, String) - src = ccall(:jl_uncompress_ir, Any, (Any, Ptr{Cvoid}, Any), mi.def, C_NULL, src)::CodeInfo + src = _uncompressed_ir(codeinst, src) else isa(src, CodeInfo) || return nothing end method_info = MethodInfo(src) ir = inflate_ir(src, mi) + argtypes = va_process_argtypes(optimizer_lattice(interp), argtypes, mi) return IRInterpretationState(interp, method_info, ir, mi, argtypes, world, - src.min_world, src.max_world) + codeinst.min_world, codeinst.max_world) end # AbsIntState @@ -762,10 +854,13 @@ end frame_parent(sv::InferenceState) = sv.parent::Union{Nothing,AbsIntState} frame_parent(sv::IRInterpretationState) = sv.parent::Union{Nothing,AbsIntState} -is_constproped(sv::InferenceState) = any(sv.result.overridden_by_const) +function is_constproped(sv::InferenceState) + (;overridden_by_const) = sv.result + return overridden_by_const !== nothing +end is_constproped(::IRInterpretationState) = true -is_cached(sv::InferenceState) = sv.cached +is_cached(sv::InferenceState) = !iszero(sv.cache_mode & CACHE_MODE_GLOBAL) is_cached(::IRInterpretationState) = false method_info(sv::InferenceState) = sv.method_info @@ -780,9 +875,16 @@ frame_world(sv::IRInterpretationState) = sv.world callers_in_cycle(sv::InferenceState) = sv.callers_in_cycle callers_in_cycle(sv::IRInterpretationState) = () -is_effect_overridden(sv::AbsIntState, effect::Symbol) = is_effect_overridden(frame_instance(sv), effect) -function is_effect_overridden(linfo::MethodInstance, effect::Symbol) - def = linfo.def +function is_effect_overridden(sv::AbsIntState, effect::Symbol) + if is_effect_overridden(frame_instance(sv), effect) + return true + elseif is_effect_overridden(decode_statement_effects_override(sv), effect) + return true + end + return false +end +function is_effect_overridden(mi::MethodInstance, effect::Symbol) + def = mi.def return isa(def, Method) && is_effect_overridden(def, effect) end is_effect_overridden(method::Method, effect::Symbol) = is_effect_overridden(decode_effects_override(method.purity), effect) @@ -853,6 +955,9 @@ end get_curr_ssaflag(sv::InferenceState) = sv.src.ssaflags[sv.currpc] get_curr_ssaflag(sv::IRInterpretationState) = sv.ir.stmts[sv.curridx][:flag] +has_curr_ssaflag(sv::InferenceState, flag::UInt32) = has_flag(sv.src.ssaflags[sv.currpc], flag) +has_curr_ssaflag(sv::IRInterpretationState, flag::UInt32) = has_flag(sv.ir.stmts[sv.curridx][:flag], flag) + function set_curr_ssaflag!(sv::InferenceState, flag::UInt32, mask::UInt32=typemax(UInt32)) curr_flag = sv.src.ssaflags[sv.currpc] sv.src.ssaflags[sv.currpc] = (curr_flag & ~mask) | flag @@ -863,10 +968,10 @@ function set_curr_ssaflag!(sv::IRInterpretationState, flag::UInt32, mask::UInt32 end add_curr_ssaflag!(sv::InferenceState, flag::UInt32) = sv.src.ssaflags[sv.currpc] |= flag -add_curr_ssaflag!(sv::IRInterpretationState, flag::UInt32) = sv.ir.stmts[sv.curridx][:flag] |= flag +add_curr_ssaflag!(sv::IRInterpretationState, flag::UInt32) = add_flag!(sv.ir.stmts[sv.curridx], flag) sub_curr_ssaflag!(sv::InferenceState, flag::UInt32) = sv.src.ssaflags[sv.currpc] &= ~flag -sub_curr_ssaflag!(sv::IRInterpretationState, flag::UInt32) = sv.ir.stmts[sv.curridx][:flag] &= ~flag +sub_curr_ssaflag!(sv::IRInterpretationState, flag::UInt32) = sub_flag!(sv.ir.stmts[sv.curridx], flag) function merge_effects!(::AbstractInterpreter, caller::InferenceState, effects::Effects) if effects.effect_free === EFFECT_FREE_GLOBALLY @@ -877,6 +982,9 @@ function merge_effects!(::AbstractInterpreter, caller::InferenceState, effects:: end merge_effects!(::AbstractInterpreter, ::IRInterpretationState, ::Effects) = return +decode_statement_effects_override(sv::AbsIntState) = + decode_statement_effects_override(get_curr_ssaflag(sv)) + struct InferenceLoopState sig rt diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 4a0f7d8071553..1428d4b5ec34f 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -15,31 +15,71 @@ const SLOT_USEDUNDEF = 32 # slot has uses that might raise UndefVarError # NOTE make sure to sync the flag definitions below with julia.h and `jl_code_info_set_ir` in method.c -const IR_FLAG_NULL = UInt32(0) +const IR_FLAG_NULL = zero(UInt32) # This statement is marked as @inbounds by user. # Ff replaced by inlining, any contained boundschecks may be removed. -const IR_FLAG_INBOUNDS = UInt32(1) << 0 +const IR_FLAG_INBOUNDS = one(UInt32) << 0 # This statement is marked as @inline by user -const IR_FLAG_INLINE = UInt32(1) << 1 +const IR_FLAG_INLINE = one(UInt32) << 1 # This statement is marked as @noinline by user -const IR_FLAG_NOINLINE = UInt32(1) << 2 -const IR_FLAG_THROW_BLOCK = UInt32(1) << 3 -# This statement may be removed if its result is unused. In particular, -# it must be both :effect_free and :nothrow. -# TODO: Separate these out. -const IR_FLAG_EFFECT_FREE = UInt32(1) << 4 -# This statement was proven not to throw -const IR_FLAG_NOTHROW = UInt32(1) << 5 -# This is :consistent -const IR_FLAG_CONSISTENT = UInt32(1) << 6 +const IR_FLAG_NOINLINE = one(UInt32) << 2 +# This statement is on a code path that eventually `throw`s. +const IR_FLAG_THROW_BLOCK = one(UInt32) << 3 # An optimization pass has updated this statement in a way that may # have exposed information that inference did not see. Re-running # inference on this statement may be profitable. -const IR_FLAG_REFINED = UInt32(1) << 7 -# This is :noub == ALWAYS_TRUE -const IR_FLAG_NOUB = UInt32(1) << 8 - -const IR_FLAGS_EFFECTS = IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW | IR_FLAG_CONSISTENT | IR_FLAG_NOUB +const IR_FLAG_REFINED = one(UInt32) << 4 +# This statement is proven :consistent +const IR_FLAG_CONSISTENT = one(UInt32) << 5 +# This statement is proven :effect_free +const IR_FLAG_EFFECT_FREE = one(UInt32) << 6 +# This statement is proven :nothrow +const IR_FLAG_NOTHROW = one(UInt32) << 7 +# This statement is proven :terminates +const IR_FLAG_TERMINATES = one(UInt32) << 8 +# This statement is proven :noub +const IR_FLAG_NOUB = one(UInt32) << 9 +# TODO: Both of these should eventually go away once +# This statement is :effect_free == EFFECT_FREE_IF_INACCESSIBLEMEMONLY +const IR_FLAG_EFIIMO = one(UInt32) << 10 +# This statement is :inaccessiblememonly == INACCESSIBLEMEM_OR_ARGMEMONLY +const IR_FLAG_INACCESSIBLEMEM_OR_ARGMEM = one(UInt32) << 11 + +const NUM_IR_FLAGS = 12 # sync with julia.h + +const IR_FLAGS_EFFECTS = + IR_FLAG_CONSISTENT | IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW | IR_FLAG_TERMINATES | IR_FLAG_NOUB + +const IR_FLAGS_REMOVABLE = IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW | IR_FLAG_TERMINATES + +const IR_FLAGS_NEEDS_EA = IR_FLAG_EFIIMO | IR_FLAG_INACCESSIBLEMEM_OR_ARGMEM + +has_flag(curr::UInt32, flag::UInt32) = (curr & flag) == flag + +function flags_for_effects(effects::Effects) + flags = zero(UInt32) + if is_consistent(effects) + flags |= IR_FLAG_CONSISTENT + end + if is_effect_free(effects) + flags |= IR_FLAG_EFFECT_FREE + elseif is_effect_free_if_inaccessiblememonly(effects) + flags |= IR_FLAG_EFIIMO + end + if is_nothrow(effects) + flags |= IR_FLAG_NOTHROW + end + if is_terminates(effects) + flags |= IR_FLAG_TERMINATES + end + if is_inaccessiblemem_or_argmemonly(effects) + flags |= IR_FLAG_INACCESSIBLEMEM_OR_ARGMEM + end + if is_noub(effects) + flags |= IR_FLAG_NOUB + end + return flags +end const TOP_TUPLE = GlobalRef(Core, :tuple) @@ -54,7 +94,7 @@ is_inlineable(@nospecialize src::MaybeCompressed) = set_inlineable!(src::CodeInfo, val::Bool) = src.inlining_cost = (val ? MIN_INLINE_COST : MAX_INLINE_COST) -function inline_cost_clamp(x::Int)::InlineCostType +function inline_cost_clamp(x::Int) x > MAX_INLINE_COST && return MAX_INLINE_COST x < MIN_INLINE_COST && return MIN_INLINE_COST return convert(InlineCostType, x) @@ -70,42 +110,17 @@ is_declared_noinline(@nospecialize src::MaybeCompressed) = # OptimizationState # ##################### -is_source_inferred(@nospecialize src::MaybeCompressed) = - ccall(:jl_ir_flag_inferred, Bool, (Any,), src) - -function inlining_policy(interp::AbstractInterpreter, - @nospecialize(src), @nospecialize(info::CallInfo), stmt_flag::UInt32, mi::MethodInstance, - argtypes::Vector{Any}) +# return whether this src should be inlined. If so, retrieve_ir_for_inlining must return an IRCode from it +function src_inlining_policy(interp::AbstractInterpreter, + @nospecialize(src), @nospecialize(info::CallInfo), stmt_flag::UInt32) if isa(src, MaybeCompressed) - is_source_inferred(src) || return nothing src_inlineable = is_stmt_inline(stmt_flag) || is_inlineable(src) - return src_inlineable ? src : nothing - elseif src === nothing && is_stmt_inline(stmt_flag) - # if this statement is forced to be inlined, make an additional effort to find the - # inferred source in the local cache - # we still won't find a source for recursive call because the "single-level" inlining - # seems to be more trouble and complex than it's worth - inf_result = cache_lookup(optimizer_lattice(interp), mi, argtypes, get_inference_cache(interp)) - inf_result === nothing && return nothing - src = inf_result.src - if isa(src, CodeInfo) - src_inferred = is_source_inferred(src) - return src_inferred ? src : nothing - else - return nothing - end + return src_inlineable elseif isa(src, IRCode) - return src - elseif isa(src, SemiConcreteResult) - if is_declared_noinline(mi.def::Method) - # For `NativeInterpreter`, `SemiConcreteResult` may be produced for - # a `@noinline`-declared method when it's marked as `@constprop :aggressive`. - # Suppress the inlining here. - return nothing - end - return src + return true end - return nothing + @assert !isa(src, CodeInstance) # handled by caller + return false end struct InliningState{Interp<:AbstractInterpreter} @@ -118,7 +133,7 @@ function InliningState(sv::InferenceState, interp::AbstractInterpreter) return InliningState(edges, sv.world, interp) end function InliningState(interp::AbstractInterpreter) - return InliningState(Any[], get_world_counter(interp), interp) + return InliningState(Any[], get_inference_world(interp), interp) end # get `code_cache(::AbstractInterpreter)` from `state::InliningState` @@ -144,7 +159,7 @@ function OptimizationState(sv::InferenceState, interp::AbstractInterpreter) sv.sptypes, sv.slottypes, inlining, sv.cfg, sv.unreachable, sv.bb_vartables, sv.insert_coverage) end -function OptimizationState(linfo::MethodInstance, src::CodeInfo, interp::AbstractInterpreter) +function OptimizationState(mi::MethodInstance, src::CodeInfo, interp::AbstractInterpreter) # prepare src for running optimization passes if it isn't already nssavalues = src.ssavaluetypes if nssavalues isa Int @@ -152,7 +167,7 @@ function OptimizationState(linfo::MethodInstance, src::CodeInfo, interp::Abstrac else nssavalues = length(src.ssavaluetypes::Vector{Any}) end - sptypes = sptypes_from_meth_instance(linfo) + sptypes = sptypes_from_meth_instance(mi) nslots = length(src.slotflags) slottypes = src.slottypes if slottypes === nothing @@ -160,7 +175,7 @@ function OptimizationState(linfo::MethodInstance, src::CodeInfo, interp::Abstrac end stmt_info = CallInfo[ NoCallInfo() for i = 1:nssavalues ] # cache some useful state computations - def = linfo.def + def = mi.def mod = isa(def, Method) ? def.module : def # Allow using the global MI cache, but don't track edges. # This method is mostly used for unit testing the optimizer @@ -174,13 +189,13 @@ function OptimizationState(linfo::MethodInstance, src::CodeInfo, interp::Abstrac for slot = 1:nslots ]) end - return OptimizationState(linfo, src, nothing, stmt_info, mod, sptypes, slottypes, inlining, cfg, unreachable, bb_vartables, false) + return OptimizationState(mi, src, nothing, stmt_info, mod, sptypes, slottypes, inlining, cfg, unreachable, bb_vartables, false) end -function OptimizationState(linfo::MethodInstance, interp::AbstractInterpreter) - world = get_world_counter(interp) - src = retrieve_code_info(linfo, world) +function OptimizationState(mi::MethodInstance, interp::AbstractInterpreter) + world = get_inference_world(interp) + src = retrieve_code_info(mi, world) src === nothing && return nothing - return OptimizationState(linfo, src, interp) + return OptimizationState(mi, src, interp) end function argextype end # imported by EscapeAnalysis @@ -199,14 +214,13 @@ function ir_to_codeinf!(opt::OptimizationState) (; linfo, src) = opt src = ir_to_codeinf!(src, opt.ir::IRCode) opt.ir = nothing - validate_code_in_debug_mode(linfo, src, "optimized") + maybe_validate_code(linfo, src, "optimized") return src end function ir_to_codeinf!(src::CodeInfo, ir::IRCode) replace_code_newstyle!(src, ir) widen_all_consts!(src) - src.inferred = true return src end @@ -224,8 +238,6 @@ function widen_all_consts!(src::CodeInfo) end end - src.rettype = widenconst(src.rettype) - return src end @@ -235,9 +247,9 @@ end _topmod(sv::OptimizationState) = _topmod(sv.mod) -is_stmt_inline(stmt_flag::UInt32) = stmt_flag & IR_FLAG_INLINE ≠ 0 -is_stmt_noinline(stmt_flag::UInt32) = stmt_flag & IR_FLAG_NOINLINE ≠ 0 -is_stmt_throw_block(stmt_flag::UInt32) = stmt_flag & IR_FLAG_THROW_BLOCK ≠ 0 +is_stmt_inline(stmt_flag::UInt32) = has_flag(stmt_flag, IR_FLAG_INLINE) +is_stmt_noinline(stmt_flag::UInt32) = has_flag(stmt_flag, IR_FLAG_NOINLINE) +is_stmt_throw_block(stmt_flag::UInt32) = has_flag(stmt_flag, IR_FLAG_THROW_BLOCK) function new_expr_effect_flags(𝕃ₒ::AbstractLattice, args::Vector{Any}, src::Union{IRCode,IncrementalCompact}, pattern_match=nothing) Targ = args[1] @@ -276,20 +288,20 @@ end """ stmt_effect_flags(stmt, rt, src::Union{IRCode,IncrementalCompact}) -> - (consistent::Bool, effect_free_and_nothrow::Bool, nothrow::Bool) + (consistent::Bool, removable::Bool, nothrow::Bool) -Returns a tuple of `(:consistent, :effect_free_and_nothrow, :nothrow)` flags for a given statement. +Returns a tuple of `(:consistent, :removable, :nothrow)` flags for a given statement. """ function stmt_effect_flags(𝕃ₒ::AbstractLattice, @nospecialize(stmt), @nospecialize(rt), src::Union{IRCode,IncrementalCompact}) # TODO: We're duplicating analysis from inference here. isa(stmt, PiNode) && return (true, true, true) isa(stmt, PhiNode) && return (true, true, true) isa(stmt, ReturnNode) && return (true, false, true) + isa(stmt, EnterNode) && return (true, false, true) isa(stmt, GotoNode) && return (true, false, true) isa(stmt, GotoIfNot) && return (true, false, ⊑(𝕃ₒ, argextype(stmt.cond, src), Bool)) if isa(stmt, GlobalRef) - nothrow = isdefined(stmt.mod, stmt.name) - consistent = nothrow && isconst(stmt.mod, stmt.name) + nothrow = consistent = isdefinedconst_globalref(stmt) return (consistent, nothrow, nothrow) elseif isa(stmt, Expr) (; head, args) = stmt @@ -316,12 +328,14 @@ function stmt_effect_flags(𝕃ₒ::AbstractLattice, @nospecialize(stmt), @nospe isa(f, Builtin) || return (false, false, false) # Needs to be handled in inlining to look at the callee effects f === Core._apply_iterate && return (false, false, false) - argtypes = Any[argextype(args[arg], src) for arg in 1:length(args)] - effects = builtin_effects(𝕃ₒ, f, ArgInfo(args, argtypes), rt) + argtypes = Any[argextype(args[arg], src) for arg in 2:length(args)] + effects = builtin_effects(𝕃ₒ, f, argtypes, rt) consistent = is_consistent(effects) effect_free = is_effect_free(effects) nothrow = is_nothrow(effects) - return (consistent, effect_free & nothrow, nothrow) + terminates = is_terminates(effects) + removable = effect_free & nothrow & terminates + return (consistent, removable, nothrow) elseif head === :new return new_expr_effect_flags(𝕃ₒ, args, src) elseif head === :foreigncall @@ -331,7 +345,9 @@ function stmt_effect_flags(𝕃ₒ::AbstractLattice, @nospecialize(stmt), @nospe consistent = is_consistent(effects) effect_free = is_effect_free(effects) nothrow = is_nothrow(effects) - return (consistent, effect_free & nothrow, nothrow) + terminates = is_terminates(effects) + removable = effect_free & nothrow & terminates + return (consistent, removable, nothrow) elseif head === :new_opaque_closure length(args) < 4 && return (false, false, false) typ = argextype(args[1], src) @@ -358,6 +374,29 @@ function stmt_effect_flags(𝕃ₒ::AbstractLattice, @nospecialize(stmt), @nospe return (true, true, true) end +function recompute_effects_flags(𝕃ₒ::AbstractLattice, @nospecialize(stmt), @nospecialize(rt), + src::Union{IRCode,IncrementalCompact}) + flag = IR_FLAG_NULL + (consistent, removable, nothrow) = stmt_effect_flags(𝕃ₒ, stmt, rt, src) + if consistent + flag |= IR_FLAG_CONSISTENT + end + if removable + flag |= IR_FLAGS_REMOVABLE + elseif nothrow + flag |= IR_FLAG_NOTHROW + end + if !(isexpr(stmt, :call) || isexpr(stmt, :invoke)) + # There is a bit of a subtle point here, which is that some non-call + # statements (e.g. PiNode) can be UB:, however, we consider it + # illegal to introduce such statements that actually cause UB (for any + # input). Ideally that'd be handled at insertion time (TODO), but for + # the time being just do that here. + flag |= IR_FLAG_NOUB + end + return flag +end + """ argextype(x, src::Union{IRCode,IncrementalCompact}) -> t argextype(x, src::CodeInfo, sptypes::Vector{VarState}) -> t @@ -396,8 +435,8 @@ function argextype( elseif isa(x, QuoteNode) return Const(x.value) elseif isa(x, GlobalRef) - return abstract_eval_globalref(x) - elseif isa(x, PhiNode) + return abstract_eval_globalref_type(x) + elseif isa(x, PhiNode) || isa(x, PhiCNode) || isa(x, UpsilonNode) return Any elseif isa(x, PiNode) return x.typ @@ -484,25 +523,32 @@ function visit_bb_phis!(callback, ir::IRCode, bb::Int) end function any_stmt_may_throw(ir::IRCode, bb::Int) - for stmt in ir.cfg.blocks[bb].stmts - if (ir[SSAValue(stmt)][:flag] & IR_FLAG_NOTHROW) != 0 + for idx in ir.cfg.blocks[bb].stmts + if !has_flag(ir[SSAValue(idx)], IR_FLAG_NOTHROW) return true end end return false end -function conditional_successors_may_throw(lazypostdomtree::LazyPostDomtree, ir::IRCode, bb::Int) +visit_conditional_successors(callback, ir::IRCode, bb::Int) = # used for test + visit_conditional_successors(callback, LazyPostDomtree(ir), ir, bb) +function visit_conditional_successors(callback, lazypostdomtree::LazyPostDomtree, ir::IRCode, bb::Int) visited = BitSet((bb,)) worklist = Int[bb] while !isempty(worklist) - thisbb = pop!(worklist) + thisbb = popfirst!(worklist) for succ in ir.cfg.blocks[thisbb].succs succ in visited && continue push!(visited, succ) - postdominates(get!(lazypostdomtree), succ, thisbb) && continue - any_stmt_may_throw(ir, succ) && return true - push!(worklist, succ) + if postdominates(get!(lazypostdomtree), succ, bb) + # this successor is not conditional, so no need to visit it further + continue + elseif callback(succ) + return true + else + push!(worklist, succ) + end end end return false @@ -531,11 +577,10 @@ function get!(lazyagdomtree::LazyAugmentedDomtree) cfg_insert_edge!(cfg, bb, length(cfg.blocks)) end end - domtree = construct_domtree(cfg.blocks) + domtree = construct_domtree(cfg) return lazyagdomtree.agdomtree = AugmentedDomtree(cfg, domtree) end -# TODO refine `:effect_free` using EscapeAnalysis mutable struct PostOptAnalysisState const result::InferenceResult const ir::IRCode @@ -543,8 +588,10 @@ mutable struct PostOptAnalysisState const tpdum::TwoPhaseDefUseMap const lazypostdomtree::LazyPostDomtree const lazyagdomtree::LazyAugmentedDomtree + const ea_analysis_pending::Vector{Int} all_retpaths_consistent::Bool all_effect_free::Bool + effect_free_if_argmem_only::Union{Nothing,Bool} all_nothrow::Bool all_noub::Bool any_conditional_ub::Bool @@ -553,12 +600,14 @@ mutable struct PostOptAnalysisState tpdum = TwoPhaseDefUseMap(length(ir.stmts)) lazypostdomtree = LazyPostDomtree(ir) lazyagdomtree = LazyAugmentedDomtree(ir) - return new(result, ir, inconsistent, tpdum, lazypostdomtree, lazyagdomtree, true, true, true, true, false) + return new(result, ir, inconsistent, tpdum, lazypostdomtree, lazyagdomtree, Int[], + true, true, nothing, true, true, false) end end give_up_refinements!(sv::PostOptAnalysisState) = - sv.all_retpaths_consistent = sv.all_effect_free = sv.all_nothrow = sv.all_noub = false + sv.all_retpaths_consistent = sv.all_effect_free = sv.effect_free_if_argmem_only = + sv.all_nothrow = sv.all_noub = false function any_refinable(sv::PostOptAnalysisState) effects = sv.result.ipo_effects @@ -568,12 +617,47 @@ function any_refinable(sv::PostOptAnalysisState) (!is_noub(effects) & sv.all_noub)) end -function refine_effects!(sv::PostOptAnalysisState) +struct GetNativeEscapeCache{CodeCache} + code_cache::CodeCache + GetNativeEscapeCache(code_cache::CodeCache) where CodeCache = new{CodeCache}(code_cache) +end +GetNativeEscapeCache(interp::AbstractInterpreter) = GetNativeEscapeCache(code_cache(interp)) +function ((; code_cache)::GetNativeEscapeCache)(mi::MethodInstance) + codeinst = get(code_cache, mi, nothing) + codeinst isa CodeInstance || return false + argescapes = traverse_analysis_results(codeinst) do @nospecialize result + return result isa EscapeAnalysis.ArgEscapeCache ? result : nothing + end + if argescapes !== nothing + return argescapes + end + effects = decode_effects(codeinst.ipo_purity_bits) + if is_effect_free(effects) && is_inaccessiblememonly(effects) + # We might not have run EA on simple frames without any escapes (e.g. when optimization + # is skipped when result is constant-folded by abstract interpretation). If those + # frames aren't inlined, the accuracy of EA for caller context takes a big hit. + # This is a HACK to avoid that, but obviously, a more comprehensive fix would be ideal. + return true + end + return false +end + +function refine_effects!(interp::AbstractInterpreter, sv::PostOptAnalysisState) + if !is_effect_free(sv.result.ipo_effects) && sv.all_effect_free && !isempty(sv.ea_analysis_pending) + ir = sv.ir + nargs = length(ir.argtypes) + estate = EscapeAnalysis.analyze_escapes(ir, nargs, optimizer_lattice(interp), GetNativeEscapeCache(interp)) + argescapes = EscapeAnalysis.ArgEscapeCache(estate) + stack_analysis_result!(sv.result, argescapes) + validate_mutable_arg_escapes!(estate, sv) + end + any_refinable(sv) || return false effects = sv.result.ipo_effects sv.result.ipo_effects = Effects(effects; consistent = sv.all_retpaths_consistent ? ALWAYS_TRUE : effects.consistent, - effect_free = sv.all_effect_free ? ALWAYS_TRUE : effects.effect_free, + effect_free = sv.all_effect_free ? ALWAYS_TRUE : + sv.effect_free_if_argmem_only === true ? EFFECT_FREE_IF_INACCESSIBLEMEMONLY : effects.effect_free, nothrow = sv.all_nothrow ? true : effects.nothrow, noub = sv.all_noub ? (sv.any_conditional_ub ? NOUB_IF_NOINBOUNDS : ALWAYS_TRUE) : effects.noub) return true @@ -584,52 +668,131 @@ function is_ipo_dataflow_analysis_profitable(effects::Effects) is_nothrow(effects) && is_noub(effects)) end -function is_getfield_with_boundscheck_arg(@nospecialize(stmt), sv::PostOptAnalysisState) - is_known_call(stmt, getfield, sv.ir) || return false - length(stmt.args) < 4 && return false +function iscall_with_boundscheck(@nospecialize(stmt), sv::PostOptAnalysisState) + isexpr(stmt, :call) || return false + ft = argextype(stmt.args[1], sv.ir) + f = singleton_type(ft) + f === nothing && return false + if f === getfield + nargs = 4 + elseif f === memoryref || f === memoryrefget || f === memoryref_isassigned + nargs = 4 + elseif f === memoryrefset! + nargs = 5 + else + return false + end + length(stmt.args) < nargs && return false boundscheck = stmt.args[end] argextype(boundscheck, sv.ir) === Bool || return false isa(boundscheck, SSAValue) || return false return true end +function check_all_args_noescape!(sv::PostOptAnalysisState, ir::IRCode, @nospecialize(stmt), + estate::EscapeAnalysis.EscapeState) + stmt isa Expr || return false + if isexpr(stmt, :invoke) + startidx = 2 + elseif isexpr(stmt, :new) + startidx = 1 + else + return false + end + for i = startidx:length(stmt.args) + arg = stmt.args[i] + argt = argextype(arg, ir) + if is_mutation_free_argtype(argt) + continue + end + # See if we can find the allocation + if isa(arg, Argument) + if EscapeAnalysis.has_no_escape(EscapeAnalysis.ignore_argescape(estate[arg])) + # Even if we prove everything else effect_free, the best we can + # say is :effect_free_if_argmem_only + if sv.effect_free_if_argmem_only === nothing + sv.effect_free_if_argmem_only = true + end + else + sv.effect_free_if_argmem_only = false + end + return false + elseif isa(arg, SSAValue) + EscapeAnalysis.has_no_escape(estate[arg]) || return false + check_all_args_noescape!(sv, ir, ir[arg][:stmt], estate) || return false + else + return false + end + end + return true +end + +function validate_mutable_arg_escapes!(estate::EscapeAnalysis.EscapeState, sv::PostOptAnalysisState) + ir = sv.ir + for idx in sv.ea_analysis_pending + # See if any mutable memory was allocated in this function and determined + # not to escape. + inst = ir[SSAValue(idx)] + stmt = inst[:stmt] + if !check_all_args_noescape!(sv, ir, stmt, estate) + return sv.all_effect_free = false + end + end + return true +end + function is_conditional_noub(inst::Instruction, sv::PostOptAnalysisState) - # Special case: `:boundscheck` into `getfield` stmt = inst[:stmt] - is_getfield_with_boundscheck_arg(stmt, sv) || return false - barg = stmt.args[end] + iscall_with_boundscheck(stmt, sv) || return false + barg = stmt.args[end]::SSAValue bstmt = sv.ir[barg][:stmt] isexpr(bstmt, :boundscheck) || return false # If IR_FLAG_INBOUNDS is already set, no more conditional ub (!isempty(bstmt.args) && bstmt.args[1] === false) && return false - sv.any_conditional_ub = true return true end function scan_non_dataflow_flags!(inst::Instruction, sv::PostOptAnalysisState) flag = inst[:flag] + # If we can prove that the argmem does not escape the current function, we can + # refine this to :effect_free. + needs_ea_validation = has_flag(flag, IR_FLAGS_NEEDS_EA) stmt = inst[:stmt] - if !isterminator(stmt) && stmt !== nothing - # ignore control flow node – they are not removable on their own and thus not - # have `IR_FLAG_EFFECT_FREE` but still do not taint `:effect_free`-ness of - # the whole method invocation - sv.all_effect_free &= !iszero(flag & IR_FLAG_EFFECT_FREE) - end - sv.all_nothrow &= !iszero(flag & IR_FLAG_NOTHROW) - if iszero(flag & IR_FLAG_NOUB) - if !is_conditional_noub(inst, sv) + if !needs_ea_validation + if !isterminator(stmt) && stmt !== nothing + # ignore control flow node – they are not removable on their own and thus not + # have `IR_FLAG_EFFECT_FREE` but still do not taint `:effect_free`-ness of + # the whole method invocation + sv.all_effect_free &= has_flag(flag, IR_FLAG_EFFECT_FREE) + end + elseif sv.all_effect_free + if (isexpr(stmt, :invoke) || isexpr(stmt, :new) || + # HACK for performance: limit the scope of EA to code with object field access only, + # since its abilities to reason about e.g. arrays are currently very limited anyways. + is_known_call(stmt, setfield!, sv.ir)) + push!(sv.ea_analysis_pending, inst.idx) + else + sv.all_effect_free = false + end + end + sv.all_nothrow &= has_flag(flag, IR_FLAG_NOTHROW) + if !has_flag(flag, IR_FLAG_NOUB) + # Special case: `:boundscheck` into `getfield` or memory operations is `:noub_if_noinbounds` + if is_conditional_noub(inst, sv) + sv.any_conditional_ub = true + else sv.all_noub = false end end end -function scan_inconsistency!(inst::Instruction, idx::Int, sv::PostOptAnalysisState) +function scan_inconsistency!(inst::Instruction, sv::PostOptAnalysisState) flag = inst[:flag] - stmt_inconsistent = iszero(flag & IR_FLAG_CONSISTENT) + stmt_inconsistent = !has_flag(flag, IR_FLAG_CONSISTENT) stmt = inst[:stmt] - # Special case: For getfield, we allow inconsistency of the :boundscheck argument + # Special case: For `getfield` and memory operations, we allow inconsistency of the :boundscheck argument (; inconsistent, tpdum) = sv - if is_getfield_with_boundscheck_arg(stmt, sv) + if iscall_with_boundscheck(stmt, sv) for i = 1:(length(stmt.args)-1) val = stmt.args[i] if isa(val, SSAValue) @@ -646,7 +809,7 @@ function scan_inconsistency!(inst::Instruction, idx::Int, sv::PostOptAnalysisSta end end end - stmt_inconsistent && push!(inconsistent, idx) + stmt_inconsistent && push!(inconsistent, inst.idx) return stmt_inconsistent end @@ -654,11 +817,10 @@ struct ScanStmt sv::PostOptAnalysisState end -function ((; sv)::ScanStmt)(inst::Instruction, idx::Int, lstmt::Int, bb::Int) +function ((; sv)::ScanStmt)(inst::Instruction, lstmt::Int, bb::Int) stmt = inst[:stmt] - flag = inst[:flag] - if isexpr(stmt, :enter) + if isa(stmt, EnterNode) # try/catch not yet modeled give_up_refinements!(sv) return nothing @@ -666,9 +828,9 @@ function ((; sv)::ScanStmt)(inst::Instruction, idx::Int, lstmt::Int, bb::Int) scan_non_dataflow_flags!(inst, sv) - stmt_inconsistent = scan_inconsistency!(inst, idx, sv) + stmt_inconsistent = scan_inconsistency!(inst, sv) - if stmt_inconsistent && idx == lstmt + if stmt_inconsistent && inst.idx == lstmt if isa(stmt, ReturnNode) && isdefined(stmt, :val) sv.all_retpaths_consistent = false elseif isa(stmt, GotoIfNot) @@ -678,8 +840,10 @@ function ((; sv)::ScanStmt)(inst::Instruction, idx::Int, lstmt::Int, bb::Int) # inconsistent region. if !sv.result.ipo_effects.terminates sv.all_retpaths_consistent = false - elseif conditional_successors_may_throw(sv.lazypostdomtree, sv.ir, bb) - # Check if there are potential throws that require + elseif visit_conditional_successors(sv.lazypostdomtree, sv.ir, bb) do succ::Int + return any_stmt_may_throw(sv.ir, succ) + end + # check if this `GotoIfNot` leads to conditional throws, which taints consistency sv.all_retpaths_consistent = false else (; cfg, domtree) = get!(sv.lazyagdomtree) @@ -707,12 +871,14 @@ function ((; sv)::ScanStmt)(inst::Instruction, idx::Int, lstmt::Int, bb::Int) end function check_inconsistentcy!(sv::PostOptAnalysisState, scanner::BBScanner) - scan!(ScanStmt(sv), scanner, true) - complete!(sv.tpdum); push!(scanner.bb_ip, 1) - populate_def_use_map!(sv.tpdum, scanner) (; ir, inconsistent, tpdum) = sv + + scan!(ScanStmt(sv), scanner, false) + complete!(tpdum); push!(scanner.bb_ip, 1) + populate_def_use_map!(tpdum, scanner) + stmt_ip = BitSetBoundedMinPrioritySet(length(ir.stmts)) - for def in sv.inconsistent + for def in inconsistent for use in tpdum[def] if !(use in inconsistent) push!(inconsistent, use) @@ -720,11 +886,12 @@ function check_inconsistentcy!(sv::PostOptAnalysisState, scanner::BBScanner) end end end + lazydomtree = LazyDomtree(ir) while !isempty(stmt_ip) idx = popfirst!(stmt_ip) inst = ir[SSAValue(idx)] stmt = inst[:stmt] - if is_getfield_with_boundscheck_arg(stmt, sv) + if iscall_with_boundscheck(stmt, sv) any_non_boundscheck_inconsistent = false for i = 1:(length(stmt.args)-1) val = stmt.args[i] @@ -736,12 +903,11 @@ function check_inconsistentcy!(sv::PostOptAnalysisState, scanner::BBScanner) any_non_boundscheck_inconsistent || continue elseif isa(stmt, ReturnNode) sv.all_retpaths_consistent = false - else isa(stmt, GotoIfNot) + elseif isa(stmt, GotoIfNot) bb = block_for_inst(ir, idx) cfg = ir.cfg blockliveness = BlockLiveness(cfg.blocks[bb].succs, nothing) - domtree = construct_domtree(cfg.blocks) - for succ in iterated_dominance_frontier(cfg, blockliveness, domtree) + for succ in iterated_dominance_frontier(cfg, blockliveness, get!(lazydomtree)) visit_bb_phis!(ir, succ) do phiidx::Int push!(inconsistent, phiidx) push!(stmt_ip, phiidx) @@ -755,7 +921,11 @@ function check_inconsistentcy!(sv::PostOptAnalysisState, scanner::BBScanner) end function ipo_dataflow_analysis!(interp::AbstractInterpreter, ir::IRCode, result::InferenceResult) - is_ipo_dataflow_analysis_profitable(result.ipo_effects) || return false + if !is_ipo_dataflow_analysis_profitable(result.ipo_effects) + return false + end + + @assert isempty(ir.new_nodes) "IRCode should be compacted before post-opt analysis" sv = PostOptAnalysisState(result, ir) scanner = BBScanner(ir) @@ -767,7 +937,7 @@ function ipo_dataflow_analysis!(interp::AbstractInterpreter, ir::IRCode, result: check_inconsistentcy!(sv, scanner) else # No longer any dataflow concerns, just scan the flags - scan!(scanner, false) do inst::Instruction, idx::Int, lstmt::Int, bb::Int + scan!(scanner, false) do inst::Instruction, lstmt::Int, bb::Int scan_non_dataflow_flags!(inst, sv) # bail out early if there are no possibilities to refine the effects if !any_refinable(sv) @@ -778,7 +948,7 @@ function ipo_dataflow_analysis!(interp::AbstractInterpreter, ir::IRCode, result: end end - return refine_effects!(sv) + return refine_effects!(interp, sv) end # run the optimization work @@ -819,67 +989,159 @@ function run_passes_ipo_safe( # @timeit "verify 2" verify_ir(ir) @pass "compact 2" ir = compact!(ir) @pass "SROA" ir = sroa_pass!(ir, sv.inlining) - @pass "ADCE" ir = adce_pass!(ir, sv.inlining) - @pass "compact 3" ir = compact!(ir, true) - if JLOptions().debug_level == 2 - @timeit "verify 3" (verify_ir(ir, true, false, optimizer_lattice(sv.inlining.interp)); verify_linetable(ir.linetable)) + @pass "ADCE" (ir, made_changes) = adce_pass!(ir, sv.inlining) + if made_changes + @pass "compact 3" ir = compact!(ir, true) + end + if is_asserts() + @timeit "verify 3" begin + verify_ir(ir, true, false, optimizer_lattice(sv.inlining.interp)) + verify_linetable(ir.debuginfo, length(ir.stmts)) + end end @label __done__ # used by @pass return ir end -function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) - linetable = ci.linetable - if !isa(linetable, Vector{LineInfoNode}) - linetable = collect(LineInfoNode, linetable::Vector{Any})::Vector{LineInfoNode} +function strip_trailing_junk!(code::Vector{Any}, ssavaluetypes::Vector{Any}, ssaflags::Vector, debuginfo::DebugInfoStream, cfg::CFG, info::Vector{CallInfo}) + # Remove `nothing`s at the end, we don't handle them well + # (we expect the last instruction to be a terminator) + codelocs = debuginfo.codelocs + for i = length(code):-1:1 + if code[i] !== nothing + resize!(code, i) + resize!(ssavaluetypes, i) + resize!(codelocs, 3i) + resize!(info, i) + resize!(ssaflags, i) + break + end end + # If the last instruction is not a terminator, add one. This can + # happen for implicit return on dead branches. + term = code[end] + if !isa(term, GotoIfNot) && !isa(term, GotoNode) && !isa(term, ReturnNode) + push!(code, ReturnNode()) + push!(ssavaluetypes, Union{}) + push!(codelocs, 0, 0, 0) + push!(info, NoCallInfo()) + push!(ssaflags, IR_FLAG_NOTHROW) + + # Update CFG to include appended terminator + old_range = cfg.blocks[end].stmts + new_range = StmtRange(first(old_range), last(old_range) + 1) + cfg.blocks[end] = BasicBlock(cfg.blocks[end], new_range) + (length(cfg.index) == length(cfg.blocks)) && (cfg.index[end] += 1) + end + nothing +end +function changed_lineinfo(di::DebugInfo, codeloc::Int, prevloc::Int) + while true + next = getdebugidx(di, codeloc) + line = next[1] + line < 0 && return false # invalid info + line == 0 && next[2] == 0 && return false # no new info + prevloc <= 0 && return true # no old info + prev = getdebugidx(di, prevloc) + next === prev && return false # exactly identical + prevline = prev[1] + prevline < 0 && return true # previous invalid info, now valid + edge = next[2] + edge === prev[2] || return true # change to this edge + linetable = di.linetable + # check for change to line number here + if linetable === nothing || line == 0 + line == prevline || return true + else + changed_lineinfo(linetable::DebugInfo, Int(line), Int(prevline)) && return true + end + # check for change to edge here + edge == 0 && return false # no edge here + di = di.edges[Int(edge)]::DebugInfo + codeloc = Int(next[3]) + prevloc = Int(prev[3]) + end +end + +function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) # Update control-flow to reflect any unreachable branches. ssavaluetypes = ci.ssavaluetypes::Vector{Any} - code = copy_exprargs(ci.code) + ci.code = code = copy_exprargs(ci.code) + di = DebugInfoStream(sv.linfo, ci.debuginfo, length(code)) + codelocs = di.codelocs + ssaflags = ci.ssaflags for i = 1:length(code) expr = code[i] - if !(i in sv.unreachable) && isa(expr, GotoIfNot) - # Replace this live GotoIfNot with: - # - no-op if :nothrow and the branch target is unreachable - # - cond if :nothrow and both targets are unreachable - # - typeassert if must-throw - block = block_for_inst(sv.cfg, i) - if ssavaluetypes[i] === Bottom - destblock = block_for_inst(sv.cfg, expr.dest) - cfg_delete_edge!(sv.cfg, block, block + 1) - ((block + 1) != destblock) && cfg_delete_edge!(sv.cfg, block, destblock) - expr = Expr(:call, Core.typeassert, expr.cond, Bool) - elseif i + 1 in sv.unreachable - @assert (ci.ssaflags[i] & IR_FLAG_NOTHROW) != 0 - cfg_delete_edge!(sv.cfg, block, block + 1) - expr = GotoNode(expr.dest) - elseif expr.dest in sv.unreachable - @assert (ci.ssaflags[i] & IR_FLAG_NOTHROW) != 0 - cfg_delete_edge!(sv.cfg, block, block_for_inst(sv.cfg, expr.dest)) - expr = nothing + if !(i in sv.unreachable) + if isa(expr, GotoIfNot) + # Replace this live GotoIfNot with: + # - no-op if :nothrow and the branch target is unreachable + # - cond if :nothrow and both targets are unreachable + # - typeassert if must-throw + block = block_for_inst(sv.cfg, i) + if ssavaluetypes[i] === Bottom + destblock = block_for_inst(sv.cfg, expr.dest) + cfg_delete_edge!(sv.cfg, block, block + 1) + ((block + 1) != destblock) && cfg_delete_edge!(sv.cfg, block, destblock) + expr = Expr(:call, Core.typeassert, expr.cond, Bool) + elseif i + 1 in sv.unreachable + @assert has_flag(ssaflags[i], IR_FLAG_NOTHROW) + cfg_delete_edge!(sv.cfg, block, block + 1) + expr = GotoNode(expr.dest) + elseif expr.dest in sv.unreachable + @assert has_flag(ssaflags[i], IR_FLAG_NOTHROW) + cfg_delete_edge!(sv.cfg, block, block_for_inst(sv.cfg, expr.dest)) + expr = nothing + end + code[i] = expr + elseif isa(expr, EnterNode) + catchdest = expr.catch_dest + if catchdest in sv.unreachable + cfg_delete_edge!(sv.cfg, block_for_inst(sv.cfg, i), block_for_inst(sv.cfg, catchdest)) + if isdefined(expr, :scope) + # We've proven that nothing inside the enter region throws, + # but we don't yet know whether something might read the scope, + # so we need to retain this enter for the time being. However, + # we use the special marker `0` to indicate that setting up + # the try/catch frame is not required. + code[i] = EnterNode(expr, 0) + else + code[i] = nothing + end + end + elseif isa(expr, PhiNode) + new_edges = Int32[] + new_vals = Any[] + for j = 1:length(expr.edges) + edge = expr.edges[j] + (edge in sv.unreachable || (ssavaluetypes[edge] === Union{} && !isa(code[edge], PhiNode))) && continue + push!(new_edges, edge) + if isassigned(expr.values, j) + push!(new_vals, expr.values[j]) + else + resize!(new_vals, length(new_edges)) + end + end + code[i] = PhiNode(new_edges, new_vals) end - code[i] = expr end end # Go through and add an unreachable node after every # Union{} call. Then reindex labels. stmtinfo = sv.stmt_info - codelocs = ci.codelocs - ssaflags = ci.ssaflags meta = Expr[] idx = 1 oldidx = 1 nstmts = length(code) ssachangemap = labelchangemap = blockchangemap = nothing - prevloc = zero(eltype(ci.codelocs)) + prevloc = 0 while idx <= length(code) - codeloc = codelocs[idx] - if sv.insert_coverage && codeloc != prevloc && codeloc != 0 + if sv.insert_coverage && changed_lineinfo(ci.debuginfo, oldidx, prevloc) # insert a side-effect instruction before the current instruction in the same basic block insert!(code, idx, Expr(:code_coverage_effect)) - insert!(codelocs, idx, codeloc) + splice!(codelocs, 3idx-2:3idx-3, (codelocs[3idx-2], codelocs[3idx-1], codelocs[3idx-0])) insert!(ssavaluetypes, idx, Nothing) insert!(stmtinfo, idx, NoCallInfo()) insert!(ssaflags, idx, IR_FLAG_NULL) @@ -898,9 +1160,9 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) end blockchangemap[block_for_inst(sv.cfg, oldidx)] += 1 idx += 1 - prevloc = codeloc + prevloc = oldidx end - if ssavaluetypes[idx] === Union{} && !(oldidx in sv.unreachable) + if ssavaluetypes[idx] === Union{} && !(oldidx in sv.unreachable) && !isa(code[idx], PhiNode) # We should have converted any must-throw terminators to an equivalent w/o control-flow edges @assert !isterminator(code[idx]) @@ -920,13 +1182,13 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) # terminator with an explicit `unreachable` marker. if block_end > idx code[block_end] = ReturnNode() - codelocs[block_end] = codelocs[idx] + codelocs[3block_end-2], codelocs[3block_end-1], codelocs[3block_end-0] = (codelocs[3idx-2], codelocs[3idx-1], codelocs[3idx-0]) ssavaluetypes[block_end] = Union{} stmtinfo[block_end] = NoCallInfo() ssaflags[block_end] = IR_FLAG_NOTHROW # Verify that type-inference did its job - if JLOptions().debug_level == 2 + if is_asserts() for i = (oldidx + 1):last(sv.cfg.blocks[block].stmts) @assert i in sv.unreachable end @@ -935,7 +1197,7 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) idx += block_end - idx else insert!(code, idx + 1, ReturnNode()) - insert!(codelocs, idx + 1, codelocs[idx]) + splice!(codelocs, 3idx-2:3idx-3, (codelocs[3idx-2], codelocs[3idx-1], codelocs[3idx-0])) insert!(ssavaluetypes, idx + 1, Union{}) insert!(stmtinfo, idx + 1, NoCallInfo()) insert!(ssaflags, idx + 1, IR_FLAG_NOTHROW) @@ -972,14 +1234,14 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) for i = 1:length(code) code[i] = process_meta!(meta, code[i]) end - strip_trailing_junk!(ci, sv.cfg, code, stmtinfo) + strip_trailing_junk!(code, ssavaluetypes, ssaflags, di, sv.cfg, stmtinfo) types = Any[] stmts = InstructionStream(code, types, stmtinfo, codelocs, ssaflags) # NOTE this `argtypes` contains types of slots yet: it will be modified to contain the # types of call arguments only once `slot2reg` converts this `IRCode` to the SSA form # and eliminates slots (see below) argtypes = sv.slottypes - return IRCode(stmts, sv.cfg, linetable, argtypes, meta, sv.sptypes) + return IRCode(stmts, sv.cfg, di, argtypes, meta, sv.sptypes) end function process_meta!(meta::Vector{Expr}, @nospecialize stmt) @@ -994,7 +1256,7 @@ function slot2reg(ir::IRCode, ci::CodeInfo, sv::OptimizationState) # need `ci` for the slot metadata, IR for the code svdef = sv.linfo.def nargs = isa(svdef, Method) ? Int(svdef.nargs) : 0 - @timeit "domtree 1" domtree = construct_domtree(ir.cfg.blocks) + @timeit "domtree 1" domtree = construct_domtree(ir) defuse_insts = scan_slot_def_use(nargs, ci, ir.stmts.stmt) 𝕃ₒ = optimizer_lattice(sv.inlining.interp) @timeit "construct_ssa" ir = construct_ssa!(ci, ir, sv, domtree, defuse_insts, 𝕃ₒ) # consumes `ir` @@ -1014,6 +1276,7 @@ isknowntype(@nospecialize T) = (T === Union{}) || isa(T, Const) || isconcretetyp function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptypes::Vector{VarState}, params::OptimizationParams, error_path::Bool = false) + #=const=# UNKNOWN_CALL_COST = 20 head = ex.head if is_meta_expr_head(head) return 0 @@ -1059,8 +1322,8 @@ function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptyp return cost end end - # unknown/unhandled intrinsic - return params.inline_nonleaf_penalty + # unknown/unhandled intrinsic: hopefully the caller gets a slightly better answer after the inlining + return UNKNOWN_CALL_COST end if isa(f, Builtin) && f !== invoke # The efficiency of operations like a[i] and s.b @@ -1071,12 +1334,12 @@ function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptyp # tuple iteration/destructuring makes that impossible # return plus_saturate(argcost, isknowntype(extyp) ? 1 : params.inline_nonleaf_penalty) return 0 - elseif (f === Core.arrayref || f === Core.const_arrayref) && length(ex.args) >= 3 - atyp = argextype(ex.args[3], src, sptypes) - return isknowntype(atyp) ? 4 : error_path ? params.inline_error_path_cost : params.inline_nonleaf_penalty - elseif f === Core.arrayset && length(ex.args) >= 3 + elseif (f === Core.memoryrefget || f === Core.memoryref_isassigned) && length(ex.args) >= 3 + atyp = argextype(ex.args[2], src, sptypes) + return isknowntype(atyp) ? 1 : error_path ? params.inline_error_path_cost : params.inline_nonleaf_penalty + elseif f === Core.memoryrefset! && length(ex.args) >= 3 atyp = argextype(ex.args[2], src, sptypes) - return isknowntype(atyp) ? 8 : error_path ? params.inline_error_path_cost : params.inline_nonleaf_penalty + return isknowntype(atyp) ? 5 : error_path ? params.inline_error_path_cost : params.inline_nonleaf_penalty elseif f === typeassert && isconstType(widenconst(argextype(ex.args[3], src, sptypes))) return 1 end @@ -1084,7 +1347,7 @@ function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptyp if fidx === nothing # unknown/unhandled builtin # Use the generic cost of a direct function call - return 20 + return UNKNOWN_CALL_COST end return T_FFUNC_COST[fidx] end @@ -1093,17 +1356,23 @@ 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 || head === :invoke_modify + elseif head === :foreigncall + foreigncall = ex.args[1] + if foreigncall isa QuoteNode && foreigncall.value === :jl_string_ptr + return 1 + end + return 20 + elseif 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 # consideration. This way, non-inlined error branches do not # prevent inlining. extyp = line == -1 ? Any : argextype(SSAValue(line), src, sptypes) - return extyp === Union{} ? 0 : 20 + return extyp === Union{} ? 0 : UNKNOWN_CALL_COST elseif head === :(=) if ex.args[1] isa GlobalRef - cost = 20 + cost = UNKNOWN_CALL_COST else cost = 0 end @@ -1114,12 +1383,6 @@ function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptyp return cost elseif head === :copyast return 100 - elseif head === :enter - # try/catch is a couple function calls, - # but don't inline functions with try/catch - # since these aren't usually performance-sensitive functions, - # and llvm is more likely to miscompile them when these functions get large - return typemax(Int) end return 0 end @@ -1138,18 +1401,25 @@ function statement_or_branch_cost(@nospecialize(stmt), line::Int, src::Union{Cod thiscost = dst(stmt.label) < line ? 40 : 0 elseif stmt isa GotoIfNot thiscost = dst(stmt.dest) < line ? 40 : 0 + elseif stmt isa EnterNode + # try/catch is a couple function calls, + # but don't inline functions with try/catch + # since these aren't usually performance-sensitive functions, + # and llvm is more likely to miscompile them when these functions get large + thiscost = typemax(Int) end return thiscost end -function inline_cost(ir::IRCode, params::OptimizationParams, - cost_threshold::Integer=params.inline_cost_threshold)::InlineCostType - bodycost::Int = 0 - for line = 1:length(ir.stmts) - stmt = ir[SSAValue(line)][:stmt] - thiscost = statement_or_branch_cost(stmt, line, ir, ir.sptypes, params) +function inline_cost(ir::IRCode, params::OptimizationParams, cost_threshold::Int) + bodycost = 0 + for i = 1:length(ir.stmts) + stmt = ir[SSAValue(i)][:stmt] + thiscost = statement_or_branch_cost(stmt, i, ir, ir.sptypes, params) bodycost = plus_saturate(bodycost, thiscost) - bodycost > cost_threshold && return MAX_INLINE_COST + if bodycost > cost_threshold + return MAX_INLINE_COST + end end return inline_cost_clamp(bodycost) end @@ -1233,14 +1503,26 @@ function renumber_ir_elements!(body::Vector{Any}, ssachangemap::Vector{Int}, lab i += 1 end end + elseif isa(el, EnterNode) + tgt = el.catch_dest + if tgt != 0 + was_deleted = labelchangemap[tgt] == typemin(Int) + if was_deleted + @assert !isdefined(el, :scope) + body[i] = nothing + else + if isdefined(el, :scope) && isa(el.scope, SSAValue) + body[i] = EnterNode(tgt + labelchangemap[tgt], SSAValue(el.scope.id + ssachangemap[el.scope.id])) + else + body[i] = EnterNode(el, tgt + labelchangemap[tgt]) + end + end + end elseif isa(el, Expr) if el.head === :(=) && el.args[2] isa Expr el = el.args[2]::Expr end - if el.head === :enter - tgt = el.args[1]::Int - el.args[1] = tgt + labelchangemap[tgt] - elseif !is_meta_expr_head(el.head) + if !is_meta_expr_head(el.head) args = el.args for i = 1:length(args) el = args[i] diff --git a/base/compiler/sort.jl b/base/compiler/sort.jl index 71d2f8a51cd59..6c8571f6198e6 100644 --- a/base/compiler/sort.jl +++ b/base/compiler/sort.jl @@ -1,7 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license # reference on sorted binary search: -# http://www.tbray.org/ongoing/When/200x/2003/03/22/Binary +# https://www.tbray.org/ongoing/When/200x/2003/03/22/Binary # index of the first value of vector a that is greater than or equal to x; # returns lastindex(v)+1 if x is greater than all values in v. diff --git a/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl b/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl index 6a6994d497d8e..f74cb90e6ab51 100644 --- a/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl +++ b/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl @@ -15,9 +15,7 @@ const _TOP_MOD = ccall(:jl_base_relative_to, Any, (Any,), EscapeAnalysis)::Modul # imports import ._TOP_MOD: ==, getindex, setindex! # usings -using Core: - MethodMatch, SimpleVector, - arrayref, arrayset, arraysize, ifelse, sizeof +using Core: MethodMatch, SimpleVector, ifelse, sizeof using Core.IR using ._TOP_MOD: # Base definitions @__MODULE__, @assert, @eval, @goto, @inbounds, @inline, @label, @noinline, @@ -27,10 +25,9 @@ using ._TOP_MOD: # Base definitions unwrap_unionall, !, !=, !==, &, *, +, -, :, <, <<, =>, >, |, ∈, ∉, ∩, ∪, ≠, ≤, ≥, ⊆ using Core.Compiler: # Core.Compiler specific definitions Bottom, IRCode, IR_FLAG_NOTHROW, InferenceResult, SimpleInferenceLattice, - alloc_array_ndims, argextype, array_builtin_common_typecheck, arrayset_typecheck, - check_effect_free!, fieldcount_noerror, hasintersect, intrinsic_nothrow, + argextype, fieldcount_noerror, hasintersect, has_flag, intrinsic_nothrow, is_meta_expr_head, isbitstype, isexpr, println, setfield!_nothrow, singleton_type, - try_compute_field, try_compute_fieldidx, widenconst, ⊑ + try_compute_field, try_compute_fieldidx, widenconst, ⊑, AbstractLattice include(x) = _TOP_MOD.include(@__MODULE__, x) if _TOP_MOD === Core.Compiler @@ -40,13 +37,12 @@ else end const AInfo = IdSet{Any} -const 𝕃ₒ = SimpleInferenceLattice.instance """ x::EscapeInfo A lattice for escape information, which holds the following properties: -- `x.Analyzed::Bool`: not formally part of the lattice, only indicates `x` has not been analyzed or not +- `x.Analyzed::Bool`: not formally part of the lattice, only indicates whether `x` has been analyzed - `x.ReturnEscape::Bool`: indicates `x` can escape to the caller via return - `x.ThrownEscape::BitSet`: records SSA statement numbers where `x` can be thrown as exception: * `isempty(x.ThrownEscape)`: `x` will never be thrown in this call frame (the bottom) @@ -601,11 +597,12 @@ struct LivenessChange <: Change end const Changes = Vector{Change} -struct AnalysisState{T} +struct AnalysisState{GetEscapeCache, Lattice<:AbstractLattice} ir::IRCode estate::EscapeState changes::Changes - get_escape_cache::T + 𝕃ₒ::Lattice + get_escape_cache::GetEscapeCache end """ @@ -613,17 +610,17 @@ end Analyzes escape information in `ir`: - `nargs`: the number of actual arguments of the analyzed call -- `get_escape_cache(::MethodInstance) -> Union{Nothing,ArgEscapeCache}`: +- `get_escape_cache(::MethodInstance) -> Union{Bool,ArgEscapeCache}`: retrieves cached argument escape information """ -function analyze_escapes(ir::IRCode, nargs::Int, get_escape_cache) +function analyze_escapes(ir::IRCode, nargs::Int, 𝕃ₒ::AbstractLattice, get_escape_cache) stmts = ir.stmts nstmts = length(stmts) + length(ir.new_nodes.stmts) tryregions, arrayinfo = compute_frameinfo(ir) estate = EscapeState(nargs, nstmts, arrayinfo) changes = Changes() # keeps changes that happen at current statement - astate = AnalysisState(ir, estate, changes, get_escape_cache) + astate = AnalysisState(ir, estate, changes, 𝕃ₒ, get_escape_cache) local debug_itr_counter = 0 while true @@ -655,7 +652,7 @@ function analyze_escapes(ir::IRCode, nargs::Int, get_escape_cache) elseif is_meta_expr_head(head) # meta expressions doesn't account for any usages continue - elseif head === :enter || head === :leave || head === :the_exception || head === :pop_exception + elseif head === :leave || head === :the_exception || head === :pop_exception # ignore these expressions since escapes via exceptions are handled by `escape_exception!` # `escape_exception!` conservatively propagates `AllEscape` anyway, # and so escape information imposed on `:the_exception` isn't computed @@ -669,6 +666,9 @@ function analyze_escapes(ir::IRCode, nargs::Int, get_escape_cache) else add_conservative_changes!(astate, pc, stmt.args) end + elseif isa(stmt, EnterNode) + # Handled via escape_exception! + continue elseif isa(stmt, ReturnNode) if isdefined(stmt, :val) add_escape_change!(astate, stmt.val, ReturnEscape(pc)) @@ -731,45 +731,12 @@ function compute_frameinfo(ir::IRCode) for idx in 1:nstmts+nnewnodes inst = ir[SSAValue(idx)] stmt = inst[:stmt] - if isexpr(stmt, :enter) + if isa(stmt, EnterNode) @assert idx ≤ nstmts "try/catch inside new_nodes unsupported" tryregions === nothing && (tryregions = UnitRange{Int}[]) - leave_block = stmt.args[1]::Int + leave_block = stmt.catch_dest leave_pc = first(ir.cfg.blocks[leave_block].stmts) push!(tryregions, idx:leave_pc) - elseif isexpr(stmt, :foreigncall) - args = stmt.args - name = args[1] - nn = normalize(name) - isa(nn, Symbol) || @goto next_stmt - ndims = alloc_array_ndims(nn) - ndims === nothing && @goto next_stmt - if ndims ≠ 0 - length(args) ≥ ndims+6 || @goto next_stmt - dims = Int[] - for i in 1:ndims - dim = argextype(args[i+6], ir) - isa(dim, Const) || @goto next_stmt - dim = dim.val - isa(dim, Int) || @goto next_stmt - push!(dims, dim) - end - else - length(args) ≥ 7 || @goto next_stmt - dims = argextype(args[7], ir) - if isa(dims, Const) - dims = dims.val - isa(dims, Tuple{Vararg{Int}}) || @goto next_stmt - dims = collect(Int, dims) - else - dims === Tuple{} || @goto next_stmt - dims = Int[] - end - end - if arrayinfo === nothing - arrayinfo = ArrayInfo() - end - arrayinfo[idx] = dims elseif arrayinfo !== nothing # TODO this super limited alias analysis is able to handle only very simple cases # this should be replaced with a proper forward dimension analysis @@ -1011,7 +978,7 @@ end error("unexpected assignment found: inspect `Main.pc` and `Main.pc`") end -is_nothrow(ir::IRCode, pc::Int) = ir[SSAValue(pc)][:flag] & IR_FLAG_NOTHROW ≠ 0 +is_nothrow(ir::IRCode, pc::Int) = has_flag(ir[SSAValue(pc)], IR_FLAG_NOTHROW) # NOTE if we don't maintain the alias set that is separated from the lattice state, we can do # something like below: it essentially incorporates forward escape propagation in our default @@ -1097,11 +1064,14 @@ function escape_invoke!(astate::AnalysisState, pc::Int, args::Vector{Any}) first_idx, last_idx = 2, length(args) # TODO inspect `astate.ir.stmts[pc][:info]` and use const-prop'ed `InferenceResult` if available cache = astate.get_escape_cache(mi) - if cache === nothing - return add_conservative_changes!(astate, pc, args, 2) - else - cache = cache::ArgEscapeCache + if cache isa Bool + if cache + return nothing # guaranteed to have no escape + else + return add_conservative_changes!(astate, pc, args, 2) + end end + cache = cache::ArgEscapeCache ret = SSAValue(pc) retinfo = astate.estate[ret] # escape information imposed on the call statement method = mi.def::Method @@ -1163,19 +1133,6 @@ function escape_foreigncall!(astate::AnalysisState, pc::Int, args::Vector{Any}) name = args[1] nn = normalize(name) if isa(nn, Symbol) - boundserror_ninds = array_resize_info(nn) - if boundserror_ninds !== nothing - boundserror, ninds = boundserror_ninds - escape_array_resize!(boundserror, ninds, astate, pc, args) - return - end - if is_array_copy(nn) - escape_array_copy!(astate, pc, args) - return - elseif is_array_isassigned(nn) - escape_array_isassigned!(astate, pc, args) - return - end # if nn === :jl_gc_add_finalizer_th # # TODO add `FinalizerEscape` ? # end @@ -1498,10 +1455,10 @@ function escape_builtin!(::typeof(setfield!), astate::AnalysisState, pc::Int, ar add_escape_change!(astate, val, ssainfo) # compute the throwness of this setfield! call here since builtin_nothrow doesn't account for that @label add_thrown_escapes - if length(args) == 4 && setfield!_nothrow(𝕃ₒ, + if length(args) == 4 && setfield!_nothrow(astate.𝕃ₒ, argextype(args[2], ir), argextype(args[3], ir), argextype(args[4], ir)) return true - elseif length(args) == 3 && setfield!_nothrow(𝕃ₒ, + elseif length(args) == 3 && setfield!_nothrow(astate.𝕃ₒ, argextype(args[2], ir), argextype(args[3], ir)) return true else @@ -1510,139 +1467,6 @@ function escape_builtin!(::typeof(setfield!), astate::AnalysisState, pc::Int, ar end end -function escape_builtin!(::typeof(arrayref), astate::AnalysisState, pc::Int, args::Vector{Any}) - length(args) ≥ 4 || return false - # check potential thrown escapes from this arrayref call - argtypes = Any[argextype(args[i], astate.ir) for i in 2:length(args)] - boundcheckt = argtypes[1] - aryt = argtypes[2] - if !array_builtin_common_typecheck(boundcheckt, aryt, argtypes, 3) - add_thrown_escapes!(astate, pc, args, 2) - end - ary = args[3] - inbounds = isa(boundcheckt, Const) && !boundcheckt.val::Bool - inbounds || add_escape_change!(astate, ary, ThrownEscape(pc)) - # we don't track precise index information about this array and thus don't know what values - # can be referenced here: directly propagate the escape information imposed on the return - # value of this `arrayref` call to the array itself as the most conservative propagation - # but also with updated index information - estate = astate.estate - if isa(ary, SSAValue) || isa(ary, Argument) - aryinfo = estate[ary] - else - # unanalyzable object, so the return value is also unanalyzable - add_escape_change!(astate, SSAValue(pc), ⊤) - return true - end - AliasInfo = aryinfo.AliasInfo - if isa(AliasInfo, Bool) - AliasInfo && @goto conservative_propagation - # AliasInfo of this array hasn't been analyzed yet: set AliasInfo now - idx = array_nd_index(astate, ary, args[4:end]) - if isa(idx, Int) - AliasInfo = IndexableElements(IdDict{Int,AInfo}()) - @goto record_indexable_use - end - AliasInfo = Unindexable() - @goto record_unindexable_use - elseif isa(AliasInfo, IndexableElements) - idx = array_nd_index(astate, ary, args[4:end]) - if !isa(idx, Int) - AliasInfo = merge_to_unindexable(AliasInfo) - @goto record_unindexable_use - end - @label record_indexable_use - info = get!(()->AInfo(), AliasInfo.infos, idx) - push!(info, LocalUse(pc)) - add_escape_change!(astate, ary, EscapeInfo(aryinfo, AliasInfo)) # update with new AliasInfo - elseif isa(AliasInfo, Unindexable) - @label record_unindexable_use - push!(AliasInfo.info, LocalUse(pc)) - add_escape_change!(astate, ary, EscapeInfo(aryinfo, AliasInfo)) # update with new AliasInfo - else - # this object has been used as struct, but it is used as array here (thus should throw) - # update ary's element information and just handle this case conservatively - aryinfo = escape_unanalyzable_obj!(astate, ary, aryinfo) - @label conservative_propagation - # at the extreme case, an element of `ary` may point to `ary` itself - # so add the alias change here as the most conservative propagation - add_alias_change!(astate, ary, SSAValue(pc)) - end - return true -end - -function escape_builtin!(::typeof(arrayset), astate::AnalysisState, pc::Int, args::Vector{Any}) - length(args) ≥ 5 || return false - # check potential escapes from this arrayset call - # NOTE here we essentially only need to account for TypeError, assuming that - # UndefRefError or BoundsError don't capture any of the arguments here - argtypes = Any[argextype(args[i], astate.ir) for i in 2:length(args)] - boundcheckt = argtypes[1] - aryt = argtypes[2] - valt = argtypes[3] - if !(array_builtin_common_typecheck(boundcheckt, aryt, argtypes, 4) && - arrayset_typecheck(aryt, valt)) - add_thrown_escapes!(astate, pc, args, 2) - end - ary = args[3] - val = args[4] - inbounds = isa(boundcheckt, Const) && !boundcheckt.val::Bool - inbounds || add_escape_change!(astate, ary, ThrownEscape(pc)) - # we don't track precise index information about this array and won't record what value - # is being assigned here: directly propagate the escape information of this array to - # the value being assigned as the most conservative propagation - estate = astate.estate - if isa(ary, SSAValue) || isa(ary, Argument) - aryinfo = estate[ary] - else - # unanalyzable object (e.g. obj::GlobalRef): escape field value conservatively - add_escape_change!(astate, val, ⊤) - return true - end - AliasInfo = aryinfo.AliasInfo - if isa(AliasInfo, Bool) - AliasInfo && @goto conservative_propagation - # AliasInfo of this array hasn't been analyzed yet: set AliasInfo now - idx = array_nd_index(astate, ary, args[5:end]) - if isa(idx, Int) - AliasInfo = IndexableElements(IdDict{Int,AInfo}()) - @goto escape_indexable_def - end - AliasInfo = Unindexable() - @goto escape_unindexable_def - elseif isa(AliasInfo, IndexableElements) - idx = array_nd_index(astate, ary, args[5:end]) - if !isa(idx, Int) - AliasInfo = merge_to_unindexable(AliasInfo) - @goto escape_unindexable_def - end - @label escape_indexable_def - info = get!(()->AInfo(), AliasInfo.infos, idx) - add_alias_escapes!(astate, val, info) - push!(info, LocalDef(pc)) - add_escape_change!(astate, ary, EscapeInfo(aryinfo, AliasInfo)) # update with new AliasInfo - # propagate the escape information of this array ignoring elements information - add_escape_change!(astate, val, ignore_aliasinfo(aryinfo)) - elseif isa(AliasInfo, Unindexable) - @label escape_unindexable_def - add_alias_escapes!(astate, val, AliasInfo.info) - push!(AliasInfo.info, LocalDef(pc)) - add_escape_change!(astate, ary, EscapeInfo(aryinfo, AliasInfo)) # update with new AliasInfo - # propagate the escape information of this array ignoring elements information - add_escape_change!(astate, val, ignore_aliasinfo(aryinfo)) - else - # this object has been used as struct, but it is used as array here (thus should throw) - # update ary's element information and just handle this case conservatively - aryinfo = escape_unanalyzable_obj!(astate, ary, aryinfo) - @label conservative_propagation - add_alias_change!(astate, val, ary) - end - # also propagate escape information imposed on the return value of this `arrayset` - ssainfo = estate[SSAValue(pc)] - add_escape_change!(astate, ary, ssainfo) - return true -end - # NOTE this function models and thus should be synced with the implementation of: # size_t array_nd_index(jl_array_t *a, jl_value_t **args, size_t nidxs, ...) function array_nd_index(astate::AnalysisState, @nospecialize(ary), args::Vector{Any}, nidxs::Int = length(args)) @@ -1676,63 +1500,6 @@ function array_nd_index(astate::AnalysisState, @nospecialize(ary), args::Vector{ return i end -function escape_builtin!(::typeof(arraysize), astate::AnalysisState, pc::Int, args::Vector{Any}) - length(args) == 3 || return false - ary = args[2] - dim = args[3] - if !arraysize_typecheck(ary, dim, astate.ir) - add_escape_change!(astate, ary, ThrownEscape(pc)) - add_escape_change!(astate, dim, ThrownEscape(pc)) - end - # NOTE we may still see "arraysize: dimension out of range", but it doesn't capture anything - return true -end - -function arraysize_typecheck(@nospecialize(ary), @nospecialize(dim), ir::IRCode) - aryt = argextype(ary, ir) - aryt ⊑ Array || return false - dimt = argextype(dim, ir) - dimt ⊑ Int || return false - return true -end - -# returns nothing if this isn't array resizing operation, -# otherwise returns true if it can throw BoundsError and false if not -function array_resize_info(name::Symbol) - if name === :jl_array_grow_beg || name === :jl_array_grow_end - return false, 1 - elseif name === :jl_array_del_beg || name === :jl_array_del_end - return true, 1 - elseif name === :jl_array_grow_at || name === :jl_array_del_at - return true, 2 - else - return nothing - end -end - -# NOTE may potentially throw "cannot resize array with shared data" error, -# but just ignore it since it doesn't capture anything -function escape_array_resize!(boundserror::Bool, ninds::Int, - astate::AnalysisState, pc::Int, args::Vector{Any}) - length(args) ≥ 6+ninds || return add_fallback_changes!(astate, pc, args) - ary = args[6] - aryt = argextype(ary, astate.ir) - aryt ⊑ Array || return add_fallback_changes!(astate, pc, args) - for i in 1:ninds - ind = args[i+6] - indt = argextype(ind, astate.ir) - indt ⊑ Integer || return add_fallback_changes!(astate, pc, args) - end - if boundserror - # this array resizing can potentially throw `BoundsError`, impose it now - add_escape_change!(astate, ary, ThrownEscape(pc)) - end - # give up indexing analysis whenever we see array resizing - # (since we track array dimensions only globally) - mark_unindexable!(astate, ary) - add_liveness_changes!(astate, pc, args, 6) -end - function mark_unindexable!(astate::AnalysisState, @nospecialize(ary)) isa(ary, SSAValue) || return aryinfo = astate.estate[ary] @@ -1742,8 +1509,6 @@ function mark_unindexable!(astate::AnalysisState, @nospecialize(ary)) add_escape_change!(astate, ary, EscapeInfo(aryinfo, AliasInfo)) end -is_array_copy(name::Symbol) = name === :jl_array_copy - # FIXME this implementation is very conservative, improve the accuracy and solve broken test cases function escape_array_copy!(astate::AnalysisState, pc::Int, args::Vector{Any}) length(args) ≥ 6 || return add_fallback_changes!(astate, pc, args) @@ -1760,53 +1525,4 @@ function escape_array_copy!(astate::AnalysisState, pc::Int, args::Vector{Any}) add_liveness_changes!(astate, pc, args, 6) end -is_array_isassigned(name::Symbol) = name === :jl_array_isassigned - -function escape_array_isassigned!(astate::AnalysisState, pc::Int, args::Vector{Any}) - if !array_isassigned_nothrow(args, astate.ir) - add_thrown_escapes!(astate, pc, args) - end - add_liveness_changes!(astate, pc, args, 6) -end - -function array_isassigned_nothrow(args::Vector{Any}, src::IRCode) - # if !validate_foreigncall_args(args, - # :jl_array_isassigned, Cint, svec(Any,Csize_t), 0, :ccall) - # return false - # end - length(args) ≥ 7 || return false - arytype = argextype(args[6], src) - arytype ⊑ Array || return false - idxtype = argextype(args[7], src) - idxtype ⊑ Csize_t || return false - return true -end - -# # COMBAK do we want to enable this (and also backport this to Base for array allocations?) -# using Core.Compiler: Cint, svec -# function validate_foreigncall_args(args::Vector{Any}, -# name::Symbol, @nospecialize(rt), argtypes::SimpleVector, nreq::Int, convention::Symbol) -# length(args) ≥ 5 || return false -# normalize(args[1]) === name || return false -# args[2] === rt || return false -# args[3] === argtypes || return false -# args[4] === vararg || return false -# normalize(args[5]) === convention || return false -# return true -# end -# -# using Core: ImmutableArray, arrayfreeze, mutating_arrayfreeze, arraythaw -# -# escape_builtin!(::typeof(arrayfreeze), astate::AnalysisState, pc::Int, args::Vector{Any}) = -# is_safe_immutable_array_op(Array, astate, args) -# escape_builtin!(::typeof(mutating_arrayfreeze), astate::AnalysisState, pc::Int, args::Vector{Any}) = -# is_safe_immutable_array_op(Array, astate, args) -# escape_builtin!(::typeof(arraythaw), astate::AnalysisState, pc::Int, args::Vector{Any}) = -# is_safe_immutable_array_op(ImmutableArray, astate, args) -# function is_safe_immutable_array_op(@nospecialize(arytype), astate::AnalysisState, args::Vector{Any}) -# length(args) == 2 || return false -# argextype(args[2], astate.ir) ⊑ arytype || return false -# return true -# end - end # baremodule EscapeAnalysis diff --git a/base/compiler/ssair/domtree.jl b/base/compiler/ssair/domtree.jl index 934cd456dd945..81b46c7179d9a 100644 --- a/base/compiler/ssair/domtree.jl +++ b/base/compiler/ssair/domtree.jl @@ -82,6 +82,8 @@ struct DFSTree # (preorder number -> preorder number) # Storing it this way saves a few lookups in the snca_compress! algorithm to_parent_pre::Vector{PreNumber} + + _worklist::Vector{Tuple{BBNumber, PreNumber, Bool}} end function DFSTree(n_blocks::Int) @@ -89,14 +91,16 @@ function DFSTree(n_blocks::Int) Vector{BBNumber}(undef, n_blocks), zeros(PostNumber, n_blocks), Vector{BBNumber}(undef, n_blocks), - zeros(PreNumber, n_blocks)) + zeros(PreNumber, n_blocks), + Vector{Tuple{BBNumber, PreNumber, Bool}}()) end copy(D::DFSTree) = DFSTree(copy(D.to_pre), copy(D.from_pre), copy(D.to_post), copy(D.from_post), - copy(D.to_parent_pre)) + copy(D.to_parent_pre), + copy(D._worklist)) function copy!(dst::DFSTree, src::DFSTree) copy!(dst.to_pre, src.to_pre) @@ -106,17 +110,26 @@ function copy!(dst::DFSTree, src::DFSTree) copy!(dst.to_parent_pre, src.to_parent_pre) return dst end +function resize!(D::DFSTree, n::Integer) + resize!(D.to_pre, n) + resize!(D.from_pre, n) + resize!(D.to_post, n) + resize!(D.from_post, n) + resize!(D.to_parent_pre, n) +end length(D::DFSTree) = length(D.from_pre) function DFS!(D::DFSTree, blocks::Vector{BasicBlock}, is_post_dominator::Bool) - copy!(D, DFSTree(length(blocks))) + resize!(D, length(blocks)) + fill!(D.to_pre, 0) + to_visit = D._worklist # always starts empty if is_post_dominator # TODO: We're using -1 as the virtual exit node here. Would it make # sense to actually have a real BB for the exit always? - to_visit = Tuple{BBNumber, PreNumber, Bool}[(-1, 0, false)] + push!(to_visit, (-1, 0, false)) else - to_visit = Tuple{BBNumber, PreNumber, Bool}[(1, 0, false)] + push!(to_visit, (1, 0, false)) end pre_num = is_post_dominator ? 0 : 1 post_num = 1 @@ -189,7 +202,7 @@ DFS(blocks::Vector{BasicBlock}, is_post_dominator::Bool=false) = DFS!(DFSTree(0) """ Keeps the per-BB state of the Semi NCA algorithm. In the original formulation, there are three separate length `n` arrays, `label`, `semi` and `ancestor`. -Instead, for efficiency, we use one array in a array-of-structs style setup. +Instead, for efficiency, we use one array in an array-of-structs style setup. """ struct SNCAData semi::PreNumber @@ -332,10 +345,7 @@ function SNCA!(domtree::GenericDomTree{IsPostDom}, blocks::Vector{BasicBlock}, m ancestors = copy(D.to_parent_pre) relevant_blocks = IsPostDom ? (1:max_pre) : (2:max_pre) for w::PreNumber in reverse(relevant_blocks) - # LLVM initializes this to the parent, the paper initializes this to - # `w`, but it doesn't really matter (the parent is a predecessor, so at - # worst we'll discover it below). Save a memory reference here. - semi_w = typemax(PreNumber) + semi_w = ancestors[w] last_linked = PreNumber(w + 1) for v ∈ dom_edges(domtree, blocks, D.from_pre[w]) # For the purpose of the domtree, ignore virtual predecessors into diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 0a5b5c6580595..d6853d83b61bc 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -12,14 +12,16 @@ struct InliningTodo mi::MethodInstance # The IR of the inlinee ir::IRCode + # The DebugInfo table for the inlinee + di::DebugInfo # If the function being inlined is a single basic block we can use a # simpler inlining algorithm. This flag determines whether that's allowed linear_inline_eligible::Bool # Effects of the call statement effects::Effects end -function InliningTodo(mi::MethodInstance, ir::IRCode, effects::Effects) - return InliningTodo(mi, ir, linear_inline_eligible(ir), effects) +function InliningTodo(mi::MethodInstance, (ir, di)::Tuple{IRCode, DebugInfo}, effects::Effects) + return InliningTodo(mi, ir, di, linear_inline_eligible(ir), effects) end struct ConstantCase @@ -298,75 +300,35 @@ function finish_cfg_inline!(state::CFGInliningState) end end -# duplicated from IRShow -function normalize_method_name(m) - if m isa Method - return m.name - elseif m isa MethodInstance - return (m.def::Method).name - elseif m isa Symbol - return m - else - return Symbol("") - end -end -@noinline method_name(m::LineInfoNode) = normalize_method_name(m.method) - -inline_node_is_duplicate(topline::LineInfoNode, line::LineInfoNode) = - topline.module === line.module && - method_name(topline) === method_name(line) && - topline.file === line.file && - topline.line === line.line - -function ir_inline_linetable!(linetable::Vector{LineInfoNode}, inlinee_ir::IRCode, - inlinee::MethodInstance, inlined_at::Int32) - inlinee_def = inlinee.def::Method - coverage = coverage_enabled(inlinee_def.module) - linetable_offset::Int32 = length(linetable) - # Append the linetable of the inlined function to our line table - topline::Int32 = linetable_offset + Int32(1) - coverage_by_path = JLOptions().code_coverage == 3 - push!(linetable, LineInfoNode(inlinee_def.module, inlinee_def.name, inlinee_def.file, inlinee_def.line, inlined_at)) - oldlinetable = inlinee_ir.linetable - extra_coverage_line = zero(Int32) - for oldline in eachindex(oldlinetable) - entry = oldlinetable[oldline] - if !coverage && coverage_by_path && is_file_tracked(entry.file) - # include topline coverage entry if in path-specific coverage mode, and any file falls under path - coverage = true - end - newentry = LineInfoNode(entry.module, entry.method, entry.file, entry.line, - (entry.inlined_at > 0 ? entry.inlined_at + linetable_offset + (oldline == 1) : inlined_at)) - if oldline == 1 - # check for a duplicate on the first iteration (likely true) - if inline_node_is_duplicate(linetable[topline], newentry) - continue - else - linetable_offset += 1 - end +# TODO append `inlinee_debuginfo` to inner linetable when `inlined_at[2] ≠ 0` +function ir_inline_linetable!(debuginfo::DebugInfoStream, inlinee_debuginfo::DebugInfo, inlined_at::NTuple{3,Int32}) + # Append the linetable of the inlined function to our edges table + linetable_offset = 1 + while true + if linetable_offset > length(debuginfo.edges) + push!(debuginfo.edges, inlinee_debuginfo) + break + elseif debuginfo.edges[linetable_offset] === inlinee_debuginfo + break end - push!(linetable, newentry) - end - if coverage && inlinee_ir.stmts[1][:line] + linetable_offset != topline - extra_coverage_line = topline + linetable_offset += 1 end - return linetable_offset, extra_coverage_line + return (inlined_at[1], Int32(linetable_offset), Int32(0)) end function ir_prepare_inlining!(insert_node!::Inserter, inline_target::Union{IRCode, IncrementalCompact}, - ir::IRCode, mi::MethodInstance, inlined_at::Int32, argexprs::Vector{Any}) + ir::IRCode, di::DebugInfo, mi::MethodInstance, inlined_at::NTuple{3,Int32}, argexprs::Vector{Any}) def = mi.def::Method - linetable = inline_target isa IRCode ? inline_target.linetable : inline_target.ir.linetable - topline::Int32 = length(linetable) + Int32(1) - linetable_offset, extra_coverage_line = ir_inline_linetable!(linetable, ir, mi, inlined_at) - if extra_coverage_line != 0 - insert_node!(NewInstruction(Expr(:code_coverage_effect), Nothing, extra_coverage_line)) + debuginfo = inline_target isa IRCode ? inline_target.debuginfo : inline_target.ir.debuginfo + topline = new_inlined_at = ir_inline_linetable!(debuginfo, di, inlined_at) + if should_insert_coverage(def.module, di) + insert_node!(NewInstruction(Expr(:code_coverage_effect), Nothing, topline)) end spvals_ssa = nothing if !validate_sparams(mi.sparam_vals) # N.B. This works on the caller-side argexprs, (i.e. before the va fixup below) spvals_ssa = insert_node!( - effect_free_and_nothrow(NewInstruction(Expr(:call, Core._compute_sparams, def, argexprs...), SimpleVector, topline))) + removable_if_unused(NewInstruction(Expr(:call, Core._compute_sparams, def, argexprs...), SimpleVector, topline))) end if def.isva nargs_def = Int(def.nargs::Int32) @@ -380,31 +342,27 @@ function ir_prepare_inlining!(insert_node!::Inserter, inline_target::Union{IRCod NewInstruction(Expr(:call, GlobalRef(Core, :getfield), argexprs[1], QuoteNode(:captures)), ir.argtypes[1], topline)) end - return SSASubstitute(mi, argexprs, spvals_ssa, linetable_offset) + return SSASubstitute(mi, argexprs, spvals_ssa, new_inlined_at) end -function adjust_boundscheck!(inline_compact, idx′, stmt, boundscheck) +function adjust_boundscheck!(inline_compact::IncrementalCompact, idx′::Int, stmt::Expr, boundscheck::Symbol) if boundscheck === :off - length(stmt.args) == 0 && push!(stmt.args, false) + isempty(stmt.args) && push!(stmt.args, false) elseif boundscheck !== :propagate - length(stmt.args) == 0 && push!(stmt.args, true) + isempty(stmt.args) && push!(stmt.args, true) end + return nothing end function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector{Any}, item::InliningTodo, boundscheck::Symbol, todo_bbs::Vector{Tuple{Int, Int}}) # Ok, do the inlining here inlined_at = compact.result[idx][:line] + ssa_substitute = ir_prepare_inlining!(InsertHere(compact), compact, item.ir, item.di, item.mi, inlined_at, argexprs) + boundscheck = has_flag(compact.result[idx], IR_FLAG_INBOUNDS) ? :off : boundscheck - ssa_substitute = ir_prepare_inlining!(InsertHere(compact), compact, item.ir, item.mi, inlined_at, argexprs) - - if boundscheck === :default || boundscheck === :propagate - if (compact.result[idx][:flag] & IR_FLAG_INBOUNDS) != 0 - boundscheck = :off - end - end # If the iterator already moved on to the next basic block, - # temporarily re-open in again. + # temporarily re-open it again. local return_value # Special case inlining that maintains the current basic block if there's only one BB in the target new_new_offset = length(compact.new_new_nodes) @@ -412,14 +370,16 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector if item.linear_inline_eligible #compact[idx] = nothing inline_compact = IncrementalCompact(compact, item.ir, compact.result_idx) - for ((_, idx′), stmt′) in inline_compact + @assert isempty(inline_compact.perm) && isempty(inline_compact.pending_perm) "linetable not in canonical form (missing compact call)" + for ((lineidx, idx′), stmt′) in inline_compact # This dance is done to maintain accurate usage counts in the # face of rename_arguments! mutating in place - should figure out # something better eventually. inline_compact[idx′] = nothing + # alter the line number information for InsertBefore to point to the current instruction in the new linetable + inline_compact[SSAValue(idx′)][:line] = (ssa_substitute.inlined_at[1], ssa_substitute.inlined_at[2], Int32(lineidx)) insert_node! = InsertBefore(inline_compact, SSAValue(idx′)) - stmt′ = ssa_substitute!(insert_node!, inline_compact[SSAValue(idx′)], stmt′, - ssa_substitute, boundscheck) + stmt′ = ssa_substitute_op!(insert_node!, inline_compact[SSAValue(idx′)], stmt′, ssa_substitute) if isa(stmt′, ReturnNode) val = stmt′.val return_value = SSAValue(idx′) @@ -427,7 +387,7 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector inline_compact.result[idx′][:type] = argextype(val, isa(val, Argument) || isa(val, Expr) ? compact : inline_compact) # Everything legal in value position is guaranteed to be effect free in stmt position - inline_compact.result[idx′][:flag] = IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW + inline_compact.result[idx′][:flag] = IR_FLAGS_REMOVABLE break elseif isexpr(stmt′, :boundscheck) adjust_boundscheck!(inline_compact, idx′, stmt′, boundscheck) @@ -446,11 +406,12 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector pn = PhiNode() #compact[idx] = nothing inline_compact = IncrementalCompact(compact, item.ir, compact.result_idx) - for ((_, idx′), stmt′) in inline_compact + @assert isempty(inline_compact.perm) && isempty(inline_compact.pending_perm) "linetable not in canonical form (missing compact call)" + for ((lineidx, idx′), stmt′) in inline_compact inline_compact[idx′] = nothing + inline_compact[SSAValue(idx′)][:line] = (ssa_substitute.inlined_at[1], ssa_substitute.inlined_at[2], Int32(lineidx)) insert_node! = InsertBefore(inline_compact, SSAValue(idx′)) - stmt′ = ssa_substitute!(insert_node!, inline_compact[SSAValue(idx′)], stmt′, - ssa_substitute, boundscheck) + stmt′ = ssa_substitute_op!(insert_node!, inline_compact[SSAValue(idx′)], stmt′, ssa_substitute) if isa(stmt′, ReturnNode) if isdefined(stmt′, :val) val = stmt′.val @@ -461,8 +422,8 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector end elseif isa(stmt′, GotoNode) stmt′ = GotoNode(stmt′.label + bb_offset) - elseif isa(stmt′, Expr) && stmt′.head === :enter - stmt′ = Expr(:enter, stmt′.args[1]::Int + bb_offset) + elseif isa(stmt′, EnterNode) + stmt′ = EnterNode(stmt′, stmt′.catch_dest == 0 ? 0 : stmt′.catch_dest + bb_offset) elseif isa(stmt′, GotoIfNot) stmt′ = GotoIfNot(stmt′.cond, stmt′.dest + bb_offset) elseif isa(stmt′, PhiNode) @@ -486,7 +447,7 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector end function fix_va_argexprs!(insert_node!::Inserter, inline_target::Union{IRCode, IncrementalCompact}, - argexprs::Vector{Any}, nargs_def::Int, line_idx::Int32) + argexprs::Vector{Any}, nargs_def::Int, line_idx::NTuple{3,Int32}) newargexprs = argexprs[1:(nargs_def-1)] tuple_call = Expr(:call, TOP_TUPLE) tuple_typs = Any[] @@ -579,11 +540,11 @@ function ir_inline_unionsplit!(compact::IncrementalCompact, idx::Int, argexprs:: @assert nparams == fieldcount(mtype) if !(i == ncases && fully_covered) for i = 1:nparams - a, m = fieldtype(atype, i), fieldtype(mtype, i) + aft, mft = fieldtype(atype, i), fieldtype(mtype, i) # If this is always true, we don't need to check for it - a <: m && continue + aft <: mft && continue # Generate isa check - isa_expr = Expr(:call, isa, argexprs[i], m) + isa_expr = Expr(:call, isa, argexprs[i], mft) ssa = insert_node_here!(compact, NewInstruction(isa_expr, Bool, line)) if cond === true cond = ssa @@ -602,10 +563,10 @@ function ir_inline_unionsplit!(compact::IncrementalCompact, idx::Int, argexprs:: for i = 1:nparams argex = argexprs[i] (isa(argex, SSAValue) || isa(argex, Argument)) || continue - a, m = fieldtype(atype, i), fieldtype(mtype, i) - if !(a <: m) + aft, mft = fieldtype(atype, i), fieldtype(mtype, i) + if !(aft <: mft) argexprs′[i] = insert_node_here!(compact, - NewInstruction(PiNode(argex, m), m, line)) + NewInstruction(PiNode(argex, mft), mft, line)) end end end @@ -659,10 +620,7 @@ function batch_inline!(ir::IRCode, todo::Vector{Pair{Int,Any}}, propagate_inboun end finish_cfg_inline!(state) - boundscheck = :default - if boundscheck === :default && propagate_inbounds - boundscheck = :propagate - end + boundscheck = propagate_inbounds ? :propagate : :default let compact = IncrementalCompact(ir, CFGTransformState!(state.new_cfg_blocks, false)) # This needs to be a minimum and is more of a size hint @@ -697,7 +655,7 @@ function batch_inline!(ir::IRCode, todo::Vector{Pair{Int,Any}}, propagate_inboun for aidx in 1:length(argexprs) aexpr = argexprs[aidx] if isa(aexpr, Expr) || isa(aexpr, GlobalRef) - ninst = effect_free_and_nothrow(NewInstruction(aexpr, argextype(aexpr, compact), compact.result[idx][:line])) + ninst = removable_if_unused(NewInstruction(aexpr, argextype(aexpr, compact), compact.result[idx][:line])) argexprs[aidx] = insert_node_here!(compact, ninst) end end @@ -715,8 +673,8 @@ function batch_inline!(ir::IRCode, todo::Vector{Pair{Int,Any}}, propagate_inboun end elseif isa(stmt, GotoNode) compact[idx] = GotoNode(state.bb_rename[stmt.label]) - elseif isa(stmt, Expr) && stmt.head === :enter - compact[idx] = Expr(:enter, state.bb_rename[stmt.args[1]::Int]) + elseif isa(stmt, EnterNode) + compact[idx] = EnterNode(stmt, stmt.catch_dest == 0 ? 0 : state.bb_rename[stmt.catch_dest]) elseif isa(stmt, GotoIfNot) compact[idx] = GotoIfNot(stmt.cond, state.bb_rename[stmt.dest]) elseif isa(stmt, PhiNode) @@ -758,7 +716,7 @@ function rewrite_apply_exprargs!(todo::Vector{Pair{Int,Any}}, ti = ti.parameters[2]::DataType # checked by `is_valid_type_for_apply_rewrite` end for p in ti.parameters - if isa(p, DataType) && isdefined(p, :instance) + if issingletontype(p) # replace singleton types with their equivalent Const object p = Const(p.instance) elseif isconstType(p) @@ -832,7 +790,7 @@ function compileable_specialization(mi::MethodInstance, effects::Effects, end end add_inlining_backedge!(et, mi) # to the dispatch lookup - push!(et.edges, method.sig, mi_invoke) # add_inlining_backedge to the invoke call + mi_invoke !== mi && push!(et.edges, method.sig, mi_invoke) # add_inlining_backedge to the invoke call, if that is different return InvokeCase(mi_invoke, effects, info) end @@ -842,10 +800,10 @@ function compileable_specialization(match::MethodMatch, effects::Effects, return compileable_specialization(mi, effects, et, info; compilesig_invokes) end -struct CachedResult - src::Any +struct InferredResult + src::Any # CodeInfo or IRCode effects::Effects - CachedResult(@nospecialize(src), effects::Effects) = new(src, effects) + InferredResult(@nospecialize(src), effects::Effects) = new(src, effects) end @inline function get_cached_result(state::InliningState, mi::MethodInstance) code = get(code_cache(state), mi, nothing) @@ -853,39 +811,51 @@ end if use_const_api(code) # in this case function can be inlined to a constant return ConstantCase(quoted(code.rettype_const)) - else - src = @atomic :monotonic code.inferred end - effects = decode_effects(code.ipo_purity_bits) - return CachedResult(src, effects) + return code end - return CachedResult(nothing, Effects()) + return nothing +end +@inline function get_local_result(inf_result::InferenceResult) + effects = inf_result.ipo_effects + if is_foldable_nothrow(effects) + res = inf_result.result + if isa(res, Const) && is_inlineable_constant(res.val) + # use constant calling convention + return ConstantCase(quoted(res.val)) + end + end + return InferredResult(inf_result.src, effects) end # the general resolver for usual and const-prop'ed calls -function resolve_todo(mi::MethodInstance, result::Union{MethodMatch,InferenceResult}, - argtypes::Vector{Any}, @nospecialize(info::CallInfo), flag::UInt32, - state::InliningState; invokesig::Union{Nothing,Vector{Any}}=nothing) +function resolve_todo(mi::MethodInstance, result::Union{Nothing,InferenceResult,VolatileInferenceResult}, + @nospecialize(info::CallInfo), flag::UInt32, state::InliningState; + invokesig::Union{Nothing,Vector{Any}}=nothing) et = InliningEdgeTracker(state, invokesig) + preserve_local_sources = true if isa(result, InferenceResult) - src = result.src - effects = result.ipo_effects - if is_foldable_nothrow(effects) - res = result.result - if isa(res, Const) && is_inlineable_constant(res.val) - # use constant calling convention - add_inlining_backedge!(et, mi) - return ConstantCase(quoted(res.val)) - end - end + inferred_result = get_local_result(result) + elseif isa(result, VolatileInferenceResult) + inferred_result = get_local_result(result.inf_result) + # volatile inference result can be inlined destructively + preserve_local_sources = !result.inf_result.is_src_volatile | OptimizationParams(state.interp).preserve_local_sources else - cached_result = get_cached_result(state, mi) - if cached_result isa ConstantCase - add_inlining_backedge!(et, mi) - return cached_result - end - (; src, effects) = cached_result + inferred_result = get_cached_result(state, mi) + end + if inferred_result isa ConstantCase + add_inlining_backedge!(et, mi) + return inferred_result + end + if inferred_result isa InferredResult + (; src, effects) = inferred_result + elseif inferred_result isa CodeInstance + src = @atomic :monotonic inferred_result.inferred + effects = decode_effects(inferred_result.ipo_purity_bits) + else + src = nothing + effects = Effects() end # the duplicated check might have been done already within `analyze_method!`, but still @@ -895,17 +865,19 @@ function resolve_todo(mi::MethodInstance, result::Union{MethodMatch,InferenceRes compilesig_invokes=OptimizationParams(state.interp).compilesig_invokes) end - src = inlining_policy(state.interp, src, info, flag, mi, argtypes) - src === nothing && return compileable_specialization(mi, effects, et, info; - compilesig_invokes=OptimizationParams(state.interp).compilesig_invokes) + src_inlining_policy(state.interp, src, info, flag) || + return compileable_specialization(mi, effects, et, info; + compilesig_invokes=OptimizationParams(state.interp).compilesig_invokes) add_inlining_backedge!(et, mi) - return InliningTodo(mi, retrieve_ir_for_inlining(mi, src), effects) + ir = inferred_result isa CodeInstance ? retrieve_ir_for_inlining(inferred_result, src) : + retrieve_ir_for_inlining(mi, src, preserve_local_sources) + return InliningTodo(mi, ir, effects) end # the special resolver for :invoke-d call -function resolve_todo(mi::MethodInstance, argtypes::Vector{Any}, - @nospecialize(info::CallInfo), flag::UInt32, state::InliningState) +function resolve_todo(mi::MethodInstance, @nospecialize(info::CallInfo), flag::UInt32, + state::InliningState) if !OptimizationParams(state.interp).inlining || is_stmt_noinline(flag) return nothing end @@ -917,14 +889,22 @@ function resolve_todo(mi::MethodInstance, argtypes::Vector{Any}, add_inlining_backedge!(et, mi) return cached_result end - (; src, effects) = cached_result - - src = inlining_policy(state.interp, src, info, flag, mi, argtypes) - - src === nothing && return nothing + if cached_result isa InferredResult + (; src, effects) = cached_result + elseif cached_result isa CodeInstance + src = @atomic :monotonic cached_result.inferred + effects = decode_effects(cached_result.ipo_purity_bits) + else + src = nothing + effects = Effects() + end + preserve_local_sources = true + src_inlining_policy(state.interp, src, info, flag) || return nothing + ir = cached_result isa CodeInstance ? retrieve_ir_for_inlining(cached_result, src) : + retrieve_ir_for_inlining(mi, src, preserve_local_sources) add_inlining_backedge!(et, mi) - return InliningTodo(mi, retrieve_ir_for_inlining(mi, src), effects) + return InliningTodo(mi, ir, effects) end function validate_sparams(sparams::SimpleVector) @@ -944,7 +924,8 @@ end function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, @nospecialize(info::CallInfo), flag::UInt32, state::InliningState; - allow_typevars::Bool, invokesig::Union{Nothing,Vector{Any}}=nothing) + allow_typevars::Bool, invokesig::Union{Nothing,Vector{Any}}=nothing, + volatile_inf_result::Union{Nothing,VolatileInferenceResult}=nothing) method = match.method spec_types = match.spec_types @@ -961,7 +942,8 @@ function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, if !match.fully_covers # type-intersection was not able to give us a simple list of types, so # ir_inline_unionsplit won't be able to deal with inlining this - if !(spec_types isa DataType && length(spec_types.parameters) == length(argtypes) && !isvarargtype(spec_types.parameters[end])) + if !(spec_types isa DataType && length(spec_types.parameters) == npassedargs && + !isvarargtype(spec_types.parameters[end])) return nothing end end @@ -973,31 +955,25 @@ function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, # Get the specialization for this method signature # (later we will decide what to do with it) mi = specialize_method(match) - return resolve_todo(mi, match, argtypes, info, flag, state; invokesig) + return resolve_todo(mi, volatile_inf_result, info, flag, state; invokesig) end -function retrieve_ir_for_inlining(mi::MethodInstance, src::String) - src = ccall(:jl_uncompress_ir, Any, (Any, Ptr{Cvoid}, Any), mi.def, C_NULL, src)::CodeInfo - return inflate_ir!(src, mi) +function retrieve_ir_for_inlining(cached_result::CodeInstance, src::MaybeCompressed) + src = _uncompressed_ir(cached_result, src)::CodeInfo + return inflate_ir!(src, cached_result.def), src.debuginfo end -retrieve_ir_for_inlining(mi::MethodInstance, src::CodeInfo) = inflate_ir(src, mi) -retrieve_ir_for_inlining(mi::MethodInstance, ir::IRCode) = copy(ir) - -function flags_for_effects(effects::Effects) - flags::UInt32 = 0 - if is_consistent(effects) - flags |= IR_FLAG_CONSISTENT - end - if is_effect_free(effects) - flags |= IR_FLAG_EFFECT_FREE - end - if is_nothrow(effects) - flags |= IR_FLAG_NOTHROW +function retrieve_ir_for_inlining(mi::MethodInstance, src::CodeInfo, preserve_local_sources::Bool) + if preserve_local_sources + src = copy(src) end - if is_noub(effects, false) - flags |= IR_FLAG_NOUB + return inflate_ir!(src, mi), src.debuginfo +end +function retrieve_ir_for_inlining(mi::MethodInstance, ir::IRCode, preserve_local_sources::Bool) + if preserve_local_sources + ir = copy(ir) end - return flags + ir.debuginfo.def = mi + return ir, DebugInfo(ir.debuginfo, length(ir.stmts)) end function handle_single_case!(todo::Vector{Pair{Int,Any}}, @@ -1008,9 +984,13 @@ function handle_single_case!(todo::Vector{Pair{Int,Any}}, elseif isa(case, InvokeCase) is_foldable_nothrow(case.effects) && inline_const_if_inlineable!(ir[SSAValue(idx)]) && return nothing isinvoke && rewrite_invoke_exprargs!(stmt) - stmt.head = :invoke - pushfirst!(stmt.args, case.invoke) - ir[SSAValue(idx)][:flag] |= flags_for_effects(case.effects) + if stmt.head === :invoke + stmt.args[1] = case.invoke + else + stmt.head = :invoke + pushfirst!(stmt.args, case.invoke) + end + add_flag!(ir[SSAValue(idx)], flags_for_effects(case.effects)) elseif case === nothing # Do, well, nothing else @@ -1182,18 +1162,21 @@ function handle_invoke_call!(todo::Vector{Pair{Int,Any}}, invokesig = sig.argtypes if isa(result, ConcreteResult) item = concrete_result_item(result, info, state; invokesig) + elseif isa(result, SemiConcreteResult) + item = semiconcrete_result_item(result, info, flag, state) else argtypes = invoke_rewrite(sig.argtypes) if isa(result, ConstPropResult) mi = result.result.linfo validate_sparams(mi.sparam_vals) || return nothing if Union{} !== argtypes_to_type(argtypes) <: mi.def.sig - item = resolve_todo(mi, result.result, argtypes, info, flag, state; invokesig) + item = resolve_todo(mi, result.result, info, flag, state; invokesig) handle_single_case!(todo, ir, idx, stmt, item, true) return nothing end end - item = analyze_method!(match, argtypes, info, flag, state; allow_typevars=false, invokesig) + volatile_inf_result = result isa VolatileInferenceResult ? result : nothing + item = analyze_method!(match, argtypes, info, flag, state; allow_typevars=false, invokesig, volatile_inf_result) end handle_single_case!(todo, ir, idx, stmt, item, true) return nothing @@ -1227,28 +1210,12 @@ end # As a matter of convenience, this pass also computes effect-freenes. # For primitives, we do that right here. For proper calls, we will # discover this when we consult the caches. -function check_effect_free!(ir::IRCode, idx::Int, @nospecialize(stmt), @nospecialize(rt), state::InliningState) - return check_effect_free!(ir, idx, stmt, rt, optimizer_lattice(state.interp)) -end -function check_effect_free!(ir::IRCode, idx::Int, @nospecialize(stmt), @nospecialize(rt), 𝕃ₒ::AbstractLattice) - (consistent, effect_free_and_nothrow, nothrow) = stmt_effect_flags(𝕃ₒ, stmt, rt, ir) - if consistent - ir.stmts[idx][:flag] |= IR_FLAG_CONSISTENT - end - if effect_free_and_nothrow - ir.stmts[idx][:flag] |= IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW - elseif nothrow - ir.stmts[idx][:flag] |= IR_FLAG_NOTHROW - end - if !isexpr(stmt, :call) && !isexpr(stmt, :invoke) - # There is a bit of a subtle point here, which is that some non-call - # statements (e.g. PiNode) can be UB:, however, we consider it - # illegal to introduce such statements that actually cause UB (for any - # input). Ideally that'd be handled at insertion time (TODO), but for - # the time being just do that here. - ir.stmts[idx][:flag] |= IR_FLAG_NOUB - end - return effect_free_and_nothrow +add_inst_flag!(inst::Instruction, ir::IRCode, state::InliningState) = + add_inst_flag!(inst, ir, optimizer_lattice(state.interp)) +function add_inst_flag!(inst::Instruction, ir::IRCode, 𝕃ₒ::AbstractLattice) + flags = recompute_effects_flags(𝕃ₒ, inst[:stmt], inst[:type], ir) + add_flag!(inst, flags) + return !iszero(flags & IR_FLAGS_REMOVABLE) end # Handles all analysis and inlining of intrinsics and builtins. In particular, @@ -1257,11 +1224,11 @@ end function process_simple!(todo::Vector{Pair{Int,Any}}, ir::IRCode, idx::Int, state::InliningState) inst = ir[SSAValue(idx)] stmt = inst[:stmt] - rt = inst[:type] if !(stmt isa Expr) - check_effect_free!(ir, idx, stmt, rt, state) + add_inst_flag!(inst, ir, state) return nothing end + rt = inst[:type] head = stmt.head if head !== :call if head === :splatnew @@ -1273,7 +1240,7 @@ function process_simple!(todo::Vector{Pair{Int,Any}}, ir::IRCode, idx::Int, stat sig === nothing && return nothing return stmt, sig end - check_effect_free!(ir, idx, stmt, rt, state) + add_inst_flag!(inst, ir, state) return nothing end @@ -1291,7 +1258,7 @@ function process_simple!(todo::Vector{Pair{Int,Any}}, ir::IRCode, idx::Int, stat return nothing end - if check_effect_free!(ir, idx, stmt, rt, state) + if add_inst_flag!(inst, ir, state) if sig.f === typeassert || ⊑(optimizer_lattice(state.interp), sig.ft, typeof(typeassert)) # typeassert is a no-op if effect free inst[:stmt] = stmt.args[2] @@ -1299,17 +1266,25 @@ function process_simple!(todo::Vector{Pair{Int,Any}}, ir::IRCode, idx::Int, stat end end - if (sig.f !== Core.invoke && sig.f !== Core.finalizer && sig.f !== modifyfield!) && - is_builtin(optimizer_lattice(state.interp), sig) - # No inlining for builtins (other invoke/apply/typeassert/finalizer) - return nothing + if is_builtin(optimizer_lattice(state.interp), sig) + let f = sig.f + if (f !== Core.invoke && + f !== Core.finalizer && + f !== modifyfield! && + f !== Core.modifyglobal! && + f !== Core.memoryrefmodify! && + f !== atomic_pointermodify) + # No inlining defined for most builtins (just invoke/apply/typeassert/finalizer), so attempt an early exit for them + return nothing + end + end end # Special case inliners for regular functions lateres = late_inline_special_case!(ir, idx, stmt, rt, sig, state) if isa(lateres, SomeCase) inst[:stmt] = lateres.val - check_effect_free!(ir, idx, lateres.val, rt, state) + add_inst_flag!(inst, ir, state) return nothing end @@ -1319,21 +1294,16 @@ end function handle_any_const_result!(cases::Vector{InliningCase}, @nospecialize(result), match::MethodMatch, argtypes::Vector{Any}, @nospecialize(info::CallInfo), flag::UInt32, state::InliningState; - allow_abstract::Bool, allow_typevars::Bool) + allow_typevars::Bool) if isa(result, ConcreteResult) - return handle_concrete_result!(cases, result, info, state) - end - if isa(result, SemiConcreteResult) - result = inlining_policy(state.interp, result, info, flag, result.mi, argtypes) - if isa(result, SemiConcreteResult) - return handle_semi_concrete_result!(cases, result, info, flag, state; allow_abstract) - end - end - if isa(result, ConstPropResult) - return handle_const_prop_result!(cases, result, argtypes, info, flag, state; allow_abstract, allow_typevars) + return handle_concrete_result!(cases, result, match, info, state) + elseif isa(result, SemiConcreteResult) + return handle_semi_concrete_result!(cases, result, match, info, flag, state) + elseif isa(result, ConstPropResult) + return handle_const_prop_result!(cases, result, match, info, flag, state; allow_typevars) else - @assert result === nothing - return handle_match!(cases, match, argtypes, info, flag, state; allow_abstract, allow_typevars) + @assert result === nothing || result isa VolatileInferenceResult + return handle_match!(cases, match, argtypes, info, flag, state; allow_typevars, volatile_inf_result = result) end end @@ -1362,13 +1332,10 @@ function compute_inlining_cases(@nospecialize(info::CallInfo), flag::UInt32, sig nunion === nothing && return nothing cases = InliningCase[] argtypes = sig.argtypes - local handled_all_cases::Bool = true + local handled_all_cases = local fully_covered = true local revisit_idx = nothing - local only_method = nothing - local meth::MethodLookupResult local all_result_count = 0 - local joint_effects::Effects = EFFECTS_TOTAL - local fully_covered::Bool = true + local joint_effects = EFFECTS_TOTAL for i = 1:nunion meth = getsplit(info, i) if meth.ambig @@ -1379,71 +1346,47 @@ function compute_inlining_cases(@nospecialize(info::CallInfo), flag::UInt32, sig # No applicable methods; try next union split handled_all_cases = false continue - else - if length(meth) == 1 && only_method !== false - if only_method === nothing - only_method = meth[1].method - elseif only_method !== meth[1].method - only_method = false - end - else - only_method = false - end end - local split_fully_covered::Bool = false + local split_fully_covered = false for (j, match) in enumerate(meth) all_result_count += 1 result = getresult(info, all_result_count) joint_effects = merge_effects(joint_effects, info_effects(result, match, state)) split_fully_covered |= match.fully_covers if !validate_sparams(match.sparams) - if !match.fully_covers - handled_all_cases = false - continue - end - if revisit_idx === nothing - revisit_idx = (i, j, all_result_count) + if match.fully_covers + if revisit_idx === nothing + revisit_idx = (i, j, all_result_count) + else + handled_all_cases = false + revisit_idx = nothing + end else handled_all_cases = false - revisit_idx = nothing end + elseif !(match.spec_types <: match.method.sig) # the requirement for correct union-split + handled_all_cases = false else handled_all_cases &= handle_any_const_result!(cases, - result, match, argtypes, info, flag, state; allow_abstract=true, allow_typevars=false) + result, match, argtypes, info, flag, state; allow_typevars=false) end end fully_covered &= split_fully_covered end - (handled_all_cases && fully_covered) || (joint_effects = Effects(joint_effects; nothrow=false)) - - if handled_all_cases && revisit_idx !== nothing - # we handled everything except one match with unmatched sparams, - # so try to handle it by bypassing validate_sparams - (i, j, k) = revisit_idx - match = getsplit(info, i)[j] - result = getresult(info, k) - handled_all_cases &= handle_any_const_result!(cases, - result, match, argtypes, info, flag, state; allow_abstract=true, allow_typevars=true) - elseif length(cases) == 0 && only_method isa Method - # if the signature is fully covered and there is only one applicable method, - # we can try to inline it even in the presence of unmatched sparams - # -- But don't try it if we already tried to handle the match in the revisit_idx - # case, because that'll (necessarily) be the same method. - if nsplit(info)::Int > 1 - atype = argtypes_to_type(argtypes) - (metharg, methsp) = ccall(:jl_type_intersection_with_env, Any, (Any, Any), atype, only_method.sig)::SimpleVector - match = MethodMatch(metharg, methsp::SimpleVector, only_method, true) - result = nothing - else - @assert length(meth) == 1 - match = meth[1] - result = getresult(info, 1) + (handled_all_cases & fully_covered) || (joint_effects = Effects(joint_effects; nothrow=false)) + + if handled_all_cases + if revisit_idx !== nothing + # we handled everything except one match with unmatched sparams, + # so try to handle it by bypassing validate_sparams + (i, j, k) = revisit_idx + match = getsplit(info, i)[j] + result = getresult(info, k) + handled_all_cases &= handle_any_const_result!(cases, + result, match, argtypes, info, flag, state; allow_typevars=true) end - handle_any_const_result!(cases, - result, match, argtypes, info, flag, state; allow_abstract=true, allow_typevars=true) - fully_covered = handled_all_cases = match.fully_covers - elseif !handled_all_cases + elseif !isempty(cases) # if we've not seen all candidates, union split is valid only for dispatch tuples filter!(case::InliningCase->isdispatchtuple(case.sig), cases) end @@ -1457,37 +1400,34 @@ function handle_call!(todo::Vector{Pair{Int,Any}}, cases = compute_inlining_cases(info, flag, sig, state) cases === nothing && return nothing cases, all_covered, joint_effects = cases - handle_cases!(todo, ir, idx, stmt, argtypes_to_type(sig.argtypes), cases, - all_covered, joint_effects) + atype = argtypes_to_type(sig.argtypes) + handle_cases!(todo, ir, idx, stmt, atype, cases, all_covered, joint_effects) end function handle_match!(cases::Vector{InliningCase}, match::MethodMatch, argtypes::Vector{Any}, @nospecialize(info::CallInfo), flag::UInt32, state::InliningState; - allow_abstract::Bool, allow_typevars::Bool) + allow_typevars::Bool, volatile_inf_result::Union{Nothing,VolatileInferenceResult}) spec_types = match.spec_types - allow_abstract || isdispatchtuple(spec_types) || return false # We may see duplicated dispatch signatures here when a signature gets widened # during abstract interpretation: for the purpose of inlining, we can just skip # processing this dispatch candidate (unless unmatched type parameters are present) !allow_typevars && any(case::InliningCase->case.sig === spec_types, cases) && return true - item = analyze_method!(match, argtypes, info, flag, state; allow_typevars) + item = analyze_method!(match, argtypes, info, flag, state; allow_typevars, volatile_inf_result) item === nothing && return false push!(cases, InliningCase(spec_types, item)) return true end -function handle_const_prop_result!(cases::Vector{InliningCase}, - result::ConstPropResult, argtypes::Vector{Any}, @nospecialize(info::CallInfo), - flag::UInt32, state::InliningState; - allow_abstract::Bool, allow_typevars::Bool) +function handle_const_prop_result!(cases::Vector{InliningCase}, result::ConstPropResult, + match::MethodMatch, @nospecialize(info::CallInfo), flag::UInt32, state::InliningState; + allow_typevars::Bool) mi = result.result.linfo - spec_types = mi.specTypes - allow_abstract || isdispatchtuple(spec_types) || return false + spec_types = match.spec_types if !validate_sparams(mi.sparam_vals) (allow_typevars && !may_have_fcalls(mi.def::Method)) || return false end - item = resolve_todo(mi, result.result, argtypes, info, flag, state) + item = resolve_todo(mi, result.result, info, flag, state) item === nothing && return false push!(cases, InliningCase(spec_types, item)) return true @@ -1496,21 +1436,30 @@ end function semiconcrete_result_item(result::SemiConcreteResult, @nospecialize(info::CallInfo), flag::UInt32, state::InliningState) mi = result.mi - if !OptimizationParams(state.interp).inlining || is_stmt_noinline(flag) - et = InliningEdgeTracker(state) + et = InliningEdgeTracker(state) + + if (!OptimizationParams(state.interp).inlining || is_stmt_noinline(flag) || + # For `NativeInterpreter`, `SemiConcreteResult` may be produced for + # a `@noinline`-declared method when it's marked as `@constprop :aggressive`. + # Suppress the inlining here (unless inlining is requested at the callsite). + (is_declared_noinline(mi.def::Method) && !is_stmt_inline(flag))) return compileable_specialization(mi, result.effects, et, info; compilesig_invokes=OptimizationParams(state.interp).compilesig_invokes) - else - return InliningTodo(mi, retrieve_ir_for_inlining(mi, result.ir), result.effects) end + src_inlining_policy(state.interp, result.ir, info, flag) || + return compileable_specialization(mi, result.effects, et, info; + compilesig_invokes=OptimizationParams(state.interp).compilesig_invokes) + + add_inlining_backedge!(et, mi) + preserve_local_sources = OptimizationParams(state.interp).preserve_local_sources + ir = retrieve_ir_for_inlining(mi, result.ir, preserve_local_sources) + return InliningTodo(mi, ir, result.effects) end function handle_semi_concrete_result!(cases::Vector{InliningCase}, result::SemiConcreteResult, - @nospecialize(info::CallInfo), flag::UInt32, state::InliningState; - allow_abstract::Bool) + match::MethodMatch, @nospecialize(info::CallInfo), flag::UInt32, state::InliningState) mi = result.mi - spec_types = mi.specTypes - allow_abstract || isdispatchtuple(spec_types) || return false + spec_types = match.spec_types validate_sparams(mi.sparam_vals) || return false item = semiconcrete_result_item(result, info, flag, state) item === nothing && return false @@ -1518,10 +1467,11 @@ function handle_semi_concrete_result!(cases::Vector{InliningCase}, result::SemiC return true end -function handle_concrete_result!(cases::Vector{InliningCase}, result::ConcreteResult, @nospecialize(info::CallInfo), state::InliningState) +function handle_concrete_result!(cases::Vector{InliningCase}, result::ConcreteResult, + match::MethodMatch, @nospecialize(info::CallInfo), state::InliningState) case = concrete_result_item(result, info, state) case === nothing && return false - push!(cases, InliningCase(result.mi.specTypes, case)) + push!(cases, InliningCase(match.spec_types, case)) return true end @@ -1540,21 +1490,21 @@ function concrete_result_item(result::ConcreteResult, @nospecialize(info::CallIn end function handle_cases!(todo::Vector{Pair{Int,Any}}, ir::IRCode, idx::Int, stmt::Expr, - @nospecialize(atype), cases::Vector{InliningCase}, fully_covered::Bool, + @nospecialize(atype), cases::Vector{InliningCase}, all_covered::Bool, joint_effects::Effects) # If we only have one case and that case is fully covered, we may either # be able to do the inlining now (for constant cases), or push it directly # onto the todo list - if fully_covered && length(cases) == 1 + if all_covered && length(cases) == 1 handle_single_case!(todo, ir, idx, stmt, cases[1].item) elseif length(cases) > 0 isa(atype, DataType) || return nothing for case in cases isa(case.sig, DataType) || return nothing end - push!(todo, idx=>UnionSplit(fully_covered, atype, cases)) + push!(todo, idx=>UnionSplit(all_covered, atype, cases)) else - ir[SSAValue(idx)][:flag] |= flags_for_effects(joint_effects) + add_flag!(ir[SSAValue(idx)], flags_for_effects(joint_effects)) end return nothing end @@ -1566,24 +1516,21 @@ function handle_opaque_closure_call!(todo::Vector{Pair{Int,Any}}, if isa(result, ConstPropResult) mi = result.result.linfo validate_sparams(mi.sparam_vals) || return nothing - item = resolve_todo(mi, result.result, sig.argtypes, info, flag, state) + item = resolve_todo(mi, result.result, info, flag, state) elseif isa(result, ConcreteResult) item = concrete_result_item(result, info, state) + elseif isa(result, SemiConcreteResult) + item = item = semiconcrete_result_item(result, info, flag, state) else - if isa(result, SemiConcreteResult) - result = inlining_policy(state.interp, result, info, flag, result.mi, sig.argtypes) - end - if isa(result, SemiConcreteResult) - item = semiconcrete_result_item(result, info, flag, state) - else - item = analyze_method!(info.match, sig.argtypes, info, flag, state; allow_typevars=false) - end + @assert result === nothing || result isa VolatileInferenceResult + volatile_inf_result = result + item = analyze_method!(info.match, sig.argtypes, info, flag, state; allow_typevars=false, volatile_inf_result) end handle_single_case!(todo, ir, idx, stmt, item) return nothing end -function handle_modifyfield!_call!(ir::IRCode, idx::Int, stmt::Expr, info::ModifyFieldInfo, state::InliningState) +function handle_modifyop!_call!(ir::IRCode, idx::Int, stmt::Expr, info::ModifyOpInfo, state::InliningState) info = info.info info isa MethodResultPure && (info = info.info) info isa ConstCallInfo && (info = info.call) @@ -1601,8 +1548,7 @@ function handle_modifyfield!_call!(ir::IRCode, idx::Int, stmt::Expr, info::Modif end function handle_finalizer_call!(ir::IRCode, idx::Int, stmt::Expr, info::FinalizerInfo, - state::InliningState) - + state::InliningState) # Finalizers don't return values, so if their execution is not observable, # we can just not register them if is_removable_if_unused(info.effects) @@ -1643,13 +1589,11 @@ function handle_finalizer_call!(ir::IRCode, idx::Int, stmt::Expr, info::Finalize return nothing end -function handle_invoke_expr!(todo::Vector{Pair{Int,Any}}, +function handle_invoke_expr!(todo::Vector{Pair{Int,Any}}, ir::IRCode, idx::Int, stmt::Expr, @nospecialize(info::CallInfo), flag::UInt32, sig::Signature, state::InliningState) mi = stmt.args[1]::MethodInstance - case = resolve_todo(mi, sig.argtypes, info, flag, state) - if case !== nothing - push!(todo, idx=>(case::InliningTodo)) - end + case = resolve_todo(mi, info, flag, state) + handle_single_case!(todo, ir, idx, stmt, case, false) return nothing end @@ -1659,7 +1603,7 @@ function inline_const_if_inlineable!(inst::Instruction) inst[:stmt] = quoted(rt.val) return true end - inst[:flag] |= IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW + add_flag!(inst, IR_FLAGS_REMOVABLE) return false end @@ -1677,7 +1621,7 @@ function assemble_inline_todo!(ir::IRCode, state::InliningState) # `NativeInterpreter` won't need this, but provide a support for `:invoke` exprs here # for external `AbstractInterpreter`s that may run the inlining pass multiple times if isexpr(stmt, :invoke) - handle_invoke_expr!(todo, idx, stmt, info, flag, sig, state) + handle_invoke_expr!(todo, ir, idx, stmt, info, flag, sig, state) continue end @@ -1694,8 +1638,8 @@ function assemble_inline_todo!(ir::IRCode, state::InliningState) # handle special cased builtins if isa(info, OpaqueClosureCallInfo) handle_opaque_closure_call!(todo, ir, idx, stmt, info, flag, sig, state) - elseif isa(info, ModifyFieldInfo) - handle_modifyfield!_call!(ir, idx, stmt, info, state) + elseif isa(info, ModifyOpInfo) + handle_modifyop!_call!(ir, idx, stmt, info, state) elseif isa(info, InvokeCallInfo) handle_invoke_call!(todo, ir, idx, stmt, info, flag, sig, state) elseif isa(info, FinalizerInfo) @@ -1764,6 +1708,8 @@ function early_inline_special_case( elseif cond.val === false return SomeCase(stmt.args[4]) end + elseif ⊑(optimizer_lattice(state.interp), cond, Bool) && stmt.args[3] === stmt.args[4] + return SomeCase(stmt.args[3]) end end return nothing @@ -1784,7 +1730,7 @@ function late_inline_special_case!( return SomeCase(quoted(type.val)) end cmp_call = Expr(:call, GlobalRef(Core, :(===)), stmt.args[2], stmt.args[3]) - cmp_call_ssa = insert_node!(ir, idx, effect_free_and_nothrow(NewInstruction(cmp_call, Bool))) + cmp_call_ssa = insert_node!(ir, idx, removable_if_unused(NewInstruction(cmp_call, Bool))) not_call = Expr(:call, GlobalRef(Core.Intrinsics, :not_int), cmp_call_ssa) return SomeCase(not_call) elseif length(argtypes) == 3 && istopfunction(f, :(>:)) @@ -1818,30 +1764,24 @@ struct SSASubstitute mi::MethodInstance arg_replacements::Vector{Any} spvals_ssa::Union{Nothing,SSAValue} - linetable_offset::Int32 -end - -function ssa_substitute!(insert_node!::Inserter, subst_inst::Instruction, @nospecialize(val), - ssa_substitute::SSASubstitute, boundscheck::Symbol) - subst_inst[:line] += ssa_substitute.linetable_offset - return ssa_substitute_op!(insert_node!, subst_inst, val, ssa_substitute, boundscheck) + inlined_at::NTuple{3,Int32} # TODO: add a map also, so that ssaidx doesn't need to equal inlined_idx? end function insert_spval!(insert_node!::Inserter, spvals_ssa::SSAValue, spidx::Int, do_isdefined::Bool) ret = insert_node!( - effect_free_and_nothrow(NewInstruction(Expr(:call, Core._svec_ref, spvals_ssa, spidx), Any))) + removable_if_unused(NewInstruction(Expr(:call, Core._svec_ref, spvals_ssa, spidx), Any))) tcheck_not = nothing if do_isdefined tcheck = insert_node!( - effect_free_and_nothrow(NewInstruction(Expr(:call, Core.isa, ret, Core.TypeVar), Bool))) + removable_if_unused(NewInstruction(Expr(:call, Core.isa, ret, Core.TypeVar), Bool))) tcheck_not = insert_node!( - effect_free_and_nothrow(NewInstruction(Expr(:call, not_int, tcheck), Bool))) + removable_if_unused(NewInstruction(Expr(:call, not_int, tcheck), Bool))) end return (ret, tcheck_not) end function ssa_substitute_op!(insert_node!::Inserter, subst_inst::Instruction, @nospecialize(val), - ssa_substitute::SSASubstitute, boundscheck::Symbol) + ssa_substitute::SSASubstitute) if isa(val, Argument) return ssa_substitute.arg_replacements[val.n] end @@ -1856,7 +1796,7 @@ function ssa_substitute_op!(insert_node!::Inserter, subst_inst::Instruction, @no return quoted(val) else flag = subst_inst[:flag] - maybe_undef = (flag & IR_FLAG_NOTHROW) == 0 && isa(val, TypeVar) + maybe_undef = !has_flag(flag, IR_FLAG_NOTHROW) && isa(val, TypeVar) (ret, tcheck_not) = insert_spval!(insert_node!, ssa_substitute.spvals_ssa::SSAValue, spidx, maybe_undef) if maybe_undef insert_node!( @@ -1894,10 +1834,10 @@ function ssa_substitute_op!(insert_node!::Inserter, subst_inst::Instruction, @no end end end - isa(val, Union{SSAValue, NewSSAValue}) && return val # avoid infinite loop + isa(val, AnySSAValue) && return val # avoid infinite loop urs = userefs(val) for op in urs - op[] = ssa_substitute_op!(insert_node!, subst_inst, op[], ssa_substitute, boundscheck) + op[] = ssa_substitute_op!(insert_node!, subst_inst, op[], ssa_substitute) end return urs[] end diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index 1e01e0f476daa..aa7b7e7464da7 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -2,7 +2,7 @@ Core.PhiNode() = Core.PhiNode(Int32[], Any[]) -isterminator(@nospecialize(stmt)) = isa(stmt, GotoNode) || isa(stmt, GotoIfNot) || isa(stmt, ReturnNode) +isterminator(@nospecialize(stmt)) = isa(stmt, GotoNode) || isa(stmt, GotoIfNot) || isa(stmt, ReturnNode) || isa(stmt, EnterNode) || isexpr(stmt, :leave) struct CFG blocks::Vector{BasicBlock} @@ -60,16 +60,18 @@ block_for_inst(cfg::CFG, inst::Int) = block_for_inst(cfg.index, inst) # This is a fake dest to force the next stmt to start a bb idx < length(stmts) && push!(jump_dests, idx+1) push!(jump_dests, stmt.label) + elseif isa(stmt, EnterNode) + # :enter starts/ends a BB + push!(jump_dests, idx) + push!(jump_dests, idx+1) + # The catch block is a jump dest + if stmt.catch_dest != 0 + push!(jump_dests, stmt.catch_dest) + end elseif isa(stmt, Expr) if stmt.head === :leave # :leave terminates a BB push!(jump_dests, idx+1) - elseif stmt.head === :enter - # :enter starts/ends a BB - push!(jump_dests, idx) - push!(jump_dests, idx+1) - # The catch block is a jump dest - push!(jump_dests, stmt.args[1]::Int) end end if isa(stmt, PhiNode) @@ -80,7 +82,7 @@ block_for_inst(cfg::CFG, inst::Int) = block_for_inst(cfg.index, inst) end end end - # and add add one more basic block start after the last statement + # and add one more basic block start after the last statement for i = length(stmts):-1:1 if stmts[i] !== nothing push!(jump_dests, i+1) @@ -125,14 +127,16 @@ function compute_basic_blocks(stmts::Vector{Any}) push!(blocks[block′].preds, num) push!(b.succs, block′) end - elseif isexpr(terminator, :enter) + elseif isa(terminator, EnterNode) # :enter gets a virtual edge to the exception handler and # the exception handler gets a virtual edge from outside # the function. - block′ = block_for_inst(basic_block_index, terminator.args[1]::Int) - push!(blocks[block′].preds, num) - push!(blocks[block′].preds, 0) - push!(b.succs, block′) + if terminator.catch_dest != 0 + block′ = block_for_inst(basic_block_index, terminator.catch_dest) + push!(blocks[block′].preds, num) + push!(blocks[block′].preds, 0) + push!(b.succs, block′) + end end # statement fall-through if num + 1 <= length(blocks) @@ -170,6 +174,52 @@ function first_insert_for_bb(code::Vector{Any}, cfg::CFG, block::Int) return lastnonphiidx end +# mutable version of the compressed DebugInfo +mutable struct DebugInfoStream + def::Union{MethodInstance,Symbol,Nothing} + linetable::Union{Nothing,DebugInfo} + edges::Vector{DebugInfo} + firstline::Int32 # the starting line for this block (specified by having an index of 0) + codelocs::Vector{Int32} # for each statement: + # index into linetable (if defined), else a line number (in the file represented by def) + # then index into edges + # then index into edges[linetable] + function DebugInfoStream(codelocs::Vector{Int32}) + return new(nothing, nothing, DebugInfo[], 0, codelocs) + end + # DebugInfoStream(def::Union{MethodInstance,Nothing}, di::DebugInfo, nstmts::Int) = + # if debuginfo_file1(di.def) === debuginfo_file1(di.def) + # new(def, di.linetable, Core.svec(di.edges...), getdebugidx(di, 0), + # ccall(:jl_uncompress_codelocs, Any, (Any, Int), di.codelocs, nstmts)::Vector{Int32}) + # else + function DebugInfoStream(def::Union{MethodInstance,Nothing}, di::DebugInfo, nstmts::Int) + codelocs = zeros(Int32, nstmts * 3) + for i = 1:nstmts + codelocs[3i - 2] = i + end + return new(def, di, DebugInfo[], 0, codelocs) + end + global copy(di::DebugInfoStream) = new(di.def, di.linetable, di.edges, di.firstline, di.codelocs) +end + +Core.DebugInfo(di::DebugInfoStream, nstmts::Int) = + DebugInfo(something(di.def), di.linetable, Core.svec(di.edges...), + ccall(:jl_compress_codelocs, Any, (Int32, Any, Int), di.firstline, di.codelocs, nstmts)::String) + +getdebugidx(debuginfo::DebugInfo, pc::Int) = + ccall(:jl_uncompress1_codeloc, NTuple{3,Int32}, (Any, Int), debuginfo.codelocs, pc) + +function getdebugidx(debuginfo::DebugInfoStream, pc::Int) + if 3 <= 3pc <= length(debuginfo.codelocs) + return (debuginfo.codelocs[3pc-2], debuginfo.codelocs[3pc-1], debuginfo.codelocs[3pc-0]) + elseif pc == 0 + return (Int32(debuginfo.firstline), Int32(0), Int32(0)) + else + return (Int32(-1), Int32(0), Int32(0)) + end +end + + # SSA values that need renaming struct OldSSAValue id::Int @@ -199,7 +249,6 @@ end const AnySSAValue = Union{SSAValue, OldSSAValue, NewSSAValue} - # SSA-indexed nodes struct InstructionStream stmt::Vector{Any} @@ -207,13 +256,16 @@ struct InstructionStream info::Vector{CallInfo} line::Vector{Int32} flag::Vector{UInt32} + function InstructionStream(stmts::Vector{Any}, type::Vector{Any}, info::Vector{CallInfo}, line::Vector{Int32}, flag::Vector{UInt32}) + return new(stmts, type, info, line, flag) + end end function InstructionStream(len::Int) stmts = Vector{Any}(undef, len) types = Vector{Any}(undef, len) info = Vector{CallInfo}(undef, len) fill!(info, NoCallInfo()) - lines = fill(Int32(0), len) + lines = fill(Int32(0), 3len) flags = fill(IR_FLAG_NULL, len) return InstructionStream(stmts, types, info, lines, flags) end @@ -238,10 +290,10 @@ function resize!(stmts::InstructionStream, len) resize!(stmts.stmt, len) resize!(stmts.type, len) resize!(stmts.info, len) - resize!(stmts.line, len) + resize!(stmts.line, 3len) resize!(stmts.flag, len) for i in (old_length + 1):len - stmts.line[i] = 0 + stmts.line[3i-2], stmts.line[3i-1], stmts.line[3i] = NoLineUpdate stmts.flag[i] = IR_FLAG_NULL stmts.info[i] = NoCallInfo() end @@ -257,11 +309,20 @@ Instruction(is::InstructionStream) = Instruction(is, add_new_idx!(is)) @inline function getindex(node::Instruction, fld::Symbol) (fld === :inst) && (fld = :stmt) # deprecated isdefined(node, fld) && return getfield(node, fld) - return getfield(getfield(node, :data), fld)[getfield(node, :idx)] + fldarray = getfield(getfield(node, :data), fld) + fldidx = getfield(node, :idx) + (fld === :line) && return (fldarray[3fldidx-2], fldarray[3fldidx-1], fldarray[3fldidx-0]) + return fldarray[fldidx] end @inline function setindex!(node::Instruction, @nospecialize(val), fld::Symbol) (fld === :inst) && (fld = :stmt) # deprecated - getfield(getfield(node, :data), fld)[getfield(node, :idx)] = val + fldarray = getfield(getfield(node, :data), fld) + fldidx = getfield(node, :idx) + if fld === :line + (fldarray[3fldidx-2], fldarray[3fldidx-1], fldarray[3fldidx-0]) = val::NTuple{3,Int32} + else + fldarray[fldidx] = val + end return node end @@ -270,7 +331,7 @@ function setindex!(is::InstructionStream, newval::Instruction, idx::Int) is.stmt[idx] = newval[:stmt] is.type[idx] = newval[:type] is.info[idx] = newval[:info] - is.line[idx] = newval[:line] + (is.line[3idx-2], is.line[3idx-1], is.line[3idx-0]) = newval[:line] is.flag[idx] = newval[:flag] return is end @@ -283,6 +344,10 @@ function setindex!(node::Instruction, newval::Instruction) return node end +has_flag(inst::Instruction, flag::UInt32) = has_flag(inst[:flag], flag) +add_flag!(inst::Instruction, flag::UInt32) = inst[:flag] |= flag +sub_flag!(inst::Instruction, flag::UInt32) = inst[:flag] &= ~flag + struct NewNodeInfo # Insertion position (interpretation depends on which array this is in) pos::Int @@ -306,14 +371,15 @@ struct NewInstruction stmt::Any type::Any info::CallInfo - line::Union{Int32,Nothing} # if nothing, copy the line from previous statement in the insertion location + line::Union{NTuple{3,Int32},Nothing} # if nothing, copy the line from previous statement in the insertion location flag::Union{UInt32,Nothing} # if nothing, IR flags will be recomputed on insertion function NewInstruction(@nospecialize(stmt), @nospecialize(type), @nospecialize(info::CallInfo), - line::Union{Int32,Nothing}, flag::Union{UInt32,Nothing}) + line::Union{NTuple{3,Int32},Int32,Nothing}, flag::Union{UInt32,Nothing}) + line isa Int32 && (line = (line, zero(Int32), zero(Int32))) return new(stmt, type, info, line, flag) end end -function NewInstruction(@nospecialize(stmt), @nospecialize(type), line::Union{Int32,Nothing}=nothing) +function NewInstruction(@nospecialize(stmt), @nospecialize(type), line::Union{NTuple{3,Int32},Int32,Nothing}=nothing) return NewInstruction(stmt, type, NoCallInfo(), line, nothing) end @nospecialize @@ -321,7 +387,7 @@ function NewInstruction(newinst::NewInstruction; stmt::Any=newinst.stmt, type::Any=newinst.type, info::CallInfo=newinst.info, - line::Union{Int32,Nothing}=newinst.line, + line::Union{NTuple{3,Int32},Int32,Nothing}=newinst.line, flag::Union{UInt32,Nothing}=newinst.flag) return NewInstruction(stmt, type, info, line, flag) end @@ -329,42 +395,56 @@ function NewInstruction(inst::Instruction; stmt::Any=inst[:stmt], type::Any=inst[:type], info::CallInfo=inst[:info], - line::Union{Int32,Nothing}=inst[:line], + line::Union{NTuple{3,Int32},Int32,Nothing}=inst[:line], flag::Union{UInt32,Nothing}=inst[:flag]) return NewInstruction(stmt, type, info, line, flag) end @specialize -effect_free_and_nothrow(newinst::NewInstruction) = NewInstruction(newinst; flag=add_flag(newinst, IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW)) -with_flags(newinst::NewInstruction, flags::UInt32) = NewInstruction(newinst; flag=add_flag(newinst, flags)) -without_flags(newinst::NewInstruction, flags::UInt32) = NewInstruction(newinst; flag=sub_flag(newinst, flags)) +removable_if_unused(newinst::NewInstruction) = add_flag(newinst, IR_FLAGS_REMOVABLE) function add_flag(newinst::NewInstruction, newflag::UInt32) flag = newinst.flag - flag === nothing && return newflag - return flag | newflag + if flag === nothing + flag = newflag + else + flag |= newflag + end + return NewInstruction(newinst; flag) end function sub_flag(newinst::NewInstruction, newflag::UInt32) flag = newinst.flag - flag === nothing && return IR_FLAG_NULL - return flag & ~newflag + if flag === nothing + flag = IR_FLAG_NULL + else + flag &= ~newflag + end + return NewInstruction(newinst; flag) end struct IRCode stmts::InstructionStream argtypes::Vector{Any} sptypes::Vector{VarState} - linetable::Vector{LineInfoNode} + debuginfo::DebugInfoStream cfg::CFG new_nodes::NewNodeStream meta::Vector{Expr} - function IRCode(stmts::InstructionStream, cfg::CFG, linetable::Vector{LineInfoNode}, argtypes::Vector{Any}, meta::Vector{Expr}, sptypes::Vector{VarState}) - return new(stmts, argtypes, sptypes, linetable, cfg, NewNodeStream(), meta) + function IRCode(stmts::InstructionStream, cfg::CFG, debuginfo::DebugInfoStream, argtypes::Vector{Any}, meta::Vector{Expr}, sptypes::Vector{VarState}) + return new(stmts, argtypes, sptypes, debuginfo, cfg, NewNodeStream(), meta) end function IRCode(ir::IRCode, stmts::InstructionStream, cfg::CFG, new_nodes::NewNodeStream) - return new(stmts, ir.argtypes, ir.sptypes, ir.linetable, cfg, new_nodes, ir.meta) + di = ir.debuginfo + @assert di.codelocs === stmts.line + return new(stmts, ir.argtypes, ir.sptypes, di, cfg, new_nodes, ir.meta) + end + global function copy(ir::IRCode) + di = ir.debuginfo + stmts = copy(ir.stmts) + di = copy(di) + di.edges = copy(di.edges) + di.codelocs = stmts.line + return new(stmts, copy(ir.argtypes), copy(ir.sptypes), di, copy(ir.cfg), copy(ir.new_nodes), copy(ir.meta)) end - global copy(ir::IRCode) = new(copy(ir.stmts), copy(ir.argtypes), copy(ir.sptypes), - copy(ir.linetable), copy(ir.cfg), copy(ir.new_nodes), copy(ir.meta)) end """ @@ -375,14 +455,23 @@ for debugging and unit testing of IRCode APIs. The compiler itself should genera from the frontend or one of the caches. """ function IRCode() - ir = IRCode(InstructionStream(1), CFG([BasicBlock(1:1, Int[], Int[])], Int[1]), LineInfoNode[], Any[], Expr[], VarState[]) + stmts = InstructionStream(1) + debuginfo = DebugInfoStream(stmts.line) + stmts.line[1] = 1 + ir = IRCode(stmts, CFG([BasicBlock(1:1, Int[], Int[])], Int[1]), debuginfo, Any[], Expr[], VarState[]) ir[SSAValue(1)][:stmt] = ReturnNode(nothing) ir[SSAValue(1)][:type] = Nothing ir[SSAValue(1)][:flag] = 0x00 - ir[SSAValue(1)][:line] = Int32(0) + ir[SSAValue(1)][:line] = NoLineUpdate return ir end +construct_domtree(ir::IRCode) = construct_domtree(ir.cfg) +construct_domtree(cfg::CFG) = construct_domtree(cfg.blocks) + +construct_postdomtree(ir::IRCode) = construct_postdomtree(ir.cfg) +construct_postdomtree(cfg::CFG) = construct_postdomtree(cfg.blocks) + function block_for_inst(ir::IRCode, inst::Int) if inst > length(ir.stmts) inst = ir.new_nodes.info[inst - length(ir.stmts)].pos @@ -446,11 +535,15 @@ struct UndefToken end; const UNDEF_TOKEN = UndefToken() isdefined(stmt, :val) || return OOB_TOKEN op == 1 || return OOB_TOKEN return stmt.val + elseif isa(stmt, EnterNode) + isdefined(stmt, :scope) || return OOB_TOKEN + op == 1 || return OOB_TOKEN + return stmt.scope elseif isa(stmt, PiNode) isdefined(stmt, :val) || return OOB_TOKEN op == 1 || return OOB_TOKEN return stmt.val - elseif isa(stmt, Union{SSAValue, NewSSAValue, GlobalRef}) + elseif isa(stmt, Union{AnySSAValue, GlobalRef}) op == 1 || return OOB_TOKEN return stmt elseif isa(stmt, UpsilonNode) @@ -502,16 +595,19 @@ end stmt = GotoIfNot(v, stmt.dest) elseif isa(stmt, ReturnNode) op == 1 || throw(BoundsError()) - stmt = typeof(stmt)(v) - elseif isa(stmt, Union{SSAValue, NewSSAValue, GlobalRef}) + stmt = ReturnNode(v) + elseif isa(stmt, EnterNode) + op == 1 || throw(BoundsError()) + stmt = EnterNode(stmt.catch_dest, v) + elseif isa(stmt, Union{AnySSAValue, GlobalRef}) op == 1 || throw(BoundsError()) stmt = v elseif isa(stmt, UpsilonNode) op == 1 || throw(BoundsError()) - stmt = typeof(stmt)(v) + stmt = UpsilonNode(v) elseif isa(stmt, PiNode) op == 1 || throw(BoundsError()) - stmt = typeof(stmt)(v, stmt.typ) + stmt = PiNode(v, stmt.typ) elseif isa(stmt, PhiNode) op > length(stmt.values) && throw(BoundsError()) isassigned(stmt.values, op) || throw(BoundsError()) @@ -533,8 +629,8 @@ end function userefs(@nospecialize(x)) relevant = (isa(x, Expr) && is_relevant_expr(x)) || - isa(x, GotoIfNot) || isa(x, ReturnNode) || isa(x, SSAValue) || isa(x, NewSSAValue) || - isa(x, PiNode) || isa(x, PhiNode) || isa(x, PhiCNode) || isa(x, UpsilonNode) + isa(x, GotoIfNot) || isa(x, ReturnNode) || isa(x, SSAValue) || isa(x, OldSSAValue) || isa(x, NewSSAValue) || + isa(x, PiNode) || isa(x, PhiNode) || isa(x, PhiCNode) || isa(x, UpsilonNode) || isa(x, EnterNode) return UseRefIterator(x, relevant) end @@ -574,7 +670,7 @@ function insert_node!(ir::IRCode, pos::SSAValue, newinst::NewInstruction, attach end node = add_inst!(ir.new_nodes, posid, attach_after) newline = something(newinst.line, ir[pos][:line]) - newflag = recompute_inst_flag(newinst, ir) + newflag = recompute_newinst_flag(newinst, ir) node = inst_from_newinst!(node, newinst, newline, newflag) return SSAValue(length(ir.stmts) + node.idx) end @@ -587,6 +683,7 @@ struct CFGTransformState result_bbs::Vector{BasicBlock} bb_rename_pred::Vector{Int} bb_rename_succ::Vector{Int} + domtree::Union{Nothing, DomTree} end # N.B.: Takes ownership of the CFG array @@ -622,11 +719,14 @@ function CFGTransformState!(blocks::Vector{BasicBlock}, allow_cfg_transforms::Bo let blocks = blocks, bb_rename = bb_rename result_bbs = BasicBlock[blocks[i] for i = 1:length(blocks) if bb_rename[i] != -1] end + # TODO: This could be done by just renaming the domtree + domtree = construct_domtree(result_bbs) else bb_rename = Vector{Int}() result_bbs = blocks + domtree = nothing end - return CFGTransformState(allow_cfg_transforms, allow_cfg_transforms, result_bbs, bb_rename, bb_rename) + return CFGTransformState(allow_cfg_transforms, allow_cfg_transforms, result_bbs, bb_rename, bb_rename, domtree) end mutable struct IncrementalCompact @@ -659,6 +759,7 @@ mutable struct IncrementalCompact perm = sort!(collect(eachindex(info)); by=i::Int->(2info[i].pos+info[i].attach_after, i)) new_len = length(code.stmts) + length(info) result = InstructionStream(new_len) + code.debuginfo.codelocs = result.line used_ssas = fill(0, new_len) new_new_used_ssas = Vector{Int}() blocks = code.cfg.blocks @@ -681,7 +782,7 @@ mutable struct IncrementalCompact bb_rename = Vector{Int}() pending_nodes = NewNodeStream() pending_perm = Int[] - return new(code, parent.result, CFGTransformState(false, false, parent.cfg_transform.result_bbs, bb_rename, bb_rename), + return new(code, parent.result, CFGTransformState(false, false, parent.cfg_transform.result_bbs, bb_rename, bb_rename, nothing), ssa_rename, parent.used_ssas, parent.late_fixup, perm, 1, parent.new_new_nodes, parent.new_new_used_ssas, pending_nodes, pending_perm, @@ -760,6 +861,16 @@ function dominates_ssa(compact::IncrementalCompact, domtree::DomTree, x::AnySSAV xb = block_for_inst(compact, x) yb = block_for_inst(compact, y) if xb == yb + if isa(compact[x][:stmt], PhiNode) + if isa(compact[y][:stmt], PhiNode) + # A node dominates another only if it dominates all uses of that note. + # Usually that is not a distinction. However, for phi nodes, the use + # occurs on the edge to the predecessor block. Thus, by definition, for + # any other PhiNode in the same BB there must be (at least) one edge + # that this phi node does not dominate. + return false + end + end xinfo = yinfo = nothing if isa(x, OldSSAValue) x′ = compact.ssa_rename[x.id]::SSAValue @@ -829,7 +940,7 @@ function add_pending!(compact::IncrementalCompact, pos::Int, attach_after::Bool) end function inst_from_newinst!(node::Instruction, newinst::NewInstruction, - newline::Int32=newinst.line::Int32, newflag::UInt32=newinst.flag::UInt32) + newline::NTuple{3,Int32}=newinst.line::NTuple{3,Int32}, newflag::UInt32=newinst.flag::UInt32) node[:stmt] = newinst.stmt node[:type] = newinst.type node[:info] = newinst.info @@ -838,29 +949,14 @@ function inst_from_newinst!(node::Instruction, newinst::NewInstruction, return node end -function recompute_inst_flag(newinst::NewInstruction, src::Union{IRCode,IncrementalCompact}) +function recompute_newinst_flag(newinst::NewInstruction, src::Union{IRCode,IncrementalCompact}) flag = newinst.flag flag !== nothing && return flag - flag = IR_FLAG_NULL - (consistent, effect_free_and_nothrow, nothrow) = stmt_effect_flags( - fallback_lattice, newinst.stmt, newinst.type, src) - if consistent - flag |= IR_FLAG_CONSISTENT - end - if effect_free_and_nothrow - flag |= IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW - elseif nothrow - flag |= IR_FLAG_NOTHROW - end - if !isexpr(newinst.stmt, :call) && !isexpr(newinst.stmt, :invoke) - # See comment in check_effect_free! - flag |= IR_FLAG_NOUB - end - return flag + return recompute_effects_flags(fallback_lattice, newinst.stmt, newinst.type, src) end function insert_node!(compact::IncrementalCompact, @nospecialize(before), newinst::NewInstruction, attach_after::Bool=false) - newflag = recompute_inst_flag(newinst, compact) + newflag = recompute_newinst_flag(newinst, compact) if isa(before, SSAValue) if before.id < compact.result_idx count_added_node!(compact, newinst.stmt) @@ -918,22 +1014,32 @@ function insert_node!(compact::IncrementalCompact, @nospecialize(before), newins end end -function insert_node_here!(compact::IncrementalCompact, newinst::NewInstruction, reverse_affinity::Bool=false) - newline = newinst.line::Int32 - refinish = false +function did_just_finish_bb(compact) result_idx = compact.result_idx result_bbs = compact.cfg_transform.result_bbs - if reverse_affinity && - ((compact.active_result_bb == length(result_bbs) + 1) || - result_idx == first(result_bbs[compact.active_result_bb].stmts)) + (compact.active_result_bb == length(result_bbs) + 1) || + result_idx == first(result_bbs[compact.active_result_bb].stmts) +end + +function maybe_reopen_bb!(compact) + if did_just_finish_bb(compact) compact.active_result_bb -= 1 - refinish = true + return true end + return false +end + +function insert_node_here!(compact::IncrementalCompact, newinst::NewInstruction, reverse_affinity::Bool=false) + newline = newinst.line::NTuple{3,Int32} + refinish = false + result_idx = compact.result_idx + result_bbs = compact.cfg_transform.result_bbs + refinish = reverse_affinity && maybe_reopen_bb!(compact) if result_idx > length(compact.result) @assert result_idx == length(compact.result) + 1 resize!(compact, result_idx) end - newflag = recompute_inst_flag(newinst, compact) + newflag = recompute_newinst_flag(newinst, compact) node = inst_from_newinst!(compact.result[result_idx], newinst, newline, newflag) count_added_node!(compact, newinst.stmt) && push!(compact.late_fixup, result_idx) compact.result_idx = result_idx + 1 @@ -942,6 +1048,21 @@ function insert_node_here!(compact::IncrementalCompact, newinst::NewInstruction, return inst end +function delete_inst_here!(compact::IncrementalCompact) + # If we already closed this bb, reopen it for our modification + refinish = maybe_reopen_bb!(compact) + + # Delete the statement, update refcounts etc + compact[SSAValue(compact.result_idx-1)] = nothing + + # Pretend that we never compacted this statement in the first place + compact.result_idx -= 1 + + refinish && finish_current_bb!(compact, 0) + + return nothing +end + function getindex(view::TypesView, v::OldSSAValue) id = v.id ir = view.ir.ir @@ -974,14 +1095,13 @@ function kill_current_uses!(compact::IncrementalCompact, @nospecialize(stmt)) end end -function setindex!(compact::IncrementalCompact, @nospecialize(v), idx::SSAValue) - @assert idx.id < compact.result_idx - (compact.result[idx.id][:stmt] === v) && return compact +function setindex!(compact::IncrementalCompact, @nospecialize(v), ssa::Union{SSAValue, NewSSAValue}) + (compact[ssa][:stmt] === v) && return compact # Kill count for current uses - kill_current_uses!(compact, compact.result[idx.id][:stmt]) - compact.result[idx.id][:stmt] = v + kill_current_uses!(compact, compact[ssa][:stmt]) + compact[ssa][:stmt] = v # Add count for new use - count_added_node!(compact, v) && push!(compact.late_fixup, idx.id) + count_added_node!(compact, v) && isa(ssa, SSAValue) && push!(compact.late_fixup, ssa.id) return compact end @@ -1058,7 +1178,7 @@ function find_ssavalue_uses1(compact::IncrementalCompact) end function _oracle_check(compact::IncrementalCompact) - (observed_used_ssas, observed_used_newssas) = Core.Compiler.find_ssavalue_uses1(compact) + (observed_used_ssas, observed_used_newssas) = find_ssavalue_uses1(compact) for i = 1:length(observed_used_ssas) if observed_used_ssas[i] != compact.used_ssas[i] return (observed_used_ssas, observed_used_newssas, SSAValue(i)) @@ -1112,7 +1232,7 @@ end (this::Refiner)() = (this.result_flags[this.result_idx] |= IR_FLAG_REFINED; nothing) function process_phinode_values(old_values::Vector{Any}, late_fixup::Vector{Int}, - processed_idx::Int, result_idx::Int, + already_inserted, result_idx::Int, ssa_rename::Vector{Any}, used_ssas::Vector{Int}, new_new_used_ssas::Vector{Int}, do_rename_ssa::Bool, @@ -1123,7 +1243,7 @@ function process_phinode_values(old_values::Vector{Any}, late_fixup::Vector{Int} val = old_values[i] if isa(val, SSAValue) if do_rename_ssa - if val.id > processed_idx + if !already_inserted(i, OldSSAValue(val.id)) push!(late_fixup, result_idx) val = OldSSAValue(val.id) else @@ -1133,7 +1253,7 @@ function process_phinode_values(old_values::Vector{Any}, late_fixup::Vector{Int} used_ssas[val.id] += 1 end elseif isa(val, OldSSAValue) - if val.id > processed_idx + if !already_inserted(i, val) push!(late_fixup, result_idx) else # Always renumber these. do_rename_ssa applies only to actual SSAValues @@ -1141,13 +1261,15 @@ function process_phinode_values(old_values::Vector{Any}, late_fixup::Vector{Int} end elseif isa(val, NewSSAValue) if val.id < 0 - push!(late_fixup, result_idx) new_new_used_ssas[-val.id] += 1 else @assert do_rename_ssa val = SSAValue(val.id) end end + if isa(val, NewSSAValue) + push!(late_fixup, result_idx) + end values[i] = val end return values @@ -1168,6 +1290,9 @@ function renumber_ssa2(val::SSAValue, ssanums::Vector{Any}, used_ssas::Vector{In end if isa(val, SSAValue) used_ssas[val.id] += 1 + elseif isa(val, NewSSAValue) + @assert val.id < 0 + new_new_used_ssas[-val.id] += 1 end return val end @@ -1222,19 +1347,25 @@ end # N.B.: from and to are non-renamed indices function kill_edge!(compact::IncrementalCompact, active_bb::Int, from::Int, to::Int) - # Note: We recursively kill as many edges as are obviously dead. However, this - # may leave dead loops in the IR. We kill these later in a CFG cleanup pass (or - # worstcase during codegen). - (; bb_rename_pred, bb_rename_succ, result_bbs) = compact.cfg_transform + # Note: We recursively kill as many edges as are obviously dead. + (; bb_rename_pred, bb_rename_succ, result_bbs, domtree) = compact.cfg_transform preds = result_bbs[bb_rename_succ[to]].preds succs = result_bbs[bb_rename_pred[from]].succs deleteat!(preds, findfirst(x::Int->x==bb_rename_pred[from], preds)::Int) deleteat!(succs, findfirst(x::Int->x==bb_rename_succ[to], succs)::Int) + if domtree !== nothing + domtree_delete_edge!(domtree, result_bbs, bb_rename_pred[from], bb_rename_succ[to]) + end # Check if the block is now dead - if length(preds) == 0 - for succ in copy(result_bbs[bb_rename_succ[to]].succs) - kill_edge!(compact, active_bb, to, findfirst(x::Int->x==succ, bb_rename_pred)::Int) + if length(preds) == 0 || (domtree !== nothing && bb_unreachable(domtree, bb_rename_succ[to])) + to_succs = result_bbs[bb_rename_succ[to]].succs + for succ in copy(to_succs) + new_succ = findfirst(x::Int->x==succ, bb_rename_pred) + new_succ === nothing && continue + kill_edge!(compact, active_bb, to, new_succ) end + empty!(preds) + empty!(to_succs) if to < active_bb # Kill all statements in the block stmts = result_bbs[bb_rename_succ[to]].stmts @@ -1270,8 +1401,8 @@ function kill_edge!(compact::IncrementalCompact, active_bb::Int, from::Int, to:: else stmts = compact.ir.cfg.blocks[to].stmts for stmt in CompactPeekIterator(compact, first(stmts), last(stmts)) - stmt === nothing && continue - isa(stmt, PhiNode) || break + is_valid_phiblock_stmt(stmt) || break + isa(stmt, PhiNode) || continue i = findfirst(x::Int32->x==from, stmt.edges) if i !== nothing deleteat!(stmt.edges, i) @@ -1293,6 +1424,7 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr (; result, ssa_rename, late_fixup, used_ssas, new_new_used_ssas) = compact (; cfg_transforms_enabled, fold_constant_branches, bb_rename_succ, bb_rename_pred, result_bbs) = compact.cfg_transform mark_refined! = Refiner(result.flag, result_idx) + already_inserted_phi_arg = already_inserted_ssa(compact, processed_idx) if stmt === nothing ssa_rename[idx] = stmt elseif isa(stmt, OldSSAValue) @@ -1306,7 +1438,7 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr elseif isa(stmt, GlobalRef) total_flags = IR_FLAG_CONSISTENT | IR_FLAG_EFFECT_FREE flag = result[result_idx][:flag] - if (flag & total_flags) == total_flags + if has_flag(flag, total_flags) ssa_rename[idx] = stmt else ssa_rename[idx] = SSAValue(result_idx) @@ -1350,13 +1482,20 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr result[result_idx][:stmt] = GotoIfNot(cond, label) result_idx += 1 end + elseif cfg_transforms_enabled && isa(stmt, EnterNode) + stmt = renumber_ssa2!(stmt, ssa_rename, used_ssas, new_new_used_ssas, late_fixup, result_idx, do_rename_ssa, mark_refined!)::EnterNode + if stmt.catch_dest != 0 + label = bb_rename_succ[stmt.catch_dest] + @assert label > 0 + result[result_idx][:stmt] = EnterNode(stmt, label) + else + result[result_idx][:stmt] = stmt + end + ssa_rename[idx] = SSAValue(result_idx) + result_idx += 1 elseif isa(stmt, Expr) stmt = renumber_ssa2!(stmt, ssa_rename, used_ssas, new_new_used_ssas, late_fixup, result_idx, do_rename_ssa, mark_refined!)::Expr - if cfg_transforms_enabled && isexpr(stmt, :enter) - label = bb_rename_succ[stmt.args[1]::Int] - @assert label > 0 - stmt.args[1] = label - elseif isexpr(stmt, :throw_undef_if_not) + if isexpr(stmt, :throw_undef_if_not) cond = stmt.args[2] if isa(cond, Bool) && cond === true # cond was folded to true - this statement @@ -1364,6 +1503,21 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr ssa_rename[idx] = nothing return result_idx end + elseif isexpr(stmt, :leave) + let i = 1 + while i <= length(stmt.args) + if stmt.args[i] === nothing + deleteat!(stmt.args, i) + else + i += 1 + end + end + end + if isempty(stmt.args) + # This :leave is dead + ssa_rename[idx] = nothing + return result_idx + end end typ = inst[:type] if isa(typ, Const) && is_inlineable_constant(typ.val) @@ -1401,7 +1555,7 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr ssa_rename[idx] = SSAValue(result_idx) result[result_idx][:stmt] = stmt result_idx += 1 - elseif isa(stmt, ReturnNode) || isa(stmt, UpsilonNode) || isa(stmt, GotoIfNot) + elseif isa(stmt, ReturnNode) || isa(stmt, UpsilonNode) || isa(stmt, GotoIfNot) || isa(stmt, EnterNode) ssa_rename[idx] = SSAValue(result_idx) result[result_idx][:stmt] = renumber_ssa2!(stmt, ssa_rename, used_ssas, new_new_used_ssas, late_fixup, result_idx, do_rename_ssa, mark_refined!) result_idx += 1 @@ -1446,18 +1600,16 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr values = stmt.values end - values = process_phinode_values(values, late_fixup, processed_idx, result_idx, ssa_rename, used_ssas, new_new_used_ssas, do_rename_ssa, mark_refined!) - # Don't remove the phi node if it is before the definition of its value - # because doing so can create forward references. This should only - # happen with dead loops, but can cause problems when optimization - # passes look at all code, dead or not. This check should be - # unnecessary when DCE can remove those dead loops entirely, so this is - # just to be safe. - before_def = isassigned(values, 1) && (v = values[1]; isa(v, OldSSAValue)) && idx < v.id - if length(edges) == 1 && isassigned(values, 1) && !before_def && - length(cfg_transforms_enabled ? - result_bbs[bb_rename_succ[active_bb]].preds : - compact.ir.cfg.blocks[active_bb].preds) == 1 + values = process_phinode_values(values, late_fixup, already_inserted_phi_arg, result_idx, ssa_rename, used_ssas, new_new_used_ssas, do_rename_ssa, mark_refined!) + + # Quick egality check for PhiNode that may be replaced with its incoming + # value without needing to set the `Refined` flag. We can't do the actual + # refinement check, because we do not have access to the lattice here. + # Users may call `reprocess_phi_node!` inside the compaction loop to + # revisit PhiNodes with the proper lattice refinement check. + if may_replace_phi(values, cfg_transforms_enabled ? + result_bbs[bb_rename_succ[active_bb]] : + compact.ir.cfg.blocks[active_bb], idx) && argextype(values[1], compact) === inst[:type] # There's only one predecessor left - just replace it v = values[1] @assert !isa(v, NewSSAValue) @@ -1485,7 +1637,7 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr push!(values, value) end end - result[result_idx][:stmt] = PhiCNode(process_phinode_values(values, late_fixup, processed_idx, result_idx, ssa_rename, used_ssas, new_new_used_ssas, do_rename_ssa, mark_refined!)) + result[result_idx][:stmt] = PhiCNode(process_phinode_values(values, late_fixup, already_inserted_phi_arg, result_idx, ssa_rename, used_ssas, new_new_used_ssas, do_rename_ssa, mark_refined!)) result_idx += 1 else if isa(stmt, SSAValue) @@ -1498,7 +1650,7 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr else # Constant assign, replace uses of this ssa value with its result end - if (inst[:flag] & IR_FLAG_REFINED) != 0 && !isa(stmt, Refined) + if has_flag(inst, IR_FLAG_REFINED) && !isa(stmt, Refined) # If we're compacting away an instruction that was marked as refined, # leave a marker in the ssa_rename, so we can taint any users. stmt = Refined(stmt) @@ -1508,6 +1660,38 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr return result_idx end +function may_replace_phi(values::Vector{Any}, phi_bb::BasicBlock, idx::Int) + length(values) == 1 || return false + isassigned(values, 1) || return false + length(phi_bb.preds) == 1 || return false + + # Don't remove the phi node if it is before the definition of its value + # because doing so can create forward references. This should only + # happen with dead loops, but can cause problems when optimization + # passes look at all code, dead or not. This check should be + # unnecessary when DCE can remove those dead loops entirely, so this is + # just to be safe. + v = values[1] + before_def = isa(v, OldSSAValue) && idx < v.id + return !before_def +end + +function reprocess_phi_node!(𝕃ₒ::AbstractLattice, compact::IncrementalCompact, phi::PhiNode, old_idx::Int) + phi_bb = compact.active_result_bb + did_just_finish_bb(compact) && (phi_bb -= 1) + may_replace_phi(phi.values, compact.cfg_transform.result_bbs[phi_bb], compact.idx) || return false + + # There's only one predecessor left - just replace it + v = phi.values[1] + if !⊑(𝕃ₒ, compact[compact.ssa_rename[old_idx]][:type], argextype(v, compact)) + v = Refined(v) + end + compact.ssa_rename[old_idx] = v + + delete_inst_here!(compact) + return true +end + function resize!(compact::IncrementalCompact, nnewnodes::Int) old_length = length(compact.result) resize!(compact.result, nnewnodes) @@ -1518,6 +1702,8 @@ function resize!(compact::IncrementalCompact, nnewnodes::Int) return compact end +const NoLineUpdate = (Int32(0), Int32(0), Int32(0)) + function finish_current_bb!(compact::IncrementalCompact, active_bb::Int, old_result_idx::Int=compact.result_idx, unreachable::Bool=false) (;result_bbs, cfg_transforms_enabled, bb_rename_succ) = compact.cfg_transform @@ -1534,9 +1720,9 @@ function finish_current_bb!(compact::IncrementalCompact, active_bb::Int, length(compact.result) < old_result_idx && resize!(compact, old_result_idx) node = compact.result[old_result_idx] if unreachable - node[:stmt], node[:type], node[:line] = ReturnNode(), Union{}, 0 + node[:stmt], node[:type], node[:line] = ReturnNode(), Union{}, NoLineUpdate else - node[:stmt], node[:type], node[:line] = nothing, Nothing, 0 + node[:stmt], node[:type], node[:line], node[:flag] = nothing, Nothing, NoLineUpdate, IR_FLAGS_EFFECTS end compact.result_idx = old_result_idx + 1 elseif cfg_transforms_enabled && compact.result_idx - 1 == first(bb.stmts) @@ -1591,13 +1777,27 @@ struct CompactPeekIterator compact::IncrementalCompact start_idx::Int end_idx::Int + include_stmts_before_start::Bool end +CompactPeekIterator(compact::IncrementalCompact, start_idx::Int, end_idx::Int) = + CompactPeekIterator(compact, start_idx, end_idx, false) + function CompactPeekIterator(compact::IncrementalCompact, start_idx::Int) return CompactPeekIterator(compact, start_idx, 0) end -entry_at_idx(entry::NewNodeInfo, idx::Int) = entry.attach_after ? entry.pos == idx - 1 : entry.pos == idx +function entry_at_idx(entry::NewNodeInfo, idx::Int, start_idx::Int, include_stmts_before_start::Bool) + if entry.attach_after + if !include_stmts_before_start + entry.pos >= start_idx || return false + end + return entry.pos == idx - 1 + else + return entry.pos == idx + end +end + function iterate(it::CompactPeekIterator, (idx, aidx, bidx)::NTuple{3, Int}=(it.start_idx, it.compact.new_nodes_idx, 1)) if it.end_idx > 0 && idx > it.end_idx return nothing @@ -1609,7 +1809,7 @@ function iterate(it::CompactPeekIterator, (idx, aidx, bidx)::NTuple{3, Int}=(it. if compact.new_nodes_idx <= length(compact.perm) new_nodes = compact.ir.new_nodes for eidx in aidx:length(compact.perm) - if entry_at_idx(new_nodes.info[compact.perm[eidx]], idx) + if entry_at_idx(new_nodes.info[compact.perm[eidx]], idx, it.start_idx, it.include_stmts_before_start) entry = new_nodes.stmts[compact.perm[eidx]] return (entry[:stmt], (idx, eidx+1, bidx)) end @@ -1617,7 +1817,7 @@ function iterate(it::CompactPeekIterator, (idx, aidx, bidx)::NTuple{3, Int}=(it. end if !isempty(compact.pending_perm) for eidx in bidx:length(compact.pending_perm) - if entry_at_idx(compact.pending_nodes.info[compact.pending_perm[eidx]], idx) + if entry_at_idx(compact.pending_nodes.info[compact.pending_perm[eidx]], idx, it.start_idx, it.include_stmts_before_start) entry = compact.pending_nodes.stmts[compact.pending_perm[eidx]] return (entry[:stmt], (idx, aidx, eidx+1)) end @@ -1733,8 +1933,7 @@ function maybe_erase_unused!(callback::Function, compact::IncrementalCompact, id stmt = inst[:stmt] stmt === nothing && return false inst[:type] === Bottom && return false - effect_free = (inst[:flag] & (IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW)) == IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW - effect_free || return false + has_flag(inst, IR_FLAGS_REMOVABLE) || return false foreachssa(stmt) do val::SSAValue if compact.used_ssas[val.id] == 1 if val.id < idx || in_worklist @@ -1782,14 +1981,22 @@ function fixup_node(compact::IncrementalCompact, @nospecialize(stmt), reify_new_ return FixedNode(stmt, true) end elseif isa(stmt, OldSSAValue) - val = compact.ssa_rename[stmt.id] - if isa(val, Refined) - val = val.val + node = compact.ssa_rename[stmt.id] + if isa(node, Refined) + node = node.val end - if isa(val, SSAValue) - compact.used_ssas[val.id] += 1 + needs_fixup = false + if isa(node, NewSSAValue) + (;node, needs_fixup) = fixup_node(compact, node, reify_new_nodes) + end + if isa(node, SSAValue) + compact.used_ssas[node.id] += 1 + elseif isa(node, NewSSAValue) + compact.new_new_used_ssas[-node.id] += 1 + elseif isa(node, OldSSAValue) + return fixup_node(compact, node, reify_new_nodes) end - return FixedNode(val, false) + return FixedNode(node, needs_fixup) else urs = userefs(stmt) fixup = false diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index 0e34cf9ce70bc..05d78e8706072 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -5,39 +5,44 @@ function collect_limitations!(@nospecialize(typ), ::IRInterpretationState) return typ end -function concrete_eval_invoke(interp::AbstractInterpreter, - inst::Expr, mi::MethodInstance, irsv::IRInterpretationState) - world = frame_world(irsv) - mi_cache = WorldView(code_cache(interp), world) - code = get(mi_cache, mi, nothing) - code === nothing && return Pair{Any,Tuple{Bool,Bool}}(nothing, (false, false)) - argtypes = collect_argtypes(interp, inst.args[2:end], nothing, irsv) - argtypes === nothing && return Pair{Any,Tuple{Bool,Bool}}(Bottom, (false, false)) - effects = decode_effects(code.ipo_purity_bits) +function concrete_eval_invoke(interp::AbstractInterpreter, ci::CodeInstance, argtypes::Vector{Any}, parent::IRInterpretationState) + world = frame_world(parent) + effects = decode_effects(ci.ipo_purity_bits) if (is_foldable(effects) && is_all_const_arg(argtypes, #=start=#1) && (is_nonoverlayed(interp) || is_nonoverlayed(effects))) args = collect_const_args(argtypes, #=start=#1) - value = let world = get_world_counter(interp) - try - Core._call_in_world_total(world, args...) - catch - return Pair{Any,Tuple{Bool,Bool}}(Bottom, (false, is_noub(effects, false))) - end + value = try + Core._call_in_world_total(world, args...) + catch + return Pair{Any,Tuple{Bool,Bool}}(Bottom, (false, is_noub(effects))) end return Pair{Any,Tuple{Bool,Bool}}(Const(value), (true, true)) else - if is_constprop_edge_recursed(mi, irsv) - return Pair{Any,Tuple{Bool,Bool}}(nothing, (is_nothrow(effects), is_noub(effects, false))) + mi = ci.def + if is_constprop_edge_recursed(mi, parent) + return Pair{Any,Tuple{Bool,Bool}}(nothing, (is_nothrow(effects), is_noub(effects))) end - newirsv = IRInterpretationState(interp, code, mi, argtypes, world) + newirsv = IRInterpretationState(interp, ci, mi, argtypes, world) if newirsv !== nothing - newirsv.parent = irsv + newirsv.parent = parent return ir_abstract_constant_propagation(interp, newirsv) end - return Pair{Any,Tuple{Bool,Bool}}(nothing, (is_nothrow(effects), is_noub(effects, false))) + return Pair{Any,Tuple{Bool,Bool}}(nothing, (is_nothrow(effects), is_noub(effects))) end end +function abstract_eval_invoke_inst(interp::AbstractInterpreter, inst::Instruction, irsv::IRInterpretationState) + stmt = inst[:stmt] + mi = stmt.args[1]::MethodInstance + world = frame_world(irsv) + mi_cache = WorldView(code_cache(interp), world) + code = get(mi_cache, mi, nothing) + code === nothing && return Pair{Any,Tuple{Bool,Bool}}(nothing, (false, false)) + argtypes = collect_argtypes(interp, stmt.args[2:end], nothing, irsv) + argtypes === nothing && return Pair{Any,Tuple{Bool,Bool}}(Bottom, (false, false)) + return concrete_eval_invoke(interp, code, argtypes, irsv) +end + abstract_eval_ssavalue(s::SSAValue, sv::IRInterpretationState) = abstract_eval_ssavalue(s, sv.ir) function abstract_eval_phi_stmt(interp::AbstractInterpreter, phi::PhiNode, ::Int, irsv::IRInterpretationState) @@ -46,22 +51,29 @@ end function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, irsv::IRInterpretationState) si = StmtInfo(true) # TODO better job here? - (; rt, effects, info) = abstract_call(interp, arginfo, si, irsv) - irsv.ir.stmts[irsv.curridx][:info] = info - return RTEffects(rt, effects) + call = abstract_call(interp, arginfo, si, irsv) + irsv.ir.stmts[irsv.curridx][:info] = call.info + return call end +function kill_block!(ir::IRCode, bb::Int) + # Kill the entire block + stmts = ir.cfg.blocks[bb].stmts + for bidx = stmts + inst = ir[SSAValue(bidx)] + inst[:stmt] = nothing + inst[:type] = Bottom + inst[:flag] = IR_FLAGS_REMOVABLE + end + ir[SSAValue(last(stmts))][:stmt] = ReturnNode() + return +end +kill_block!(ir::IRCode) = (bb::Int)->kill_block!(ir, bb) + function update_phi!(irsv::IRInterpretationState, from::Int, to::Int) ir = irsv.ir if length(ir.cfg.blocks[to].preds) == 0 - # Kill the entire block - for bidx = ir.cfg.blocks[to].stmts - inst = ir[SSAValue(bidx)] - inst[:stmt] = nothing - inst[:type] = Bottom - inst[:flag] = IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW - end - return + kill_block!(ir, to) end for sidx = ir.cfg.blocks[to].stmts stmt = ir[SSAValue(sidx)][:stmt] @@ -83,22 +95,27 @@ function kill_terminator_edges!(irsv::IRInterpretationState, term_idx::Int, bb:: ir = irsv.ir stmt = ir[SSAValue(term_idx)][:stmt] if isa(stmt, GotoIfNot) - kill_edge!(ir, bb, stmt.dest, update_phi!(irsv)) - kill_edge!(ir, bb, bb+1, update_phi!(irsv)) + kill_edge!(irsv, bb, stmt.dest) + kill_edge!(irsv, bb, bb+1) elseif isa(stmt, GotoNode) - kill_edge!(ir, bb, stmt.label, update_phi!(irsv)) + kill_edge!(irsv, bb, stmt.label) elseif isa(stmt, ReturnNode) # Nothing to do else - @assert !isexpr(stmt, :enter) - kill_edge!(ir, bb, bb+1, update_phi!(irsv)) + @assert !isa(stmt, EnterNode) + kill_edge!(irsv, bb, bb+1) end end -function reprocess_instruction!(interp::AbstractInterpreter, idx::Int, bb::Union{Int,Nothing}, - @nospecialize(stmt), @nospecialize(typ), irsv::IRInterpretationState) +function kill_edge!(irsv::IRInterpretationState, from::Int, to::Int) + kill_edge!(get!(irsv.lazyreachability), irsv.ir.cfg, from, to, + update_phi!(irsv), kill_block!(irsv.ir)) +end + +function reprocess_instruction!(interp::AbstractInterpreter, inst::Instruction, idx::Int, + bb::Union{Int,Nothing}, irsv::IRInterpretationState) ir = irsv.ir - inst = ir[SSAValue(idx)] + stmt = inst[:stmt] if isa(stmt, GotoIfNot) cond = stmt.cond condval = maybe_extract_const_bool(argextype(cond, ir)) @@ -109,14 +126,14 @@ function reprocess_instruction!(interp::AbstractInterpreter, idx::Int, bb::Union if bb === nothing bb = block_for_inst(ir, idx) end - inst[:flag] |= IR_FLAG_NOTHROW + add_flag!(inst, IR_FLAG_NOTHROW) if condval inst[:stmt] = nothing inst[:type] = Any - kill_edge!(ir, bb, stmt.dest, update_phi!(irsv)) + kill_edge!(irsv, bb, stmt.dest) else inst[:stmt] = GotoNode(stmt.dest) - kill_edge!(ir, bb, bb+1, update_phi!(irsv)) + kill_edge!(irsv, bb, bb+1) end return true end @@ -127,14 +144,14 @@ function reprocess_instruction!(interp::AbstractInterpreter, idx::Int, bb::Union head = stmt.head if head === :call || head === :foreigncall || head === :new || head === :splatnew || head === :static_parameter || head === :isdefined || head === :boundscheck (; rt, effects) = abstract_eval_statement_expr(interp, stmt, nothing, irsv) - inst[:flag] |= flags_for_effects(effects) + add_flag!(inst, flags_for_effects(effects)) elseif head === :invoke - rt, (nothrow, noub) = concrete_eval_invoke(interp, stmt, stmt.args[1]::MethodInstance, irsv) + rt, (nothrow, noub) = abstract_eval_invoke_inst(interp, inst, irsv) if nothrow - inst[:flag] |= IR_FLAG_NOTHROW + add_flag!(inst, IR_FLAG_NOTHROW) end if noub - inst[:flag] |= IR_FLAG_NOUB + add_flag!(inst, IR_FLAG_NOUB) end elseif head === :throw_undef_if_not condval = maybe_extract_const_bool(argextype(stmt.args[2], ir)) @@ -148,11 +165,19 @@ function reprocess_instruction!(interp::AbstractInterpreter, idx::Int, bb::Union elseif head === :gc_preserve_begin || head === :gc_preserve_end return false + elseif head === :leave + return false else + Core.println(stmt) error("reprocess_instruction!: unhandled expression found") end elseif isa(stmt, PhiNode) rt = abstract_eval_phi_stmt(interp, stmt, idx, irsv) + elseif isa(stmt, UpsilonNode) + rt = argextype(stmt.val, irsv.ir) + elseif isa(stmt, PhiCNode) + # Currently not modeled + return false elseif isa(stmt, ReturnNode) # Handled at the very end return false @@ -168,11 +193,11 @@ function reprocess_instruction!(interp::AbstractInterpreter, idx::Int, bb::Union if rt !== nothing if isa(rt, Const) inst[:type] = rt - if is_inlineable_constant(rt.val) && (inst[:flag] & (IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW)) == IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW + if is_inlineable_constant(rt.val) && has_flag(inst, IR_FLAGS_REMOVABLE) inst[:stmt] = quoted(rt.val) end return true - elseif !⊑(typeinf_lattice(interp), typ, rt) + elseif !⊑(typeinf_lattice(interp), inst[:type], rt) inst[:type] = rt return true end @@ -193,8 +218,8 @@ function process_terminator!(@nospecialize(stmt), bb::Int, bb_ip::BitSetBoundedM backedge || push!(bb_ip, stmt.dest) push!(bb_ip, bb+1) return backedge - elseif isexpr(stmt, :enter) - dest = stmt.args[1]::Int + elseif isa(stmt, EnterNode) + dest = stmt.catch_dest @assert dest > bb push!(bb_ip, dest) push!(bb_ip, bb+1) @@ -226,7 +251,7 @@ function scan!(callback, scanner::BBScanner, forwards_only::Bool) lstmt = last(stmts) for idx = stmts inst = ir[SSAValue(idx)] - ret = callback(inst, idx, lstmt, bb) + ret = callback(inst, lstmt, bb) ret === nothing && return true ret::Bool || break idx == lstmt && process_terminator!(inst[:stmt], bb, bb_ip) && forwards_only && return false @@ -236,11 +261,11 @@ function scan!(callback, scanner::BBScanner, forwards_only::Bool) end function populate_def_use_map!(tpdum::TwoPhaseDefUseMap, scanner::BBScanner) - scan!(scanner, false) do inst::Instruction, idx::Int, lstmt::Int, bb::Int + scan!(scanner, false) do inst::Instruction, lstmt::Int, bb::Int for ur in userefs(inst) val = ur[] if isa(val, SSAValue) - push!(tpdum[val.id], idx) + push!(tpdum[val.id], inst.idx) end end return true @@ -249,12 +274,21 @@ end populate_def_use_map!(tpdum::TwoPhaseDefUseMap, ir::IRCode) = populate_def_use_map!(tpdum, BBScanner(ir)) +function is_all_const_call(@nospecialize(stmt), interp::AbstractInterpreter, irsv::IRInterpretationState) + isexpr(stmt, :call) || return false + @inbounds for i = 2:length(stmt.args) + argtype = abstract_eval_value(interp, stmt.args[i], nothing, irsv) + is_const_argtype(argtype) || return false + end + return true +end + function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IRInterpretationState; externally_refined::Union{Nothing,BitSet} = nothing) - interp = switch_to_irinterp(interp) - (; ir, tpdum, ssa_refined) = irsv + @assert isempty(ir.new_nodes) "IRCode should be compacted before irinterp" + all_rets = Int[] scanner = BBScanner(ir) @@ -263,15 +297,19 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR # Fast path: Scan both use counts and refinement in one single pass of # of the instructions. In the absence of backedges, this will # converge. - completed_scan = scan!(scanner, true) do inst::Instruction, idx::Int, lstmt::Int, bb::Int + completed_scan = scan!(scanner, true) do inst::Instruction, lstmt::Int, bb::Int + idx = inst.idx irsv.curridx = idx - stmt = ir.stmts[idx][:stmt] - typ = ir.stmts[idx][:type] - flag = ir.stmts[idx][:flag] + stmt = inst[:stmt] + typ = inst[:type] + flag = inst[:flag] any_refined = false - if (flag & IR_FLAG_REFINED) != 0 + if has_flag(flag, IR_FLAG_REFINED) + any_refined = true + sub_flag!(inst, IR_FLAG_REFINED) + elseif is_all_const_call(stmt, interp, irsv) + # force reinference on calls with all constant arguments any_refined = true - ir.stmts[idx][:flag] &= ~IR_FLAG_REFINED end for ur in userefs(stmt) val = ur[] @@ -287,16 +325,15 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR delete!(ssa_refined, idx) end check_ret!(stmt, idx) - is_terminator_or_phi = (isa(stmt, PhiNode) || isa(stmt, GotoNode) || - isa(stmt, GotoIfNot) || isa(stmt, ReturnNode) || isexpr(stmt, :enter)) + is_terminator_or_phi = (isa(stmt, PhiNode) || isterminator(stmt)) if typ === Bottom && !(idx == lstmt && is_terminator_or_phi) return true end - if (any_refined && reprocess_instruction!(interp, idx, bb, stmt, typ, irsv)) || + if (any_refined && reprocess_instruction!(interp, inst, idx, bb, irsv)) || (externally_refined !== nothing && idx in externally_refined) push!(ssa_refined, idx) - stmt = ir.stmts[idx][:stmt] - typ = ir.stmts[idx][:type] + stmt = inst[:stmt] + typ = inst[:type] end if typ === Bottom && !is_terminator_or_phi kill_terminator_edges!(irsv, lstmt, bb) @@ -316,12 +353,13 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR stmt_ip = BitSetBoundedMinPrioritySet(length(ir.stmts)) # Slow Path Phase 1.A: Complete use scanning - scan!(scanner, false) do inst::Instruction, idx::Int, lstmt::Int, bb::Int + scan!(scanner, false) do inst::Instruction, lstmt::Int, bb::Int + idx = inst.idx irsv.curridx = idx stmt = inst[:stmt] flag = inst[:flag] - if (flag & IR_FLAG_REFINED) != 0 - inst[:flag] &= ~IR_FLAG_REFINED + if has_flag(flag, IR_FLAG_REFINED) + sub_flag!(inst, IR_FLAG_REFINED) push!(stmt_ip, idx) end check_ret!(stmt, idx) @@ -356,9 +394,7 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR idx = popfirst!(stmt_ip) irsv.curridx = idx inst = ir[SSAValue(idx)] - stmt = inst[:stmt] - typ = inst[:type] - if reprocess_instruction!(interp, idx, nothing, stmt, typ, irsv) + if reprocess_instruction!(interp, inst, idx, nothing, irsv) append!(stmt_ip, tpdum[idx]) end end @@ -378,9 +414,14 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR nothrow = noub = true for idx = 1:length(ir.stmts) + if ir[SSAValue(idx)][:stmt] === nothing + # skip `nothing` statement, which might be inserted as a dummy node, + # e.g. by `finish_current_bb!` without explicitly marking it as `:nothrow` + continue + end flag = ir[SSAValue(idx)][:flag] - nothrow &= !iszero(flag & IR_FLAG_NOTHROW) - noub &= !iszero(flag & IR_FLAG_NOUB) + nothrow &= has_flag(flag, IR_FLAG_NOTHROW) + noub &= has_flag(flag, IR_FLAG_NOUB) (nothrow | noub) || break end diff --git a/base/compiler/ssair/legacy.jl b/base/compiler/ssair/legacy.jl index 8f557f4fd2241..b45db03875801 100644 --- a/base/compiler/ssair/legacy.jl +++ b/base/compiler/ssair/legacy.jl @@ -1,16 +1,16 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license """ - inflate_ir!(ci::CodeInfo, linfo::MethodInstance) -> ir::IRCode + inflate_ir!(ci::CodeInfo, mi::MethodInstance) -> ir::IRCode inflate_ir!(ci::CodeInfo, sptypes::Vector{VarState}, argtypes::Vector{Any}) -> ir::IRCode Inflates `ci::CodeInfo`-IR to `ir::IRCode`-format. This should be used with caution as it is a in-place transformation where the fields of the original `ci::CodeInfo` are modified. """ -function inflate_ir!(ci::CodeInfo, linfo::MethodInstance) - sptypes = sptypes_from_meth_instance(linfo) - argtypes, _ = matching_cache_argtypes(fallback_lattice, linfo) +function inflate_ir!(ci::CodeInfo, mi::MethodInstance) + sptypes = sptypes_from_meth_instance(mi) + argtypes = matching_cache_argtypes(fallback_lattice, mi) return inflate_ir!(ci, sptypes, argtypes) end function inflate_ir!(ci::CodeInfo, sptypes::Vector{VarState}, argtypes::Vector{Any}) @@ -25,9 +25,8 @@ function inflate_ir!(ci::CodeInfo, sptypes::Vector{VarState}, argtypes::Vector{A code[i] = GotoIfNot(stmt.cond, block_for_inst(cfg, stmt.dest)) elseif isa(stmt, PhiNode) code[i] = PhiNode(Int32[block_for_inst(cfg, Int(edge)) for edge in stmt.edges], stmt.values) - elseif isexpr(stmt, :enter) - stmt.args[1] = block_for_inst(cfg, stmt.args[1]::Int) - code[i] = stmt + elseif isa(stmt, EnterNode) + code[i] = EnterNode(stmt, stmt.catch_dest == 0 ? 0 : block_for_inst(cfg, stmt.catch_dest)) end end nstmts = length(code) @@ -36,24 +35,23 @@ function inflate_ir!(ci::CodeInfo, sptypes::Vector{VarState}, argtypes::Vector{A ssavaluetypes = Any[ Any for i = 1:ssavaluetypes::Int ] end info = CallInfo[NoCallInfo() for i = 1:nstmts] - stmts = InstructionStream(code, ssavaluetypes, info, ci.codelocs, ci.ssaflags) - linetable = ci.linetable - if !isa(linetable, Vector{LineInfoNode}) - linetable = collect(LineInfoNode, linetable::Vector{Any})::Vector{LineInfoNode} - end + di = DebugInfoStream(nothing, ci.debuginfo, nstmts) + stmts = InstructionStream(code, ssavaluetypes, info, di.codelocs, ci.ssaflags) meta = Expr[] - return IRCode(stmts, cfg, linetable, argtypes, meta, sptypes) + return IRCode(stmts, cfg, di, argtypes, meta, sptypes) end """ - inflate_ir(ci::CodeInfo, linfo::MethodInstance) -> ir::IRCode - inflate_ir(ci::CodeInfo, sptypes::Vector{VarState}, argtypes::Vector{Any}) -> ir::IRCode inflate_ir(ci::CodeInfo) -> ir::IRCode + inflate_ir(ci::CodeInfo, mi::MethodInstance) -> ir::IRCode + inflate_ir(ci::CodeInfo, argtypes::Vector{Any}) -> ir::IRCode + inflate_ir(ci::CodeInfo, sptypes::Vector{VarState}, argtypes::Vector{Any}) -> ir::IRCode Non-destructive version of `inflate_ir!`. Mainly used for testing or interactive use. """ -inflate_ir(ci::CodeInfo, linfo::MethodInstance) = inflate_ir!(copy(ci), linfo) +inflate_ir(ci::CodeInfo, mi::MethodInstance) = inflate_ir!(copy(ci), mi) +inflate_ir(ci::CodeInfo, argtypes::Vector{Any}) = inflate_ir(ci, VarState[], argtypes) inflate_ir(ci::CodeInfo, sptypes::Vector{VarState}, argtypes::Vector{Any}) = inflate_ir!(copy(ci), sptypes, argtypes) function inflate_ir(ci::CodeInfo) parent = ci.parent @@ -74,15 +72,17 @@ function replace_code_newstyle!(ci::CodeInfo, ir::IRCode) stmts = ir.stmts code = ci.code = stmts.stmt ssavaluetypes = ci.ssavaluetypes = stmts.type - codelocs = ci.codelocs = stmts.line + codelocs = stmts.line ssaflags = ci.ssaflags = stmts.flag - linetable = ci.linetable = ir.linetable + debuginfo = ir.debuginfo for metanode in ir.meta push!(code, metanode) - push!(codelocs, 1) + push!(codelocs, 1, 0, 0) push!(ssavaluetypes, Any) push!(ssaflags, IR_FLAG_NULL) end + @assert debuginfo.codelocs === stmts.line "line table not from debuginfo" + ci.debuginfo = DebugInfo(debuginfo, length(code)) # Translate BB Edges to statement edges # (and undo normalization for now) for i = 1:length(code) @@ -93,9 +93,8 @@ function replace_code_newstyle!(ci::CodeInfo, ir::IRCode) code[i] = GotoIfNot(stmt.cond, first(ir.cfg.blocks[stmt.dest].stmts)) elseif isa(stmt, PhiNode) code[i] = PhiNode(Int32[edge == 0 ? 0 : last(ir.cfg.blocks[edge].stmts) for edge in stmt.edges], stmt.values) - elseif isexpr(stmt, :enter) - stmt.args[1] = first(ir.cfg.blocks[stmt.args[1]::Int].stmts) - code[i] = stmt + elseif isa(stmt, EnterNode) + code[i] = EnterNode(stmt, stmt.catch_dest == 0 ? 0 : first(ir.cfg.blocks[stmt.catch_dest].stmts)) end end end diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index af3d91d8b69f4..8ccc366176765 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -6,6 +6,13 @@ function is_known_call(@nospecialize(x), @nospecialize(func), ir::Union{IRCode,I return singleton_type(ft) === func end +function is_known_invoke_or_call(@nospecialize(x), @nospecialize(func), ir::Union{IRCode,IncrementalCompact}) + isinvoke = isexpr(x, :invoke) + (isinvoke || isexpr(x, :call)) || return false + ft = argextype(x.args[isinvoke ? 2 : 1], ir) + return singleton_type(ft) === func +end + struct SSAUse kind::Symbol idx::Int @@ -183,8 +190,18 @@ function collect_leaves(compact::IncrementalCompact, @nospecialize(val), @nospec return walk_to_defs(compact, val, typeconstraint, predecessors, 𝕃ₒ) end -function simple_walk(compact::IncrementalCompact, @nospecialize(defssa#=::AnySSAValue=#), - callback = (@nospecialize(pi), @nospecialize(idx)) -> false) +function trivial_walker(@nospecialize(pi), @nospecialize(idx)) + return nothing +end + +function pi_walker(@nospecialize(pi), @nospecialize(idx)) + if isa(pi, PiNode) + return LiftedValue(pi.val) + end + return nothing +end + +function simple_walk(compact::IncrementalCompact, @nospecialize(defssa#=::AnySSAValue=#), callback=trivial_walker) while true if isa(defssa, OldSSAValue) if already_inserted(compact, defssa) @@ -200,27 +217,26 @@ function simple_walk(compact::IncrementalCompact, @nospecialize(defssa#=::AnySSA end end def = compact[defssa][:stmt] - if isa(def, PiNode) - if callback(def, defssa) - return defssa - end - def = def.val - if isa(def, SSAValue) - is_old(compact, defssa) && (def = OldSSAValue(def.id)) - else - return def - end - defssa = def - elseif isa(def, AnySSAValue) + if isa(def, AnySSAValue) callback(def, defssa) if isa(def, SSAValue) is_old(compact, defssa) && (def = OldSSAValue(def.id)) end defssa = def - elseif isa(def, Union{PhiNode, PhiCNode, Expr, GlobalRef}) + elseif isa(def, Union{PhiNode, PhiCNode, GlobalRef}) return defssa else - return def + new_def = callback(def, defssa) + if new_def === nothing + return defssa + end + new_def = new_def.val + if !isa(new_def, AnySSAValue) + return new_def + elseif isa(new_def, SSAValue) + is_old(compact, defssa) && (new_def = OldSSAValue(new_def.id)) + end + defssa = new_def end end end @@ -230,8 +246,9 @@ function simple_walk_constraint(compact::IncrementalCompact, @nospecialize(defss callback = function (@nospecialize(pi), @nospecialize(idx)) if isa(pi, PiNode) typeconstraint = typeintersect(typeconstraint, widenconst(pi.typ)) + return LiftedValue(pi.val) end - return false + return nothing end def = simple_walk(compact, defssa, callback) return Pair{Any, Any}(def, typeconstraint) @@ -266,7 +283,9 @@ function walk_to_defs(compact::IncrementalCompact, @nospecialize(defssa), @nospe def = compact[defssa][:stmt] values = predecessors(def, compact) if values !== nothing - push!(visited_philikes, defssa) + if isa(def, PhiNode) || length(values) > 1 + push!(visited_philikes, defssa) + end possible_predecessors = Int[] for n in 1:length(values) @@ -329,17 +348,23 @@ function record_immutable_preserve!(new_preserves::Vector{Any}, def::Expr, compa end function already_inserted(compact::IncrementalCompact, old::OldSSAValue) - id = old.id - if id < length(compact.ir.stmts) - return id < compact.idx - end - id -= length(compact.ir.stmts) - if id < length(compact.ir.new_nodes) - return already_inserted(compact, OldSSAValue(compact.ir.new_nodes.info[id].pos)) + already_inserted_ssa(compact, compact.idx-1)(0, old) +end + +function already_inserted_ssa(compact::IncrementalCompact, processed_idx::Int) + return function did_already_insert(phi_arg::Int, old::OldSSAValue) + id = old.id + if id <= length(compact.ir.stmts) + return id <= processed_idx + end + id -= length(compact.ir.stmts) + if id <= length(compact.ir.new_nodes) + return did_already_insert(phi_arg, OldSSAValue(compact.ir.new_nodes.info[id].pos)) + end + id -= length(compact.ir.new_nodes) + @assert id <= length(compact.pending_nodes) + return !(id in compact.pending_perm) end - id -= length(compact.ir.new_nodes) - @assert id <= length(compact.pending_nodes) - return !(id in compact.pending_perm) end function is_pending(compact::IncrementalCompact, old::OldSSAValue) @@ -382,7 +407,9 @@ function lift_leaves(compact::IncrementalCompact, field::Int, elseif isexpr(def, :new) typ = unwrap_unionall(widenconst(types(compact)[leaf])) (isa(typ, DataType) && !isabstracttype(typ)) || return nothing - @assert !ismutabletype(typ) + if ismutabletype(typ) + isconst(typ, field) || return nothing + end if length(def.args) < 1+field if field > fieldcount(typ) return nothing @@ -464,19 +491,15 @@ function lift_arg!( if is_old(compact, leaf) && isa(lifted, SSAValue) lifted = OldSSAValue(lifted.id) if already_inserted(compact, lifted) - lifted = compact.ssa_rename[lifted.id] - if isa(lifted, Refined) - lifted = lifted.val + new_lifted = compact.ssa_rename[lifted.id] + if isa(new_lifted, Refined) + new_lifted = new_lifted.val + end + # Special case: If lifted happens to be the statement we're currently processing, + # leave it as old SSAValue in case we decide to handle this in the renamer + if !isa(new_lifted, SSAValue) || new_lifted != SSAValue(compact.result_idx-1) + lifted = new_lifted end - end - end - if isa(lifted, GlobalRef) || isa(lifted, Expr) - lifted = insert_node!(compact, leaf, effect_free_and_nothrow(NewInstruction(lifted, argextype(lifted, compact)))) - compact[leaf] = nothing - stmt.args[argidx] = lifted - compact[leaf] = stmt - if isa(leaf, SSAValue) && leaf.id < compact.result_idx - push!(compact.late_fixup, leaf.id) end end lifted_leaves[cache_key] = LiftedValue(lifted) @@ -593,10 +616,12 @@ function lift_comparison_leaves!(@specialize(tfunc), end # perform lifting - lifted_val = perform_lifting!(compact, - visited_philikes, cmp, Bool, lifted_leaves::LiftedLeaves, val, nothing)::LiftedValue + (lifted_val, nest) = perform_lifting!(compact, + visited_philikes, cmp, Bool, lifted_leaves::LiftedLeaves, val, nothing) - compact[idx] = lifted_val.val + compact[idx] = (lifted_val::LiftedValue).val + + finish_phi_nest!(compact, nest) end struct IfElseCall @@ -614,13 +639,14 @@ end struct SkipToken end; const SKIP_TOKEN = SkipToken() function lifted_value(compact::IncrementalCompact, @nospecialize(old_node_ssa#=::AnySSAValue=#), @nospecialize(old_value), - lifted_philikes::Vector{LiftedPhilike}, lifted_leaves::Union{LiftedLeaves, LiftedDefs}, reverse_mapping::IdDict{AnySSAValue, Int}) + lifted_philikes::Vector{LiftedPhilike}, lifted_leaves::Union{LiftedLeaves, LiftedDefs}, reverse_mapping::IdDict{AnySSAValue, Int}, + walker_callback) val = old_value if is_old(compact, old_node_ssa) && isa(val, SSAValue) val = OldSSAValue(val.id) end if isa(val, AnySSAValue) - val = simple_walk(compact, val) + val = simple_walk(compact, val, def_walker(lifted_leaves, reverse_mapping, walker_callback)) end if val in keys(lifted_leaves) lifted_val = lifted_leaves[val] @@ -630,8 +656,7 @@ function lifted_value(compact::IncrementalCompact, @nospecialize(old_node_ssa#=: lifted_val === nothing && return UNDEF_TOKEN val = lifted_val.val if isa(val, AnySSAValue) - callback = (@nospecialize(pi), @nospecialize(idx)) -> true - val = simple_walk(compact, val, callback) + val = simple_walk(compact, val, pi_walker) end return val elseif isa(val, AnySSAValue) && val in keys(reverse_mapping) @@ -642,80 +667,23 @@ function lifted_value(compact::IncrementalCompact, @nospecialize(old_node_ssa#=: end function is_old(compact, @nospecialize(old_node_ssa)) - isa(old_node_ssa, OldSSAValue) && - !is_pending(compact, old_node_ssa) && - !already_inserted(compact, old_node_ssa) + isa(old_node_ssa, OldSSAValue) || return false + is_pending(compact, old_node_ssa) && return false + already_inserted(compact, old_node_ssa) && return false + return true end -function perform_lifting!(compact::IncrementalCompact, - visited_philikes::Vector{AnySSAValue}, @nospecialize(cache_key), - @nospecialize(result_t), lifted_leaves::Union{LiftedLeaves, LiftedDefs}, @nospecialize(stmt_val), - lazydomtree::Union{LazyDomtree,Nothing}) - reverse_mapping = IdDict{AnySSAValue, Int}() - for id in 1:length(visited_philikes) - reverse_mapping[visited_philikes[id]] = id - end - - # Check if all the lifted leaves are the same - local the_leaf - all_same = true - for (_, val) in lifted_leaves - if !@isdefined(the_leaf) - the_leaf = val - continue - end - if val !== the_leaf - all_same = false - end - end - - the_leaf_val = isa(the_leaf, LiftedValue) ? the_leaf.val : nothing - if !isa(the_leaf_val, SSAValue) - all_same = false - end - - if all_same - dominates_all = true - if lazydomtree !== nothing - domtree = get!(lazydomtree) - for item in visited_philikes - if !dominates_ssa(compact, domtree, the_leaf_val, item) - dominates_all = false - break - end - end - if dominates_all - return the_leaf - end - end - end - - # Insert PhiNodes - nphilikes = length(visited_philikes) - lifted_philikes = Vector{LiftedPhilike}(undef, nphilikes) - for i = 1:nphilikes - old_ssa = visited_philikes[i] - old_inst = compact[old_ssa] - old_node = old_inst[:stmt]::Union{PhiNode,Expr} - if isa(old_node, PhiNode) - new_node = PhiNode() - ssa = insert_node!(compact, old_ssa, effect_free_and_nothrow(NewInstruction(new_node, result_t))) - lifted_philikes[i] = LiftedPhilike(ssa, new_node, true) - else - @assert is_known_call(old_node, Core.ifelse, compact) - ifelse_func, condition = old_node.args - if is_old(compact, old_ssa) && isa(condition, SSAValue) - condition = OldSSAValue(condition.id) - end - - new_node = Expr(:call, ifelse_func, condition) # Renamed then_result, else_result added below - new_inst = NewInstruction(new_node, result_t, NoCallInfo(), old_inst[:line], old_inst[:flag]) - - ssa = insert_node!(compact, old_ssa, new_inst, #= attach_after =# true) - lifted_philikes[i] = LiftedPhilike(ssa, IfElseCall(new_node), true) - end - end +struct PhiNest{C} + visited_philikes::Vector{AnySSAValue} + lifted_philikes::Vector{LiftedPhilike} + lifted_leaves::Union{LiftedLeaves, LiftedDefs} + reverse_mapping::IdDict{AnySSAValue, Int} + walker_callback::C +end +function finish_phi_nest!(compact::IncrementalCompact, nest::PhiNest) + (;visited_philikes, lifted_philikes, lifted_leaves, reverse_mapping, walker_callback) = nest + nphilikes = length(lifted_philikes) # Fix up arguments for i = 1:nphilikes (old_node_ssa, lf) = visited_philikes[i], lifted_philikes[i] @@ -729,7 +697,7 @@ function perform_lifting!(compact::IncrementalCompact, for i = 1:length(old_node.values) isassigned(old_node.values, i) || continue val = lifted_value(compact, old_node_ssa, old_node.values[i], - lifted_philikes, lifted_leaves, reverse_mapping) + lifted_philikes, lifted_leaves, reverse_mapping, walker_callback) val !== SKIP_TOKEN && push!(new_node.edges, old_node.edges[i]) if val === UNDEF_TOKEN resize!(new_node.values, length(new_node.values)+1) @@ -743,9 +711,9 @@ function perform_lifting!(compact::IncrementalCompact, then_result, else_result = old_node.args[3], old_node.args[4] then_result = lifted_value(compact, old_node_ssa, then_result, - lifted_philikes, lifted_leaves, reverse_mapping) + lifted_philikes, lifted_leaves, reverse_mapping, walker_callback) else_result = lifted_value(compact, old_node_ssa, else_result, - lifted_philikes, lifted_leaves, reverse_mapping) + lifted_philikes, lifted_leaves, reverse_mapping, walker_callback) # In cases where the Core.ifelse condition is statically-known, e.g., thanks # to a PiNode from a guarding conditional, replace with the remaining branch. @@ -753,8 +721,7 @@ function perform_lifting!(compact::IncrementalCompact, only_result = (then_result === SKIP_TOKEN) ? else_result : then_result # Replace Core.ifelse(%cond, %a, %b) with %a - compact[lf.ssa][:stmt] = only_result - should_count && _count_added_node!(compact, only_result) + compact[lf.ssa] = only_result # Note: Core.ifelse(%cond, %a, %b) has observable effects (!nothrow), but since # we have not deleted the preceding statement that this was derived from, this @@ -774,19 +741,116 @@ function perform_lifting!(compact::IncrementalCompact, push!(lfnode.call.args, else_result) end end +end + +function def_walker(lifted_leaves::Union{LiftedLeaves, LiftedDefs}, reverse_mapping::IdDict{AnySSAValue, Int}, walker_callback) + function (@nospecialize(walk_def), @nospecialize(defssa)) + if (defssa in keys(lifted_leaves)) || (isa(defssa, AnySSAValue) && defssa in keys(reverse_mapping)) + return nothing + end + isa(walk_def, PiNode) && return LiftedValue(walk_def.val) + return walker_callback(walk_def, defssa) + end +end + +function perform_lifting!(compact::IncrementalCompact, + visited_philikes::Vector{AnySSAValue}, @nospecialize(cache_key), + @nospecialize(result_t), lifted_leaves::Union{LiftedLeaves, LiftedDefs}, @nospecialize(stmt_val), + lazydomtree::Union{LazyDomtree,Nothing}, walker_callback = trivial_walker) + reverse_mapping = IdDict{AnySSAValue, Int}() + for id in 1:length(visited_philikes) + reverse_mapping[visited_philikes[id]] = id + end + + # Check if all the lifted leaves are the same + local the_leaf + all_same = true + for (_, val) in lifted_leaves + if !@isdefined(the_leaf) + the_leaf = val + continue + end + if val !== the_leaf + all_same = false + end + end + + if all_same && isa(the_leaf, LiftedValue) + dominates_all = true + the_leaf_val = the_leaf.val + if isa(the_leaf_val, AnySSAValue) + if lazydomtree === nothing + # Must conservatively assume this + dominates_all = false + else + # This code guards against the possibility of accidentally forwarding a value from a + # previous iteration. Consider for example: + # + # %p = phi(%arg, %t) + # %b = <...> + # %c = getfield(%p, 1) + # %t = tuple(%b) + # + # It would be incorrect to replace `%c` by `%b`, because that would read the value of + # `%b` in the *current* iteration, while the value of `%b` that comes in via `%p` is + # that of the previous iteration. + domtree = get!(lazydomtree) + for item in visited_philikes + if !dominates_ssa(compact, domtree, the_leaf_val, item) + dominates_all = false + break + end + end + end + end + if dominates_all + if isa(the_leaf_val, OldSSAValue) + the_leaf = LiftedValue(simple_walk(compact, the_leaf_val)) + end + return Pair{Any, PhiNest}(the_leaf, PhiNest(visited_philikes, Vector{LiftedPhilike}(undef, 0), lifted_leaves, reverse_mapping, walker_callback)) + end + end + + # Insert PhiNodes + nphilikes = length(visited_philikes) + lifted_philikes = Vector{LiftedPhilike}(undef, nphilikes) + for i = 1:nphilikes + old_ssa = visited_philikes[i] + old_inst = compact[old_ssa] + old_node = old_inst[:stmt]::Union{PhiNode,Expr} + if isa(old_node, PhiNode) + new_node = PhiNode() + ssa = insert_node!(compact, old_ssa, removable_if_unused(NewInstruction(new_node, result_t))) + lifted_philikes[i] = LiftedPhilike(ssa, new_node, true) + else + @assert is_known_call(old_node, Core.ifelse, compact) + ifelse_func, condition = old_node.args + if is_old(compact, old_ssa) && isa(condition, SSAValue) + condition = OldSSAValue(condition.id) + end + + new_node = Expr(:call, ifelse_func, condition) # Renamed then_result, else_result added below + new_inst = NewInstruction(new_node, result_t, NoCallInfo(), old_inst[:line], old_inst[:flag]) + + ssa = insert_node!(compact, old_ssa, new_inst, #= attach_after =# true) + lifted_philikes[i] = LiftedPhilike(ssa, IfElseCall(new_node), true) + end + end # Fixup the stmt itself if isa(stmt_val, Union{SSAValue, OldSSAValue}) - stmt_val = simple_walk(compact, stmt_val) + stmt_val = simple_walk(compact, stmt_val, def_walker(lifted_leaves, reverse_mapping, walker_callback)) end if stmt_val in keys(lifted_leaves) - return lifted_leaves[stmt_val] + stmt_val = lifted_leaves[stmt_val] elseif isa(stmt_val, AnySSAValue) && stmt_val in keys(reverse_mapping) - return LiftedValue(lifted_philikes[reverse_mapping[stmt_val]].ssa) + stmt_val = LiftedValue(lifted_philikes[reverse_mapping[stmt_val]].ssa) + else + error() end - return stmt_val # N.B. should never happen + return Pair{Any, PhiNest}(stmt_val, PhiNest(visited_philikes, lifted_philikes, lifted_leaves, reverse_mapping, walker_callback)) end function lift_svec_ref!(compact::IncrementalCompact, idx::Int, stmt::Expr) @@ -817,7 +881,112 @@ function lift_svec_ref!(compact::IncrementalCompact, idx::Int, stmt::Expr) return end -# TODO: We could do the whole lifing machinery here, but really all +function lift_leaves_keyvalue(compact::IncrementalCompact, @nospecialize(key), + leaves::Vector{Any}, 𝕃ₒ::AbstractLattice) + # For every leaf, the lifted value + lifted_leaves = LiftedLeaves() + for i = 1:length(leaves) + leaf = leaves[i] + cache_key = leaf + if isa(leaf, AnySSAValue) + (def, leaf) = walk_to_def(compact, leaf) + if is_known_invoke_or_call(def, Core.OptimizedGenerics.KeyValue.set, compact) + @assert isexpr(def, :invoke) + if length(def.args) in (5, 6) + set_key = def.args[end-1] + set_val_idx = length(def.args) + elseif length(def.args) == 4 + # Key is deleted + # TODO: Model this + return nothing + elseif length(def.args) == 3 + # The whole collection is deleted + # TODO: Model this + return nothing + else + return nothing + end + if set_key === key || (egal_tfunc(𝕃ₒ, argextype(key, compact), argextype(set_key, compact)) == Const(true)) + lift_arg!(compact, leaf, cache_key, def, set_val_idx, lifted_leaves) + continue + end + end + end + return nothing + end + return lifted_leaves +end + +function keyvalue_predecessors(@nospecialize(key), 𝕃ₒ::AbstractLattice) + function(@nospecialize(def), compact::IncrementalCompact) + if is_known_invoke_or_call(def, Core.OptimizedGenerics.KeyValue.set, compact) + @assert isexpr(def, :invoke) + if length(def.args) in (5, 6) + collection = def.args[end-2] + set_key = def.args[end-1] + set_val_idx = length(def.args) + elseif length(def.args) == 4 + collection = def.args[end-1] + # Key is deleted + # TODO: Model this + return nothing + elseif length(def.args) == 3 + collection = def.args[end] + # The whole collection is deleted + # TODO: Model this + return nothing + else + return nothing + end + if set_key === key || (egal_tfunc(𝕃ₒ, argextype(key, compact), argextype(set_key, compact)) == Const(true)) + # This is an actual def + return nothing + end + return Any[collection] + end + return phi_or_ifelse_predecessors(def, compact) + end +end + +function lift_keyvalue_get!(compact::IncrementalCompact, idx::Int, stmt::Expr, 𝕃ₒ::AbstractLattice) + collection = stmt.args[end-1] + key = stmt.args[end] + + leaves, visited_philikes = collect_leaves(compact, collection, Any, 𝕃ₒ, keyvalue_predecessors(key, 𝕃ₒ)) + isempty(leaves) && return + + lifted_leaves = lift_leaves_keyvalue(compact, key, leaves, 𝕃ₒ) + lifted_leaves === nothing && return + + result_t = Union{} + for v in values(lifted_leaves) + v === nothing && return + result_t = tmerge(𝕃ₒ, result_t, argextype(v.val, compact)) + end + + function keyvalue_walker(@nospecialize(def), _) + if is_known_invoke_or_call(def, Core.OptimizedGenerics.KeyValue.set, compact) + @assert length(def.args) in (5, 6) + return LiftedValue(def.args[end-2]) + end + return nothing + end + (lifted_val, nest) = perform_lifting!(compact, + visited_philikes, key, result_t, lifted_leaves, collection, nothing, + keyvalue_walker) + + compact[idx] = lifted_val === nothing ? nothing : Expr(:call, GlobalRef(Core, :tuple), lifted_val.val) + finish_phi_nest!(compact, nest) + if lifted_val !== nothing + if !⊑(𝕃ₒ, compact[SSAValue(idx)][:type], tuple_tfunc(𝕃ₒ, Any[result_t])) + add_flag!(compact[SSAValue(idx)], IR_FLAG_REFINED) + end + end + + return +end + +# TODO: We could do the whole lifting machinery here, but really all # we want to do is clean this up when it got inserted by inlining, # which always targets simple `svec` call or `_compute_sparams`, # so this specialized lifting would be enough @@ -932,22 +1101,22 @@ end function refine_new_effects!(𝕃ₒ::AbstractLattice, compact::IncrementalCompact, idx::Int, stmt::Expr) inst = compact[SSAValue(idx)] - if (inst[:flag] & (IR_FLAG_NOTHROW | IR_FLAG_EFFECT_FREE)) == (IR_FLAG_NOTHROW | IR_FLAG_EFFECT_FREE) + if has_flag(inst, IR_FLAGS_REMOVABLE) return # already accurate end - (consistent, effect_free_and_nothrow, nothrow) = new_expr_effect_flags(𝕃ₒ, stmt.args, compact, pattern_match_typeof) + (consistent, removable, nothrow) = new_expr_effect_flags(𝕃ₒ, stmt.args, compact, pattern_match_typeof) if consistent - inst[:flag] |= IR_FLAG_CONSISTENT + add_flag!(inst, IR_FLAG_CONSISTENT) end - if effect_free_and_nothrow - inst[:flag] |= IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW + if removable + add_flag!(inst, IR_FLAGS_REMOVABLE) elseif nothrow - inst[:flag] |= IR_FLAG_NOTHROW + add_flag!(inst, IR_FLAG_NOTHROW) end return nothing end -function fold_ifelse!(compact::IncrementalCompact, idx::Int, stmt::Expr) +function fold_ifelse!(compact::IncrementalCompact, idx::Int, stmt::Expr, 𝕃ₒ::AbstractLattice) length(stmt.args) == 4 || return false condarg = stmt.args[2] condtyp = argextype(condarg, compact) @@ -959,6 +1128,9 @@ function fold_ifelse!(compact::IncrementalCompact, idx::Int, stmt::Expr) compact[idx] = stmt.args[4] return true end + elseif ⊑(𝕃ₒ, condtyp, Bool) && stmt.args[3] === stmt.args[4] + compact[idx] = stmt.args[3] + return true end return false end @@ -971,8 +1143,26 @@ struct IntermediaryCollector intermediaries::SPCSet end function (this::IntermediaryCollector)(@nospecialize(pi), @nospecialize(ssa)) - push!(this.intermediaries, ssa.id) - return false + if !isa(pi, Expr) + push!(this.intermediaries, ssa.id) + end + return nothing +end + +function update_scope_mapping!(scope_mapping, bb, val) + current_mapping = scope_mapping[bb] + if current_mapping != SSAValue(0) + if val == SSAValue(0) + # Unreachable bbs will have SSAValue(0), but can branch into + # try/catch regions. We could validate with the domtree, but that's + # quite expensive for a debug check, so simply allow this without + # making any changes to mapping. + return + end + @assert current_mapping == val + return + end + scope_mapping[bb] = val end """ @@ -999,10 +1189,60 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) defuses = nothing # will be initialized once we encounter mutability in order to reduce dynamic allocations # initialization of domtree is delayed to avoid the expensive computation in many cases lazydomtree = LazyDomtree(ir) - for ((_, idx), stmt) in compact + scope_mapping::Union{Vector{SSAValue}, Nothing} = nothing + for ((old_idx, idx), stmt) in compact + # If we encounter any EnterNode with set :scope, propagate the current scope for all basic blocks, so + # we have easy access for current_scope folding below. + if !isa(stmt, Expr) + bb = compact.active_result_bb - 1 + if scope_mapping !== nothing && did_just_finish_bb(compact) + this_scope = scope_mapping[bb] + if isa(stmt, GotoIfNot) + update_scope_mapping!(scope_mapping, stmt.dest, this_scope) + update_scope_mapping!(scope_mapping, bb+1, this_scope) + elseif isa(stmt, GotoNode) + update_scope_mapping!(scope_mapping, stmt.label, this_scope) + elseif isa(stmt, EnterNode) + if stmt.catch_dest != 0 + update_scope_mapping!(scope_mapping, stmt.catch_dest, this_scope) + end + isdefined(stmt, :scope) || update_scope_mapping!(scope_mapping, bb+1, this_scope) + elseif !isa(stmt, ReturnNode) + update_scope_mapping!(scope_mapping, bb+1, this_scope) + end + end + if isa(stmt, EnterNode) + if isdefined(stmt, :scope) + if scope_mapping === nothing + scope_mapping = SSAValue[SSAValue(0) for i = 1:length(compact.cfg_transform.result_bbs)] + end + update_scope_mapping!(scope_mapping, bb+1, SSAValue(idx)) + end + end + continue + end + if scope_mapping !== nothing && did_just_finish_bb(compact) + bb = compact.active_result_bb - 1 + bbs = scope_mapping[bb] + if isexpr(stmt, :leave) && bbs != SSAValue(0) + # Here we want to count the number of scopes that we're leaving, + # which is the same as the number of EnterNodes being referenced + # by `stmt.args`. Which have :scope set. In practice, the frontend + # does emit these in order, so we could simply go to the last one, + # but we want to avoid making that semantic assumption. + for i = 1:length(stmt.args) + scope = stmt.args[i] + scope === nothing && continue + enter = compact[scope][:inst] + @assert isa(enter, EnterNode) + isdefined(enter, :scope) || continue + bbs = scope_mapping[block_for_inst(compact, bbs)] + end + end + update_scope_mapping!(scope_mapping, bb+1, bbs) + end # check whether this statement is `getfield` / `setfield!` (or other "interesting" statement) - isa(stmt, Expr) || continue - is_setfield = is_isdefined = is_finalizer = false + is_setfield = is_isdefined = is_finalizer = is_keyvalue_get = false field_ordering = :unspecified if is_known_call(stmt, setfield!, compact) 4 <= length(stmt.args) <= 5 || continue @@ -1045,11 +1285,7 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) preserved_arg = stmt.args[pidx] isa(preserved_arg, SSAValue) || continue let intermediaries = SPCSet() - callback = function (@nospecialize(pi), @nospecialize(ssa)) - push!(intermediaries, ssa.id) - return false - end - def = simple_walk(compact, preserved_arg, callback) + def = simple_walk(compact, preserved_arg, IntermediaryCollector(intermediaries)) isa(def, SSAValue) || continue defidx = def.id def = compact[def][:stmt] @@ -1091,7 +1327,18 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) elseif is_known_call(stmt, isa, compact) lift_comparison!(isa, compact, idx, stmt, 𝕃ₒ) elseif is_known_call(stmt, Core.ifelse, compact) - fold_ifelse!(compact, idx, stmt) + fold_ifelse!(compact, idx, stmt, 𝕃ₒ) + elseif is_known_invoke_or_call(stmt, Core.OptimizedGenerics.KeyValue.get, compact) + 2 == (length(stmt.args) - (isexpr(stmt, :invoke) ? 2 : 1)) || continue + lift_keyvalue_get!(compact, idx, stmt, 𝕃ₒ) + elseif is_known_call(stmt, Core.current_scope, compact) + length(stmt.args) == 1 || continue + scope_mapping !== nothing || continue + bb = compact.active_result_bb + did_just_finish_bb(compact) && (bb -= 1) + enter_ssa = scope_mapping[bb] + enter_ssa == SSAValue(0) && continue + compact[SSAValue(idx)] = (compact[enter_ssa][:stmt]::EnterNode).scope elseif isexpr(stmt, :new) refine_new_effects!(𝕃ₒ, compact, idx, stmt) end @@ -1167,9 +1414,26 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) result_t = tmerge(𝕃ₒ, result_t, argextype(v.val, compact)) end - lifted_val = perform_lifting!(compact, + (lifted_val, nest) = perform_lifting!(compact, visited_philikes, field, result_t, lifted_leaves, val, lazydomtree) + should_delete_node = false + line = compact[SSAValue(idx)][:line] + if lifted_val !== nothing && !⊑(𝕃ₒ, compact[SSAValue(idx)][:type], result_t) + compact[idx] = lifted_val === nothing ? nothing : lifted_val.val + add_flag!(compact[SSAValue(idx)], IR_FLAG_REFINED) + elseif lifted_val === nothing || isa(lifted_val.val, AnySSAValue) + # Save some work in a later compaction, by inserting this into the renamer now, + # but only do this if we didn't set the REFINED flag, to save work for irinterp + # in revisiting only the renamings that came through *this* idx. + compact.ssa_rename[old_idx] = lifted_val === nothing ? nothing : lifted_val.val + should_delete_node = true + else + compact[idx] = lifted_val === nothing ? nothing : lifted_val.val + end + + finish_phi_nest!(compact, nest) + # Insert the undef check if necessary if any_undef if lifted_val === nothing @@ -1179,23 +1443,29 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) for (k, v) in pairs(lifted_leaves) lifted_leaves_def[k] = v === nothing ? false : true end - def_val = perform_lifting!(compact, - visited_philikes, field, Bool, lifted_leaves_def, val, lazydomtree).val + (def_val, nest) = perform_lifting!(compact, + visited_philikes, field, Bool, lifted_leaves_def, val, lazydomtree) + def_val = (def_val::LiftedValue).val + finish_phi_nest!(compact, nest) + end + throw_expr = Expr(:throw_undef_if_not, Symbol("##getfield##"), def_val) + if should_delete_node + # Replace the node we already have rather than deleting/re-inserting. + # This way it is easier to handle BB boundary corner cases. + compact[SSAValue(idx)] = throw_expr + compact[SSAValue(idx)][:type] = Nothing + compact[SSAValue(idx)][:flag] = IR_FLAG_EFFECT_FREE | IR_FLAG_CONSISTENT | IR_FLAG_NOUB + should_delete_node = false + else + ni = NewInstruction(throw_expr, Nothing, line) + insert_node!(compact, SSAValue(idx), ni) end - insert_node!(compact, SSAValue(idx), NewInstruction( - Expr(:throw_undef_if_not, Symbol("##getfield##"), def_val), Nothing)) - else # val must be defined @assert lifted_val !== nothing end - compact[idx] = lifted_val === nothing ? nothing : lifted_val.val - if lifted_val !== nothing - if !⊑(𝕃ₒ, compact[SSAValue(idx)][:type], result_t) - compact[SSAValue(idx)][:flag] |= IR_FLAG_REFINED - end - end + should_delete_node && delete_inst_here!(compact) end non_dce_finish!(compact) @@ -1233,12 +1503,11 @@ function try_inline_finalizer!(ir::IRCode, argexprs::Vector{Any}, idx::Int, end src = @atomic :monotonic code.inferred else - src = nothing + return false end - src = inlining_policy(inlining.interp, src, info, IR_FLAG_NULL, mi, Any[]) - src === nothing && return false - src = retrieve_ir_for_inlining(mi, src) + src_inlining_policy(inlining.interp, src, info, IR_FLAG_NULL) || return false + src, di = retrieve_ir_for_inlining(code, src) # For now: Require finalizer to only have one basic block length(src.cfg.blocks) == 1 || return false @@ -1247,8 +1516,8 @@ function try_inline_finalizer!(ir::IRCode, argexprs::Vector{Any}, idx::Int, add_inlining_backedge!(et, mi) # TODO: Should there be a special line number node for inlined finalizers? - inlined_at = ir[SSAValue(idx)][:line] - ssa_substitute = ir_prepare_inlining!(InsertBefore(ir, SSAValue(idx)), ir, src, mi, inlined_at, argexprs) + inline_at = ir[SSAValue(idx)][:line] + ssa_substitute = ir_prepare_inlining!(InsertBefore(ir, SSAValue(idx)), ir, src, di, mi, inline_at, argexprs) # TODO: Use the actual inliner here rather than open coding this special purpose inliner. ssa_rename = Vector{Any}(undef, length(src.stmts)) @@ -1259,17 +1528,16 @@ function try_inline_finalizer!(ir::IRCode, argexprs::Vector{Any}, idx::Int, stmt′ = ssamap(stmt′) do ssa::SSAValue ssa_rename[ssa.id] end - stmt′ = ssa_substitute_op!(InsertBefore(ir, SSAValue(idx)), inst, stmt′, - ssa_substitute, :default) + stmt′ = ssa_substitute_op!(InsertBefore(ir, SSAValue(idx)), inst, stmt′, ssa_substitute) ssa_rename[idx′] = insert_node!(ir, idx, - NewInstruction(inst; stmt=stmt′, line=inst[:line]+ssa_substitute.linetable_offset), + NewInstruction(inst; stmt=stmt′, line=(ssa_substitute.inlined_at[1], ssa_substitute.inlined_at[2], Int32(idx′))), attach_after) end return true end -is_nothrow(ir::IRCode, ssa::SSAValue) = (ir[ssa][:flag] & IR_FLAG_NOTHROW) ≠ 0 +is_nothrow(ir::IRCode, ssa::SSAValue) = has_flag(ir[ssa], IR_FLAG_NOTHROW) function reachable_blocks(cfg::CFG, from_bb::Int, to_bb::Union{Nothing,Int} = nothing) worklist = Int[from_bb] @@ -1379,13 +1647,13 @@ function try_resolve_finalizer!(ir::IRCode, idx::Int, finalizer_idx::Int, defuse end # Ok, legality check complete. Figure out the exact statement where we're - # gonna inline the finalizer. + # going to inline the finalizer. loc = bb_insert_idx === nothing ? first(ir.cfg.blocks[bb_insert_block].stmts) : bb_insert_idx::Int attach_after = bb_insert_idx !== nothing finalizer_stmt = ir[SSAValue(finalizer_idx)][:stmt] argexprs = Any[finalizer_stmt.args[2], finalizer_stmt.args[3]] - flags = info isa FinalizerInfo ? flags_for_effects(info.effects) : IR_FLAG_NULL + flag = info isa FinalizerInfo ? flags_for_effects(info.effects) : IR_FLAG_NULL if length(finalizer_stmt.args) >= 4 inline = finalizer_stmt.args[4] if inline === nothing @@ -1395,11 +1663,13 @@ function try_resolve_finalizer!(ir::IRCode, idx::Int, finalizer_idx::Int, defuse if inline::Bool && try_inline_finalizer!(ir, argexprs, loc, mi, info, inlining, attach_after) # the finalizer body has been inlined else - insert_node!(ir, loc, with_flags(NewInstruction(Expr(:invoke, mi, argexprs...), Nothing), flags), attach_after) + newinst = add_flag(NewInstruction(Expr(:invoke, mi, argexprs...), Nothing), flag) + insert_node!(ir, loc, newinst, attach_after) end end else - insert_node!(ir, loc, with_flags(NewInstruction(Expr(:call, argexprs...), Nothing), flags), attach_after) + newinst = add_flag(NewInstruction(Expr(:call, argexprs...), Nothing), flag) + insert_node!(ir, loc, newinst, attach_after) end # Erase the call to `finalizer` ir[SSAValue(finalizer_idx)][:stmt] = nothing @@ -1541,9 +1811,10 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse # Now go through all uses and rewrite them for use in du.uses if use.kind === :getfield - ir[SSAValue(use.idx)][:stmt] = compute_value_for_use(ir, domtree, allblocks, + inst = ir[SSAValue(use.idx)] + inst[:stmt] = compute_value_for_use(ir, domtree, allblocks, du, phinodes, fidx, use.idx) - ir[SSAValue(use.idx)][:flag] |= IR_FLAG_REFINED + add_flag!(inst, IR_FLAG_REFINED) elseif use.kind === :isdefined continue # already rewritten if possible elseif use.kind === :nopreserve @@ -1589,7 +1860,7 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse # know we have removed all uses of the mutable allocation. # As a result, if we ever do prove nothrow, we can delete # this statement then. - ir[ssa][:flag] |= IR_FLAG_EFFECT_FREE + add_flag!(ir[ssa], IR_FLAG_EFFECT_FREE) end end end @@ -1736,9 +2007,15 @@ function adce_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) phi_uses = fill(0, length(ir.stmts) + length(ir.new_nodes)) all_phis = Int[] unionphis = Pair{Int,Any}[] # sorted - compact = IncrementalCompact(ir) - for ((_, idx), stmt) in compact + compact = IncrementalCompact(ir, true) + made_changes = false + for ((old_idx, idx), stmt) in compact if isa(stmt, PhiNode) + if reprocess_phi_node!(𝕃ₒ, compact, stmt, old_idx) + # Phi node has a single predecessor and was deleted + made_changes = true + continue + end push!(all_phis, idx) if is_some_union(compact.result[idx][:type]) push!(unionphis, Pair{Int,Any}(idx, Union{})) @@ -1758,7 +2035,7 @@ function adce_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) # nullify safe `typeassert` calls ty, isexact = instanceof_tfunc(argextype(stmt.args[3], compact), true) if isexact && ⊑(𝕃ₒ, argextype(stmt.args[2], compact), ty) - compact[idx] = nothing + delete_inst_here!(compact) continue end end @@ -1800,6 +2077,7 @@ function adce_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) if t === Union{} stmt = compact[SSAValue(phi)][:stmt]::PhiNode kill_phi!(compact, phi_uses, 1:length(stmt.values), SSAValue(phi), stmt, true) + made_changes = true continue elseif t === Any continue @@ -1821,16 +2099,17 @@ function adce_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) end compact.result[phi][:type] = t kill_phi!(compact, phi_uses, to_drop, SSAValue(phi), stmt, false) + made_changes = true end # Perform simple DCE for unused values extra_worklist = Int[] for (idx, nused) in Iterators.enumerate(compact.used_ssas) idx >= compact.result_idx && break nused == 0 || continue - adce_erase!(phi_uses, extra_worklist, compact, idx, false) + made_changes |= adce_erase!(phi_uses, extra_worklist, compact, idx, false) end while !isempty(extra_worklist) - adce_erase!(phi_uses, extra_worklist, compact, pop!(extra_worklist), true) + made_changes |= adce_erase!(phi_uses, extra_worklist, compact, pop!(extra_worklist), true) end # Go back and erase any phi cycles changed = true @@ -1851,10 +2130,12 @@ function adce_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) while !isempty(extra_worklist) if adce_erase!(phi_uses, extra_worklist, compact, pop!(extra_worklist), true) changed = true + made_changes = true end end end - return complete(compact) + + return Pair{IRCode, Bool}(complete(compact), made_changes) end function is_bb_empty(ir::IRCode, bb::BasicBlock) @@ -1870,14 +2151,6 @@ end function is_legal_bb_drop(ir::IRCode, bbidx::Int, bb::BasicBlock) # For the time being, don't drop the first bb, because it has special predecessor semantics. bbidx == 1 && return false - # If the block we're going to is the same as the fallthrow, it's always legal to drop - # the block. - length(bb.stmts) == 0 && return true - if length(bb.stmts) == 1 - stmt = ir[SSAValue(first(bb.stmts))][:stmt] - stmt === nothing && return true - ((stmt::GotoNode).label == bbidx + 1) && return true - end return true end @@ -1903,10 +2176,10 @@ function legalize_bb_drop_pred!(ir::IRCode, bb::BasicBlock, bbidx::Int, bbs::Vec end ir[last_fallthrough_term_ssa] = nothing kill_edge!(bbs, last_fallthrough, terminator.dest) - elseif isexpr(terminator, :enter) - return false elseif isa(terminator, GotoNode) return true + elseif isterminator(terminator) + return false end # Hack, but effective. If we have a predecessor with a fall-through terminator, change the # instruction numbering to merge the blocks now such that below processing will properly @@ -1915,45 +2188,86 @@ function legalize_bb_drop_pred!(ir::IRCode, bb::BasicBlock, bbidx::Int, bbs::Vec return true end -is_terminator(@nospecialize(stmt)) = isa(stmt, GotoNode) || isa(stmt, GotoIfNot) || isexpr(stmt, :enter) +function follow_map(map::Vector{Int}, idx::Int) + while map[idx] ≠ 0 + idx = map[idx] + end + return idx +end -function cfg_simplify!(ir::IRCode) - bbs = ir.cfg.blocks - merge_into = zeros(Int, length(bbs)) - merged_succ = zeros(Int, length(bbs)) - dropped_bbs = Vector{Int}() # sorted - function follow_merge_into(idx::Int) - while merge_into[idx] != 0 - idx = merge_into[idx] - end - return idx +function ascend_eliminated_preds(bbs::Vector{BasicBlock}, pred::Int) + pred == 0 && return pred + while pred != 1 && length(bbs[pred].preds) == 1 && length(bbs[pred].succs) == 1 + pred = bbs[pred].preds[1] end - function follow_merged_succ(idx::Int) - while merged_succ[idx] != 0 - idx = merged_succ[idx] + return pred +end + +# Compute (renamed) successors and predecessors given (renamed) block +function compute_succs(merged_succ::Vector{Int}, bbs::Vector{BasicBlock}, result_bbs::Vector{Int}, bb_rename_succ::Vector{Int}, i::Int) + orig_bb = follow_map(merged_succ, result_bbs[i]) + return Int[bb_rename_succ[i] for i in bbs[orig_bb].succs] +end + +function compute_preds(bbs::Vector{BasicBlock}, result_bbs::Vector{Int}, bb_rename_pred::Vector{Int}, i::Int) + orig_bb = result_bbs[i] + preds = copy(bbs[orig_bb].preds) + res = Int[] + while !isempty(preds) + pred = popfirst!(preds) + if pred == 0 + push!(res, 0) + continue + end + r = bb_rename_pred[pred] + (r == -2 || r == -1) && continue + if r == -3 + prepend!(preds, bbs[pred].preds) + else + push!(res, r) end - return idx end - function ascend_eliminated_preds(pred) - while pred != 1 && length(bbs[pred].preds) == 1 && length(bbs[pred].succs) == 1 - pred = bbs[pred].preds[1] + return res +end + +function add_preds!(all_new_preds::Vector{Int32}, bbs::Vector{BasicBlock}, bb_rename_pred::Vector{Int}, old_edge::Int32) + preds = copy(bbs[old_edge].preds) + while !isempty(preds) + old_edge′ = popfirst!(preds) + if old_edge′ == 0 + push!(all_new_preds, old_edge′) + continue + end + new_edge = bb_rename_pred[old_edge′] + if new_edge > 0 && new_edge ∉ all_new_preds + push!(all_new_preds, Int32(new_edge)) + elseif new_edge == -3 + prepend!(preds, bbs[old_edge′].preds) end - return pred end +end + +function cfg_simplify!(ir::IRCode) + bbs = ir.cfg.blocks + merge_into = zeros(Int, length(bbs)) + merged_succ = zeros(Int, length(bbs)) + dropped_bbs = Vector{Int}() # sorted # Walk the CFG from the entry block and aggressively combine blocks for (idx, bb) in enumerate(bbs) if length(bb.succs) == 1 succ = bb.succs[1] if length(bbs[succ].preds) == 1 && succ != 1 - # Can't merge blocks with :enter terminator even if they - # only have one successor. - if isexpr(ir[SSAValue(last(bb.stmts))][:stmt], :enter) + # Can't merge blocks with a non-GotoNode terminator, even if they + # only have one successor, because it would not be legal to have that + # terminator in the middle of a basic block. + terminator = ir[SSAValue(last(bb.stmts))][:stmt] + if !isa(terminator, GotoNode) && isterminator(terminator) continue end # Prevent cycles by making sure we don't end up back at `idx` # by following what is to be merged into `succ` - if follow_merged_succ(succ) != idx + if follow_map(merged_succ, succ) != idx merge_into[succ] = idx merged_succ[idx] = succ end @@ -1961,13 +2275,13 @@ function cfg_simplify!(ir::IRCode) # If this BB is empty, we can still merge it as long as none of our successor's phi nodes # reference our predecessors. found_interference = false - preds = Int[ascend_eliminated_preds(pred) for pred in bb.preds] + preds = Int[ascend_eliminated_preds(bbs, pred) for pred in bb.preds] for idx in bbs[succ].stmts stmt = ir[SSAValue(idx)][:stmt] stmt === nothing && continue isa(stmt, PhiNode) || break for edge in stmt.edges - edge = ascend_eliminated_preds(edge) + edge = ascend_eliminated_preds(bbs, Int(edge)) for pred in preds if pred == edge found_interference = true @@ -1986,14 +2300,14 @@ function cfg_simplify!(ir::IRCode) # Assign new BB numbers in DFS order, dropping unreachable blocks max_bb_num = 1 - bb_rename_succ = fill(0, length(bbs)) + bb_rename_succ = zeros(Int, length(bbs)) worklist = BitSetBoundedMinPrioritySet(length(bbs)) push!(worklist, 1) while !isempty(worklist) i = popfirst!(worklist) # Drop blocks that will be merged away if merge_into[i] != 0 - bb_rename_succ[i] = -1 + bb_rename_succ[i] = typemin(Int) end # Mark dropped blocks for fixup if !isempty(searchsorted(dropped_bbs, i)) @@ -2013,7 +2327,7 @@ function cfg_simplify!(ir::IRCode) # we have to schedule that block next while merged_succ[curr] != 0 if bb_rename_succ[curr] == 0 - bb_rename_succ[curr] = -1 + bb_rename_succ[curr] = typemin(Int) end curr = merged_succ[curr] end @@ -2024,9 +2338,10 @@ function cfg_simplify!(ir::IRCode) if bb_rename_succ[terminator.dest] == 0 push!(worklist, terminator.dest) end - elseif isexpr(terminator, :enter) - if bb_rename_succ[terminator.args[1]] == 0 - push!(worklist, terminator.args[1]) + elseif isa(terminator, EnterNode) + catchbb = terminator.catch_dest + if bb_rename_succ[catchbb] == 0 + push!(worklist, catchbb) end end ncurr = curr + 1 @@ -2052,9 +2367,9 @@ function cfg_simplify!(ir::IRCode) resolved_all = true for bb in dropped_bbs obb = bb_rename_succ[bb] - if obb < -1 + if obb < 0 && obb != typemin(Int) nsucc = bb_rename_succ[-obb] - if nsucc == -1 + if nsucc == typemin(Int) nsucc = -merge_into[-obb] end bb_rename_succ[bb] = nsucc @@ -2067,8 +2382,10 @@ function cfg_simplify!(ir::IRCode) bb_rename_pred = zeros(Int, length(bbs)) for i = 1:length(bbs) if bb_rename_succ[i] == 0 - bb_rename_succ[i] = -1 + bb_rename_succ[i] = -2 bb_rename_pred[i] = -2 + elseif bb_rename_succ[i] == typemin(Int) + bb_rename_succ[i] = -2 end end @@ -2112,7 +2429,7 @@ function cfg_simplify!(ir::IRCode) elseif is_multi bb_rename_pred[i] = -3 else - bbnum = follow_merge_into(pred) + bbnum = follow_map(merge_into, pred) bb_rename_pred[i] = bb_rename_succ[bbnum] end end @@ -2134,59 +2451,23 @@ function cfg_simplify!(ir::IRCode) bb_starts[i+1] = bb_starts[i] + result_bbs_lengths[i] end - cresult_bbs = let result_bbs = result_bbs, - merged_succ = merged_succ, - merge_into = merge_into, - bbs = bbs, - bb_rename_succ = bb_rename_succ - - # Compute (renamed) successors and predecessors given (renamed) block - function compute_succs(i::Int) - orig_bb = follow_merged_succ(result_bbs[i]) - return Int[bb_rename_succ[i] for i in bbs[orig_bb].succs] - end - function compute_preds(i::Int) - orig_bb = result_bbs[i] - preds = bbs[orig_bb].preds - res = Int[] - function scan_preds!(preds::Vector{Int}) - for pred in preds - if pred == 0 - push!(res, 0) - continue - end - r = bb_rename_pred[pred] - (r == -2 || r == -1) && continue - if r == -3 - scan_preds!(bbs[pred].preds) - else - push!(res, r) - end - end - end - scan_preds!(preds) - return res - end - - BasicBlock[ - BasicBlock(StmtRange(bb_starts[i], - i+1 > length(bb_starts) ? - length(compact.result) : bb_starts[i+1]-1), - compute_preds(i), - compute_succs(i)) - for i = 1:length(result_bbs)] - end + cresult_bbs = BasicBlock[ + BasicBlock(StmtRange(bb_starts[i], + i+1 > length(bb_starts) ? length(compact.result) : bb_starts[i+1]-1), + compute_preds(bbs, result_bbs, bb_rename_pred, i), + compute_succs(merged_succ, bbs, result_bbs, bb_rename_succ, i)) + for i = 1:length(result_bbs)] # Fixup terminators for any blocks that would have caused double edges for (bbidx, (new_bb, old_bb)) in enumerate(zip(cresult_bbs, result_bbs)) @assert length(new_bb.succs) <= 2 length(new_bb.succs) <= 1 && continue if new_bb.succs[1] == new_bb.succs[2] - old_bb2 = findfirst(x::Int->x==bbidx, bb_rename_pred) + old_bb2 = findfirst(x::Int->x==bbidx, bb_rename_pred)::Int terminator = ir[SSAValue(last(bbs[old_bb2].stmts))] @assert terminator[:stmt] isa GotoIfNot # N.B.: The dest will be renamed in process_node! below - terminator[:stmt] = GotoNode(terminator[:stmt].dest) + terminator[:stmt] = GotoNode(terminator[:stmt].dest::Int) pop!(new_bb.succs) new_succ = cresult_bbs[new_bb.succs[1]] for (i, nsp) in enumerate(new_succ.preds) @@ -2201,25 +2482,32 @@ function cfg_simplify!(ir::IRCode) # Run instruction compaction to produce the result, # but we're messing with the CFG # so we don't want compaction to do so independently - compact = IncrementalCompact(ir, CFGTransformState(true, false, cresult_bbs, bb_rename_pred, bb_rename_succ)) + compact = IncrementalCompact(ir, CFGTransformState(true, false, cresult_bbs, bb_rename_pred, bb_rename_succ, nothing)) result_idx = 1 for (idx, orig_bb) in enumerate(result_bbs) ms = orig_bb bb_start = true while ms != 0 - for i in bbs[ms].stmts + old_bb_stmts = bbs[ms].stmts + for i in old_bb_stmts node = ir.stmts[i] compact.result[compact.result_idx] = node - if isa(node[:stmt], GotoNode) && merged_succ[ms] != 0 + stmt = node[:stmt] + if isa(stmt, GotoNode) && merged_succ[ms] != 0 # If we merged a basic block, we need remove the trailing GotoNode (if any) compact.result[compact.result_idx][:stmt] = nothing - elseif isa(node[:stmt], PhiNode) - phi = node[:stmt] + elseif isa(stmt, PhiNode) + phi = stmt values = phi.values (; ssa_rename, late_fixup, used_ssas, new_new_used_ssas) = compact ssa_rename[i] = SSAValue(compact.result_idx) - processed_idx = i - renamed_values = process_phinode_values(values, late_fixup, processed_idx, compact.result_idx, ssa_rename, used_ssas, new_new_used_ssas, true, nothing) + already_inserted = function (i::Int, val::OldSSAValue) + if val.id in old_bb_stmts + return val.id <= i + end + return bb_rename_pred[phi.edges[i]] < idx + end + renamed_values = process_phinode_values(values, late_fixup, already_inserted, compact.result_idx, ssa_rename, used_ssas, new_new_used_ssas, true, nothing) edges = Int32[] values = Any[] sizehint!(edges, length(phi.edges)); sizehint!(values, length(renamed_values)) @@ -2233,20 +2521,16 @@ function cfg_simplify!(ir::IRCode) else resize!(values, length(values)+1) end + elseif new_edge == -1 + @assert length(phi.edges) == 1 + if isassigned(renamed_values, old_index) + push!(edges, -1) + push!(values, renamed_values[old_index]) + end elseif new_edge == -3 # Multiple predecessors, we need to expand out this phi all_new_preds = Int32[] - function add_preds!(old_edge) - for old_edge′ in bbs[old_edge].preds - new_edge = bb_rename_pred[old_edge′] - if new_edge > 0 && !in(new_edge, all_new_preds) - push!(all_new_preds, new_edge) - elseif new_edge == -3 - add_preds!(old_edge′) - end - end - end - add_preds!(old_edge) + add_preds!(all_new_preds, bbs, bb_rename_pred, old_edge) append!(edges, all_new_preds) if isassigned(renamed_values, old_index) val = renamed_values[old_index] diff --git a/base/compiler/ssair/show.jl b/base/compiler/ssair/show.jl index e9e2aa75f755e..7f2854959ce5e 100644 --- a/base/compiler/ssair/show.jl +++ b/base/compiler/ssair/show.jl @@ -52,12 +52,12 @@ function print_stmt(io::IO, idx::Int, @nospecialize(stmt), used::BitSet, maxleng stmt = stmt::Expr # TODO: why is this here, and not in Base.show_unquoted print(io, "invoke ") - linfo = stmt.args[1]::Core.MethodInstance + mi = stmt.args[1]::Core.MethodInstance show_unquoted(io, stmt.args[2], indent) print(io, "(") # XXX: this is wrong if `sig` is not a concretetype method # more correct would be to use `fieldtype(sig, i)`, but that would obscure / discard Varargs information in show - sig = linfo.specTypes == Tuple ? Core.svec() : Base.unwrap_unionall(linfo.specTypes).parameters::Core.SimpleVector + sig = mi.specTypes == Tuple ? Core.svec() : Base.unwrap_unionall(mi.specTypes).parameters::Core.SimpleVector print_arg(i) = sprint(; context=io) do io show_unquoted(io, stmt.args[i], indent) if (i - 1) <= length(sig) @@ -67,8 +67,12 @@ function print_stmt(io::IO, idx::Int, @nospecialize(stmt), used::BitSet, maxleng join(io, (print_arg(i) for i = 3:length(stmt.args)), ", ") print(io, ")") # given control flow information, we prefer to print these with the basic block #, instead of the ssa % - elseif isexpr(stmt, :enter) && length((stmt::Expr).args) == 1 && (stmt::Expr).args[1] isa Int - print(io, "\$(Expr(:enter, #", (stmt::Expr).args[1]::Int, "))") + elseif isa(stmt, EnterNode) + print(io, "enter #", stmt.catch_dest, "") + if isdefined(stmt, :scope) + print(io, " with scope ") + show_unquoted(io, stmt.scope, indent) + end elseif stmt isa GotoNode print(io, "goto #", stmt.label) elseif stmt isa PhiNode @@ -141,24 +145,13 @@ function show_unquoted_gotoifnot(io::IO, stmt::GotoIfNot, indent::Int, prefix::S show_unquoted(io, stmt.cond, indent) end -function compute_inlining_depth(linetable::Vector, iline::Int32) - iline == 0 && return 1 - depth = -1 - while iline != 0 - depth += 1 - lineinfo = linetable[iline]::LineInfoNode - iline = lineinfo.inlined_at - end - return depth -end - function should_print_ssa_type(@nospecialize node) if isa(node, Expr) - return !(node.head in (:gc_preserve_begin, :gc_preserve_end, :meta, :enter, :leave)) + return !(node.head in (:gc_preserve_begin, :gc_preserve_end, :meta, :leave)) end return !isa(node, PiNode) && !isa(node, GotoIfNot) && !isa(node, GotoNode) && !isa(node, ReturnNode) && - !isa(node, QuoteNode) + !isa(node, QuoteNode) && !isa(node, EnterNode) end function default_expr_type_printer(io::IO; @nospecialize(type), used::Bool, show_type::Bool=true, _...) @@ -167,32 +160,27 @@ function default_expr_type_printer(io::IO; @nospecialize(type), used::Bool, show return nothing end -function normalize_method_name(m) +function method_name(@nospecialize m) + if m isa LineInfoNode + m = m.method + end + if m isa MethodInstance + m = m.def + end if m isa Method - return m.name - elseif m isa MethodInstance - return (m.def::Method).name - elseif m isa Symbol + m = m.name + end + if m isa Module + return :var"top-level scope" + end + if m isa Symbol return m - else - return Symbol("") end + return :var"" end -@noinline method_name(m::LineInfoNode) = normalize_method_name(m.method) - -# converts the linetable for line numbers -# into a list in the form: -# 1 outer-most-frame -# 2 inlined-frame -# 3 innermost-frame -function compute_loc_stack(linetable::Vector, line::Int32) - stack = Int[] - while line != 0 - entry = linetable[line]::LineInfoNode - pushfirst!(stack, line) - line = entry.inlined_at - end - return stack +@noinline function normalize_method_name(@nospecialize m) + name = method_name(m) + return name === :var"" ? :none : name end """ @@ -250,7 +238,7 @@ We get: └── return %3 │ ``` -Even though we were in the `f` scope since the first statement, it tooks us two statements +Even though we were in the `f` scope since the first statement, it took us two statements to catch up and print the intermediate scopes. Which scope is printed is indicated both by the indentation of the method name and by an increased thickness of the appropriate line for the scope. @@ -261,83 +249,72 @@ function compute_ir_line_annotations(code::IRCode) loc_lineno = String[] cur_group = 1 last_lineno = 0 - last_stack = Int[] + last_stack = LineInfoNode[] # nb. only file, line, and method are populated in this last_printed_depth = 0 - linetable = code.linetable - lines = code.stmts.line - last_line = zero(eltype(lines)) - for idx in 1:length(lines) + debuginfo = code.debuginfo + def = :var"unknown scope" + for idx in 1:length(code.stmts) buf = IOBuffer() - line = lines[idx] print(buf, "│") - depth = compute_inlining_depth(linetable, line) - iline = line - lineno = 0 + stack = buildLineInfoNode(debuginfo, def, idx) + lineno::Int = 0 loc_method = "" - if line != 0 - stack = compute_loc_stack(linetable, line) - lineno = linetable[stack[1]].line + isempty(stack) && (stack = last_stack) + if !isempty(stack) + lineno = stack[1].line x = min(length(last_stack), length(stack)) - if length(stack) != 0 - # Compute the last depth that was in common - first_mismatch = let last_stack=last_stack - findfirst(i->last_stack[i] != stack[i], 1:x) - end - # If the first mismatch is the last stack frame, that might just - # be a line number mismatch in inner most frame. Ignore those - if length(last_stack) == length(stack) && first_mismatch == length(stack) - last_entry, entry = linetable[last_stack[end]], linetable[stack[end]] - if method_name(last_entry) === method_name(entry) && last_entry.file === entry.file - first_mismatch = nothing - end + depth = length(stack) - 1 + # Compute the last depth that was in common + first_mismatch = let last_stack=last_stack + findfirst(i->last_stack[i] != stack[i], 1:x) + end + # If the first mismatch is the last stack frame, that might just + # be a line number mismatch in inner most frame. Ignore those + if length(last_stack) == length(stack) && first_mismatch == length(stack) + last_entry, entry = last_stack[end], stack[end] + if method_name(last_entry) === method_name(entry) && last_entry.file === entry.file + first_mismatch = nothing end - last_depth = something(first_mismatch, x+1)-1 - if min(depth, last_depth) > last_printed_depth - printing_depth = min(depth, last_printed_depth + 1) - last_printed_depth = printing_depth - elseif length(stack) > length(last_stack) || first_mismatch !== nothing - printing_depth = min(depth, last_depth + 1) - last_printed_depth = printing_depth - else - printing_depth = 0 + end + last_depth = something(first_mismatch, x+1)-1 + if min(depth, last_depth) > last_printed_depth + printing_depth = min(depth, last_printed_depth + 1) + last_printed_depth = printing_depth + elseif length(stack) > length(last_stack) || first_mismatch !== nothing + printing_depth = min(depth, last_depth + 1) + last_printed_depth = printing_depth + else + printing_depth = 0 + end + stole_one = false + if printing_depth != 0 + for _ in 1:(printing_depth-1) + print(buf, "│") end - stole_one = false - if printing_depth != 0 - for _ in 1:(printing_depth-1) + if printing_depth <= last_depth-1 && first_mismatch === nothing + print(buf, "┃") + for _ in printing_depth+1:min(depth, last_depth) print(buf, "│") end - if printing_depth <= last_depth-1 && first_mismatch === nothing - print(buf, "┃") - for _ in printing_depth+1:min(depth, last_depth) - print(buf, "│") - end - else - stole_one = true - print(buf, "╻") - end else - for _ in 1:min(depth, last_depth) - print(buf, "│") - end + stole_one = true + print(buf, "╻") end - print(buf, "╷"^max(0, depth - last_depth - stole_one)) - if printing_depth != 0 - if length(stack) == printing_depth - loc_method = line - else - loc_method = stack[printing_depth + 1] - end - loc_method = method_name(linetable[loc_method]) + else + for _ in 1:min(depth, last_depth) + print(buf, "│") end - loc_method = string(" "^printing_depth, loc_method) end + print(buf, "╷"^max(0, depth - last_depth - stole_one)) + if printing_depth != 0 + loc_method = normalize_method_name(stack[printing_depth + 1]) + end + loc_method = string(" "^printing_depth, loc_method) last_stack = stack - entry = linetable[line] end push!(loc_annotations, String(take!(buf))) push!(loc_lineno, (lineno != 0 && lineno != last_lineno) ? string(lineno) : "") push!(loc_methods, loc_method) - last_line = line (lineno != 0) && (last_lineno = lineno) nothing end @@ -346,19 +323,87 @@ end Base.show(io::IO, code::Union{IRCode, IncrementalCompact}) = show_ir(io, code) +# A line_info_preprinter for disabling line info printing lineinfo_disabled(io::IO, linestart::String, idx::Int) = "" -function DILineInfoPrinter(linetable::Vector, showtypes::Bool=false) +# utility function to extract the file name from a DebugInfo object +function debuginfo_file1(debuginfo::Union{DebugInfo,DebugInfoStream}) + def = debuginfo.def + if def isa MethodInstance + def = def.def + end + if def isa Method + def = def.file + end + if def isa Symbol + return def + end + return :var"" +end + +# utility function to extract the first line number and file of a block of code +function debuginfo_firstline(debuginfo::Union{DebugInfo,DebugInfoStream}) + linetable = debuginfo.linetable + while linetable != nothing + debuginfo = linetable + linetable = debuginfo.linetable + end + codeloc = getdebugidx(debuginfo, 0) + return debuginfo_file1(debuginfo), codeloc[1] +end + +struct LineInfoNode + method # ::Union{Method,MethodInstance,Symbol} + file::Symbol + line::Int32 +end + +# utility function for converting a debuginfo object a particular pc to list of LineInfoNodes representing the inlining info at that pc for function `def` +# which is either `nothing` (macro-expand), a module (top-level), a Method (unspecialized code) or a MethodInstance (specialized code) +# Returns `false` if the line info should not be updated with this info because this +# statement has no effect on the line numbers. The `scopes` will still be populated however +# with as much information as was available about the inlining at that statement. +function append_scopes!(scopes::Vector{LineInfoNode}, pc::Int, debuginfo, @nospecialize(def)) + doupdate = true + while true + debuginfo.def isa Symbol || (def = debuginfo.def) + codeloc = getdebugidx(debuginfo, pc) + line::Int = codeloc[1] + inl_to::Int = codeloc[2] + doupdate &= line != 0 || inl_to != 0 # disabled debug info--no update + if debuginfo.linetable === nothing || pc <= 0 || line < 0 + line < 0 && (doupdate = false; line = 0) # broken debug info + push!(scopes, LineInfoNode(def, debuginfo_file1(debuginfo), Int32(line))) + else + doupdate = append_scopes!(scopes, line, debuginfo.linetable::DebugInfo, def) && doupdate + end + inl_to == 0 && return doupdate + def = :var"macro expansion" + debuginfo = debuginfo.edges[inl_to] + pc::Int = codeloc[3] + end +end + +# utility wrapper around `append_scopes!` that returns an empty list instead of false +# when there is no applicable line update +function buildLineInfoNode(debuginfo, @nospecialize(def), pc::Int) + DI = LineInfoNode[] + append_scopes!(DI, pc, debuginfo, def) || empty!(DI) + return DI +end + +# A default line_info_preprinter for printing accurate line number information +function DILineInfoPrinter(debuginfo, def, showtypes::Bool=false) context = LineInfoNode[] context_depth = Ref(0) indent(s::String) = s^(max(context_depth[], 1) - 1) - function emit_lineinfo_update(io::IO, linestart::String, lineidx::Int32) + function emit_lineinfo_update(io::IO, linestart::String, pc::Int) # internal configuration options: linecolor = :yellow collapse = showtypes ? false : true indent_all = true - # convert lineidx to a vector - if lineidx == typemin(Int32) + # convert pc to a vector + if pc == 0 # sentinel value: reset internal (and external) state pops = indent("└") if !isempty(pops) @@ -368,13 +413,10 @@ function DILineInfoPrinter(linetable::Vector, showtypes::Bool=false) end empty!(context) context_depth[] = 0 - elseif lineidx > 0 # just skip over lines with no debug info at all - DI = LineInfoNode[] - while lineidx != 0 - entry = linetable[lineidx]::LineInfoNode - push!(DI, entry) - lineidx = entry.inlined_at - end + return "" + end + DI = reverse!(buildLineInfoNode(debuginfo, def, pc)) + if !isempty(DI) # FOR DEBUGGING, or if you just like very excessive output: # this prints out the context in full for every statement #empty!(context) @@ -504,6 +546,7 @@ function DILineInfoPrinter(linetable::Vector, showtypes::Bool=false) #end end indent_all || return "" + context_depth[] <= 1 && return "" return sprint(io -> printstyled(io, indent("│"), color=linecolor), context=io) end return emit_lineinfo_update @@ -565,10 +608,8 @@ end function statement_indices_to_labels(stmt, cfg::CFG) # convert statement index to labels, as expected by print_stmt - if stmt isa Expr - if stmt.head === :enter && length(stmt.args) == 1 && stmt.args[1] isa Int - stmt = Expr(:enter, block_for_inst(cfg, stmt.args[1]::Int)) - end + if stmt isa EnterNode + stmt = EnterNode(stmt, stmt.catch_dest == 0 ? 0 : block_for_inst(cfg, stmt.catch_dest)) elseif isa(stmt, GotoIfNot) stmt = GotoIfNot(stmt.cond, block_for_inst(cfg, stmt.dest)) elseif stmt isa GotoNode @@ -801,18 +842,8 @@ end _strip_color(s::String) = replace(s, r"\e\[\d+m"a => "") -function statementidx_lineinfo_printer(f, code::IRCode) - printer = f(code.linetable) - function (io::IO, indent::String, idx::Int) - printer(io, indent, idx > 0 ? code.stmts[idx][:line] : typemin(Int32)) - end -end -function statementidx_lineinfo_printer(f, code::CodeInfo) - printer = f(code.linetable) - function (io::IO, indent::String, idx::Int) - printer(io, indent, idx > 0 ? code.codelocs[idx] : typemin(Int32)) - end -end +statementidx_lineinfo_printer(f, code::IRCode) = f(code.debuginfo, :var"unknown scope") +statementidx_lineinfo_printer(f, code::CodeInfo) = f(code.debuginfo, :var"unknown scope") statementidx_lineinfo_printer(code) = statementidx_lineinfo_printer(DILineInfoPrinter, code) function stmts_used(io::IO, code::IRCode, warn_unset_entry=true) diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index 39e092332aae6..f2bfa0e4c5476 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -161,40 +161,6 @@ function rename_uses!(ir::IRCode, ci::CodeInfo, idx::Int, @nospecialize(stmt), r return fixemup!(stmt::SlotNumber->true, stmt::SlotNumber->renames[slot_id(stmt)], ir, ci, idx, stmt) end -function strip_trailing_junk!(ci::CodeInfo, cfg::CFG, code::Vector{Any}, info::Vector{CallInfo}) - # Remove `nothing`s at the end, we don't handle them well - # (we expect the last instruction to be a terminator) - ssavaluetypes = ci.ssavaluetypes::Vector{Any} - (; codelocs, ssaflags) = ci - for i = length(code):-1:1 - if code[i] !== nothing - resize!(code, i) - resize!(ssavaluetypes, i) - resize!(codelocs, i) - resize!(info, i) - resize!(ssaflags, i) - break - end - end - # If the last instruction is not a terminator, add one. This can - # happen for implicit return on dead branches. - term = code[end] - if !isa(term, GotoIfNot) && !isa(term, GotoNode) && !isa(term, ReturnNode) - push!(code, ReturnNode()) - push!(ssavaluetypes, Union{}) - push!(codelocs, 0) - push!(info, NoCallInfo()) - push!(ssaflags, IR_FLAG_NOTHROW) - - # Update CFG to include appended terminator - old_range = cfg.blocks[end].stmts - new_range = StmtRange(first(old_range), last(old_range) + 1) - cfg.blocks[end] = BasicBlock(cfg.blocks[end], new_range) - (length(cfg.index) == length(cfg.blocks)) && (cfg.index[end] += 1) - end - nothing -end - # maybe use expr_type? function typ_for_val(@nospecialize(x), ci::CodeInfo, ir::IRCode, idx::Int, slottypes::Vector{Any}) if isa(x, Expr) @@ -207,7 +173,7 @@ function typ_for_val(@nospecialize(x), ci::CodeInfo, ir::IRCode, idx::Int, slott end return (ci.ssavaluetypes::Vector{Any})[idx] end - isa(x, GlobalRef) && return abstract_eval_globalref(x) + isa(x, GlobalRef) && return abstract_eval_globalref_type(x) isa(x, SSAValue) && return (ci.ssavaluetypes::Vector{Any})[x.id] isa(x, Argument) && return slottypes[x.n] isa(x, NewSSAValue) && return types(ir)[new_to_regular(x, length(ir.stmts))] @@ -229,7 +195,7 @@ Run iterated dominance frontier. The algorithm we have here essentially follows LLVM, which itself is a a cleaned up version of the linear-time algorithm described in [^SG95]. -The algorithm here, is quite straightforward. Suppose we have a CFG: +The algorithm here is quite straightforward. Suppose we have a CFG: A -> B -> D -> F \\-> C ------>/ @@ -464,20 +430,18 @@ function domsort_ssa!(ir::IRCode, domtree::DomTree) # Add an explicit goto node in the next basic block (we accounted for this above) nidx = inst_range[end] + 1 node = result[nidx] - node[:stmt], node[:type], node[:line] = GotoNode(bb_rename[bb + 1]), Any, 0 + node[:stmt], node[:type], node[:line] = GotoNode(bb_rename[bb + 1]), Any, NoLineUpdate end result[inst_range[end]][:stmt] = GotoIfNot(terminator.cond, bb_rename[terminator.dest]) elseif !isa(terminator, ReturnNode) - if isa(terminator, Expr) - if terminator.head === :enter - terminator.args[1] = bb_rename[terminator.args[1]] - end + if isa(terminator, EnterNode) + result[inst_range[end]][:stmt] = EnterNode(terminator, terminator.catch_dest == 0 ? 0 : bb_rename[terminator.catch_dest]) end if bb_rename[bb + 1] != new_bb + 1 # Add an explicit goto node nidx = inst_range[end] + 1 node = result[nidx] - node[:stmt], node[:type], node[:line] = GotoNode(bb_rename[bb + 1]), Any, 0 + node[:stmt], node[:type], node[:line] = GotoNode(bb_rename[bb + 1]), Any, NoLineUpdate inst_range = first(inst_range):(last(inst_range) + 1) end end @@ -506,6 +470,7 @@ function domsort_ssa!(ir::IRCode, domtree::DomTree) end new_node[:stmt] = renumber_ssa!(new_node_inst, inst_rename, true) end + ir.debuginfo.codelocs = result.line new_ir = IRCode(ir, result, cfg, new_new_nodes) return new_ir end @@ -576,15 +541,15 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, sv::OptimizationState, catch_entry_blocks = TryCatchRegion[] for idx in 1:length(code) stmt = code[idx] - if isexpr(stmt, :enter) + if isa(stmt, EnterNode) push!(catch_entry_blocks, TryCatchRegion( block_for_inst(cfg, idx), - block_for_inst(cfg, stmt.args[1]::Int))) + block_for_inst(cfg, stmt.catch_dest))) end end # Record the correct exception handler for all critical sections - handler_at = compute_trycatch(code, BitSet()) + handler_at, handlers = compute_trycatch(code, BitSet()) phi_slots = Vector{Int}[Int[] for _ = 1:length(ir.cfg.blocks)] live_slots = Vector{Int}[Int[] for _ = 1:length(ir.cfg.blocks)] @@ -627,10 +592,12 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, sv::OptimizationState, # The slot is live-in into this block. We need to # Create a PhiC node in the catch entry block and # an upsilon node in the corresponding enter block + varstate = sv.bb_vartables[li] + if varstate === nothing + continue + end node = PhiCNode(Any[]) insertpoint = first_insert_for_bb(code, cfg, li) - varstate = sv.bb_vartables[li] - @assert varstate !== nothing vt = varstate[idx] phic_ssa = NewSSAValue( insert_node!(ir, insertpoint, @@ -690,6 +657,9 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, sv::OptimizationState, new_nodes = ir.new_nodes @timeit "SSA Rename" while !isempty(worklist) (item::Int, pred, incoming_vals) = pop!(worklist) + if sv.bb_vartables[item] === nothing + continue + end # Rename existing phi nodes first, because their uses occur on the edge # TODO: This isn't necessary if inlining stops replacing arguments by slots. for idx in cfg.blocks[item].stmts @@ -810,9 +780,9 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, sv::OptimizationState, incoming_vals[id] = Pair{Any, Any}(thisval, thisdef) has_pinode[id] = false enter_idx = idx - while handler_at[enter_idx] != 0 - enter_idx = handler_at[enter_idx] - leave_block = block_for_inst(cfg, code[enter_idx].args[1]::Int) + while handler_at[enter_idx][1] != 0 + (; enter_idx) = handlers[handler_at[enter_idx][1]] + leave_block = block_for_inst(cfg, (code[enter_idx]::EnterNode).catch_dest) cidx = findfirst((; slot)::NewPhiCNode2->slot_id(slot)==id, new_phic_nodes[leave_block]) if cidx !== nothing node = thisdef ? UpsilonNode(thisval) : UpsilonNode() @@ -874,8 +844,9 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, sv::OptimizationState, else new_code[idx] = GotoIfNot(stmt.cond, new_dest) end - elseif isexpr(stmt, :enter) - new_code[idx] = Expr(:enter, block_for_inst(cfg, stmt.args[1]::Int)) + elseif isa(stmt, EnterNode) + except_bb = stmt.catch_dest == 0 ? 0 : block_for_inst(cfg, stmt.catch_dest) + new_code[idx] = EnterNode(stmt, except_bb) ssavalmap[idx] = SSAValue(idx) # Slot to store token for pop_exception elseif isexpr(stmt, :leave) || isexpr(stmt, :(=)) || isa(stmt, ReturnNode) || isexpr(stmt, :meta) || isa(stmt, NewvarNode) diff --git a/base/compiler/ssair/tarjan.jl b/base/compiler/ssair/tarjan.jl new file mode 100644 index 0000000000000..3727fe218dc1d --- /dev/null +++ b/base/compiler/ssair/tarjan.jl @@ -0,0 +1,313 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +using Core.Compiler: DomTree, CFG, BasicBlock, StmtRange, dominates + +struct SCCStackItem + v::Int32 + # which child of `v` to scan + child::Int32 + # the location of `parent` in the stack + parent::Int32 + # the index in the (pre-order traversal of the) DFS tree + preorder::Int32 + # the minimum node (by pre-order index) reachable from any node in the DFS sub-tree rooted at `v` + minpreorder::Int32 + # whether this node is reachable from BasicBlock #1 + live::Bool +end + +function SCCStackItem(item::SCCStackItem; child=item.child, + minpreorder=item.minpreorder, live=item.live) + return SCCStackItem( + item.v, # v + child, # child + item.parent, # parent + item.preorder, # preorder + minpreorder, # minpreorder + live, # live + ) +end + +struct CFGReachability + irreducible::BitVector # BBNumber -> Bool + scc::Vector{Int} # BBNumber -> SCCNumber + domtree::DomTree + + _worklist::Vector{Int} # for node removal + _stack::Vector{SCCStackItem} # for Tarjan's SCC algorithm +end + +function CFGReachability(cfg::CFG, domtree::DomTree) + n_blocks = length(cfg.blocks) + reachability = CFGReachability( + BitVector(undef, n_blocks), # irreducible + zeros(Int, n_blocks), # scc + domtree, # domtree + Int[], # _worklist + SCCStackItem[], # _stack + ) + tarjan!(reachability, cfg; + # reducible back-edges don't need to be considered for reachability + filter = (from::Int,to::Int)->!dominates(domtree, to, from) + ) + return reachability +end + +bb_unreachable(reach::CFGReachability, bb::Int) = reach.scc[bb] == 0 + +bb_in_irreducible_loop(reach::CFGReachability, bb::Int) = reach.irreducible[bb] + +# Returns `true` if a node is 'rooted' as reachable, i.e. it is has an incoming +# edge from a resolved SCC other than its own (or it is BasicBlock #1). +# +# `tarjan!` takes the transitive closure of this relation in order to detect +# which BasicBlocks are unreachable. +function _bb_externally_reachable(reach::CFGReachability, cfg::CFG, bb::Int; filter) + (; scc) = reach + bb == 1 && return true + for pred in cfg.blocks[bb].preds + scc[pred] <= 0 && continue + !filter(pred, bb) && continue + @assert scc[pred] != scc[bb] + return true + end + return false +end + +""" + tarjan!(reach::CFGReachability, cfg::CFG, root::Int=1) + +Tarjan's strongly-connected components algorithm. Traverses the CFG starting at `root`, ignoring +nodes with resolved SCC's and updating outputs for all un-resolved nodes. + +Returns true if any node was discovered to be unreachable, false otherwise. + +Outputs: + - `reach.scc`: strongly-connected components, ignoring backedges to (natural) loops + - `reach.irreducible`: true iff a BasicBlock is part of a (non-trivial) SCC / irreducible loop + - `reach._worklist`: if performing an incremental update (`root != 1`), any traversed nodes that + are unreachable from BasicBlock #1 are enqueued to this worklist +""" +function tarjan!(reach::CFGReachability, cfg::CFG; root::Int=1, + filter = (from::Int,to::Int)->true, +) + (; scc, irreducible) = reach + scc[root] != 0 && return scc + live = _bb_externally_reachable(reach, cfg, root; filter) + + # the original algorithm has a separate stack and worklist (unrelated to `reach._worklist`) + # here we use a single combined stack for improved memory/cache efficiency + stack = reach._stack + push!(stack, SCCStackItem( + root, # v + 1, # child + 0, # parent + 1, # preorder + 1, # minpreorder + live, # live + )) + scc[root] = -1 + cursor = length(stack) + + # worklist length before any new unreachable nodes are added + worklist_len = length(reach._worklist) + + # last (pre-order) DFS label assigned to a node + preorder_id = 1 + while true + (; v, child, minpreorder, live) = item = stack[cursor] + + bb = cfg.blocks[v] + if child <= length(bb.succs) # queue next child + stack[cursor] = item = SCCStackItem(item; child=child+1) + succ = bb.succs[child] + + # ignore any edges that don't pass the filter + !filter(convert(Int, v), succ) && continue + + if scc[succ] < 0 + # next child is already in DFS tree + child_preorder = stack[-scc[succ]].preorder + + # only need to update `minpreorder` for `v` + stack[cursor] = item = SCCStackItem(item; + minpreorder=min(minpreorder, child_preorder)) + elseif scc[succ] == 0 + # next child is a new element in DFS tree + preorder_id += 1 + live = live || _bb_externally_reachable(reach, cfg, succ; filter) + push!(stack, SCCStackItem( + succ, # v + 1, # child + cursor, # parent (index in stack) + preorder_id, # preorder + preorder_id, # minpreorder + live, # live + )) + scc[succ] = -length(stack) + cursor = length(stack) + else end # next child is a resolved SCC (do nothing) + else # v's children are processed, finalize v + if item.minpreorder == item.preorder + has_one_element = stack[end].v == v + while true + item = pop!(stack) + if live + scc[item.v] = v + scan_subgraph!(reach, cfg, convert(Int, item.v), + #= filter =# (pred,x)->(filter(pred, x) && scc[x] > typemax(Int)÷2), + #= action =# (x)->(scc[x] -= typemax(Int)÷2;), + ) + else # this offset marks a node as 'maybe-dead' + scc[item.v] = v + typemax(Int)÷2 + push!(reach._worklist, item.v) + end + irreducible[item.v] = !has_one_element + (item.v == v) && break + end + item.parent == 0 && break # all done + elseif live + stack[item.parent] = SCCStackItem(stack[item.parent]; live=true) + end + + # update `minpreorder` for parent + parent = stack[item.parent] + minpreorder = min(parent.minpreorder, item.minpreorder) + stack[item.parent] = SCCStackItem(parent; minpreorder) + + cursor = item.parent + end + end + + worklist = reach._worklist + + # filter the worklist, leaving any nodes not proven to be reachable from BB #1 + n_popped = 0 + for i = (worklist_len + 1):length(worklist) + @assert worklist[i] != 1 + @assert scc[worklist[i]] > 0 + if scc[worklist[i]] > typemax(Int)÷2 + # node is unreachable, enqueue it + scc[worklist[i]] = 0 + worklist[i - n_popped] = worklist[i] + else + n_popped += 1 + end + end + resize!(worklist, length(worklist) - n_popped) + + return length(worklist) > worklist_len # if true, a (newly) unreachable node was enqueued +end + +""" +Scan the subtree rooted at `root`, excluding `root` itself + +Note: This function will not detect cycles for you. The `filter` provided must + protect against infinite cycle traversal. +""" +function scan_subgraph!(reach::CFGReachability, cfg::CFG, root::Int, filter, action) + worklist = reach._worklist + start_len = length(worklist) + + push!(worklist, root) + while length(worklist) > start_len + v = pop!(worklist) + for succ in cfg.blocks[v].succs + !filter(v, succ) && continue + action(succ) + push!(worklist, succ) + end + end +end + +function enqueue_if_unreachable!(reach::CFGReachability, cfg::CFG, bb::Int) + (; domtree, scc) = reach + @assert scc[bb] != 0 + + bb == 1 && return false + if bb_in_irreducible_loop(reach, bb) + # irreducible CFG + # this requires a full scan of the irreducible loop + + # any reducible back-edges do not need to be considered as part of reachability + # (very important optimization, since it means reducible CFGs will have no SCCs) + filter = (from::Int, to::Int)->!dominates(domtree, to, from) + + scc′ = scc[bb] + scc[bb] = 0 + scan_subgraph!(reach, cfg, bb, # set this SCC to 0 + #= filter =# (pred,x)->(filter(pred, x) && scc[x] == scc′), + #= action =# (x)->(scc[x] = 0;), + ) + + # re-compute the SCC's for this portion of the CFG, adding any freshly + # unreachable nodes to `reach._worklist` + return tarjan!(reach, cfg; root=bb, filter) + else + # target is a reducible CFG node + # this node lives iff it still has an incoming forward edge + for pred in cfg.blocks[bb].preds + # virtual edge does not count - if the enter is dead, that edge is + # not taken. + pred == 0 && continue + !dominates(domtree, bb, pred) && return false # forward-edge + end + scc[bb] = 0 + push!(reach._worklist, bb) + return true + end +end + +function kill_cfg_edge!(cfg::CFG, from::Int, to::Int) + preds, succs = cfg.blocks[to].preds, cfg.blocks[from].succs + deleteat!(preds, findfirst(x::Int->x==from, preds)::Int) + deleteat!(succs, findfirst(x::Int->x==to, succs)::Int) + return nothing +end + +""" +Remove from `cfg` and `reach` the edge (from → to), as well as any blocks/edges +this causes to become unreachable. + +Calls: + - `block_callback` for every unreachable block. + - `edge_callback` for every unreachable edge into a reachable block (may also + be called for blocks which are later discovered to be unreachable). +""" +function kill_edge!(reach::CFGReachability, cfg::CFG, from::Int, to::Int, + edge_callback=nothing, block_callback=nothing) + (reach.scc[from] == 0) && return # source is already unreachable + @assert reach.scc[to] != 0 + + # delete (from → to) edge + kill_cfg_edge!(cfg, from, to) + + # check for unreachable target + enqueued = enqueue_if_unreachable!(reach, cfg, to) + if !enqueued && edge_callback !== nothing + edge_callback(from, to) + end + while !isempty(reach._worklist) + node = convert(Int, pop!(reach._worklist)) + + # already marked unreachable, just need to notify + @assert reach.scc[node] == 0 && node != 1 + if block_callback !== nothing + block_callback(node) + end + + for succ in cfg.blocks[node].succs + # delete (node → succ) edge + preds = cfg.blocks[succ].preds + deleteat!(preds, findfirst(x::Int->x==node, preds)::Int) + + # check for newly unreachable target + reach.scc[succ] == 0 && continue + enqueued = enqueue_if_unreachable!(reach, cfg, succ) + if !enqueued && edge_callback !== nothing + edge_callback(node, succ) + end + end + empty!(cfg.blocks[node].succs) + end +end diff --git a/base/compiler/ssair/verify.jl b/base/compiler/ssair/verify.jl index f770c72d0fae5..95ee544530810 100644 --- a/base/compiler/ssair/verify.jl +++ b/base/compiler/ssair/verify.jl @@ -2,7 +2,8 @@ function maybe_show_ir(ir::IRCode) if isdefined(Core, :Main) - Core.Main.Base.display(ir) + # ensure we use I/O that does not yield, as this gets called during compilation + invokelatest(Core.Main.Base.show, Core.stdout, "text/plain", ir) end end @@ -47,7 +48,10 @@ function check_op(ir::IRCode, domtree::DomTree, @nospecialize(op), use_bb::Int, end use_inst = ir[op] - if isa(use_inst[:stmt], Union{GotoIfNot, GotoNode, ReturnNode}) + if isa(use_inst[:stmt], Union{GotoIfNot, GotoNode, ReturnNode}) && !(isa(use_inst[:stmt], ReturnNode) && !isdefined(use_inst[:stmt], :val)) + # Allow uses of `unreachable`, which may have been inserted when + # an earlier block got deleted, but for some reason we didn't figure + # out yet that this entire block is dead also. @verify_error "At statement %$use_idx: Invalid use of value statement or terminator %$(op.id)" error("") end @@ -69,7 +73,7 @@ function check_op(ir::IRCode, domtree::DomTree, @nospecialize(op), use_bb::Int, end end elseif isa(op, Union{OldSSAValue, NewSSAValue}) - @verify_error "Left over SSA marker" + @verify_error "At statement %$use_idx: Left over SSA marker ($op)" error("") elseif isa(op, SlotNumber) @verify_error "Left over slot detected in converted IR" @@ -107,8 +111,8 @@ function verify_ir(ir::IRCode, print::Bool=true, @verify_error "IR info length is invalid $(length(ir.stmts.info)) / $(length(ir.stmts))" error("") end - if length(ir.stmts.line) != length(ir.stmts) - @verify_error "IR line length is invalid $(length(ir.stmts.line)) / $(length(ir.stmts))" + if length(ir.stmts.line) != length(ir.stmts) * 3 + @verify_error "IR line length is invalid $(length(ir.stmts.line)) / $(length(ir.stmts) * 3)" error("") end if length(ir.stmts.flag) != length(ir.stmts) @@ -195,9 +199,14 @@ function verify_ir(ir::IRCode, print::Bool=true, @verify_error "Block $idx successors ($(block.succs)), does not match GotoIfNot terminator" error("") end - elseif isexpr(terminator, :enter) + elseif isa(terminator, EnterNode) @label enter_check - if length(block.succs) != 2 || (block.succs != Int[terminator.args[1], idx+1] && block.succs != Int[idx+1, terminator.args[1]]) + if length(block.succs) == 1 + if terminator.catch_dest != 0 + @verify_error "Block $idx successors ($(block.succs)), does not match :enter terminator" + error("") + end + elseif (block.succs != Int[terminator.catch_dest, idx+1] && block.succs != Int[idx+1, terminator.catch_dest]) @verify_error "Block $idx successors ($(block.succs)), does not match :enter terminator" error("") end @@ -207,7 +216,7 @@ function verify_ir(ir::IRCode, print::Bool=true, # statement, until we can do proper CFG manipulations during compaction. for stmt_idx in first(block.stmts):last(block.stmts) stmt = ir[SSAValue(stmt_idx)][:stmt] - if isexpr(stmt, :enter) + if isa(stmt, EnterNode) terminator = stmt @goto enter_check end @@ -316,24 +325,10 @@ function verify_ir(ir::IRCode, print::Bool=true, error("") end end - elseif (isa(stmt, GotoNode) || isa(stmt, GotoIfNot) || isexpr(stmt, :enter)) && idx != last(ir.cfg.blocks[bb].stmts) + elseif isterminator(stmt) && idx != last(ir.cfg.blocks[bb].stmts) @verify_error "Terminator $idx in bb $bb is not the last statement in the block" error("") else - if isa(stmt, Expr) || isa(stmt, ReturnNode) # TODO: make sure everything has line info - if (stmt isa ReturnNode) - if isdefined(stmt, :val) - # TODO: Disallow unreachable returns? - # bb_unreachable(domtree, Int64(edge)) - else - #@verify_error "Missing line number information for statement $idx of $ir" - end - end - if !(stmt isa ReturnNode && !isdefined(stmt, :val)) # not actually a return node, but an unreachable marker - if ir.stmts[idx][:line] <= 0 - end - end - end isforeigncall = false if isa(stmt, Expr) if stmt.head === :(=) @@ -370,7 +365,7 @@ function verify_ir(ir::IRCode, print::Bool=true, error() elseif isa(arg, SSAValue) enter_stmt = ir[arg::SSAValue][:stmt] - if !isa(enter_stmt, Nothing) && !isexpr(enter_stmt, :enter) + if !isa(enter_stmt, Nothing) && !isa(enter_stmt, EnterNode) @verify_error "Malformed :leave - argument ssavalue should point to `nothing` or :enter" error() end @@ -388,12 +383,12 @@ function verify_ir(ir::IRCode, print::Bool=true, end end -function verify_linetable(linetable::Vector{LineInfoNode}, print::Bool=true) - for i in 1:length(linetable) - line = linetable[i] - if i <= line.inlined_at - @verify_error "Misordered linetable" - error("") +function verify_linetable(di::DebugInfoStream, nstmts::Int, print::Bool=true) + @assert 3nstmts == length(di.codelocs) + for i in 1:nstmts + edge = di.codelocs[3i-1] + if !(edge == 0 || get(di.edges, edge, nothing) isa DebugInfo) + @verify_error "Malformed debuginfo index into edges" end end end diff --git a/base/compiler/stmtinfo.jl b/base/compiler/stmtinfo.jl index 9f55d56181838..25f5bb894eaa9 100644 --- a/base/compiler/stmtinfo.jl +++ b/base/compiler/stmtinfo.jl @@ -10,6 +10,7 @@ and any additional information (`call.info`) for a given generic call. """ struct CallMeta rt::Any + exct::Any effects::Effects info::CallInfo end @@ -76,6 +77,14 @@ struct SemiConcreteResult <: ConstResult effects::Effects end +# XXX Technically this does not represent a result of constant inference, but rather that of +# regular edge inference. It might be more appropriate to rename `ConstResult` and +# `ConstCallInfo` to better reflect the fact that they represent either of local or +# volatile inference result. +struct VolatileInferenceResult <: ConstResult + inf_result::InferenceResult +end + """ info::ConstCallInfo <: CallInfo @@ -213,13 +222,18 @@ struct FinalizerInfo <: CallInfo end """ - info::ModifyFieldInfo <: CallInfo + info::ModifyOpInfo <: CallInfo + +Represents a resolved call of one of: + - `modifyfield!(obj, name, op, x, [order])` + - `modifyglobal!(mod, var, op, x, order)` + - `memoryrefmodify!(memref, op, x, order, boundscheck)` + - `Intrinsics.atomic_pointermodify(ptr, op, x, order)` -Represents a resolved all of `modifyfield!(obj, name, op, x, [order])`. -`info.info` wraps the call information of `op(getfield(obj, name), x)`. +`info.info` wraps the call information of `op(getval(), x)`. """ -struct ModifyFieldInfo <: CallInfo - info::CallInfo # the callinfo for the `op(getfield(obj, name), x)` call +struct ModifyOpInfo <: CallInfo + info::CallInfo # the callinfo for the `op(getval(), x)` call end @specialize diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 7f7766e44a164..24450c4bd9952 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -95,7 +95,7 @@ add_tfunc(throw, 1, 1, @nospecs((𝕃::AbstractLattice, x)->Bottom), 0) # if isexact is false, the actual runtime type may (will) be a subtype of t # if isconcrete is true, the actual runtime type is definitely concrete (unreachable if not valid as a typeof) # if istype is true, the actual runtime value will definitely be a type (e.g. this is false for Union{Type{Int}, Int}) -function instanceof_tfunc(@nospecialize(t), astag::Bool=false) +function instanceof_tfunc(@nospecialize(t), astag::Bool=false, @nospecialize(troot) = t) if isa(t, Const) if isa(t.val, Type) && valid_as_lattice(t.val, astag) return t.val, true, isconcretetype(t.val), true @@ -103,6 +103,7 @@ function instanceof_tfunc(@nospecialize(t), astag::Bool=false) return Bottom, true, false, false # runtime throws on non-Type end t = widenconst(t) + troot = widenconst(troot) if t === Bottom return Bottom, true, true, false # runtime unreachable elseif t === typeof(Bottom) || !hasintersect(t, Type) @@ -110,10 +111,15 @@ function instanceof_tfunc(@nospecialize(t), astag::Bool=false) elseif isType(t) tp = t.parameters[1] valid_as_lattice(tp, astag) || return Bottom, true, false, false # runtime unreachable / throws on non-Type + if troot isa UnionAll + # Free `TypeVar`s inside `Type` has violated the "diagonal" rule. + # Widen them before `UnionAll` rewraping to relax concrete constraint. + tp = widen_diagonal(tp, troot) + end return tp, !has_free_typevars(tp), isconcretetype(tp), true elseif isa(t, UnionAll) t′ = unwrap_unionall(t) - t′′, isexact, isconcrete, istype = instanceof_tfunc(t′, astag) + t′′, isexact, isconcrete, istype = instanceof_tfunc(t′, astag, rewrap_unionall(t, troot)) tr = rewrap_unionall(t′′, t) if t′′ isa DataType && t′′.name !== Tuple.name && !has_free_typevars(tr) # a real instance must be within the declared bounds of the type, @@ -128,8 +134,8 @@ function instanceof_tfunc(@nospecialize(t), astag::Bool=false) end return tr, isexact, isconcrete, istype elseif isa(t, Union) - ta, isexact_a, isconcrete_a, istype_a = instanceof_tfunc(t.a, astag) - tb, isexact_b, isconcrete_b, istype_b = instanceof_tfunc(t.b, astag) + ta, isexact_a, isconcrete_a, istype_a = instanceof_tfunc(t.a, astag, troot) + tb, isexact_b, isconcrete_b, istype_b = instanceof_tfunc(t.b, astag, troot) isconcrete = isconcrete_a && isconcrete_b istype = istype_a && istype_b # most users already handle the Union case, so here we assume that @@ -178,8 +184,6 @@ add_tfunc(sdiv_int, 2, 2, math_tfunc, 20) add_tfunc(udiv_int, 2, 2, math_tfunc, 20) add_tfunc(srem_int, 2, 2, math_tfunc, 20) add_tfunc(urem_int, 2, 2, math_tfunc, 20) -add_tfunc(add_ptr, 2, 2, math_tfunc, 1) -add_tfunc(sub_ptr, 2, 2, math_tfunc, 1) add_tfunc(neg_float, 1, 1, math_tfunc, 1) add_tfunc(add_float, 2, 2, math_tfunc, 2) add_tfunc(sub_float, 2, 2, math_tfunc, 2) @@ -291,7 +295,7 @@ add_tfunc(checked_umul_int, 2, 2, chk_tfunc, 5) # ----------- @nospecs function llvmcall_tfunc(𝕃::AbstractLattice, fptr, rt, at, a...) - return instanceof_tfunc(rt, true)[1] + return instanceof_tfunc(rt)[1] end add_tfunc(Core.Intrinsics.llvmcall, 3, INT_INF, llvmcall_tfunc, 10) @@ -303,7 +307,6 @@ end add_tfunc(Core.Intrinsics.cglobal, 1, 2, cglobal_tfunc, 5) add_tfunc(Core.Intrinsics.have_fma, 1, 1, @nospecs((𝕃::AbstractLattice, x)->Bool), 1) -add_tfunc(Core.Intrinsics.arraylen, 1, 1, @nospecs((𝕃::AbstractLattice, x)->Int), 4) # builtin functions # ================= @@ -326,7 +329,7 @@ end add_tfunc(Core.ifelse, 3, 3, ifelse_tfunc, 1) @nospecs function ifelse_nothrow(𝕃::AbstractLattice, cond, x, y) - ⊑ = Core.Compiler.:⊑(𝕃) + ⊑ = partialorder(𝕃) return cond ⊑ Bool end @@ -375,7 +378,7 @@ function isdefined_nothrow(𝕃::AbstractLattice, argtypes::Vector{Any}) return isdefined_nothrow(𝕃, argtypes[1], argtypes[2]) end @nospecs function isdefined_nothrow(𝕃::AbstractLattice, x, name) - ⊑ = Core.Compiler.:⊑(𝕃) + ⊑ = partialorder(𝕃) isvarargtype(x) && return false isvarargtype(name) && return false if hasintersect(widenconst(x), Module) @@ -389,20 +392,13 @@ end return isdefined_tfunc(𝕃, arg1, sym) end @nospecs function isdefined_tfunc(𝕃::AbstractLattice, arg1, sym) - if isa(arg1, Const) - arg1t = typeof(arg1.val) - else - arg1t = widenconst(arg1) - end - if isType(arg1t) - return Bool - end + arg1t = arg1 isa Const ? typeof(arg1.val) : isconstType(arg1) ? typeof(arg1.parameters[1]) : widenconst(arg1) a1 = unwrap_unionall(arg1t) if isa(a1, DataType) && !isabstracttype(a1) if a1 === Module hasintersect(widenconst(sym), Symbol) || return Bottom if isa(sym, Const) && isa(sym.val, Symbol) && isa(arg1, Const) && - isdefined(arg1.val::Module, sym.val::Symbol) + isdefinedconst_globalref(GlobalRef(arg1.val::Module, sym.val::Symbol)) return Const(true) end elseif isa(sym, Const) @@ -428,9 +424,8 @@ end elseif idx <= 0 || (!isvatuple(a1) && idx > fieldcount(a1)) return Const(false) elseif isa(arg1, Const) - arg1v = (arg1::Const).val - if !ismutable(arg1v) || isdefined(arg1v, idx) || isconst(typeof(arg1v), idx) - return Const(isdefined(arg1v, idx)) + if !ismutabletype(a1) || isconst(a1, idx) + return Const(isdefined(arg1.val, idx)) end elseif !isvatuple(a1) fieldT = fieldtype(a1, idx) @@ -488,8 +483,8 @@ function sizeof_nothrow(@nospecialize(x)) end function _const_sizeof(@nospecialize(x)) - # Constant Vector does not have constant size - isa(x, Vector) && return Int + # Constant GenericMemory does not have constant size + isa(x, GenericMemory) && return Int size = try Core.sizeof(x) catch ex @@ -595,7 +590,8 @@ add_tfunc(svec, 0, INT_INF, @nospecs((𝕃::AbstractLattice, args...)->SimpleVec end return TypeVar end -@nospecs function typebound_nothrow(b) +@nospecs function typebound_nothrow(𝕃::AbstractLattice, b) + ⊑ = partialorder(𝕃) b = widenconst(b) (b ⊑ TypeVar) && return true if isType(b) @@ -604,30 +600,14 @@ end return false end @nospecs function typevar_nothrow(𝕃::AbstractLattice, n, lb, ub) - ⊑ = Core.Compiler.:⊑(𝕃) + ⊑ = partialorder(𝕃) n ⊑ Symbol || return false - typebound_nothrow(lb) || return false - typebound_nothrow(ub) || return false + typebound_nothrow(𝕃, lb) || return false + typebound_nothrow(𝕃, ub) || return false return true end add_tfunc(Core._typevar, 3, 3, typevar_tfunc, 100) -@nospecs function arraysize_tfunc(𝕃::AbstractLattice, ary, dim) - hasintersect(widenconst(ary), Array) || return Bottom - hasintersect(widenconst(dim), Int) || return Bottom - return Int -end -add_tfunc(arraysize, 2, 2, arraysize_tfunc, 4) - -@nospecs function arraysize_nothrow(ary, dim) - ary ⊑ Array || return false - if isa(dim, Const) - dimval = dim.val - return isa(dimval, Int) && dimval > 0 - end - return false -end - struct MemoryOrder x::Cint end const MEMORY_ORDER_UNSPECIFIED = MemoryOrder(-2) const MEMORY_ORDER_INVALID = MemoryOrder(-1) @@ -672,6 +652,9 @@ function pointer_eltype(@nospecialize(ptr)) return Any end +@nospecs function pointerarith_tfunc(𝕃::AbstractLattice, ptr, offset) + return ptr +end @nospecs function pointerref_tfunc(𝕃::AbstractLattice, a, i, align) return pointer_eltype(a) end @@ -709,12 +692,14 @@ end unw = unwrap_unionall(a) if isa(unw, DataType) && unw.name === Ptr.body.name T = unw.parameters[1] - valid_as_lattice(T, true) || return Bottom + valid_as_lattice(T) || return Bottom 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(add_ptr, 2, 2, pointerarith_tfunc, 1) +add_tfunc(sub_ptr, 2, 2, pointerarith_tfunc, 1) add_tfunc(pointerref, 3, 3, pointerref_tfunc, 4) add_tfunc(pointerset, 4, 4, pointerset_tfunc, 5) add_tfunc(atomic_fence, 1, 1, atomic_fence_tfunc, 4) @@ -824,7 +809,7 @@ end add_tfunc(typeassert, 2, 2, typeassert_tfunc, 4) @nospecs function typeassert_nothrow(𝕃::AbstractLattice, v, t) - ⊑ = Core.Compiler.:⊑(𝕃) + ⊑ = partialorder(𝕃) # ty, exact = instanceof_tfunc(t, true) # return exact && v ⊑ ty if (isType(t) && !has_free_typevars(t) && v ⊑ t.parameters[1]) || @@ -870,7 +855,7 @@ end add_tfunc(isa, 2, 2, isa_tfunc, 1) @nospecs function isa_nothrow(𝕃::AbstractLattice, obj, typ) - ⊑ = Core.Compiler.:⊑(𝕃) + ⊑ = partialorder(𝕃) return typ ⊑ Type end @@ -893,7 +878,7 @@ end add_tfunc(<:, 2, 2, subtype_tfunc, 10) @nospecs function subtype_nothrow(𝕃::AbstractLattice, lty, rty) - ⊑ = Core.Compiler.:⊑(𝕃) + ⊑ = partialorder(𝕃) return lty ⊑ Type && rty ⊑ Type end @@ -939,46 +924,43 @@ function try_compute_fieldidx(@nospecialize(typ), @nospecialize(field)) return field end -function getfield_boundscheck((; fargs, argtypes)::ArgInfo) # Symbol - farg = nothing - if length(argtypes) == 3 +function getfield_boundscheck(argtypes::Vector{Any}) + if length(argtypes) == 2 + isvarargtype(argtypes[2]) && return :unsafe return :on - elseif length(argtypes) == 4 - fargs !== nothing && (farg = fargs[4]) - boundscheck = argtypes[4] - isvarargtype(boundscheck) && return :unknown + elseif length(argtypes) == 3 + boundscheck = argtypes[3] + isvarargtype(boundscheck) && return :unsafe if widenconst(boundscheck) === Symbol return :on end - elseif length(argtypes) == 5 - fargs !== nothing && (farg = fargs[5]) - boundscheck = argtypes[5] + elseif length(argtypes) == 4 + boundscheck = argtypes[4] + isvarargtype(boundscheck) && return :unsafe else - return :unknown + return :unsafe end - isvarargtype(boundscheck) && return :unknown boundscheck = widenconditional(boundscheck) if widenconst(boundscheck) === Bool if isa(boundscheck, Const) return boundscheck.val::Bool ? :on : :off - elseif farg !== nothing && isexpr(farg, :boundscheck) - return :boundscheck end + return :unknown # including a case when specified as `:boundscheck` end - return :unknown + return :unsafe end -function getfield_nothrow(𝕃::AbstractLattice, arginfo::ArgInfo, boundscheck::Symbol=getfield_boundscheck(arginfo)) - (;argtypes) = arginfo +function getfield_nothrow(𝕃::AbstractLattice, argtypes::Vector{Any}, boundscheck::Symbol=getfield_boundscheck(argtypes)) + boundscheck === :unsafe && return false ordering = Const(:not_atomic) - if length(argtypes) == 4 - isvarargtype(argtypes[4]) && return false - if widenconst(argtypes[4]) !== Bool - ordering = argtypes[4] - end - elseif length(argtypes) == 5 - ordering = argtypes[5] - elseif length(argtypes) != 3 + if length(argtypes) == 3 + isvarargtype(argtypes[3]) && return false + if widenconst(argtypes[3]) !== Bool + ordering = argtypes[3] + end + elseif length(argtypes) == 4 + ordering = argtypes[3] + elseif length(argtypes) ≠ 2 return false end isa(ordering, Const) || return false @@ -987,7 +969,7 @@ function getfield_nothrow(𝕃::AbstractLattice, arginfo::ArgInfo, boundscheck:: if ordering !== :not_atomic # TODO: this is assuming not atomic return false end - return getfield_nothrow(𝕃, argtypes[2], argtypes[3], !(boundscheck === :off)) + return getfield_nothrow(𝕃, argtypes[1], argtypes[2], !(boundscheck === :off)) end @nospecs function getfield_nothrow(𝕃::AbstractLattice, s00, name, boundscheck::Bool) # If we don't have boundscheck off and don't know the field, don't even bother @@ -995,12 +977,12 @@ end isa(name, Const) || return false end - ⊑ = Core.Compiler.:⊑(𝕃) + ⊑ = partialorder(𝕃) # If we have s00 being a const, we can potentially refine our type-based analysis above if isa(s00, Const) || isconstType(s00) if !isa(s00, Const) - sv = s00.parameters[1] + sv = (s00::DataType).parameters[1] else sv = s00.val end @@ -1010,15 +992,16 @@ end isa(sv, Module) && return false isa(nval, Int) || return false end - return isdefined(sv, nval) + return isdefined_tfunc(𝕃, s00, name) === Const(true) end boundscheck && return false # If bounds checking is disabled and all fields are assigned, # we may assume that we don't throw isa(sv, Module) && return false name ⊑ Int || name ⊑ Symbol || return false - for i = 1:fieldcount(typeof(sv)) - isdefined(sv, i) || return false + typeof(sv).name.n_uninitialized == 0 && return true + for i = (datatype_min_ninitialized(typeof(sv)) + 1):nfields(sv) + isdefined_tfunc(𝕃, s00, Const(i)) === Const(true) || return false end return true end @@ -1257,27 +1240,22 @@ end return rewrap_unionall(R, s00) end -@nospecs function getfield_notundefined(typ0, name) - if isa(typ0, Const) && isa(name, Const) - typv = typ0.val - namev = name.val - isa(typv, Module) && return true - if isa(namev, Symbol) || isa(namev, Int) - # Fields are not allowed to transition from defined to undefined, so - # even if the field is not const, all we need to check here is that - # it is defined here. - return isdefined(typv, namev) - end +@nospecs function getfield_notuninit(typ0, name) + if isa(typ0, Const) + # If the object is Const, then we know exactly the bit patterns that + # must be returned by getfield if not an error + return true end typ0 = widenconst(typ0) typ = unwrap_unionall(typ0) if isa(typ, Union) - return getfield_notundefined(rewrap_unionall(typ.a, typ0), name) && - getfield_notundefined(rewrap_unionall(typ.b, typ0), name) + return getfield_notuninit(rewrap_unionall(typ.a, typ0), name) && + getfield_notuninit(rewrap_unionall(typ.b, typ0), name) end isa(typ, DataType) || return false - if typ.name === Tuple.name || typ.name === _NAMEDTUPLE_NAME - # tuples and named tuples can't be instantiated with undefined fields, + isabstracttype(typ) && !isconstType(typ) && return false # cannot say anything about abstract types + if typ.name.n_uninitialized == 0 + # Types such as tuples and named tuples that can't be instantiated with undefined fields, # so we don't need to be conservative here return true end @@ -1305,17 +1283,19 @@ end fcnt = fieldcount_noerror(typ) fcnt === nothing && return false 0 < fidx ≤ fcnt || return true # no undefined behavior if thrown + fidx ≤ datatype_min_ninitialized(typ) && return true # always defined ftyp = fieldtype(typ, fidx) - is_undefref_fieldtype(ftyp) && return true - return fidx ≤ datatype_min_ninitialized(typ) + is_undefref_fieldtype(ftyp) && return true # always initialized + return false end -# checks if a field of this type will not be initialized with undefined value -# and the access to that uninitialized field will cause and `UndefRefError`, e.g., +# checks if a field of this type is guaranteed to be defined to a value +# and that access to an uninitialized field will cause an `UndefRefError` or return zero # - is_undefref_fieldtype(String) === true # - is_undefref_fieldtype(Integer) === true # - is_undefref_fieldtype(Any) === true # - is_undefref_fieldtype(Int) === false # - is_undefref_fieldtype(Union{Int32,Int64}) === false +# - is_undefref_fieldtype(T) === false function is_undefref_fieldtype(@nospecialize ftyp) return !has_free_typevars(ftyp) && !allocatedinline(ftyp) end @@ -1352,7 +1332,6 @@ end return setfield!_nothrow(𝕃, s00, name, v) end @nospecs function setfield!_nothrow(𝕃::AbstractLattice, s00, name, v) - ⊑ = Core.Compiler.:⊑(𝕃) s0 = widenconst(s00) s = unwrap_unionall(s0) if isa(s, Union) @@ -1369,45 +1348,94 @@ end isconst(s, field) && return false isfieldatomic(s, field) && return false # TODO: currently we're only testing for ordering === :not_atomic v_expected = fieldtype(s0, field) + ⊑ = partialorder(𝕃) return v ⊑ v_expected end return false end -@nospecs function swapfield!_tfunc(𝕃::AbstractLattice, o, f, v, order) - return getfield_tfunc(𝕃, o, f) -end -@nospecs function swapfield!_tfunc(𝕃::AbstractLattice, o, f, v) +@nospecs function swapfield!_tfunc(𝕃::AbstractLattice, o, f, v, order=Symbol) + setfield!_tfunc(𝕃, o, f, v) === Bottom && return Bottom return getfield_tfunc(𝕃, o, f) end -@nospecs function modifyfield!_tfunc(𝕃::AbstractLattice, o, f, op, v, order) - return modifyfield!_tfunc(𝕃, o, f, op, v) -end -@nospecs function modifyfield!_tfunc(𝕃::AbstractLattice, o, f, op, v) +@nospecs function modifyfield!_tfunc(𝕃::AbstractLattice, o, f, op, v, order=Symbol) T = _fieldtype_tfunc(𝕃, o, f, isconcretetype(o)) T === Bottom && return Bottom PT = Const(Pair) return instanceof_tfunc(apply_type_tfunc(𝕃, PT, T, T), true)[1] end -function abstract_modifyfield!(interp::AbstractInterpreter, argtypes::Vector{Any}, si::StmtInfo, sv::AbsIntState) +@nospecs function replacefield!_tfunc(𝕃::AbstractLattice, o, f, x, v, success_order=Symbol, failure_order=Symbol) + T = _fieldtype_tfunc(𝕃, o, f, isconcretetype(o)) + T === Bottom && return Bottom + PT = Const(ccall(:jl_apply_cmpswap_type, Any, (Any,), T) where T) + return instanceof_tfunc(apply_type_tfunc(𝕃, PT, T), true)[1] +end +@nospecs function setfieldonce!_tfunc(𝕃::AbstractLattice, o, f, v, success_order=Symbol, failure_order=Symbol) + setfield!_tfunc(𝕃, o, f, v) === Bottom && return Bottom + isdefined_tfunc(𝕃, o, f) === Const(true) && return Const(false) + return Bool +end + +@nospecs function abstract_modifyop!(interp::AbstractInterpreter, ff, argtypes::Vector{Any}, si::StmtInfo, sv::AbsIntState) + if ff === modifyfield! + minargs = 5 + maxargs = 6 + op_argi = 4 + v_argi = 5 + elseif ff === Core.modifyglobal! + minargs = 5 + maxargs = 6 + op_argi = 4 + v_argi = 5 + elseif ff === Core.memoryrefmodify! + minargs = 6 + maxargs = 6 + op_argi = 3 + v_argi = 4 + elseif ff === atomic_pointermodify + minargs = 5 + maxargs = 5 + op_argi = 3 + v_argi = 4 + else + @assert false "unreachable" + end + nargs = length(argtypes) if !isempty(argtypes) && isvarargtype(argtypes[nargs]) - nargs - 1 <= 6 || return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) - nargs > 3 || return CallMeta(Any, Effects(), NoCallInfo()) + nargs - 1 <= maxargs || return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) + nargs + 1 >= op_argi || return CallMeta(Any, Any, Effects(), NoCallInfo()) else - 5 <= nargs <= 6 || return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + minargs <= nargs <= maxargs || return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) end 𝕃ᵢ = typeinf_lattice(interp) - o = unwrapva(argtypes[2]) - f = unwrapva(argtypes[3]) - RT = modifyfield!_tfunc(𝕃ᵢ, o, f, Any, Any) + if ff === modifyfield! + o = unwrapva(argtypes[2]) + f = unwrapva(argtypes[3]) + RT = modifyfield!_tfunc(𝕃ᵢ, o, f, Any, Any, Symbol) + TF = getfield_tfunc(𝕃ᵢ, o, f) + elseif ff === Core.modifyglobal! + o = unwrapva(argtypes[2]) + f = unwrapva(argtypes[3]) + RT = modifyglobal!_tfunc(𝕃ᵢ, o, f, Any, Any, Symbol) + TF = getglobal_tfunc(𝕃ᵢ, o, f, Symbol) + elseif ff === Core.memoryrefmodify! + o = unwrapva(argtypes[2]) + RT = memoryrefmodify!_tfunc(𝕃ᵢ, o, Any, Any, Symbol, Bool) + TF = memoryrefget_tfunc(𝕃ᵢ, o, Symbol, Bool) + elseif ff === atomic_pointermodify + o = unwrapva(argtypes[2]) + RT = atomic_pointermodify_tfunc(𝕃ᵢ, o, Any, Any, Symbol) + TF = atomic_pointerref_tfunc(𝕃ᵢ, o, Symbol) + else + @assert false "unreachable" + end info = NoCallInfo() - if nargs >= 5 && RT !== Bottom + if nargs >= v_argi && 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) + op = unwrapva(argtypes[op_argi]) + v = unwrapva(argtypes[v_argi]) callinfo = abstract_call(interp, ArgInfo(nothing, Any[op, TF, v]), StmtInfo(true), sv, #=max_methods=#1) TF2 = tmeet(callinfo.rt, widenconst(TF)) if TF2 === Bottom @@ -1415,35 +1443,23 @@ function abstract_modifyfield!(interp::AbstractInterpreter, argtypes::Vector{Any elseif isconcretetype(RT) && has_nontrivial_extended_info(𝕃ᵢ, TF2) # isconcrete condition required to form a PartialStruct RT = PartialStruct(RT, Any[TF, TF2]) end - info = ModifyFieldInfo(callinfo.info) + info = ModifyOpInfo(callinfo.info) end - return CallMeta(RT, Effects(), info) -end -@nospecs function replacefield!_tfunc(𝕃::AbstractLattice, o, f, x, v, success_order, failure_order) - return replacefield!_tfunc(𝕃, o, f, x, v) -end -@nospecs function replacefield!_tfunc(𝕃::AbstractLattice, o, f, x, v, success_order) - return replacefield!_tfunc(𝕃, o, f, x, v) -end -@nospecs function replacefield!_tfunc(𝕃::AbstractLattice, o, f, x, v) - T = _fieldtype_tfunc(𝕃, o, f, isconcretetype(o)) - T === Bottom && return Bottom - PT = Const(ccall(:jl_apply_cmpswap_type, Any, (Any,), T) where T) - return instanceof_tfunc(apply_type_tfunc(𝕃, PT, T), true)[1] + return CallMeta(RT, Any, Effects(), info) 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) add_tfunc(setfield!, 3, 4, setfield!_tfunc, 3) - add_tfunc(swapfield!, 3, 4, swapfield!_tfunc, 3) add_tfunc(modifyfield!, 4, 5, modifyfield!_tfunc, 3) add_tfunc(replacefield!, 4, 6, replacefield!_tfunc, 3) +add_tfunc(setfieldonce!, 3, 5, setfieldonce!_tfunc, 3) @nospecs function fieldtype_nothrow(𝕃::AbstractLattice, s0, name) s0 === Bottom && return true # unreachable - ⊑ = Core.Compiler.:⊑(𝕃) + ⊑ = partialorder(𝕃) if s0 === Any || s0 === Type || DataType ⊑ s0 || UnionAll ⊑ s0 # We have no idea return false @@ -1655,7 +1671,7 @@ function apply_type_nothrow(𝕃::AbstractLattice, argtypes::Vector{Any}, @nospe (headtype === Union) && return true isa(rt, Const) && return true u = headtype - # TODO: implement optimization for isvarargtype(u) and istuple occurences (which are valid but are not UnionAll) + # TODO: implement optimization for isvarargtype(u) and istuple occurrences (which are valid but are not UnionAll) for i = 2:length(argtypes) isa(u, UnionAll) || return false ai = widenconditional(argtypes[i]) @@ -1755,7 +1771,7 @@ const _tvarnames = Symbol[:_A, :_B, :_C, :_D, :_E, :_F, :_G, :_H, :_I, :_J, :_K, end return allconst ? Const(ty) : Type{ty} end - istuple = isa(headtype, Type) && (headtype == Tuple) + istuple = headtype === Tuple if !istuple && !isa(headtype, UnionAll) && !isvarargtype(headtype) return Union{} end @@ -1877,6 +1893,7 @@ const _tvarnames = Symbol[:_A, :_B, :_C, :_D, :_E, :_F, :_G, :_H, :_I, :_J, :_K, try appl = apply_type(headtype, tparams...) catch ex + ex isa InterruptException && rethrow() # type instantiation might fail if one of the type parameters doesn't # match, which could happen only if a type estimate is too coarse # and might guess a concrete value while the actual type for it is Bottom @@ -1985,51 +2002,130 @@ function tuple_tfunc(𝕃::AbstractLattice, argtypes::Vector{Any}) return anyinfo ? PartialStruct(typ, argtypes) : typ end -@nospecs function arrayref_tfunc(𝕃::AbstractLattice, boundscheck, ary, idxs...) - return _arrayref_tfunc(𝕃, boundscheck, ary, idxs) +@nospecs function memoryrefget_tfunc(𝕃::AbstractLattice, mem, order, boundscheck) + memoryref_builtin_common_errorcheck(mem, order, boundscheck) || return Bottom + return memoryref_elemtype(mem) +end +@nospecs function memoryrefset!_tfunc(𝕃::AbstractLattice, mem, item, order, boundscheck) + hasintersect(widenconst(item), memoryrefget_tfunc(𝕃, mem, order, boundscheck)) || return Bottom + return item +end +@nospecs function memoryrefswap!_tfunc(𝕃::AbstractLattice, mem, v, order, boundscheck) + memoryrefset!_tfunc(𝕃, mem, v, order, boundscheck) === Bottom && return Bottom + return memoryrefget_tfunc(𝕃, mem, order, boundscheck) +end +@nospecs function memoryrefmodify!_tfunc(𝕃::AbstractLattice, mem, op, v, order, boundscheck) + memoryrefget_tfunc(𝕃, mem, order, boundscheck) === Bottom && return Bottom + T = _memoryref_elemtype(mem) + T === Bottom && return Bottom + PT = Const(Pair) + return instanceof_tfunc(apply_type_tfunc(𝕃, PT, T, T), true)[1] +end +@nospecs function memoryrefreplace!_tfunc(𝕃::AbstractLattice, mem, x, v, success_order, failure_order, boundscheck) + memoryrefset!_tfunc(𝕃, mem, v, success_order, boundscheck) === Bottom && return Bottom + hasintersect(widenconst(failure_order), Symbol) || return Bottom + T = _memoryref_elemtype(mem) + T === Bottom && return Bottom + PT = Const(ccall(:jl_apply_cmpswap_type, Any, (Any,), T) where T) + return instanceof_tfunc(apply_type_tfunc(𝕃, PT, T), true)[1] end -@nospecs function _arrayref_tfunc(𝕃::AbstractLattice, boundscheck, ary, @nospecialize idxs::Tuple) - isempty(idxs) && return Bottom - array_builtin_common_errorcheck(boundscheck, ary, idxs) || return Bottom - return array_elmtype(ary) +@nospecs function memoryrefsetonce!_tfunc(𝕃::AbstractLattice, mem, v, success_order, failure_order, boundscheck) + memoryrefset!_tfunc(𝕃, mem, v, success_order, boundscheck) === Bottom && return Bottom + hasintersect(widenconst(failure_order), Symbol) || return Bottom + return Bool end -add_tfunc(arrayref, 3, INT_INF, arrayref_tfunc, 20) -add_tfunc(const_arrayref, 3, INT_INF, arrayref_tfunc, 20) -@nospecs function arrayset_tfunc(𝕃::AbstractLattice, boundscheck, ary, item, idxs...) - hasintersect(widenconst(item), _arrayref_tfunc(𝕃, boundscheck, ary, idxs)) || return Bottom - return ary +add_tfunc(Core.memoryrefget, 3, 3, memoryrefget_tfunc, 20) +add_tfunc(Core.memoryrefset!, 4, 4, memoryrefset!_tfunc, 20) +add_tfunc(Core.memoryrefswap!, 4, 4, memoryrefswap!_tfunc, 20) +add_tfunc(Core.memoryrefmodify!, 5, 5, memoryrefmodify!_tfunc, 20) +add_tfunc(Core.memoryrefreplace!, 6, 6, memoryrefreplace!_tfunc, 20) +add_tfunc(Core.memoryrefsetonce!, 5, 5, memoryrefsetonce!_tfunc, 20) + + +@nospecs function memoryref_isassigned_tfunc(𝕃::AbstractLattice, mem, order, boundscheck) + return _memoryref_isassigned_tfunc(𝕃, mem, order, boundscheck) end -add_tfunc(arrayset, 4, INT_INF, arrayset_tfunc, 20) +@nospecs function _memoryref_isassigned_tfunc(𝕃::AbstractLattice, mem, order, boundscheck) + memoryref_builtin_common_errorcheck(mem, order, boundscheck) || return Bottom + return Bool +end +add_tfunc(memoryref_isassigned, 3, 3, memoryref_isassigned_tfunc, 20) -@nospecs function array_builtin_common_errorcheck(boundscheck, ary, @nospecialize idxs::Tuple) - hasintersect(widenconst(boundscheck), Bool) || return false - hasintersect(widenconst(ary), Array) || return false - for i = 1:length(idxs) - idx = getfield(idxs, i) - idx = isvarargtype(idx) ? unwrapva(idx) : widenconst(idx) - hasintersect(idx, Int) || return false +@nospecs function memoryref_tfunc(𝕃::AbstractLattice, mem) + a = widenconst(unwrapva(mem)) + if !has_free_typevars(a) + unw = unwrap_unionall(a) + if isa(unw, DataType) && unw.name === GenericMemory.body.body.body.name + A = unw.parameters[1] + T = unw.parameters[2] + AS = unw.parameters[3] + T isa Type || T isa TypeVar || return Bottom + return rewrap_unionall(GenericMemoryRef{A, T, AS}, a) + end + end + return GenericMemoryRef +end +@nospecs function memoryref_tfunc(𝕃::AbstractLattice, ref, idx) + if isvarargtype(idx) + idx = unwrapva(idx) end + return memoryref_tfunc(𝕃, ref, idx, Const(true)) +end +@nospecs function memoryref_tfunc(𝕃::AbstractLattice, ref, idx, boundscheck) + memoryref_builtin_common_errorcheck(ref, Const(:not_atomic), boundscheck) || return Bottom + hasintersect(widenconst(idx), Int) || return Bottom + return ref +end +add_tfunc(memoryref, 1, 3, memoryref_tfunc, 1) + +@nospecs function memoryrefoffset_tfunc(𝕃::AbstractLattice, mem) + hasintersect(widenconst(mem), GenericMemoryRef) || return Bottom + return Int +end +add_tfunc(memoryrefoffset, 1, 1, memoryrefoffset_tfunc, 5) + +@nospecs function memoryref_builtin_common_errorcheck(mem, order, boundscheck) + hasintersect(widenconst(mem), GenericMemoryRef) || return false + hasintersect(widenconst(order), Symbol) || return false + hasintersect(widenconst(unwrapva(boundscheck)), Bool) || return false return true end -function array_elmtype(@nospecialize ary) - a = widenconst(ary) - if !has_free_typevars(a) && a <: Array - a0 = a - if isa(a, UnionAll) - a = unwrap_unionall(a0) +@nospecs function memoryref_elemtype(@nospecialize mem) + m = widenconst(mem) + if !has_free_typevars(m) && m <: GenericMemoryRef + m0 = m + if isa(m, UnionAll) + m = unwrap_unionall(m0) end - if isa(a, DataType) - T = a.parameters[1] + if isa(m, DataType) + T = m.parameters[2] valid_as_lattice(T, true) || return Bottom - return rewrap_unionall(T, a0) + return rewrap_unionall(T, m0) end end return Any end -@nospecs function opaque_closure_tfunc(𝕃::AbstractLattice, arg, lb, ub, source, env::Vector{Any}, linfo::MethodInstance) +@nospecs function _memoryref_elemtype(@nospecialize mem) + m = widenconst(mem) + if !has_free_typevars(m) && m <: GenericMemoryRef + m0 = m + if isa(m, UnionAll) + m = unwrap_unionall(m0) + end + if isa(m, DataType) + T = m.parameters[2] + valid_as_lattice(T, true) || return Bottom + has_free_typevars(T) || return Const(T) + return rewrap_unionall(Type{T}, m0) + end + end + return Type +end + +@nospecs function opaque_closure_tfunc(𝕃::AbstractLattice, arg, lb, ub, source, env::Vector{Any}, mi::MethodInstance) argt, argt_exact = instanceof_tfunc(arg) lbt, lb_exact = instanceof_tfunc(lb) if !lb_exact @@ -2043,72 +2139,106 @@ end (isa(source, Const) && isa(source.val, Method)) || return t - return PartialOpaque(t, tuple_tfunc(𝕃, env), linfo, source.val) + return PartialOpaque(t, tuple_tfunc(𝕃, env), mi, source.val) end # whether getindex for the elements can potentially throw UndefRef function array_type_undefable(@nospecialize(arytype)) + arytype = unwrap_unionall(arytype) if isa(arytype, Union) return array_type_undefable(arytype.a) || array_type_undefable(arytype.b) - elseif isa(arytype, UnionAll) - return true + elseif arytype isa DataType + elmtype = memoryref_elemtype(arytype) + # TODO: use arraytype layout instead to derive this + return !((elmtype isa DataType && isbitstype(elmtype)) || (elmtype isa Union && isbitsunion(elmtype))) + end + return true +end + +@nospecs function memoryset_typecheck(𝕃::AbstractLattice, memtype, elemtype) + # Check that we can determine the element type + isa(memtype, DataType) || return false + elemtype_expected = memoryref_elemtype(memtype) + elemtype_expected === Union{} && return false + # Check that the element type is compatible with the element we're assigning + ⊑ = partialorder(𝕃) + elemtype ⊑ elemtype_expected || return false + return true +end + +function memoryref_builtin_common_nothrow(argtypes::Vector{Any}) + if length(argtypes) == 1 + memtype = widenconst(argtypes[1]) + return memtype ⊑ GenericMemory else - elmtype = (arytype::DataType).parameters[1] - return !(elmtype isa Type && (isbitstype(elmtype) || isbitsunion(elmtype))) + if length(argtypes) == 2 + boundscheck = Const(true) + elseif length(argtypes) == 3 + boundscheck = argtypes[3] + else + return false + end + memtype = widenconst(argtypes[1]) + idx = widenconst(argtypes[2]) + idx ⊑ Int || return false + boundscheck ⊑ Bool || return false + memtype ⊑ GenericMemoryRef || return false + # If we have @inbounds (last argument is false), we're allowed to assume + # we don't throw bounds errors. + if isa(boundscheck, Const) + boundscheck.val::Bool || return true + end + # Else we can't really say anything here + # TODO: In the future we may be able to track the minimum length though inference. + return false end end -function array_builtin_common_nothrow(argtypes::Vector{Any}, isarrayref::Bool) - first_idx_idx = isarrayref ? 3 : 4 - length(argtypes) ≥ first_idx_idx || return false - boundscheck = argtypes[1] - arytype = argtypes[2] - array_builtin_common_typecheck(boundscheck, arytype, argtypes, first_idx_idx) || return false - if isarrayref +function memoryrefop_builtin_common_nothrow(𝕃::AbstractLattice, argtypes::Vector{Any}, @nospecialize f) + ismemoryset = f === memoryrefset! + nargs = ismemoryset ? 4 : 3 + length(argtypes) == nargs || return false + order = argtypes[2 + ismemoryset] + boundscheck = argtypes[3 + ismemoryset] + memtype = widenconst(argtypes[1]) + memoryref_builtin_common_typecheck(𝕃, boundscheck, memtype, order) || return false + if ismemoryset + # Additionally check element type compatibility + memoryset_typecheck(𝕃, memtype, argtypes[2]) || return false + elseif f === memoryrefget # If we could potentially throw undef ref errors, bail out now. - arytype = widenconst(arytype) - array_type_undefable(arytype) && return false + array_type_undefable(memtype) && return false end - # If we have @inbounds (first argument is false), we're allowed to assume + # If we have @inbounds (last argument is false), we're allowed to assume # we don't throw bounds errors. if isa(boundscheck, Const) boundscheck.val::Bool || return true end # Else we can't really say anything here - # TODO: In the future we may be able to track the shapes of arrays though - # inference. + # TODO: In the future we may be able to track the minimum length though inference. return false end -@nospecs function array_builtin_common_typecheck(boundscheck, arytype, - argtypes::Vector{Any}, first_idx_idx::Int) - (boundscheck ⊑ Bool && arytype ⊑ Array) || return false - for i = first_idx_idx:length(argtypes) - argtypes[i] ⊑ Int || return false - end - return true -end - -@nospecs function arrayset_typecheck(arytype, elmtype) - # Check that we can determine the element type - arytype = widenconst(arytype) - isa(arytype, DataType) || return false - elmtype_expected = arytype.parameters[1] - isa(elmtype_expected, Type) || return false - # Check that the element type is compatible with the element we're assigning - elmtype ⊑ elmtype_expected || return false - return true +@nospecs function memoryref_builtin_common_typecheck(𝕃::AbstractLattice, boundscheck, memtype, order) + ⊑ = partialorder(𝕃) + return boundscheck ⊑ Bool && memtype ⊑ GenericMemoryRef && order ⊑ Symbol end # Query whether the given builtin is guaranteed not to throw given the argtypes @nospecs function _builtin_nothrow(𝕃::AbstractLattice, f, argtypes::Vector{Any}, rt) - ⊑ = Core.Compiler.:⊑(𝕃) - if f === arrayset - array_builtin_common_nothrow(argtypes, #=isarrayref=#false) || return false - # Additionally check element type compatibility - return arrayset_typecheck(argtypes[2], argtypes[3]) - elseif f === arrayref || f === const_arrayref - return array_builtin_common_nothrow(argtypes, #=isarrayref=#true) + ⊑ = partialorder(𝕃) + if f === memoryref + return memoryref_builtin_common_nothrow(argtypes) + elseif f === memoryrefoffset + length(argtypes) == 1 || return false + memtype = widenconst(argtypes[1]) + return memtype ⊑ GenericMemoryRef + elseif f === memoryrefset! + return memoryrefop_builtin_common_nothrow(𝕃, argtypes, f) + elseif f === memoryrefget + return memoryrefop_builtin_common_nothrow(𝕃, argtypes, f) + elseif f === memoryref_isassigned + return memoryrefop_builtin_common_nothrow(𝕃, argtypes, f) elseif f === Core._expr length(argtypes) >= 1 || return false return argtypes[1] ⊑ Symbol @@ -2118,16 +2248,13 @@ end # the correct number of arguments. na = length(argtypes) (na ≠ 0 && isvarargtype(argtypes[end])) && return false - if f === arraysize - na == 2 || return false - return arraysize_nothrow(argtypes[1], argtypes[2]) - elseif f === Core._typevar + if f === Core._typevar na == 3 || return false return typevar_nothrow(𝕃, argtypes[1], argtypes[2], argtypes[3]) elseif f === invoke return false elseif f === getfield - return getfield_nothrow(𝕃, ArgInfo(nothing, Any[Const(f), argtypes...])) + return getfield_nothrow(𝕃, argtypes) elseif f === setfield! if na == 3 return setfield!_nothrow(𝕃, argtypes[1], argtypes[2], argtypes[3]) @@ -2224,9 +2351,10 @@ const _EFFECT_FREE_BUILTINS = [ isa, UnionAll, getfield, - arrayref, - arraysize, - const_arrayref, + memoryref, + memoryrefoffset, + memoryrefget, + memoryref_isassigned, isdefined, Core.sizeof, Core.ifelse, @@ -2247,7 +2375,6 @@ const _INACCESSIBLEMEM_BUILTINS = Any[ svec, fieldtype, isa, - isdefined, nfields, throw, tuple, @@ -2259,9 +2386,11 @@ const _INACCESSIBLEMEM_BUILTINS = Any[ ] const _ARGMEM_BUILTINS = Any[ - arrayref, - arrayset, - arraysize, + memoryref, + memoryrefoffset, + memoryrefget, + memoryref_isassigned, + memoryrefset!, modifyfield!, replacefield!, setfield!, @@ -2298,25 +2427,16 @@ function isdefined_effects(𝕃::AbstractLattice, argtypes::Vector{Any}) # consistent if the first arg is immutable na = length(argtypes) 2 ≤ na ≤ 3 || return EFFECTS_THROWS - obj, sym = argtypes - wobj = unwrapva(obj) + wobj, sym = argtypes + wobj = unwrapva(wobj) + sym = unwrapva(sym) consistent = CONSISTENT_IF_INACCESSIBLEMEMONLY if is_immutable_argtype(wobj) consistent = ALWAYS_TRUE - else - # Bindings/fields are not allowed to transition from defined to undefined, so even - # if the object is not immutable, we can prove `:consistent`-cy if it is defined: - if isa(wobj, Const) && isa(sym, Const) - objval = wobj.val - symval = sym.val - if isa(objval, Module) - if isa(symval, Symbol) && isdefined(objval, symval) - consistent = ALWAYS_TRUE - end - elseif (isa(symval, Symbol) || isa(symval, Int)) && isdefined(objval, symval) - consistent = ALWAYS_TRUE - end - end + elseif isdefined_tfunc(𝕃, wobj, sym) isa Const + # Some bindings/fields are not allowed to transition from defined to undefined or the reverse, so even + # if the object is not immutable, we can prove `:consistent`-cy of this: + consistent = ALWAYS_TRUE end nothrow = isdefined_nothrow(𝕃, argtypes) if hasintersect(widenconst(wobj), Module) @@ -2329,10 +2449,9 @@ function isdefined_effects(𝕃::AbstractLattice, argtypes::Vector{Any}) return Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly) end -function getfield_effects(𝕃::AbstractLattice, arginfo::ArgInfo, @nospecialize(rt)) - (;argtypes) = arginfo - length(argtypes) < 3 && return EFFECTS_THROWS - obj = argtypes[2] +function getfield_effects(𝕃::AbstractLattice, argtypes::Vector{Any}, @nospecialize(rt)) + length(argtypes) < 2 && return EFFECTS_THROWS + obj = argtypes[1] if isvarargtype(obj) return Effects(EFFECTS_TOTAL; consistent=CONSISTENT_IF_INACCESSIBLEMEMONLY, @@ -2346,16 +2465,16 @@ function getfield_effects(𝕃::AbstractLattice, arginfo::ArgInfo, @nospecialize # taint `:consistent` if accessing `isbitstype`-type object field that may be initialized # with undefined value: note that we don't need to taint `:consistent` if accessing # uninitialized non-`isbitstype` field since it will simply throw `UndefRefError` - # NOTE `getfield_notundefined` conservatively checks if this field is never initialized + # NOTE `getfield_notuninit` conservatively checks if this field is never initialized # with undefined value to avoid tainting `:consistent` too aggressively # TODO this should probably taint `:noub`, however, it would hinder concrete eval for # `REPLInterpreter` that can ignore `:consistent-cy`, causing worse completions - if !(length(argtypes) ≥ 3 && getfield_notundefined(obj, argtypes[3])) + if !(length(argtypes) ≥ 2 && getfield_notuninit(obj, argtypes[2])) consistent = ALWAYS_FALSE end noub = ALWAYS_TRUE - bcheck = getfield_boundscheck(arginfo) - nothrow = getfield_nothrow(𝕃, arginfo, bcheck) + bcheck = getfield_boundscheck(argtypes) + nothrow = getfield_nothrow(𝕃, argtypes, bcheck) if !nothrow if bcheck !== :on # If we cannot independently prove inboundsness, taint `:noub`. @@ -2370,7 +2489,7 @@ function getfield_effects(𝕃::AbstractLattice, arginfo::ArgInfo, @nospecialize end end if hasintersect(widenconst(obj), Module) - inaccessiblememonly = getglobal_effects(argtypes[2:end], rt).inaccessiblememonly + inaccessiblememonly = getglobal_effects(argtypes, rt).inaccessiblememonly elseif is_mutation_free_argtype(obj) inaccessiblememonly = ALWAYS_TRUE else @@ -2398,25 +2517,27 @@ function getglobal_effects(argtypes::Vector{Any}, @nospecialize(rt)) return Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly) end -function builtin_effects(𝕃::AbstractLattice, @nospecialize(f::Builtin), arginfo::ArgInfo, @nospecialize(rt)) +""" + builtin_effects(𝕃::AbstractLattice, f::Builtin, argtypes::Vector{Any}, rt) -> Effects + +Compute the effects of a builtin function call. `argtypes` should not include `f` itself. +""" +function builtin_effects(𝕃::AbstractLattice, @nospecialize(f::Builtin), argtypes::Vector{Any}, @nospecialize(rt)) if isa(f, IntrinsicFunction) - return intrinsic_effects(f, arginfo.argtypes[2:end]) + return intrinsic_effects(f, argtypes) end @assert !contains_is(_SPECIAL_BUILTINS, f) if f === getfield - return getfield_effects(𝕃, arginfo, rt) + return getfield_effects(𝕃, argtypes, rt) end - # TODO taint `:noub` for `arrayref` and `arrayset` here - # if this builtin call deterministically throws, # don't bother to taint the other effects other than :nothrow: # note this is safe only if we accounted for :noub already rt === Bottom && return EFFECTS_THROWS - argtypes = arginfo.argtypes[2:end] if f === isdefined return isdefined_effects(𝕃, argtypes) elseif f === getglobal @@ -2431,17 +2552,32 @@ function builtin_effects(𝕃::AbstractLattice, @nospecialize(f::Builtin), argin return Effects(EFFECTS_TOTAL; consistent = (isa(setting, Const) && setting.val === :conditional) ? ALWAYS_TRUE : ALWAYS_FALSE, nothrow = compilerbarrier_nothrow(setting, nothing)) + elseif f === Core.current_scope + nothrow = true + if length(argtypes) != 0 + if length(argtypes) != 1 || !isvarargtype(argtypes[1]) + return EFFECTS_THROWS + end + nothrow = false + end + return Effects(EFFECTS_TOTAL; + consistent = ALWAYS_FALSE, + notaskstate = false, + nothrow + ) else if contains_is(_CONSISTENT_BUILTINS, f) consistent = ALWAYS_TRUE - elseif f === arrayref || f === arrayset || f === arraysize + elseif f === memoryref || f === memoryrefoffset + consistent = ALWAYS_TRUE + elseif f === memoryrefget || f === memoryrefset! || f === memoryref_isassigned consistent = CONSISTENT_IF_INACCESSIBLEMEMONLY elseif f === Core._typevar consistent = CONSISTENT_IF_NOTRETURNED else consistent = ALWAYS_FALSE end - if f === setfield! || f === arrayset + if f === setfield! || f === memoryrefset! effect_free = EFFECT_FREE_IF_INACCESSIBLEMEMONLY elseif contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f) effect_free = ALWAYS_TRUE @@ -2456,10 +2592,77 @@ function builtin_effects(𝕃::AbstractLattice, @nospecialize(f::Builtin), argin else inaccessiblememonly = ALWAYS_FALSE end - return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow, inaccessiblememonly) + if f === memoryref || f === memoryrefget || f === memoryrefset! || f === memoryref_isassigned + noub = memoryop_noub(f, argtypes) ? ALWAYS_TRUE : ALWAYS_FALSE + else + noub = ALWAYS_TRUE + end + return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow, inaccessiblememonly, noub) + end +end + + +function memoryop_noub(@nospecialize(f), argtypes::Vector{Any}) + nargs = length(argtypes) + nargs == 0 && return true # must throw and noub + lastargtype = argtypes[end] + isva = isvarargtype(lastargtype) + if f === memoryref + if nargs == 1 && !isva + return true + elseif nargs == 2 && !isva + return true + end + expected_nargs = 3 + elseif f === memoryrefget || f === memoryref_isassigned + expected_nargs = 3 + else + @assert f === memoryrefset! "unexpected memoryop is given" + expected_nargs = 4 + end + if nargs == expected_nargs && !isva + boundscheck = widenconditional(lastargtype) + hasintersect(widenconst(boundscheck), Bool) || return true # must throw and noub + boundscheck isa Const && boundscheck.val === true && return true + elseif nargs > expected_nargs + 1 + return true # must throw and noub + elseif !isva + return true # must throw and noub end + return false end +function current_scope_tfunc(interp::AbstractInterpreter, sv::InferenceState) + pc = sv.currpc + while true + handleridx = sv.handler_at[pc][1] + if handleridx == 0 + # No local scope available - inherited from the outside + return Any + end + pchandler = sv.handlers[handleridx] + # Remember that we looked at this handler, so we get re-scheduled + # if the scope information changes + isdefined(pchandler, :scope_uses) || (pchandler.scope_uses = Int[]) + pcbb = block_for_inst(sv.cfg, pc) + if findfirst(==(pcbb), pchandler.scope_uses) === nothing + push!(pchandler.scope_uses, pcbb) + end + scope = pchandler.scopet + if scope !== nothing + # Found the scope - forward it + return scope + end + pc = pchandler.enter_idx + end +end +current_scope_tfunc(interp::AbstractInterpreter, sv) = Any + +""" + builtin_nothrow(𝕃::AbstractLattice, f::Builtin, argtypes::Vector{Any}, rt) -> Bool + +Compute throw-ness of a builtin function call. `argtypes` should not include `f` itself. +""" function builtin_nothrow(𝕃::AbstractLattice, @nospecialize(f), argtypes::Vector{Any}, @nospecialize(rt)) rt === Bottom && return false contains_is(_PURE_BUILTINS, f) && return true @@ -2469,15 +2672,21 @@ end function builtin_tfunction(interp::AbstractInterpreter, @nospecialize(f), argtypes::Vector{Any}, sv::Union{AbsIntState, Nothing}) 𝕃ᵢ = typeinf_lattice(interp) - if f === tuple - return tuple_tfunc(𝕃ᵢ, argtypes) - end if isa(f, IntrinsicFunction) if is_pure_intrinsic_infer(f) && all(@nospecialize(a) -> isa(a, Const), argtypes) argvals = anymap(@nospecialize(a) -> (a::Const).val, argtypes) try + # unroll a few cases which have specialized codegen + if length(argvals) == 1 + return Const(f(argvals[1])) + elseif length(argvals) == 2 + return Const(f(argvals[1], argvals[2])) + elseif length(argvals) == 3 + return Const(f(argvals[1], argvals[2], argvals[3])) + end return Const(f(argvals...)) - catch + catch ex # expected ErrorException, TypeError, ConcurrencyViolationError, DivideError etc. + ex isa InterruptException && rethrow() return Bottom end end @@ -2488,6 +2697,16 @@ function builtin_tfunction(interp::AbstractInterpreter, @nospecialize(f), argtyp end tf = T_IFUNC[iidx] else + if f === tuple + return tuple_tfunc(𝕃ᵢ, argtypes) + elseif f === Core.current_scope + if length(argtypes) != 0 + if length(argtypes) != 1 || !isvarargtype(argtypes[1]) + return Bottom + end + end + return current_scope_tfunc(interp, sv) + end fidx = find_tfunc(f) if fidx === nothing # unknown/unhandled builtin function @@ -2526,83 +2745,146 @@ _iszero(@nospecialize x) = x === Intrinsics.xor_int(x, x) _isneg1(@nospecialize x) = _iszero(Intrinsics.not_int(x)) _istypemin(@nospecialize x) = !_iszero(x) && Intrinsics.neg_int(x) === x -function intrinsic_nothrow(f::IntrinsicFunction, argtypes::Vector{Any}) + + +function builtin_exct(𝕃::AbstractLattice, @nospecialize(f::Builtin), argtypes::Vector{Any}, @nospecialize(rt)) + if isa(f, IntrinsicFunction) + return intrinsic_exct(𝕃, f, argtypes) + end + return Any +end + +function div_nothrow(f::IntrinsicFunction, @nospecialize(arg1), @nospecialize(arg2)) + isa(arg2, Const) || return false + + den_val = arg2.val + _iszero(den_val) && return false + f !== Intrinsics.checked_sdiv_int && return true + # Nothrow as long as we additionally don't do typemin(T)/-1 + return !_isneg1(den_val) || (isa(arg1, Const) && !_istypemin(arg1.val)) +end + +function known_is_valid_intrinsic_elptr(𝕃::AbstractLattice, @nospecialize(ptr)) + ptrT = typeof_tfunc(𝕃, ptr) + isa(ptrT, Const) || return false + return is_valid_intrinsic_elptr(ptrT.val) +end + +function intrinsic_exct(𝕃::AbstractLattice, f::IntrinsicFunction, argtypes::Vector{Any}) + if !isempty(argtypes) && isvarargtype(argtypes[end]) + return Any + end + # First check that we have the correct number of arguments iidx = Int(reinterpret(Int32, f::IntrinsicFunction)) + 1 if iidx < 1 || iidx > length(T_IFUNC) - # invalid intrinsic - return false + # invalid intrinsic (system will crash) + return Any end tf = T_IFUNC[iidx] tf = tf::Tuple{Int, Int, Any} if !(tf[1] <= length(argtypes) <= tf[2]) # wrong # of args - return false + return ArgumentError end + # TODO: We could do better for cglobal - f === Intrinsics.cglobal && return false + f === Intrinsics.cglobal && return Any # TODO: We can't know for sure, but the user should have a way to assert # that it won't - f === Intrinsics.llvmcall && return false + f === Intrinsics.llvmcall && return Any + if f === Intrinsics.checked_udiv_int || f === Intrinsics.checked_urem_int || f === Intrinsics.checked_srem_int || f === Intrinsics.checked_sdiv_int # Nothrow as long as the second argument is guaranteed not to be zero - arg2 = argtypes[2] - isa(arg2, Const) || return false arg1 = argtypes[1] + arg2 = argtypes[2] warg1 = widenconst(arg1) warg2 = widenconst(arg2) - (warg1 === warg2 && isprimitivetype(warg1)) || return false - den_val = arg2.val - _iszero(den_val) && return false - f !== Intrinsics.checked_sdiv_int && return true - # Nothrow as long as we additionally don't do typemin(T)/-1 - return !_isneg1(den_val) || (isa(arg1, Const) && !_istypemin(arg1.val)) + if !(warg1 === warg2 && isprimitivetype(warg1)) + return Union{TypeError, DivideError} + end + if !div_nothrow(f, arg1, arg2) + return DivideError + end + return Union{} end + if f === Intrinsics.pointerref # Nothrow as long as the types are ok. N.B.: dereferencability is not # modeled here, but can cause errors (e.g. ReadOnlyMemoryError). We follow LLVM here # in that it is legal to remove unused non-volatile loads. - length(argtypes) == 3 || return false - return argtypes[1] ⊑ Ptr && argtypes[2] ⊑ Int && argtypes[3] ⊑ Int + if !(argtypes[1] ⊑ Ptr && argtypes[2] ⊑ Int && argtypes[3] ⊑ Int) + return Union{TypeError, ErrorException} + end + if !known_is_valid_intrinsic_elptr(𝕃, argtypes[1]) + return ErrorException + end + return Union{} end + if f === Intrinsics.pointerset eT = pointer_eltype(argtypes[1]) - isprimitivetype(eT) || return false - return argtypes[2] ⊑ eT && argtypes[3] ⊑ Int && argtypes[4] ⊑ Int - end - if f === Intrinsics.arraylen - return argtypes[1] ⊑ Array + if !known_is_valid_intrinsic_elptr(𝕃, argtypes[1]) + return Union{TypeError, ErrorException} + end + if !(argtypes[2] ⊑ eT && argtypes[3] ⊑ Int && argtypes[4] ⊑ Int) + return TypeError + end + return Union{} end + if f === Intrinsics.bitcast ty, isexact, isconcrete = instanceof_tfunc(argtypes[1], true) xty = widenconst(argtypes[2]) - return isconcrete && isprimitivetype(ty) && isprimitivetype(xty) && Core.sizeof(ty) === Core.sizeof(xty) + if !isconcrete + return Union{ErrorException, TypeError} + end + if !(isprimitivetype(ty) && isprimitivetype(xty) && Core.sizeof(ty) === Core.sizeof(xty)) + return ErrorException + end + return Union{} end + if f in (Intrinsics.sext_int, Intrinsics.zext_int, Intrinsics.trunc_int, - Intrinsics.fptoui, Intrinsics.fptosi, Intrinsics.uitofp, - Intrinsics.sitofp, Intrinsics.fptrunc, Intrinsics.fpext) + Intrinsics.fptoui, Intrinsics.fptosi, Intrinsics.uitofp, + Intrinsics.sitofp, Intrinsics.fptrunc, Intrinsics.fpext) # If !isconcrete, `ty` may be Union{} at runtime even if we have # isprimitivetype(ty). ty, isexact, isconcrete = instanceof_tfunc(argtypes[1], true) + if !isconcrete + return Union{ErrorException, TypeError} + end xty = widenconst(argtypes[2]) - return isconcrete && isprimitivetype(ty) && isprimitivetype(xty) + if !(isprimitivetype(ty) && isprimitivetype(xty)) + return ErrorException + end + return Union{} end + if f === Intrinsics.have_fma ty, isexact, isconcrete = instanceof_tfunc(argtypes[1], true) - return isconcrete && isprimitivetype(ty) + if !(isconcrete && isprimitivetype(ty)) + return TypeError + end + return Union{} end + # The remaining intrinsics are math/bits/comparison intrinsics. They work on all # primitive types of the same type. isshift = f === shl_int || f === lshr_int || f === ashr_int argtype1 = widenconst(argtypes[1]) - isprimitivetype(argtype1) || return false + isprimitivetype(argtype1) || return ErrorException for i = 2:length(argtypes) argtype = widenconst(argtypes[i]) if isshift ? !isprimitivetype(argtype) : argtype !== argtype1 - return false + return ErrorException end end - return true + return Union{} +end + +function intrinsic_nothrow(f::IntrinsicFunction, argtypes::Vector{Any}) + return intrinsic_exct(SimpleInferenceLattice.instance, f, argtypes) === Union{} end # whether `f` is pure for inference @@ -2610,7 +2892,6 @@ function is_pure_intrinsic_infer(f::IntrinsicFunction) return !(f === Intrinsics.pointerref || # this one is volatile f === Intrinsics.pointerset || # this one is never effect-free f === Intrinsics.llvmcall || # this one is never effect-free - f === Intrinsics.arraylen || # this one is volatile f === Intrinsics.sqrt_llvm_fast || # this one may differ at runtime (by a few ulps) f === Intrinsics.have_fma || # this one depends on the runtime environment f === Intrinsics.cglobal) # cglobal lookup answer changes at runtime @@ -2631,18 +2912,12 @@ function intrinsic_effects(f::IntrinsicFunction, argtypes::Vector{Any}) if contains_is(_INCONSISTENT_INTRINSICS, f) consistent = ALWAYS_FALSE - elseif f === arraylen - consistent = CONSISTENT_IF_INACCESSIBLEMEMONLY else consistent = ALWAYS_TRUE end effect_free = !(f === Intrinsics.pointerset) ? ALWAYS_TRUE : ALWAYS_FALSE nothrow = (isempty(argtypes) || !isvarargtype(argtypes[end])) && intrinsic_nothrow(f, argtypes) - if f === arraylen - inaccessiblememonly = INACCESSIBLEMEM_OR_ARGMEMONLY - else - inaccessiblememonly = ALWAYS_TRUE - end + inaccessiblememonly = ALWAYS_TRUE return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow, inaccessiblememonly) end @@ -2650,7 +2925,7 @@ end # since abstract_call_gf_by_type is a very inaccurate model of _method and of typeinf_type, # while this assumes that it is an absolutely precise and accurate and exact model of both function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, si::StmtInfo, sv::AbsIntState) - UNKNOWN = CallMeta(Type, EFFECTS_THROWS, NoCallInfo()) + UNKNOWN = CallMeta(Type, Any, EFFECTS_THROWS, NoCallInfo()) if !(2 <= length(argtypes) <= 3) return UNKNOWN end @@ -2679,7 +2954,7 @@ function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, s end if contains_is(argtypes_vec, Union{}) - return CallMeta(Const(Union{}), EFFECTS_TOTAL, NoCallInfo()) + return CallMeta(Const(Union{}), Union{}, EFFECTS_TOTAL, NoCallInfo()) end # Run the abstract_call without restricting abstract call @@ -2697,37 +2972,36 @@ function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, s rt = widenslotwrapper(call.rt) if isa(rt, Const) # output was computed to be constant - return CallMeta(Const(typeof(rt.val)), EFFECTS_TOTAL, info) + return CallMeta(Const(typeof(rt.val)), Union{}, EFFECTS_TOTAL, info) end rt = widenconst(rt) if rt === Bottom || (isconcretetype(rt) && !iskindtype(rt)) # output cannot be improved so it is known for certain - return CallMeta(Const(rt), EFFECTS_TOTAL, info) + return CallMeta(Const(rt), Union{}, EFFECTS_TOTAL, info) elseif isa(sv, InferenceState) && !isempty(sv.pclimitations) # conservatively express uncertainty of this result # in two ways: both as being a subtype of this, and # because of LimitedAccuracy causes - return CallMeta(Type{<:rt}, EFFECTS_TOTAL, info) + return CallMeta(Type{<:rt}, Union{}, EFFECTS_TOTAL, info) elseif isa(tt, Const) || isconstType(tt) # input arguments were known for certain # XXX: this doesn't imply we know anything about rt - return CallMeta(Const(rt), EFFECTS_TOTAL, info) + return CallMeta(Const(rt), Union{}, EFFECTS_TOTAL, info) elseif isType(rt) - return CallMeta(Type{rt}, EFFECTS_TOTAL, info) + return CallMeta(Type{rt}, Union{}, EFFECTS_TOTAL, info) else - return CallMeta(Type{<:rt}, EFFECTS_TOTAL, info) + return CallMeta(Type{<:rt}, Union{}, EFFECTS_TOTAL, info) end end # a simplified model of abstract_call_gf_by_type for applicable function abstract_applicable(interp::AbstractInterpreter, argtypes::Vector{Any}, sv::AbsIntState, max_methods::Int) - length(argtypes) < 2 && return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) - isvarargtype(argtypes[2]) && return CallMeta(Bool, EFFECTS_UNKNOWN, NoCallInfo()) + length(argtypes) < 2 && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) + isvarargtype(argtypes[2]) && return CallMeta(Bool, Any, EFFECTS_UNKNOWN, NoCallInfo()) argtypes = argtypes[2:end] atype = argtypes_to_type(argtypes) - matches = find_matching_methods(typeinf_lattice(interp), argtypes, atype, method_table(interp), - InferenceParams(interp).max_union_splitting, max_methods) + matches = find_method_matches(interp, argtypes, atype; max_methods) if isa(matches, FailedMethodMatch) rt = Bool # too many matches to analyze else @@ -2762,7 +3036,7 @@ function abstract_applicable(interp::AbstractInterpreter, argtypes::Vector{Any}, end end end - return CallMeta(rt, EFFECTS_TOTAL, NoCallInfo()) + return CallMeta(rt, Union{}, EFFECTS_TOTAL, NoCallInfo()) end add_tfunc(applicable, 1, INT_INF, @nospecs((𝕃::AbstractLattice, f, args...)->Bool), 40) @@ -2771,26 +3045,26 @@ function _hasmethod_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, sv if length(argtypes) == 3 && !isvarargtype(argtypes[3]) ft′ = argtype_by_index(argtypes, 2) ft = widenconst(ft′) - ft === Bottom && return CallMeta(Bool, EFFECTS_THROWS, NoCallInfo()) + ft === Bottom && return CallMeta(Bool, Any, EFFECTS_THROWS, NoCallInfo()) typeidx = 3 elseif length(argtypes) == 2 && !isvarargtype(argtypes[2]) typeidx = 2 else - return CallMeta(Any, Effects(), NoCallInfo()) + return CallMeta(Any, Any, Effects(), NoCallInfo()) end (types, isexact, isconcrete, istype) = instanceof_tfunc(argtype_by_index(argtypes, typeidx), false) - isexact || return CallMeta(Bool, Effects(), NoCallInfo()) + isexact || return CallMeta(Bool, Any, Effects(), NoCallInfo()) unwrapped = unwrap_unionall(types) if types === Bottom || !(unwrapped isa DataType) || unwrapped.name !== Tuple.name - return CallMeta(Bool, EFFECTS_THROWS, NoCallInfo()) + return CallMeta(Bool, Any, EFFECTS_THROWS, NoCallInfo()) end if typeidx == 3 - isdispatchelem(ft) || return CallMeta(Bool, Effects(), NoCallInfo()) # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below + isdispatchelem(ft) || return CallMeta(Bool, Any, Effects(), NoCallInfo()) # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below types = rewrap_unionall(Tuple{ft, unwrapped.parameters...}, types)::Type end mt = ccall(:jl_method_table_for, Any, (Any,), types) if !isa(mt, MethodTable) - return CallMeta(Bool, EFFECTS_THROWS, NoCallInfo()) + return CallMeta(Bool, Any, EFFECTS_THROWS, NoCallInfo()) end match, valid_worlds = findsup(types, method_table(interp)) update_valid_age!(sv, valid_worlds) @@ -2802,7 +3076,7 @@ function _hasmethod_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, sv edge = specialize_method(match)::MethodInstance add_invoke_backedge!(sv, types, edge) end - return CallMeta(rt, EFFECTS_TOTAL, NoCallInfo()) + return CallMeta(rt, Any, EFFECTS_TOTAL, NoCallInfo()) end # N.B.: typename maps type equivalence classes to a single value @@ -2830,7 +3104,7 @@ end if M isa Const && s isa Const M, s = M.val, s.val if M isa Module && s isa Symbol - return isdefined(M, s) + return isdefinedconst_globalref(GlobalRef(M, s)) end end return false @@ -2845,6 +3119,8 @@ end elseif !(hasintersect(widenconst(M), Module) && hasintersect(widenconst(s), Symbol)) return Bottom end + T = get_binding_type_tfunc(𝕃, M, s) + T isa Const && return T.val return Any end @nospecs function setglobal!_tfunc(𝕃::AbstractLattice, M, s, v, order=Symbol) @@ -2853,8 +3129,39 @@ end end return v end -add_tfunc(getglobal, 2, 3, getglobal_tfunc, 1) -add_tfunc(setglobal!, 3, 4, setglobal!_tfunc, 3) +@nospecs function swapglobal!_tfunc(𝕃::AbstractLattice, M, s, v, order=Symbol) + setglobal!_tfunc(𝕃, M, s, v) === Bottom && return Bottom + return getglobal_tfunc(𝕃, M, s) +end +@nospecs function modifyglobal!_tfunc(𝕃::AbstractLattice, M, s, op, v, order=Symbol) + T = get_binding_type_tfunc(𝕃, M, s) + T === Bottom && return Bottom + T isa Const || return Pair + T = T.val + return Pair{T, T} +end +@nospecs function replaceglobal!_tfunc(𝕃::AbstractLattice, M, s, x, v, success_order=Symbol, failure_order=Symbol) + v = setglobal!_tfunc(𝕃, M, s, v) + v === Bottom && return Bottom + T = get_binding_type_tfunc(𝕃, M, s) + T === Bottom && return Bottom + T isa Const || return ccall(:jl_apply_cmpswap_type, Any, (Any,), T) where T + T = T.val + return ccall(:jl_apply_cmpswap_type, Any, (Any,), T) +end +@nospecs function setglobalonce!_tfunc(𝕃::AbstractLattice, M, s, v, success_order=Symbol, failure_order=Symbol) + setglobal!_tfunc(𝕃, M, s, v) === Bottom && return Bottom + return Bool +end + +add_tfunc(Core.getglobal, 2, 3, getglobal_tfunc, 1) +add_tfunc(Core.setglobal!, 3, 4, setglobal!_tfunc, 3) +add_tfunc(Core.swapglobal!, 3, 4, swapglobal!_tfunc, 3) +add_tfunc(Core.modifyglobal!, 4, 5, modifyglobal!_tfunc, 3) +add_tfunc(Core.replaceglobal!, 4, 6, replaceglobal!_tfunc, 3) +add_tfunc(Core.setglobalonce!, 3, 5, setglobalonce!_tfunc, 3) + + @nospecs function setglobal!_nothrow(M, s, newty, o) global_order_nothrow(o, #=loading=#false, #=storing=#true) || return false return setglobal!_nothrow(M, s, newty) @@ -2870,9 +3177,9 @@ end end function global_assignment_nothrow(M::Module, s::Symbol, @nospecialize(newty)) - if isdefined(M, s) && !isconst(M, s) + if !isconst(M, s) ty = ccall(:jl_get_binding_type, Any, (Any, Any), M, s) - return ty === nothing || newty ⊑ ty + return ty isa Type && widenconst(newty) <: ty end return false end @@ -2888,14 +3195,14 @@ end end @nospecs function get_binding_type_tfunc(𝕃::AbstractLattice, M, s) if get_binding_type_effect_free(M, s) - return Const(Core.get_binding_type((M::Const).val, (s::Const).val)) + return Const(Core.get_binding_type((M::Const).val::Module, (s::Const).val::Symbol)) end return Type end add_tfunc(Core.get_binding_type, 2, 2, get_binding_type_tfunc, 0) @nospecs function get_binding_type_nothrow(𝕃::AbstractLattice, M, s) - ⊑ = Core.Compiler.:⊑(𝕃) + ⊑ = partialorder(𝕃) return M ⊑ Module && s ⊑ Symbol end @@ -2911,90 +3218,31 @@ function foreigncall_effects(@specialize(abstract_eval), e::Expr) args = e.args name = args[1] isa(name, QuoteNode) && (name = name.value) - isa(name, Symbol) || return EFFECTS_UNKNOWN - ndims = alloc_array_ndims(name) - if ndims !== nothing - if ndims ≠ 0 - return alloc_array_effects(abstract_eval, args, ndims) - else - return new_array_effects(abstract_eval, args) - end - end - if is_array_resize(name) - return array_resize_effects() + if name === :jl_alloc_genericmemory + nothrow = new_genericmemory_nothrow(abstract_eval, args) + return Effects(EFFECTS_TOTAL; consistent=CONSISTENT_IF_NOTRETURNED, nothrow) + elseif name === :jl_genericmemory_copy_slice + return Effects(EFFECTS_TOTAL; consistent=CONSISTENT_IF_NOTRETURNED, nothrow=false) end return EFFECTS_UNKNOWN end -function is_array_resize(name::Symbol) - return name === :jl_array_grow_beg || name === :jl_array_grow_end || - name === :jl_array_del_beg || name === :jl_array_del_end || - name === :jl_array_grow_at || name === :jl_array_del_at -end - -function array_resize_effects() - return Effects(EFFECTS_TOTAL; - effect_free = EFFECT_FREE_IF_INACCESSIBLEMEMONLY, - nothrow = false, - inaccessiblememonly = INACCESSIBLEMEM_OR_ARGMEMONLY) -end - -function alloc_array_ndims(name::Symbol) - if name === :jl_alloc_array_1d - return 1 - elseif name === :jl_alloc_array_2d - return 2 - elseif name === :jl_alloc_array_3d - return 3 - elseif name === :jl_new_array - return 0 - end - return nothing -end - -function alloc_array_effects(@specialize(abstract_eval), args::Vector{Any}, ndims::Int) - nothrow = alloc_array_nothrow(abstract_eval, args, ndims) - return Effects(EFFECTS_TOTAL; consistent=CONSISTENT_IF_NOTRETURNED, nothrow) -end - -function alloc_array_nothrow(@specialize(abstract_eval), args::Vector{Any}, ndims::Int) - length(args) ≥ ndims+FOREIGNCALL_ARG_START || return false - atype = instanceof_tfunc(abstract_eval(args[FOREIGNCALL_ARG_START]))[1] - dims = Csize_t[] - for i in 1:ndims - dim = abstract_eval(args[i+FOREIGNCALL_ARG_START]) - isa(dim, Const) || return false - dimval = dim.val - isa(dimval, Int) || return false - push!(dims, reinterpret(Csize_t, dimval)) - end - return _new_array_nothrow(atype, ndims, dims) -end - -function new_array_effects(@specialize(abstract_eval), args::Vector{Any}) - nothrow = new_array_nothrow(abstract_eval, args) - return Effects(EFFECTS_TOTAL; consistent=CONSISTENT_IF_NOTRETURNED, nothrow) -end - -function new_array_nothrow(@specialize(abstract_eval), args::Vector{Any}) - length(args) ≥ FOREIGNCALL_ARG_START+1 || return false - atype = instanceof_tfunc(abstract_eval(args[FOREIGNCALL_ARG_START]))[1] - dims = abstract_eval(args[FOREIGNCALL_ARG_START+1]) - isa(dims, Const) || return dims === Tuple{} - dimsval = dims.val - isa(dimsval, Tuple{Vararg{Int}}) || return false - ndims = nfields(dimsval) - isa(ndims, Int) || return false - dims = Csize_t[reinterpret(Csize_t, dimval) for dimval in dimsval] - return _new_array_nothrow(atype, ndims, dims) -end - -function _new_array_nothrow(@nospecialize(atype), ndims::Int, dims::Vector{Csize_t}) - isa(atype, DataType) || return false - eltype = atype.parameters[1] - iskindtype(typeof(eltype)) || return false - elsz = aligned_sizeof(eltype) - return ccall(:jl_array_validate_dims, Cint, - (Ptr{Csize_t}, Ptr{Csize_t}, UInt32, Ptr{Csize_t}, Csize_t), - #=nel=#RefValue{Csize_t}(), #=tot=#RefValue{Csize_t}(), ndims, dims, elsz) == 0 +function new_genericmemory_nothrow(@nospecialize(abstract_eval), args::Vector{Any}) + length(args) ≥ 1+FOREIGNCALL_ARG_START || return false + mtype = instanceof_tfunc(abstract_eval(args[FOREIGNCALL_ARG_START]))[1] + isa(mtype, DataType) || return false + isdefined(mtype, :instance) || return false + elsz = Int(datatype_layoutsize(mtype)) + arrayelem = datatype_arrayelem(mtype) + dim = abstract_eval(args[1+FOREIGNCALL_ARG_START]) + isa(dim, Const) || return false + dimval = dim.val + isa(dimval, Int) || return false + 0 < dimval < typemax(Int) || return false + tot, ovflw = Intrinsics.checked_smul_int(dimval, elsz) + ovflw && return false + isboxed = 1; isunion = 2 + tot, ovflw = Intrinsics.checked_sadd_int(tot, arrayelem == isunion ? 1 + dimval : 1) + ovflw && return false + return true end diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index f246289b5bea0..d091fb8d2f5f8 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -5,10 +5,10 @@ const track_newly_inferred = RefValue{Bool}(false) const newly_inferred = CodeInstance[] # build (and start inferring) the inference frame for the top-level MethodInstance -function typeinf(interp::AbstractInterpreter, result::InferenceResult, cache::Symbol) - frame = InferenceState(result, cache, interp) +function typeinf(interp::AbstractInterpreter, result::InferenceResult, cache_mode::Symbol) + frame = InferenceState(result, cache_mode, interp) frame === nothing && return false - cache === :global && lock_mi_inference(interp, result.linfo) + cache_mode === :global && lock_mi_inference(interp, result.linfo) return typeinf(interp, frame) end @@ -204,9 +204,8 @@ If set to `true`, record per-method-instance timings within type inference in th __set_measure_typeinf(onoff::Bool) = __measure_typeinf__[] = onoff const __measure_typeinf__ = fill(false) -# Wrapper around `_typeinf` that optionally records the exclusive time for -# each inference performed by `NativeInterpreter`. -function typeinf(interp::NativeInterpreter, frame::InferenceState) +# Wrapper around `_typeinf` that optionally records the exclusive time for each invocation. +function typeinf(interp::AbstractInterpreter, frame::InferenceState) if __measure_typeinf__[] Timings.enter_new_timer(frame) v = _typeinf(interp, frame) @@ -216,34 +215,23 @@ function typeinf(interp::NativeInterpreter, frame::InferenceState) return _typeinf(interp, frame) end end -typeinf(interp::AbstractInterpreter, frame::InferenceState) = _typeinf(interp, frame) - -function finish!(interp::AbstractInterpreter, caller::InferenceResult) - # If we didn't transform the src for caching, we may have to transform - # it anyway for users like typeinf_ext. Do that here. - opt = caller.src - if opt isa OptimizationState{typeof(interp)} # implies `may_optimize(interp) === true` - if opt.ir !== nothing - if caller.must_be_codeinf - caller.src = ir_to_codeinf!(opt) - elseif is_inlineable(opt.src) - # TODO: If the CFG is too big, inlining becomes more expensive and if we're going to - # use this IR over and over, it's worth simplifying it. Round trips through - # CodeInstance do this implicitly, since they recompute the CFG, so try to - # match that behavior here. - # ir = cfg_simplify!(opt.ir) - caller.src = opt.ir - else - # Not cached and not inlineable - drop the ir - caller.src = nothing - end - end + +function finish!(interp::AbstractInterpreter, caller::InferenceState) + result = caller.result + valid_worlds = result.valid_worlds + if last(valid_worlds) >= get_world_counter() + # if we aren't cached, we don't need this edge + # but our caller might, so let's just make it anyways + store_backedges(result, caller.stmt_edges[1]) + end + opt = result.src + if opt isa OptimizationState + result.src = opt = ir_to_codeinf!(opt) end - return caller.src + return nothing end function _typeinf(interp::AbstractInterpreter, frame::InferenceState) - interp = switch_from_irinterp(interp) typeinf_nocycle(interp, frame) || return false # frame is now part of a higher cycle # with no active ip's, frame is done frames = frame.callers_in_cycle @@ -266,17 +254,10 @@ function _typeinf(interp::AbstractInterpreter, frame::InferenceState) end end for caller in frames - (; result ) = caller - valid_worlds = result.valid_worlds - if last(valid_worlds) >= get_world_counter() - # if we aren't cached, we don't need this edge - # but our caller might, so let's just make it anyways - store_backedges(result, caller.stmt_edges[1]) - end - if caller.cached - cache_result!(caller.interp, result) + finish!(caller.interp, caller) + if is_cached(caller) + cache_result!(caller.interp, caller.result) end - finish!(caller.interp, result) end empty!(frames) return true @@ -286,8 +267,8 @@ function is_result_constabi_eligible(result::InferenceResult) result_type = result.result return isa(result_type, Const) && is_foldable_nothrow(result.ipo_effects) && is_inlineable_constant(result_type.val) end -function CodeInstance(interp::AbstractInterpreter, result::InferenceResult, - valid_worlds::WorldRange) +function CodeInstance(interp::AbstractInterpreter, result::InferenceResult; + can_discard_trees::Bool=may_discard_trees(interp)) local const_flags::Int32 result_type = result.result @assert !(result_type === nothing || result_type isa LimitedAccuracy) @@ -320,11 +301,21 @@ function CodeInstance(interp::AbstractInterpreter, result::InferenceResult, end end relocatability = 0x0 - if const_flags == 0x3 && may_discard_trees(interp) + owner = cache_owner(interp) + if const_flags == 0x3 && can_discard_trees inferred_result = nothing relocatability = 0x1 else - inferred_result = transform_result_for_cache(interp, result.linfo, valid_worlds, result) + inferred_result = transform_result_for_cache(interp, result.linfo, result.valid_worlds, result, can_discard_trees) + if inferred_result isa CodeInfo + edges = inferred_result.debuginfo + uncompressed = inferred_result + inferred_result = maybe_compress_codeinfo(interp, result.linfo, inferred_result, can_discard_trees) + result.is_src_volatile |= uncompressed !== inferred_result + elseif owner === nothing + # The global cache can only handle objects that codegen understands + inferred_result = nothing + end if isa(inferred_result, String) t = @_gc_preserve_begin inferred_result relocatability = unsafe_load(unsafe_convert(Ptr{UInt8}, inferred_result), Core.sizeof(inferred_result)) @@ -333,22 +324,31 @@ function CodeInstance(interp::AbstractInterpreter, result::InferenceResult, relocatability = 0x1 end end - # relocatability = isa(inferred_result, String) ? inferred_result[end] : UInt8(0) - return CodeInstance(result.linfo, - widenconst(result_type), rettype_const, inferred_result, - const_flags, first(valid_worlds), last(valid_worlds), + # n.b. relocatability = (isa(inferred_result, String) && inferred_result[end]) || inferred_result === nothing + if !@isdefined edges + edges = DebugInfo(result.linfo) + end + return CodeInstance(result.linfo, owner, + widenconst(result_type), widenconst(result.exc_result), rettype_const, inferred_result, + const_flags, first(result.valid_worlds), last(result.valid_worlds), # TODO: Actually do something with non-IPO effects - encode_effects(result.ipo_effects), encode_effects(result.ipo_effects), result.argescapes, - relocatability) + encode_effects(result.ipo_effects), encode_effects(result.ipo_effects), result.analysis_results, + relocatability, edges) end -function maybe_compress_codeinfo(interp::AbstractInterpreter, linfo::MethodInstance, ci::CodeInfo) - def = linfo.def +function transform_result_for_cache(interp::AbstractInterpreter, + ::MethodInstance, valid_worlds::WorldRange, result::InferenceResult, + can_discard_trees::Bool=may_discard_trees(interp)) + return result.src +end + +function maybe_compress_codeinfo(interp::AbstractInterpreter, mi::MethodInstance, ci::CodeInfo, + can_discard_trees::Bool=may_discard_trees(interp)) + def = mi.def isa(def, Method) || return ci # don't compress toplevel code - if may_discard_trees(interp) - cache_the_tree = ci.inferred && (is_inlineable(ci) || isa_compileable_sig(linfo.specTypes, linfo.sparam_vals, def)) - else - cache_the_tree = true + cache_the_tree = true + if can_discard_trees + cache_the_tree = is_inlineable(ci) || isa_compileable_sig(mi.specTypes, mi.sparam_vals, def) end if cache_the_tree if may_compress(interp) @@ -364,51 +364,39 @@ function maybe_compress_codeinfo(interp::AbstractInterpreter, linfo::MethodInsta end end -function transform_result_for_cache(interp::AbstractInterpreter, - linfo::MethodInstance, valid_worlds::WorldRange, result::InferenceResult) - inferred_result = result.src - if inferred_result isa OptimizationState{typeof(interp)} - # TODO respect must_be_codeinf setting here? - result.src = inferred_result = ir_to_codeinf!(inferred_result) - end - if inferred_result isa CodeInfo - inferred_result.min_world = first(valid_worlds) - inferred_result.max_world = last(valid_worlds) - inferred_result = maybe_compress_codeinfo(interp, linfo, inferred_result) - end - # The global cache can only handle objects that codegen understands - if !isa(inferred_result, MaybeCompressed) - inferred_result = nothing - end - return inferred_result -end - function cache_result!(interp::AbstractInterpreter, result::InferenceResult) - valid_worlds = result.valid_worlds - if last(valid_worlds) == get_world_counter() + if last(result.valid_worlds) == get_world_counter() # if we've successfully recorded all of the backedges in the global reverse-cache, # we can now widen our applicability in the global cache too - valid_worlds = WorldRange(first(valid_worlds), typemax(UInt)) + result.valid_worlds = WorldRange(first(result.valid_worlds), typemax(UInt)) end # check if the existing linfo metadata is also sufficient to describe the current inference result # to decide if it is worth caching this - 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 + mi = result.linfo + already_inferred = already_inferred_quick_test(interp, mi) + cache = WorldView(code_cache(interp), result.valid_worlds) + if !already_inferred && haskey(cache, mi) + ci = cache[mi] + # Even if we already have a CI for this, it's possible that the new CI has more + # information (E.g. because the source was limited before, but is no longer - this + # happens during bootstrap). In that case, allow the result to be recached. + if result.src === nothing || (ci.inferred !== nothing || ci.invoke != C_NULL) + already_inferred = true + end end # TODO: also don't store inferred code if we've previously decided to interpret this function if !already_inferred - code_cache(interp)[linfo] = ci = CodeInstance(interp, result, valid_worlds) + code_cache(interp)[mi] = ci = CodeInstance(interp, result) + result.ci = ci if track_newly_inferred[] - m = linfo.def + m = mi.def if isa(m, Method) && m.module != Core ccall(:jl_push_newly_inferred, Cvoid, (Any,), ci) end end end - unlock_mi_inference(interp, linfo) + unlock_mi_inference(interp, mi) nothing end @@ -457,6 +445,8 @@ function adjust_effects(ipo_effects::Effects, def::Method) end if is_effect_overridden(override, :noub) ipo_effects = Effects(ipo_effects; noub=ALWAYS_TRUE) + elseif is_effect_overridden(override, :noub_if_noinbounds) && ipo_effects.noub !== ALWAYS_TRUE + ipo_effects = Effects(ipo_effects; noub=NOUB_IF_NOINBOUNDS) end return ipo_effects end @@ -473,6 +463,11 @@ function adjust_effects(sv::InferenceState) # always throwing an error counts or never returning both count as consistent ipo_effects = Effects(ipo_effects; consistent=ALWAYS_TRUE) end + if sv.exc_bestguess === Bottom + # if the exception type of this frame is known to be `Bottom`, + # this frame is known to be safe + ipo_effects = Effects(ipo_effects; nothrow=true) + end if is_inaccessiblemem_or_argmemonly(ipo_effects) && all(1:narguments(sv, #=include_va=#true)) do i::Int return is_mutation_free_argtype(sv.slottypes[i]) end @@ -513,6 +508,11 @@ function adjust_effects(sv::InferenceState) return ipo_effects end +function refine_exception_type(@nospecialize(exc_bestguess), ipo_effects::Effects) + ipo_effects.nothrow && return Bottom + return exc_bestguess +end + # inference completed on `me` # update the MethodInstance function finish(me::InferenceState, interp::AbstractInterpreter) @@ -529,12 +529,12 @@ function finish(me::InferenceState, interp::AbstractInterpreter) end if me.src.edges !== nothing append!(s_edges, me.src.edges::Vector) - me.src.edges = nothing end # inspect whether our inference had a limited result accuracy, # else it may be suitable to cache bestguess = me.bestguess = cycle_fix_limited(me.bestguess, me) - limited_ret = bestguess isa LimitedAccuracy + exc_bestguess = me.exc_bestguess = cycle_fix_limited(me.exc_bestguess, me) + limited_ret = bestguess isa LimitedAccuracy || exc_bestguess isa LimitedAccuracy limited_src = false if !limited_ret gt = me.ssavaluetypes @@ -548,13 +548,14 @@ function finish(me::InferenceState, interp::AbstractInterpreter) end me.result.valid_worlds = me.valid_worlds me.result.result = bestguess - me.ipo_effects = me.result.ipo_effects = adjust_effects(me) + ipo_effects = me.result.ipo_effects = me.ipo_effects = adjust_effects(me) + me.result.exc_result = me.exc_bestguess = refine_exception_type(me.exc_bestguess, ipo_effects) if limited_ret # a parent may be cached still, but not this intermediate work: # we can throw everything else away now me.result.src = nothing - me.cached = false + me.cache_mode = CACHE_MODE_NULL set_inlineable!(me.src, false) unlock_mi_inference(interp, me.linfo) elseif limited_src @@ -566,29 +567,27 @@ function finish(me::InferenceState, interp::AbstractInterpreter) # annotate fulltree with type information, # either because we are the outermost code, or we might use this later type_annotate!(interp, me) - doopt = (me.cached || me.parent !== nothing) - # Disable the optimizer if we've already determined that there's nothing for - # it to do. - if may_discard_trees(interp) && is_result_constabi_eligible(me.result) - doopt = false - end - if doopt && may_optimize(interp) + mayopt = may_optimize(interp) + doopt = mayopt && + # disable optimization if we don't use this later + (me.cache_mode != CACHE_MODE_NULL || me.parent !== nothing) && + # disable optimization if we've already obtained very accurate result + !result_is_constabi(interp, me.result, mayopt) + if doopt me.result.src = OptimizationState(me, interp) else - me.result.src = me.src::CodeInfo # stash a convenience copy of the code (e.g. for reflection) + me.result.src = me.src # for reflection etc. end end - validate_code_in_debug_mode(me.linfo, me.src, "inferred") + + maybe_validate_code(me.linfo, me.src, "inferred") nothing end # record the backedges -function store_backedges(caller::InferenceResult, edges::Vector{Any}) - isa(caller.linfo.def, Method) || return nothing # don't add backedges to toplevel method instance - return store_backedges(caller.linfo, edges) -end - +store_backedges(caller::InferenceResult, edges::Vector{Any}) = store_backedges(caller.linfo, edges) function store_backedges(caller::MethodInstance, edges::Vector{Any}) + isa(caller.def, Method) || return nothing # don't add backedges to toplevel method instance for itr in BackedgeIterator(edges) callee = itr.caller if isa(callee, MethodInstance) @@ -635,13 +634,6 @@ function record_slot_assign!(sv::InferenceState) return nothing end -function record_bestguess!(sv::InferenceState) - bestguess = sv.bestguess - @assert !(bestguess isa LimitedAccuracy) - sv.src.rettype = bestguess - return nothing -end - # find the dominating assignment to the slot `id` in the block containing statement `idx`, # returns `nothing` otherwise function find_dominating_assignment(id::Int, idx::Int, sv::InferenceState) @@ -670,8 +662,6 @@ function type_annotate!(interp::AbstractInterpreter, sv::InferenceState) # to hold all of the items assigned into it record_slot_assign!(sv) - record_bestguess!(sv) - # annotate variables load types src = sv.src stmts = src.code @@ -751,7 +741,7 @@ function merge_call_chain!(interp::AbstractInterpreter, parent::InferenceState, end function is_same_frame(interp::AbstractInterpreter, mi::MethodInstance, frame::InferenceState) - return mi === frame_instance(frame) + return mi === frame_instance(frame) && cache_owner(interp) === cache_owner(frame.interp) end function poison_callstack!(infstate::InferenceState, topmost::InferenceState) @@ -805,39 +795,49 @@ end ipo_effects(code::CodeInstance) = decode_effects(code.ipo_purity_bits) struct EdgeCallResult - rt #::Type + rt + exct edge::Union{Nothing,MethodInstance} effects::Effects - function EdgeCallResult(@nospecialize(rt), + volatile_inf_result::Union{Nothing,VolatileInferenceResult} + function EdgeCallResult(@nospecialize(rt), @nospecialize(exct), edge::Union{Nothing,MethodInstance}, - effects::Effects) - return new(rt, edge, effects) + effects::Effects, + volatile_inf_result::Union{Nothing,VolatileInferenceResult} = nothing) + return new(rt, exct, edge, effects, volatile_inf_result) end end +# return cached result of regular inference +function return_cached_result(::AbstractInterpreter, codeinst::CodeInstance, caller::AbsIntState) + rt = cached_return_type(codeinst) + effects = ipo_effects(codeinst) + update_valid_age!(caller, WorldRange(min_world(codeinst), max_world(codeinst))) + return EdgeCallResult(rt, codeinst.exctype, codeinst.def, effects) +end + # compute (and cache) an inferred AST and return the current best estimate of the result type function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize(atype), sparams::SimpleVector, caller::AbsIntState) mi = specialize_method(method, atype, sparams)::MethodInstance - code = get(code_cache(interp), mi, nothing) - if code isa CodeInstance # return existing rettype if the code is already inferred - inferred = @atomic :monotonic code.inferred - if inferred === nothing && is_stmt_inline(get_curr_ssaflag(caller)) + codeinst = get(code_cache(interp), mi, nothing) + force_inline = is_stmt_inline(get_curr_ssaflag(caller)) + if codeinst isa CodeInstance # return existing rettype if the code is already inferred + inferred = @atomic :monotonic codeinst.inferred + if inferred === nothing && force_inline # we already inferred this edge before and decided to discard the inferred code, - # nevertheless we re-infer it here again and keep it around in the local cache - # since the inliner will request to use it later - cache = :local + # nevertheless we re-infer it here again in order to propagate the re-inferred + # source to the inliner as a volatile result + cache_mode = CACHE_MODE_VOLATILE else - rt = cached_return_type(code) - effects = ipo_effects(code) - update_valid_age!(caller, WorldRange(min_world(code), max_world(code))) - return EdgeCallResult(rt, mi, effects) + @assert codeinst.def === mi "MethodInstance for cached edge does not match" + return return_cached_result(interp, codeinst, caller) end else - cache = :global # cache edge targets by default + cache_mode = CACHE_MODE_GLOBAL # cache edge targets globally by default end if ccall(:jl_get_module_infer, Cint, (Any,), method.module) == 0 && !generating_output(#=incremental=#false) add_remark!(interp, caller, "Inference is disabled for the target module") - return EdgeCallResult(Any, nothing, Effects()) + return EdgeCallResult(Any, Any, nothing, Effects()) end if !is_cached(caller) && frame_parent(caller) === nothing # this caller exists to return to the user @@ -850,12 +850,12 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize # completely new lock_mi_inference(interp, mi) result = InferenceResult(mi, typeinf_lattice(interp)) - frame = InferenceState(result, cache, interp) # always use the cache for edge targets + frame = InferenceState(result, cache_mode, interp) # always use the cache for edge targets if frame === nothing add_remark!(interp, caller, "Failed to retrieve source") # can't get the source for this, so we know nothing unlock_mi_inference(interp, mi) - return EdgeCallResult(Any, nothing, Effects()) + return EdgeCallResult(Any, Any, nothing, Effects()) end if is_cached(caller) || frame_parent(caller) !== nothing # don't involve uncached functions in cycle resolution frame.parent = caller @@ -864,16 +864,22 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize update_valid_age!(caller, frame.valid_worlds) isinferred = is_inferred(frame) edge = isinferred ? mi : nothing - effects = isinferred ? frame.ipo_effects : adjust_effects(Effects(), method) # effects are adjusted already within `finish` for ipo_effects - return EdgeCallResult(frame.bestguess, edge, effects) + effects = isinferred ? frame.result.ipo_effects : adjust_effects(Effects(), method) # effects are adjusted already within `finish` for ipo_effects + exc_bestguess = refine_exception_type(frame.exc_bestguess, effects) + # propagate newly inferred source to the inliner, allowing efficient inlining w/o deserialization: + # note that this result is cached globally exclusively, so we can use this local result destructively + volatile_inf_result = isinferred ? VolatileInferenceResult(result) : nothing + return EdgeCallResult(frame.bestguess, exc_bestguess, edge, effects, volatile_inf_result) elseif frame === true # unresolvable cycle - return EdgeCallResult(Any, nothing, Effects()) + return EdgeCallResult(Any, Any, nothing, Effects()) end # return the current knowledge about this cycle frame = frame::InferenceState update_valid_age!(caller, frame.valid_worlds) - return EdgeCallResult(frame.bestguess, nothing, adjust_effects(Effects(), method)) + effects = adjust_effects(Effects(), method) + exc_bestguess = refine_exception_type(frame.exc_bestguess, effects) + return EdgeCallResult(frame.bestguess, exc_bestguess, nothing, effects) end function cached_return_type(code::CodeInstance) @@ -897,7 +903,15 @@ end #### entry points for inferring a MethodInstance given a type signature #### -function codeinfo_for_const(interp::AbstractInterpreter, mi::MethodInstance, worlds::WorldRange, @nospecialize(val)) +""" + codeinfo_for_const(interp::AbstractInterpreter, mi::MethodInstance, worlds::WorldRange, @nospecialize(val)) + +Return a fake CodeInfo that just contains `return \$val`. This function is used in various reflection APIs when asking +for the code of a function that inference has found to just return a constant. For such functions, no code is actually +stored - the constant is used directly. However, because this is an ABI implementation detail, it is nice to maintain +consistency and just synthesize a CodeInfo when the reflection APIs ask for them - this function does that. +""" +function codeinfo_for_const(interp::AbstractInterpreter, mi::MethodInstance, @nospecialize(val)) method = mi.def::Method tree = ccall(:jl_new_code_info_uninit, Ref{CodeInfo}, ()) tree.code = Any[ ReturnNode(quoted(val)) ] @@ -905,19 +919,30 @@ function codeinfo_for_const(interp::AbstractInterpreter, mi::MethodInstance, wor tree.slotnames = ccall(:jl_uncompress_argnames, Vector{Symbol}, (Any,), method.slot_syms) tree.slotflags = fill(0x00, nargs) tree.ssavaluetypes = 1 - tree.codelocs = Int32[1] - tree.linetable = LineInfoNode[LineInfoNode(method.module, method.name, method.file, method.line, Int32(0))] + tree.debuginfo = DebugInfo(mi) tree.ssaflags = UInt32[0] set_inlineable!(tree, true) tree.parent = mi - tree.rettype = Core.Typeof(val) - tree.min_world = worlds.min_world - tree.max_world = worlds.max_world - tree.inferred = true return tree end -result_is_constabi(interp::AbstractInterpreter, run_optimizer::Bool, result::InferenceResult) = +""" + codeinstance_for_const_with_code(interp::AbstractInterpreter, code::CodeInstance) + +Given a constabi `CodeInstance`, create another (uncached) CodeInstance that contains the dummy code created +by [`codeinfo_for_const`](@ref) for use in reflection functions that require this. See [`codeinfo_for_const`](@ref) for +more details. +""" +function codeinstance_for_const_with_code(interp::AbstractInterpreter, code::CodeInstance) + src = codeinfo_for_const(interp, code.def, code.rettype_const) + return CodeInstance(code.def, cache_owner(interp), code.rettype, code.exctype, code.rettype_const, src, + Int32(0x3), code.min_world, code.max_world, + code.ipo_purity_bits, code.purity_bits, code.analysis_results, + code.relocatability, src.debuginfo) +end + +result_is_constabi(interp::AbstractInterpreter, result::InferenceResult, + run_optimizer::Bool=may_optimize(interp)) = run_optimizer && may_discard_trees(interp) && is_result_constabi_eligible(result) # compute an inferred AST and return type @@ -930,9 +955,9 @@ function typeinf_code(interp::AbstractInterpreter, mi::MethodInstance, run_optim frame = typeinf_frame(interp, mi, run_optimizer) frame === nothing && return nothing, Any is_inferred(frame) || return nothing, Any - if result_is_constabi(interp, run_optimizer, frame.result) + if result_is_constabi(interp, frame.result, run_optimizer) rt = frame.result.result::Const - return codeinfo_for_const(interp, frame.linfo, frame.result.valid_worlds, rt.val), widenconst(rt) + return codeinfo_for_const(interp, frame.linfo, rt.val), widenconst(rt) end code = frame.src rt = widenconst(ignorelimited(frame.result.result)) @@ -981,55 +1006,147 @@ typeinf_frame(interp::AbstractInterpreter, method::Method, @nospecialize(atype), function typeinf_frame(interp::AbstractInterpreter, mi::MethodInstance, run_optimizer::Bool) start_time = ccall(:jl_typeinf_timing_begin, UInt64, ()) result = InferenceResult(mi, typeinf_lattice(interp)) - frame = InferenceState(result, run_optimizer ? :global : :no, interp) + cache_mode = run_optimizer ? :global : :no + frame = InferenceState(result, cache_mode, interp) frame === nothing && return nothing typeinf(interp, frame) ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time) return frame end +# N.B.: These need to be aligned with the C side headers +""" + SOURCE_MODE_NOT_REQUIRED + +Indicates to inference that the source is not required and the only fields +of the resulting `CodeInstance` that the caller is interested in are types +and effects. Inference is still free to create a CodeInstance with source, +but is not required to do so. +""" +const SOURCE_MODE_NOT_REQUIRED = 0x0 + +""" + SOURCE_MODE_ABI + +Indicates to inference that it should return a CodeInstance that can +either be `->invoke`'d (because it has already been compiled or because +it has constabi) or one that can be made so by compiling its `->inferred` +field. + +N.B.: The `->inferred` field is volatile and the compiler may delete it. +In such a case, it will first set the `invoke` field to a method that +will block the thread until compilation is completed. +""" +const SOURCE_MODE_ABI = 0x1 + +""" + SOURCE_MODE_FORCE_SOURCE + +Indicates that inference must always produce source in the `->inferred` field. +This may mean that inference will need to re-do inference (if the `->inferred` +field was previously deleted by the JIT) or may need to synthesize source for +other kinds of CodeInstances. + +N.B.: The same caching considerations as SOURCE_MODE_ABI apply. +""" +const SOURCE_MODE_FORCE_SOURCE = 0x2 + +""" + SOURCE_MODE_FORCE_SOURCE_UNCACHED + +Like `SOURCE_MODE_FORCE_SOURCE`, but ensures that the resulting code instance is +not part of the cache hierarchy, so the `->inferred` field may be safely used +without the possibility of deletion by the compiler. +""" +const SOURCE_MODE_FORCE_SOURCE_UNCACHED = 0x3 + +function ci_has_source(code::CodeInstance) + inf = @atomic :monotonic code.inferred + return isa(inf, CodeInfo) || isa(inf, String) +end + +""" + ci_has_abi(code::CodeInstance) + +Determine whether this CodeInstance is something that could be invoked if we gave it +to the runtime system (either because it already has an ->invoke ptr, or because it +has source that could be compiled). +""" +function ci_has_abi(code::CodeInstance) + ci_has_source(code) && return true + return code.invoke !== C_NULL +end + +function ci_meets_requirement(code::CodeInstance, source_mode::UInt8, ci_is_cached::Bool) + source_mode == SOURCE_MODE_NOT_REQUIRED && return true + source_mode == SOURCE_MODE_ABI && return ci_has_abi(code) + source_mode == SOURCE_MODE_FORCE_SOURCE && return ci_has_source(code) + source_mode == SOURCE_MODE_FORCE_SOURCE_UNCACHED && return (!ci_is_cached && ci_has_source(code)) + return false +end + +_uncompressed_ir(codeinst::CodeInstance, s::String) = + ccall(:jl_uncompress_ir, Ref{CodeInfo}, (Any, Any, Any), codeinst.def.def::Method, codeinst, s) + # compute (and cache) an inferred AST and return type -function typeinf_ext(interp::AbstractInterpreter, mi::MethodInstance) - method = mi.def::Method +function typeinf_ext(interp::AbstractInterpreter, mi::MethodInstance, source_mode::UInt8) start_time = ccall(:jl_typeinf_timing_begin, UInt64, ()) code = get(code_cache(interp), mi, nothing) if code isa CodeInstance # see if this code already exists in the cache - inf = @atomic :monotonic code.inferred - if use_const_api(code) - ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time) - return codeinfo_for_const(interp, mi, WorldRange(code.min_world, code.max_world), code.rettype_const) - elseif isa(inf, CodeInfo) + if source_mode in (SOURCE_MODE_FORCE_SOURCE, SOURCE_MODE_FORCE_SOURCE_UNCACHED) && use_const_api(code) + code = codeinstance_for_const_with_code(interp, code) ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time) - if !(inf.min_world == code.min_world && - inf.max_world == code.max_world && - inf.rettype === code.rettype) - inf = copy(inf) - inf.min_world = code.min_world - inf.max_world = code.max_world - inf.rettype = code.rettype - end - return inf - elseif isa(inf, String) + return code + end + if ci_meets_requirement(code, source_mode, true) ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time) - inf = _uncompressed_ir(code, inf) - return inf + return code end end - if ccall(:jl_get_module_infer, Cint, (Any,), method.module) == 0 && !generating_output(#=incremental=#false) - return retrieve_code_info(mi, get_world_counter(interp)) + def = mi.def + if isa(def, Method) + if ccall(:jl_get_module_infer, Cint, (Any,), def.module) == 0 && !generating_output(#=incremental=#false) + src = retrieve_code_info(mi, get_inference_world(interp)) + src isa CodeInfo || return nothing + return CodeInstance(mi, cache_owner(interp), Any, Any, nothing, src, Int32(0), + get_inference_world(interp), get_inference_world(interp), + UInt32(0), UInt32(0), nothing, UInt8(0), src.debuginfo) + end end lock_mi_inference(interp, mi) result = InferenceResult(mi, typeinf_lattice(interp)) - frame = InferenceState(result, #=cache=#:global, interp) + frame = InferenceState(result, #=cache_mode=#source_mode == SOURCE_MODE_FORCE_SOURCE_UNCACHED ? :volatile : :global, interp) frame === nothing && return nothing typeinf(interp, frame) ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time) - if result_is_constabi(interp, true, frame.result) - return codeinfo_for_const(interp, frame.linfo, frame.result.valid_worlds, frame.result.result.val) + if isdefined(result, :ci) + if ci_meets_requirement(result.ci, source_mode, true) + # Inference result was cacheable and is in global cache. Return it. + return result.ci + elseif use_const_api(result.ci) + code = codeinstance_for_const_with_code(interp, result.ci) + @assert ci_meets_requirement(code, source_mode, false) + return code + end + end + + # Inference result is not cacheable or is was cacheable, but we do not want to + # store the source in the cache, but the caller wanted it anyway (e.g. for reflection). + # We construct a new CodeInstance for it that is not part of the cache hierarchy. + can_discard_trees = source_mode ≠ SOURCE_MODE_FORCE_SOURCE && + source_mode ≠ SOURCE_MODE_FORCE_SOURCE_UNCACHED && + is_result_constabi_eligible(result) + code = CodeInstance(interp, result; can_discard_trees) + + # If the caller cares about the code and this is constabi, still use our synthesis function + # anyway, because we will have not finished inferring the code inside the CodeInstance once + # we realized it was constabi, but we want reflection to pretend that we did. + if use_const_api(code) && source_mode in (SOURCE_MODE_FORCE_SOURCE, SOURCE_MODE_FORCE_SOURCE_UNCACHED) + return codeinstance_for_const_with_code(interp, code) end - frame.src.inferred || return nothing - return frame.src + @assert ci_meets_requirement(code, source_mode, false) + return code end # compute (and cache) an inferred AST and return the inferred return type @@ -1037,7 +1154,11 @@ function typeinf_type(interp::AbstractInterpreter, method::Method, @nospecialize if contains_is(unwrap_unionall(atype).parameters, Union{}) return Union{} # don't ask: it does weird and unnecessary things, if it occurs during bootstrap end - mi = specialize_method(method, atype, sparams)::MethodInstance + return typeinf_type(interp, specialize_method(method, atype, sparams)) +end +typeinf_type(interp::AbstractInterpreter, match::MethodMatch) = + typeinf_type(interp, specialize_method(match)) +function typeinf_type(interp::AbstractInterpreter, mi::MethodInstance) start_time = ccall(:jl_typeinf_timing_begin, UInt64, ()) code = get(code_cache(interp), mi, nothing) if code isa CodeInstance @@ -1053,31 +1174,13 @@ function typeinf_type(interp::AbstractInterpreter, method::Method, @nospecialize end # This is a bridge for the C code calling `jl_typeinf_func()` -typeinf_ext_toplevel(mi::MethodInstance, world::UInt) = typeinf_ext_toplevel(NativeInterpreter(world), mi) -function typeinf_ext_toplevel(interp::AbstractInterpreter, linfo::MethodInstance) - if isa(linfo.def, Method) - # method lambda - infer this specialization via the method cache - src = typeinf_ext(interp, linfo) - else - src = linfo.uninferred::CodeInfo - if !src.inferred - # toplevel lambda - infer directly - start_time = ccall(:jl_typeinf_timing_begin, UInt64, ()) - if !src.inferred - result = InferenceResult(linfo, typeinf_lattice(interp)) - frame = InferenceState(result, src, #=cache=#:global, interp) - typeinf(interp, frame) - @assert is_inferred(frame) # TODO: deal with this better - src = frame.src - end - ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time) - end - end - return src +typeinf_ext_toplevel(mi::MethodInstance, world::UInt, source_mode::UInt8) = typeinf_ext_toplevel(NativeInterpreter(world), mi, source_mode) +function typeinf_ext_toplevel(interp::AbstractInterpreter, mi::MethodInstance, source_mode::UInt8) + return typeinf_ext(interp, mi, source_mode) end function return_type(@nospecialize(f), t::DataType) # this method has a special tfunc - world = ccall(:jl_get_tls_world_age, UInt, ()) + world = tls_world_age() args = Any[_return_type, NativeInterpreter(world), Tuple{Core.Typeof(f), t.parameters...}] return ccall(:jl_call_in_typeinf_world, Any, (Ptr{Ptr{Cvoid}}, Cint), args, length(args)) end @@ -1087,7 +1190,7 @@ function return_type(@nospecialize(f), t::DataType, world::UInt) end function return_type(t::DataType) - world = ccall(:jl_get_tls_world_age, UInt, ()) + world = tls_world_age() return return_type(t, world) end @@ -1105,9 +1208,8 @@ function _return_type(interp::AbstractInterpreter, t::DataType) rt = builtin_tfunction(interp, f, args, nothing) rt = widenconst(rt) else - for match in _methods_by_ftype(t, -1, get_world_counter(interp))::Vector - match = match::MethodMatch - ty = typeinf_type(interp, match.method, match.spec_types, match.sparams) + for match in _methods_by_ftype(t, -1, get_inference_world(interp))::Vector + ty = typeinf_type(interp, match::MethodMatch) ty === nothing && return Any rt = tmerge(rt, ty) rt === Any && break diff --git a/base/compiler/typelattice.jl b/base/compiler/typelattice.jl index df6022609d612..5987c30be2b91 100644 --- a/base/compiler/typelattice.jl +++ b/base/compiler/typelattice.jl @@ -323,7 +323,7 @@ end @nospecializeinfer function isalreadyconst(@nospecialize t) isa(t, Const) && return true - isa(t, DataType) && isdefined(t, :instance) && return true + issingletontype(t) && return true return isconstType(t) end @@ -759,24 +759,6 @@ function stupdate!(lattice::AbstractLattice, state::VarTable, changes::VarTable) return changed end -function stupdate1!(lattice::AbstractLattice, state::VarTable, change::StateUpdate) - changeid = slot_id(change.var) - for i = 1:length(state) - invalidated = invalidate_slotwrapper(state[i], changeid, change.conditional) - if invalidated !== nothing - state[i] = invalidated - end - end - # and update the type of it - newtype = change.vtype - oldtype = state[changeid] - if schanged(lattice, newtype, oldtype) - state[changeid] = smerge(lattice, oldtype, newtype) - return true - end - return false -end - function stoverwrite!(state::VarTable, newstate::VarTable) for i = 1:length(state) state[i] = newstate[i] diff --git a/base/compiler/typelimits.jl b/base/compiler/typelimits.jl index 406a5494d2f20..318ac0b5c27e5 100644 --- a/base/compiler/typelimits.jl +++ b/base/compiler/typelimits.jl @@ -295,22 +295,9 @@ function type_more_complex(@nospecialize(t), @nospecialize(c), sources::SimpleVe else tupledepth = 0 end - isgenerator = (t.name.name === :Generator && t.name.module === _topmod(t.name.module)) for i = 1:length(tP) tPi = tP[i] cPi = cP[i + ntail] - if isgenerator - let tPi = unwrap_unionall(tPi), - cPi = unwrap_unionall(cPi) - if isa(tPi, DataType) && isa(cPi, DataType) && - !isabstracttype(tPi) && !isabstracttype(cPi) && - sym_isless(cPi.name.name, tPi.name.name) - # allow collect on (anonymous) Generators to nest, provided that their functions are appropriately ordered - # TODO: is there a better way? - continue - end - end - end type_more_complex(tPi, cPi, sources, depth + 1, tupledepth, 0) && return true end return false @@ -325,7 +312,7 @@ union_count_abstract(@nospecialize(x)) = !isdispatchelem(x) function issimpleenoughtype(@nospecialize t) ut = unwrap_unionall(t) ut isa DataType && ut.name.wrapper == t && return true - return unionlen(t) + union_count_abstract(t) <= MAX_TYPEUNION_LENGTH && + return max(unionlen(t), union_count_abstract(t) + 1) <= MAX_TYPEUNION_LENGTH && unioncomplexity(t) <= MAX_TYPEUNION_COMPLEXITY end @@ -820,6 +807,7 @@ end end if usep widen = rewrap_unionall(wr{p...}, wr) + widen <: wr || (widen = wr) # sometimes there are cross-constraints on wr that we may lose in this process, but that would cause future calls to this to need to return Any, which is undesirable end simplify[j] = !usep end diff --git a/base/compiler/types.jl b/base/compiler/types.jl index c53256c61ace9..30cb0fb0f39c5 100644 --- a/base/compiler/types.jl +++ b/base/compiler/types.jl @@ -12,9 +12,9 @@ If `interp::NewInterpreter` is an `AbstractInterpreter`, it is expected to provi the following methods to satisfy the `AbstractInterpreter` API requirement: - `InferenceParams(interp::NewInterpreter)` - return an `InferenceParams` instance - `OptimizationParams(interp::NewInterpreter)` - return an `OptimizationParams` instance -- `get_world_counter(interp::NewInterpreter)` - return the world age for this interpreter +- `get_inference_world(interp::NewInterpreter)` - return the world age for this interpreter - `get_inference_cache(interp::NewInterpreter)` - return the local inference cache -- `code_cache(interp::NewInterpreter)` - return the global inference cache +- `cache_owner(interp::NewInterpreter)` - return the owner of any new cache entries """ :(AbstractInterpreter) @@ -57,38 +57,66 @@ struct VarState VarState(@nospecialize(typ), undef::Bool) = new(typ, undef) end -abstract type ForwardableArgtypes end +struct AnalysisResults + result + next::AnalysisResults + AnalysisResults(@nospecialize(result), next::AnalysisResults) = new(result, next) + AnalysisResults(@nospecialize(result)) = new(result) + # NullAnalysisResults() = new(nothing) + # global const NULL_ANALYSIS_RESULTS = NullAnalysisResults() +end +const NULL_ANALYSIS_RESULTS = AnalysisResults(nothing) """ - InferenceResult(linfo::MethodInstance, [argtypes::ForwardableArgtypes, 𝕃::AbstractLattice]) + result::InferenceResult A type that represents the result of running type inference on a chunk of code. - -See also [`matching_cache_argtypes`](@ref). +There are two constructor available: +- `InferenceResult(mi::MethodInstance, [𝕃::AbstractLattice])` for regular inference, + without extended lattice information included in `result.argtypes`. +- `InferenceResult(mi::MethodInstance, argtypes::Vector{Any}, overridden_by_const::BitVector)` + for constant inference, with extended lattice information included in `result.argtypes`. """ mutable struct InferenceResult const linfo::MethodInstance const argtypes::Vector{Any} - const overridden_by_const::BitVector + const overridden_by_const::Union{Nothing,BitVector} result # extended lattice element if inferred, nothing otherwise + exc_result # like `result`, but for the thrown value src # ::Union{CodeInfo, IRCode, OptimizationState} if inferred copy is available, nothing otherwise valid_worlds::WorldRange # if inference and optimization is finished ipo_effects::Effects # if inference is finished effects::Effects # if optimization is finished - argescapes # ::ArgEscapeCache if optimized, nothing otherwise - must_be_codeinf::Bool # if this must come out as CodeInfo or leaving it as IRCode is ok - function InferenceResult(linfo::MethodInstance, cache_argtypes::Vector{Any}, overridden_by_const::BitVector) - # def = linfo.def - # nargs = def isa Method ? Int(def.nargs) : 0 - # @assert length(cache_argtypes) == nargs - return new(linfo, cache_argtypes, overridden_by_const, nothing, nothing, - WorldRange(), Effects(), Effects(), nothing, true) + analysis_results::AnalysisResults # AnalysisResults with e.g. result::ArgEscapeCache if optimized, otherwise NULL_ANALYSIS_RESULTS + is_src_volatile::Bool # `src` has been cached globally as the compressed format already, allowing `src` to be used destructively + ci::CodeInstance # CodeInstance if this result has been added to the cache + function InferenceResult(mi::MethodInstance, argtypes::Vector{Any}, overridden_by_const::Union{Nothing,BitVector}) + def = mi.def + nargs = def isa Method ? Int(def.nargs) : 0 + @assert length(argtypes) == nargs "invalid `argtypes` for `mi`" + return new(mi, argtypes, overridden_by_const, nothing, nothing, nothing, + WorldRange(), Effects(), Effects(), NULL_ANALYSIS_RESULTS, false) + end +end +function InferenceResult(mi::MethodInstance, 𝕃::AbstractLattice=fallback_lattice) + argtypes = matching_cache_argtypes(𝕃, mi) + return InferenceResult(mi, argtypes, #=overridden_by_const=#nothing) +end + +function stack_analysis_result!(inf_result::InferenceResult, @nospecialize(result)) + return inf_result.analysis_results = AnalysisResults(result, inf_result.analysis_results) +end + +function traverse_analysis_results(callback, (;analysis_results)::Union{InferenceResult,CodeInstance}) + analysis_results isa AnalysisResults || return nothing + while isdefined(analysis_results, :next) + if (result = callback(analysis_results.result)) !== nothing + return result + end + analysis_results = analysis_results.next end + return nothing end -InferenceResult(linfo::MethodInstance, 𝕃::AbstractLattice=fallback_lattice) = - InferenceResult(linfo, matching_cache_argtypes(𝕃, linfo)...) -InferenceResult(linfo::MethodInstance, argtypes::ForwardableArgtypes, 𝕃::AbstractLattice=fallback_lattice) = - InferenceResult(linfo, matching_cache_argtypes(𝕃, linfo, argtypes)...) """ inf_params::InferenceParams @@ -253,6 +281,11 @@ Parameters that control optimizer operation. optimizer license to move side effects (that are proven not observed within a particular code path) across a throwing call. Defaults to `false`. --- +- `opt_params.preserve_local_sources::Bool = false`\\ + If `true`, the inliner is restricted from modifying locally-cached sources that are + retained in `CallInfo` objects and always makes their copies before inlining them into + caller context. Defaults to `false`. +--- """ struct OptimizationParams inlining::Bool @@ -263,6 +296,7 @@ struct OptimizationParams max_tuple_splat::Int compilesig_invokes::Bool assume_fatal_throw::Bool + preserve_local_sources::Bool function OptimizationParams( inlining::Bool, @@ -272,7 +306,8 @@ struct OptimizationParams inline_error_path_cost::Int, max_tuple_splat::Int, compilesig_invokes::Bool, - assume_fatal_throw::Bool) + assume_fatal_throw::Bool, + preserve_local_sources::Bool) return new( inlining, inline_cost_threshold, @@ -281,7 +316,8 @@ struct OptimizationParams inline_error_path_cost, max_tuple_splat, compilesig_invokes, - assume_fatal_throw) + assume_fatal_throw, + preserve_local_sources) end end function OptimizationParams( @@ -293,7 +329,8 @@ function OptimizationParams( #=inline_error_path_cost::Int=# 20, #=max_tuple_splat::Int=# 32, #=compilesig_invokes::Bool=# true, - #=assume_fatal_throw::Bool=# false); + #=assume_fatal_throw::Bool=# false, + #=preserve_local_sources::Bool=# false); inlining::Bool = params.inlining, inline_cost_threshold::Int = params.inline_cost_threshold, inline_nonleaf_penalty::Int = params.inline_nonleaf_penalty, @@ -301,7 +338,8 @@ function OptimizationParams( inline_error_path_cost::Int = params.inline_error_path_cost, max_tuple_splat::Int = params.max_tuple_splat, compilesig_invokes::Bool = params.compilesig_invokes, - assume_fatal_throw::Bool = params.assume_fatal_throw) + assume_fatal_throw::Bool = params.assume_fatal_throw, + preserve_local_sources::Bool = params.preserve_local_sources) return OptimizationParams( inlining, inline_cost_threshold, @@ -310,7 +348,8 @@ function OptimizationParams( inline_error_path_cost, max_tuple_splat, compilesig_invokes, - assume_fatal_throw) + assume_fatal_throw, + preserve_local_sources) end """ @@ -331,29 +370,23 @@ struct NativeInterpreter <: AbstractInterpreter # Parameters for inference and optimization inf_params::InferenceParams opt_params::OptimizationParams - - # a boolean flag to indicate if this interpreter is performing semi concrete interpretation - irinterp::Bool end function NativeInterpreter(world::UInt = get_world_counter(); inf_params::InferenceParams = InferenceParams(), opt_params::OptimizationParams = OptimizationParams()) + curr_max_world = get_world_counter() # Sometimes the caller is lazy and passes typemax(UInt). # we cap it to the current world age for correctness if world == typemax(UInt) - world = get_world_counter() + world = curr_max_world end - # If they didn't pass typemax(UInt) but passed something more subtly # incorrect, fail out loudly. - @assert world <= get_world_counter() - + @assert world <= curr_max_world method_table = CachedMethodTable(InternalMethodTable(world)) - inf_cache = Vector{InferenceResult}() # Initially empty cache - - return NativeInterpreter(world, method_table, inf_cache, inf_params, opt_params, #=irinterp=#false) + return NativeInterpreter(world, method_table, inf_cache, inf_params, opt_params) end function NativeInterpreter(interp::NativeInterpreter; @@ -361,17 +394,16 @@ function NativeInterpreter(interp::NativeInterpreter; method_table::CachedMethodTable{InternalMethodTable} = interp.method_table, inf_cache::Vector{InferenceResult} = interp.inf_cache, inf_params::InferenceParams = interp.inf_params, - opt_params::OptimizationParams = interp.opt_params, - irinterp::Bool = interp.irinterp) - return NativeInterpreter(world, method_table, inf_cache, inf_params, opt_params, irinterp) + opt_params::OptimizationParams = interp.opt_params) + return NativeInterpreter(world, method_table, inf_cache, inf_params, opt_params) end # Quickly and easily satisfy the AbstractInterpreter API contract InferenceParams(interp::NativeInterpreter) = interp.inf_params OptimizationParams(interp::NativeInterpreter) = interp.opt_params -get_world_counter(interp::NativeInterpreter) = interp.world +get_inference_world(interp::NativeInterpreter) = interp.world get_inference_cache(interp::NativeInterpreter) = interp.inf_cache -code_cache(interp::NativeInterpreter) = WorldView(GLOBAL_CI_CACHE, get_world_counter(interp)) +cache_owner(interp::NativeInterpreter) = nothing """ already_inferred_quick_test(::AbstractInterpreter, ::MethodInstance) @@ -425,7 +457,7 @@ Returns a method table this `interp` uses for method lookup. External `AbstractInterpreter` can optionally return `OverlayMethodTable` here to incorporate customized dispatches for the overridden methods. """ -method_table(interp::AbstractInterpreter) = InternalMethodTable(get_world_counter(interp)) +method_table(interp::AbstractInterpreter) = InternalMethodTable(get_inference_world(interp)) method_table(interp::NativeInterpreter) = interp.method_table """ @@ -457,34 +489,6 @@ typeinf_lattice(::AbstractInterpreter) = InferenceLattice(BaseInferenceLattice.i ipo_lattice(::AbstractInterpreter) = InferenceLattice(IPOResultLattice.instance) optimizer_lattice(::AbstractInterpreter) = SimpleInferenceLattice.instance -typeinf_lattice(interp::NativeInterpreter) = interp.irinterp ? - InferenceLattice(SimpleInferenceLattice.instance) : - InferenceLattice(BaseInferenceLattice.instance) -ipo_lattice(interp::NativeInterpreter) = interp.irinterp ? - InferenceLattice(SimpleInferenceLattice.instance) : - InferenceLattice(IPOResultLattice.instance) -optimizer_lattice(interp::NativeInterpreter) = SimpleInferenceLattice.instance - -""" - switch_to_irinterp(interp::AbstractInterpreter) -> irinterp::AbstractInterpreter - -This interface allows `ir_abstract_constant_propagation` to convert `interp` to a new -`irinterp::AbstractInterpreter` to perform semi-concrete interpretation. -`NativeInterpreter` uses this interface to switch its lattice to `optimizer_lattice` during -semi-concrete interpretation on `IRCode`. -""" -switch_to_irinterp(interp::AbstractInterpreter) = interp -switch_to_irinterp(interp::NativeInterpreter) = NativeInterpreter(interp; irinterp=true) - -""" - switch_from_irinterp(irinterp::AbstractInterpreter) -> interp::AbstractInterpreter - -The inverse operation of `switch_to_irinterp`, allowing `typeinf` to convert `irinterp` back -to a new `interp::AbstractInterpreter` to perform ordinary abstract interpretation. -""" -switch_from_irinterp(irinterp::AbstractInterpreter) = irinterp -switch_from_irinterp(irinterp::NativeInterpreter) = NativeInterpreter(irinterp; irinterp=false) - abstract type CallInfo end @nospecialize diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index bdc0156efd824..75e8f84dc4c33 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -66,8 +66,6 @@ end is_meta_expr_head(head::Symbol) = head === :boundscheck || head === :meta || head === :loopinfo is_meta_expr(@nospecialize x) = isa(x, Expr) && is_meta_expr_head(x.head) -sym_isless(a::Symbol, b::Symbol) = ccall(:strcmp, Int32, (Ptr{UInt8}, Ptr{UInt8}), a, b) < 0 - function is_self_quoting(@nospecialize(x)) return isa(x,Number) || isa(x,AbstractString) || isa(x,Tuple) || isa(x,Type) || isa(x,Char) || x === nothing || isa(x,Function) @@ -85,13 +83,33 @@ const MAX_INLINE_CONST_SIZE = 256 function count_const_size(@nospecialize(x), count_self::Bool = true) (x isa Type || x isa Core.TypeName || x isa Symbol) && return 0 - ismutable(x) && return MAX_INLINE_CONST_SIZE + 1 + if ismutable(x) + # No definite size + (isa(x, GenericMemory) || isa(x, String) || isa(x, SimpleVector)) && + return MAX_INLINE_CONST_SIZE + 1 + if isa(x, Module) + # We allow modules, because we already assume they are externally + # rooted, so we count their contents as 0 size. + return sizeof(Ptr{Cvoid}) + end + # We allow mutable types with no mutable fields (i.e. those mutable + # types used for identity only). The intent of this function is to + # prevent the rooting of large amounts of data that may have been + # speculatively computed. If the struct can get mutated later, we + # cannot assess how much data we might end up rooting. However, if + # the struct is mutable only for identity, the query still works. + for i = 1:nfields(x) + if !isconst(typeof(x), i) + return MAX_INLINE_CONST_SIZE + 1 + end + end + end isbits(x) && return Core.sizeof(x) dt = typeof(x) sz = count_self ? sizeof(dt) : 0 sz > MAX_INLINE_CONST_SIZE && return MAX_INLINE_CONST_SIZE + 1 dtfd = DataTypeFieldDesc(dt) - for i = 1:nfields(x) + for i = 1:Int(datatype_nfields(dt)) isdefined(x, i) || continue f = getfield(x, i) if !dtfd[i].isptr && datatype_pointerfree(typeof(f)) @@ -129,26 +147,23 @@ function get_staged(mi::MethodInstance, world::UInt) end end -function retrieve_code_info(linfo::MethodInstance, world::UInt) - m = linfo.def::Method - c = nothing - if isdefined(m, :generator) - # user code might throw errors – ignore them - c = get_staged(linfo, world) - end - if c === nothing && isdefined(m, :source) - src = m.source +function retrieve_code_info(mi::MethodInstance, world::UInt) + def = mi.def + isa(def, Method) || return mi.uninferred + c = isdefined(def, :generator) ? get_staged(mi, world) : nothing + if c === nothing && isdefined(def, :source) + src = def.source if src === nothing # can happen in images built with --strip-ir return nothing elseif isa(src, String) - c = ccall(:jl_uncompress_ir, Any, (Any, Ptr{Cvoid}, Any), m, C_NULL, src) + c = ccall(:jl_uncompress_ir, Ref{CodeInfo}, (Any, Ptr{Cvoid}, Any), def, C_NULL, src) else c = copy(src::CodeInfo) end end if c isa CodeInfo - c.parent = linfo + c.parent = mi return c end return nothing @@ -164,7 +179,7 @@ end function get_nospecializeinfer_sig(method::Method, @nospecialize(atype), sparams::SimpleVector) isa(atype, DataType) || return method.sig - mt = ccall(:jl_method_table_for, Any, (Any,), atype) + mt = ccall(:jl_method_get_table, Any, (Any,), method) mt === nothing && return method.sig return ccall(:jl_normalize_to_compilable_sig, Any, (Any, Any, Any, Any, Cint), mt, atype, sparams, method, #=int return_if_compileable=#0) @@ -390,6 +405,7 @@ function find_ssavalue_uses(body::Vector{Any}, nvals::Int) for line in 1:length(body) e = body[line] if isa(e, ReturnNode) + isdefined(e, :val) || continue e = e.val elseif isa(e, GotoIfNot) e = e.cond @@ -397,15 +413,15 @@ function find_ssavalue_uses(body::Vector{Any}, nvals::Int) if isa(e, SSAValue) push!(uses[e.id], line) elseif isa(e, Expr) - find_ssavalue_uses(e, uses, line) + find_ssavalue_uses!(uses, e, line) elseif isa(e, PhiNode) - find_ssavalue_uses(e, uses, line) + find_ssavalue_uses!(uses, e, line) end end return uses end -function find_ssavalue_uses(e::Expr, uses::Vector{BitSet}, line::Int) +function find_ssavalue_uses!(uses::Vector{BitSet}, e::Expr, line::Int) head = e.head is_meta_expr_head(head) && return skiparg = (head === :(=)) @@ -415,24 +431,30 @@ function find_ssavalue_uses(e::Expr, uses::Vector{BitSet}, line::Int) elseif isa(a, SSAValue) push!(uses[a.id], line) elseif isa(a, Expr) - find_ssavalue_uses(a, uses, line) + find_ssavalue_uses!(uses, a, line) end end end -function find_ssavalue_uses(e::PhiNode, uses::Vector{BitSet}, line::Int) - for val in e.values +function find_ssavalue_uses!(uses::Vector{BitSet}, e::PhiNode, line::Int) + values = e.values + for i = 1:length(values) + isassigned(values, i) || continue + val = values[i] if isa(val, SSAValue) push!(uses[val.id], line) end end end -function is_throw_call(e::Expr) +function is_throw_call(e::Expr, code::Vector{Any}) if e.head === :call f = e.args[1] + if isa(f, SSAValue) + f = code[f.id] + end if isa(f, GlobalRef) - ff = abstract_eval_globalref(f) + ff = abstract_eval_globalref_type(f) if isa(ff, Const) && ff.val === Core.throw return true end @@ -441,14 +463,14 @@ function is_throw_call(e::Expr) return false end -function mark_throw_blocks!(src::CodeInfo, handler_at::Vector{Int}) +function mark_throw_blocks!(src::CodeInfo, handler_at::Vector{Tuple{Int, Int}}) for stmt in find_throw_blocks(src.code, handler_at) src.ssaflags[stmt] |= IR_FLAG_THROW_BLOCK end return nothing end -function find_throw_blocks(code::Vector{Any}, handler_at::Vector{Int}) +function find_throw_blocks(code::Vector{Any}, handler_at::Vector{Tuple{Int, Int}}) stmts = BitSet() n = length(code) for i in n:-1:1 @@ -460,8 +482,8 @@ function find_throw_blocks(code::Vector{Any}, handler_at::Vector{Int}) end elseif s.head === :return # see `ReturnNode` handling - elseif is_throw_call(s) - if handler_at[i] == 0 + elseif is_throw_call(s, code) + if handler_at[i][1] == 0 push!(stmts, i) end elseif i+1 in stmts @@ -496,9 +518,8 @@ end # options # ########### -is_root_module(m::Module) = false - inlining_enabled() = (JLOptions().can_inline == 1) + function coverage_enabled(m::Module) generating_output() && return false # don't alter caches cov = JLOptions().code_coverage @@ -512,9 +533,12 @@ function coverage_enabled(m::Module) end return false end + function inbounds_option() opt_check_bounds = JLOptions().check_bounds opt_check_bounds == 0 && return :default opt_check_bounds == 1 && return :on return :off end + +is_asserts() = ccall(:jl_is_assertsbuild, Cint, ()) == 1 diff --git a/base/compiler/validation.jl b/base/compiler/validation.jl index 5505f5a2b423b..3d0db1a09afcc 100644 --- a/base/compiler/validation.jl +++ b/base/compiler/validation.jl @@ -13,7 +13,7 @@ const VALID_EXPR_HEADS = IdDict{Symbol,UnitRange{Int}}( :new => 1:typemax(Int), :splatnew => 2:2, :the_exception => 0:0, - :enter => 1:1, + :enter => 1:2, :leave => 1:typemax(Int), :pop_exception => 1:1, :inbounds => 1:1, @@ -34,7 +34,10 @@ const VALID_EXPR_HEADS = IdDict{Symbol,UnitRange{Int}}( :throw_undef_if_not => 2:2, :aliasscope => 0:0, :popaliasscope => 0:0, - :new_opaque_closure => 4:typemax(Int) + :new_opaque_closure => 4:typemax(Int), + :import => 1:typemax(Int), + :using => 1:typemax(Int), + :export => 1:typemax(Int), ) # @enum isn't defined yet, otherwise I'd use it for this @@ -61,20 +64,20 @@ struct InvalidCodeError <: Exception end InvalidCodeError(kind::AbstractString) = InvalidCodeError(kind, nothing) -function validate_code_in_debug_mode(linfo::MethodInstance, src::CodeInfo, kind::String) - if JLOptions().debug_level == 2 - # this is a debug build of julia, so let's validate linfo - errors = validate_code(linfo, src) +function maybe_validate_code(mi::MethodInstance, src::CodeInfo, kind::String) + if is_asserts() + errors = validate_code(mi, src) if !isempty(errors) for e in errors - if linfo.def isa Method + if mi.def isa Method println(stderr, "WARNING: Encountered invalid ", kind, " code for method ", - linfo.def, ": ", e) + mi.def, ": ", e) else println(stderr, "WARNING: Encountered invalid ", kind, " code for top level expression in ", - linfo.def, ": ", e) + mi.def, ": ", e) end end + error("") end end end @@ -160,6 +163,13 @@ function validate_code!(errors::Vector{InvalidCodeError}, c::CodeInfo, is_top_le push!(errors, InvalidCodeError(INVALID_CALL_ARG, x.cond)) end validate_val!(x.cond) + elseif isa(x, EnterNode) + if isdefined(x, :scope) + if !is_valid_argument(x.scope) + push!(errors, InvalidCodeError(INVALID_CALL_ARG, x.scope)) + end + validate_val!(x.scope) + end elseif isa(x, ReturnNode) if isdefined(x, :val) if !is_valid_return(x.val) @@ -246,12 +256,10 @@ 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, :invoke_modify, :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, :new_opaque_closure) return true end return false end is_valid_return(@nospecialize(x)) = is_valid_argument(x) || (isa(x, Expr) && x.head === :lambda) - -is_flag_set(byte::UInt8, flag::UInt8) = (byte & flag) == flag diff --git a/base/complex.jl b/base/complex.jl index ff0209301b441..8ac126d2c6532 100644 --- a/base/complex.jl +++ b/base/complex.jl @@ -178,7 +178,7 @@ complex(x::Real, y::Real) = Complex(x, y) complex(T::Type) Return an appropriate type which can represent a value of type `T` as a complex number. -Equivalent to `typeof(complex(zero(T)))`. +Equivalent to `typeof(complex(zero(T)))` if `T` does not contain `Missing`. # Examples ```jldoctest @@ -187,6 +187,9 @@ Complex{Int64} julia> complex(Int) Complex{Int64} + +julia> complex(Union{Int, Missing}) +Union{Missing, Complex{Int64}} ``` """ complex(::Type{T}) where {T<:Real} = Complex{T} @@ -567,7 +570,7 @@ end """ cis(x) -More efficient method for `exp(im*x)` by using Euler's formula: ``cos(x) + i sin(x) = \\exp(i x)``. +More efficient method for `exp(im*x)` by using Euler's formula: ``\\cos(x) + i \\sin(x) = \\exp(i x)``. See also [`cispi`](@ref), [`sincos`](@ref), [`exp`](@ref), [`angle`](@ref). @@ -622,7 +625,10 @@ end Compute the phase angle in radians of a complex number `z`. -See also: [`atan`](@ref), [`cis`](@ref). +Returns a number `-pi ≤ angle(z) ≤ pi`, and is thus discontinuous +along the negative real axis. + +See also: [`atan`](@ref), [`cis`](@ref), [`rad2deg`](@ref). # Examples ```jldoctest @@ -632,8 +638,11 @@ julia> rad2deg(angle(1 + im)) julia> rad2deg(angle(1 - im)) -45.0 -julia> rad2deg(angle(-1 - im)) --135.0 +julia> rad2deg(angle(-1 + 1e-20im)) +180.0 + +julia> rad2deg(angle(-1 - 1e-20im)) +-180.0 ``` """ angle(z::Complex) = atan(imag(z), real(z)) @@ -1088,7 +1097,7 @@ second is used for rounding the imaginary components. which rounds to the nearest integer, with ties (fractional values of 0.5) being rounded to the nearest even integer. -# Example +# Examples ```jldoctest julia> round(3.14 + 4.5im) 3.0 + 4.0im diff --git a/base/condition.jl b/base/condition.jl index 9f62593afaf77..52781f348eb0d 100644 --- a/base/condition.jl +++ b/base/condition.jl @@ -103,17 +103,16 @@ end """ wait([x]) -Block the current task until some event occurs, depending on the type of the argument: +Block the current task until some event occurs. * [`Channel`](@ref): Wait for a value to be appended to the channel. * [`Condition`](@ref): Wait for [`notify`](@ref) on a condition and return the `val` - parameter passed to `notify`. Waiting on a condition additionally allows passing - `first=true` which results in the waiter being put _first_ in line to wake up on `notify` - instead of the usual first-in-first-out behavior. + parameter passed to `notify`. See the `Condition`-specific docstring of `wait` for + the exact behavior. * `Process`: Wait for a process or process chain to exit. The `exitcode` field of a process can be used to determine success or failure. -* [`Task`](@ref): Wait for a `Task` to finish. If the task fails with an exception, a - `TaskFailedException` (which wraps the failed task) is thrown. +* [`Task`](@ref): Wait for a `Task` to finish. See the `Task`-specific docstring of `wait` for + the exact behavior. * [`RawFD`](@ref): Wait for changes on a file descriptor (see the `FileWatching` package). If no argument is passed, the task blocks for an undefined period. A task can only be @@ -122,6 +121,16 @@ restarted by an explicit call to [`schedule`](@ref) or [`yieldto`](@ref). Often `wait` is called within a `while` loop to ensure a waited-for condition is met before proceeding. """ +function wait end + +""" + wait(c::GenericCondition; first::Bool=false) + +Wait for [`notify`](@ref) on `c` and return the `val` parameter passed to `notify`. + +If the keyword `first` is set to `true`, the waiter will be put _first_ +in line to wake up on `notify`. Otherwise, `wait` has first-in-first-out (FIFO) behavior. +""" function wait(c::GenericCondition; first::Bool=false) ct = current_task() _wait2(c, ct, first) diff --git a/base/coreio.jl b/base/coreio.jl index 3e508c64a0a64..7fc608111d5f2 100644 --- a/base/coreio.jl +++ b/base/coreio.jl @@ -11,6 +11,7 @@ struct DevNull <: IO end const devnull = DevNull() write(::DevNull, ::UInt8) = 1 unsafe_write(::DevNull, ::Ptr{UInt8}, n::UInt)::Int = n +closewrite(::DevNull) = nothing close(::DevNull) = nothing wait_close(::DevNull) = wait() bytesavailable(io::DevNull) = 0 diff --git a/base/ctypes.jl b/base/ctypes.jl index 26640ed82bef5..45f01b684902f 100644 --- a/base/ctypes.jl +++ b/base/ctypes.jl @@ -113,3 +113,7 @@ const Cfloat = Float32 Equivalent to the native `double` c-type ([`Float64`](@ref)). """ const Cdouble = Float64 + + +# we have no `Float16` alias, because C does not define a standard fp16 type. Julia follows +# the _Float16 C ABI; if that becomes standard, we can add an appropriate alias here. diff --git a/base/deepcopy.jl b/base/deepcopy.jl index b35510aa1e7ae..bee1f7994a150 100644 --- a/base/deepcopy.jl +++ b/base/deepcopy.jl @@ -65,7 +65,9 @@ function deepcopy_internal(@nospecialize(x), stackdict::IdDict) for i in 1:nf if isdefined(x, i) xi = getfield(x, i) - xi = deepcopy_internal(xi, stackdict)::typeof(xi) + if !isbits(xi) + xi = deepcopy_internal(xi, stackdict)::typeof(xi) + end ccall(:jl_set_nth_field, Cvoid, (Any, Csize_t, Any), y, i-1, xi) end end @@ -76,7 +78,9 @@ function deepcopy_internal(@nospecialize(x), stackdict::IdDict) for i in 1:nf if isdefined(x, i) xi = getfield(x, i) - xi = deepcopy_internal(xi, stackdict)::typeof(xi) + if !isbits(xi) + xi = deepcopy_internal(xi, stackdict)::typeof(xi) + end flds[i] = xi else nf = i - 1 # rest of tail must be undefined values @@ -88,30 +92,51 @@ function deepcopy_internal(@nospecialize(x), stackdict::IdDict) return y::T end -function deepcopy_internal(x::Array, stackdict::IdDict) +function deepcopy_internal(x::Memory, stackdict::IdDict) if haskey(stackdict, x) return stackdict[x]::typeof(x) end - _deepcopy_array_t(x, eltype(x), stackdict) + _deepcopy_memory_t(x, eltype(x), stackdict) end -function _deepcopy_array_t(@nospecialize(x::Array), T, stackdict::IdDict) +function _deepcopy_memory_t(@nospecialize(x::Memory), T, stackdict::IdDict) if isbitstype(T) return (stackdict[x]=copy(x)) end - dest = similar(x) + dest = typeof(x)(undef, length(x)) stackdict[x] = dest + xr = Core.memoryref(x) + dr = Core.memoryref(dest) for i = 1:length(x) - if ccall(:jl_array_isassigned, Cint, (Any, Csize_t), x, i-1) != 0 - xi = ccall(:jl_arrayref, Any, (Any, Csize_t), x, i-1) + xi = Core.memoryref(xr, i, false) + if Core.memoryref_isassigned(xi, :not_atomic, false) + xi = Core.memoryrefget(xi, :not_atomic, false) if !isbits(xi) xi = deepcopy_internal(xi, stackdict)::typeof(xi) end - ccall(:jl_arrayset, Cvoid, (Any, Any, Csize_t), dest, xi, i-1) + di = Core.memoryref(dr, i, false) + Core.memoryrefset!(di, xi, :not_atomic, false) end end return dest end +@eval function deepcopy_internal(x::Array{T, N}, stackdict::IdDict) where {T, N} + if haskey(stackdict, x) + return stackdict[x]::typeof(x) + end + stackdict[x] = $(Expr(:new, :(Array{T, N}), :(deepcopy_internal(x.ref, stackdict)), :(x.size))) +end +function deepcopy_internal(x::GenericMemoryRef, stackdict::IdDict) + if haskey(stackdict, x) + return stackdict[x]::typeof(x) + end + mem = getfield(x, :mem) + dest = GenericMemoryRef(deepcopy_internal(mem, stackdict)::typeof(mem)) + i = memoryrefoffset(x) + i == 1 || (dest = Core.memoryref(dest, i, true)) + return dest +end + function deepcopy_internal(x::Union{Dict,IdDict}, stackdict::IdDict) if haskey(stackdict, x) diff --git a/base/deprecated.jl b/base/deprecated.jl index cfccb35d98bad..5cb054f426344 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -29,11 +29,11 @@ To prevent `old` from being exported, set `export_old` to `false`. # Examples ```jldoctest -julia> @deprecate old(x) new(x) -old (generic function with 1 method) +julia> @deprecate old_export(x) new(x) +old_export (generic function with 1 method) -julia> @deprecate old(x) new(x) false -old (generic function with 1 method) +julia> @deprecate old_public(x) new(x) false +old_public (generic function with 1 method) ``` Calls to `@deprecate` without explicit type-annotations will define @@ -118,7 +118,15 @@ macro deprecate(old, new, export_old=true) end end -function depwarn(msg, funcsym; force::Bool=false) +@nospecializeinfer function depwarn(msg, funcsym; force::Bool=false) + @nospecialize + # N.B. With this use of `@invokelatest`, we're preventing the addition of backedges from + # callees, such as `convert`, to this user-facing method. This approach is designed to + # enhance the resilience of packages that utilize `depwarn` against invalidation. + return @invokelatest _depwarn(msg, funcsym, force) +end +@nospecializeinfer function _depwarn(msg, funcsym, force::Bool) + @nospecialize opts = JLOptions() if opts.depwarn == 2 throw(ErrorException(msg)) @@ -271,14 +279,10 @@ getindex(match::Core.MethodMatch, field::Int) = # these were internal functions, but some packages seem to be relying on them tuple_type_head(T::Type) = fieldtype(T, 1) tuple_type_cons(::Type, ::Type{Union{}}) = Union{} -function tuple_type_cons(::Type{S}, ::Type{T}) where T<:Tuple where S - @_foldable_meta +@assume_effects :foldable tuple_type_cons(::Type{S}, ::Type{T}) where T<:Tuple where S = Tuple{S, T.parameters...} -end -function parameter_upper_bound(t::UnionAll, idx) - @_foldable_meta - return rewrap_unionall((unwrap_unionall(t)::DataType).parameters[idx], t) -end +@assume_effects :foldable parameter_upper_bound(t::UnionAll, idx) = + rewrap_unionall((unwrap_unionall(t)::DataType).parameters[idx], t) # these were internal functions, but some packages seem to be relying on them @deprecate cat_shape(dims, shape::Tuple{}, shapes::Tuple...) cat_shape(dims, shapes) false diff --git a/base/dict.jl b/base/dict.jl index d4efe268f641a..4a63ed364b64d 100644 --- a/base/dict.jl +++ b/base/dict.jl @@ -64,9 +64,9 @@ Dict{String, Int64} with 2 entries: """ mutable struct Dict{K,V} <: AbstractDict{K,V} # Metadata: empty => 0x00, removed => 0x7f, full => 0b1[7 most significant hash bits] - slots::Vector{UInt8} - keys::Array{K,1} - vals::Array{V,1} + slots::Memory{UInt8} + keys::Memory{K} + vals::Memory{V} ndel::Int count::Int age::UInt @@ -74,14 +74,16 @@ mutable struct Dict{K,V} <: AbstractDict{K,V} maxprobe::Int function Dict{K,V}() where V where K - n = 16 - new(zeros(UInt8,n), Vector{K}(undef, n), Vector{V}(undef, n), 0, 0, 0, n, 0) + n = 0 + slots = Memory{UInt8}(undef,n) + fill!(slots, 0x0) + new(slots, Memory{K}(undef, n), Memory{V}(undef, n), 0, 0, 0, max(1, n), 0) end function Dict{K,V}(d::Dict{K,V}) where V where K new(copy(d.slots), copy(d.keys), copy(d.vals), d.ndel, d.count, d.age, d.idxfloor, d.maxprobe) end - function Dict{K, V}(slots, keys, vals, ndel, count, age, idxfloor, maxprobe) where {K, V} + function Dict{K, V}(slots::Memory{UInt8}, keys::Memory{K}, vals::Memory{V}, ndel::Int, count::Int, age::UInt, idxfloor::Int, maxprobe::Int) where {K, V} new(slots, keys, vals, ndel, count, age, idxfloor, maxprobe) end end @@ -112,45 +114,7 @@ const AnyDict = Dict{Any,Any} Dict(ps::Pair{K,V}...) where {K,V} = Dict{K,V}(ps) Dict(ps::Pair...) = Dict(ps) -function Dict(kv) - try - dict_with_eltype((K, V) -> Dict{K, V}, kv, eltype(kv)) - catch - if !isiterable(typeof(kv)) || !all(x->isa(x,Union{Tuple,Pair}),kv) - throw(ArgumentError("Dict(kv): kv needs to be an iterator of tuples or pairs")) - else - rethrow() - end - end -end - -function grow_to!(dest::AbstractDict{K, V}, itr) where V where K - y = iterate(itr) - y === nothing && return dest - ((k,v), st) = y - dest2 = empty(dest, typeof(k), typeof(v)) - dest2[k] = v - grow_to!(dest2, itr, st) -end - -# this is a special case due to (1) allowing both Pairs and Tuples as elements, -# and (2) Pair being invariant. a bit annoying. -function grow_to!(dest::AbstractDict{K,V}, itr, st) where V where K - y = iterate(itr, st) - while y !== nothing - (k,v), st = y - if isa(k,K) && isa(v,V) - dest[k] = v - else - new = empty(dest, promote_typejoin(K,typeof(k)), promote_typejoin(V,typeof(v))) - merge!(new, dest) - new[k] = v - return grow_to!(new, itr, st) - end - y = iterate(itr, st) - end - return dest -end +Dict(kv) = dict_with_eltype((K, V) -> Dict{K, V}, kv, eltype(kv)) empty(a::AbstractDict, ::Type{K}, ::Type{V}) where {K, V} = Dict{K, V}() @@ -179,18 +143,20 @@ end h.age += 1 h.idxfloor = 1 if h.count == 0 - resize!(h.slots, newsz) + # TODO: tryresize + h.slots = Memory{UInt8}(undef, newsz) fill!(h.slots, 0x0) - resize!(h.keys, newsz) - resize!(h.vals, newsz) + h.keys = Memory{K}(undef, newsz) + h.vals = Memory{V}(undef, newsz) h.ndel = 0 h.maxprobe = 0 return h end - slots = zeros(UInt8,newsz) - keys = Vector{K}(undef, newsz) - vals = Vector{V}(undef, newsz) + slots = Memory{UInt8}(undef, newsz) + fill!(slots, 0x0) + keys = Memory{K}(undef, newsz) + vals = Memory{V}(undef, newsz) age0 = h.age count = 0 maxprobe = 0 @@ -224,7 +190,7 @@ end return h end -function _sizehint!(d::Dict{T}, newsz; shrink = true) where T +function sizehint!(d::Dict{T}, newsz; shrink::Bool=true) where T oldsz = length(d.slots) # limit new element count to max_values of the key type newsz = min(max(newsz, length(d)), max_values(T)::Int) @@ -233,8 +199,6 @@ function _sizehint!(d::Dict{T}, newsz; shrink = true) where T return (shrink ? newsz == oldsz : newsz <= oldsz) ? d : rehash!(d, newsz) end -sizehint!(d::Dict{T}, newsz) where T = _sizehint!(d, newsz) - """ empty!(collection) -> collection @@ -256,20 +220,20 @@ Dict{String, Int64}() function empty!(h::Dict{K,V}) where V where K fill!(h.slots, 0x0) sz = length(h.slots) - empty!(h.keys) - empty!(h.vals) - resize!(h.keys, sz) - resize!(h.vals, sz) + for i in 1:sz + _unsetindex!(h.keys, i) + _unsetindex!(h.vals, i) + end h.ndel = 0 h.count = 0 h.maxprobe = 0 h.age += 1 - h.idxfloor = sz + h.idxfloor = max(1, sz) return h end # get the index where a key is stored, or -1 if not present -@assume_effects :terminates_locally function ht_keyindex(h::Dict{K,V}, key) where V where K +function ht_keyindex(h::Dict{K,V}, key) where V where K isempty(h) && return -1 sz = length(h.keys) iter = 0 @@ -278,9 +242,9 @@ end index, sh = hashindex(key, sz) keys = h.keys - @inbounds while true + @assume_effects :terminates_locally :noub @inbounds while true isslotempty(h,index) && return -1 - if h.slots[index] == sh + if sh == h.slots[index] k = keys[index] if (key === k || isequal(key, k)) return index @@ -300,6 +264,11 @@ end # This version is for use by setindex! and get! function ht_keyindex2_shorthash!(h::Dict{K,V}, key) where V where K sz = length(h.keys) + if sz == 0 # if Dict was empty resize and then return location to insert + rehash!(h, 4) + index, sh = hashindex(key, length(h.keys)) + return -index, sh + end iter = 0 maxprobe = h.maxprobe index, sh = hashindex(key, sz) @@ -365,7 +334,7 @@ ht_keyindex2!(h::Dict, key) = ht_keyindex2_shorthash!(h, key)[1] # Rehash now if necessary if (h.count + h.ndel)*3 > sz*2 # > 2/3 full (including tombstones) - rehash!(h, h.count > 64000 ? h.count*2 : h.count*4) + rehash!(h, h.count > 64000 ? h.count*2 : max(h.count*4, 4)) end nothing end @@ -376,7 +345,7 @@ function setindex!(h::Dict{K,V}, v0, key0) where V where K else key = convert(K, key0)::K if !(isequal(key, key0)::Bool) - throw(ArgumentError("$(limitrepr(key0)) is not a valid key for type $K")) + throw(KeyTypeError(K, key0)) end end setindex!(h, v0, key) @@ -474,7 +443,7 @@ function get!(default::Callable, h::Dict{K,V}, key0) where V where K else key = convert(K, key0)::K if !isequal(key, key0) - throw(ArgumentError("$(limitrepr(key0)) is not a valid key for type $K")) + throw(KeyTypeError(K, key0)) end end return get!(default, h, key) @@ -505,7 +474,7 @@ end function getindex(h::Dict{K,V}, key) where V where K index = ht_keyindex(h, key) - @inbounds return (index < 0) ? throw(KeyError(key)) : h.vals[index]::V + return index < 0 ? throw(KeyError(key)) : @assume_effects :noub @inbounds h.vals[index]::V end """ @@ -736,6 +705,8 @@ end isempty(t::Dict) = (t.count == 0) length(t::Dict) = t.count +@propagate_inbounds Iterators.only(t::Dict) = Iterators._only(t, first) + @propagate_inbounds function Base.iterate(v::T, i::Int = v.dict.idxfloor) where T <: Union{KeySet{<:Any, <:Dict}, ValueIterator{<:Dict}} i == 0 && return nothing i = skip_deleted(v.dict, i) @@ -773,7 +744,7 @@ function map!(f, iter::ValueIterator{<:Dict}) end function mergewith!(combine, d1::Dict{K, V}, d2::AbstractDict) where {K, V} - haslength(d2) && _sizehint!(d1, length(d1) + length(d2), shrink = false) + haslength(d2) && sizehint!(d1, length(d1) + length(d2), shrink=false) for (k, v) in d2 i, sh = ht_keyindex2_shorthash!(d1, k) if i > 0 @@ -782,7 +753,7 @@ function mergewith!(combine, d1::Dict{K, V}, d2::AbstractDict) where {K, V} if !(k isa K) k1 = convert(K, k)::K if !isequal(k, k1) - throw(ArgumentError("$(limitrepr(k)) is not a valid key for type $K")) + throw(KeyTypeError(K, k)) end k = k1 end @@ -885,10 +856,43 @@ _similar_for(c::AbstractDict, ::Type{T}, itr, isz, len) where {T} = include("hamt.jl") using .HashArrayMappedTries +using Core.OptimizedGenerics: KeyValue const HAMT = HashArrayMappedTries struct PersistentDict{K,V} <: AbstractDict{K,V} trie::HAMT.HAMT{K,V} + # Serves as a marker for an empty initialization + @noinline function KeyValue.set(::Type{PersistentDict{K, V}}) where {K, V} + new{K, V}(HAMT.HAMT{K,V}()) + end + @noinline function KeyValue.set(::Type{PersistentDict{K, V}}, ::Nothing, key, val) where {K, V} + new{K, V}(HAMT.HAMT{K, V}(key => val)) + end + @noinline Base.@assume_effects :effect_free :terminates_globally KeyValue.set( + dict::PersistentDict{K, V}, key, val) where {K, V} = @inline _keyvalueset(dict, key, val) + @noinline Base.@assume_effects :nothrow :effect_free :terminates_globally KeyValue.set( + dict::PersistentDict{K, V}, key::K, val::V) where {K, V} = @inline _keyvalueset(dict, key, val) + global function _keyvalueset(dict::PersistentDict{K, V}, key, val) where {K, V} + trie = dict.trie + h = HAMT.HashState(key) + found, present, trie, i, bi, top, hs = HAMT.path(trie, key, h, #=persistent=#true) + HAMT.insert!(found, present, trie, i, bi, hs, val) + return new{K, V}(top) + end + @noinline Base.@assume_effects :effect_free :terminates_globally KeyValue.set( + dict::PersistentDict{K, V}, key) where {K, V} = @inline _keyvalueset(dict, key) + @noinline Base.@assume_effects :nothrow :effect_free :terminates_globally KeyValue.set( + dict::PersistentDict{K, V}, key::K) where {K, V} = @inline _keyvalueset(dict, key) + global function _keyvalueset(dict::PersistentDict{K, V}, key) where {K, V} + trie = dict.trie + h = HAMT.HashState(key) + found, present, trie, i, bi, top, _ = HAMT.path(trie, key, h, #=persistent=#true) + if found && present + deleteat!(trie.data, i) + HAMT.unset!(trie, bi) + end + return new{K, V}(top) + end end """ @@ -896,11 +900,16 @@ end `PersistentDict` is a dictionary implemented as an hash array mapped trie, which is optimal for situations where you need persistence, each operation -returns a new dictonary separate from the previous one, but the underlying +returns a new dictionary separate from the previous one, but the underlying implementation is space-efficient and may share storage across multiple separate dictionaries. - PersistentDict(KV::Pair) +!!! note + It behaves like an IdDict. + +```julia +PersistentDict(KV::Pair) +``` # Examples @@ -919,25 +928,40 @@ Base.PersistentDict{Symbol, Int64} with 1 entry: """ PersistentDict -PersistentDict{K,V}() where {K,V} = PersistentDict(HAMT.HAMT{K,V}()) -PersistentDict{K,V}(KV::Pair) where {K,V} = PersistentDict(HAMT.HAMT{K,V}(KV...)) -PersistentDict(KV::Pair{K,V}) where {K,V} = PersistentDict(HAMT.HAMT{K,V}(KV...)) +PersistentDict{K,V}() where {K, V} = KeyValue.set(PersistentDict{K,V}) +function PersistentDict{K,V}(KV::Pair) where {K,V} + KeyValue.set( + PersistentDict{K, V}, + nothing, + KV...) +end +function PersistentDict(KV::Pair{K,V}) where {K,V} + KeyValue.set( + PersistentDict{K, V}, + nothing, + KV...) +end PersistentDict(dict::PersistentDict, pair::Pair) = PersistentDict(dict, pair...) PersistentDict{K,V}(dict::PersistentDict{K,V}, pair::Pair) where {K,V} = PersistentDict(dict, pair...) + + function PersistentDict(dict::PersistentDict{K,V}, key, val) where {K,V} key = convert(K, key) val = convert(V, val) - trie = dict.trie - h = hash(key) - found, present, trie, i, bi, top, hs = HAMT.path(trie, key, h, #=persistent=# true) - HAMT.insert!(found, present, trie, i, bi, hs, val) - return PersistentDict(top) + return KeyValue.set(dict, key, val) +end + +function PersistentDict{K,V}(KV::Pair, rest::Pair...) where {K,V} + dict = PersistentDict{K,V}(KV) + for (key, value) in rest + dict = PersistentDict(dict, key, value) + end + return dict end function PersistentDict(kv::Pair, rest::Pair...) dict = PersistentDict(kv) - for kv in rest - key, value = kv + for (key, value) in rest dict = PersistentDict(dict, key, value) end return dict @@ -946,84 +970,62 @@ end eltype(::PersistentDict{K,V}) where {K,V} = Pair{K,V} function in(key_val::Pair{K,V}, dict::PersistentDict{K,V}, valcmp=(==)) where {K,V} - trie = dict.trie - if HAMT.islevel_empty(trie) - return false - end - key, val = key_val - - h = hash(key) - found, present, trie, i, _, _, _ = HAMT.path(trie, key, h) - if found && present - leaf = @inbounds trie.data[i]::HAMT.Leaf{K,V} - return valcmp(val, leaf.val) && return true - end - return false + found = KeyValue.get(dict, key) + found === nothing && return false + return valcmp(val, only(found)) end function haskey(dict::PersistentDict{K}, key::K) where K - trie = dict.trie - h = hash(key) - found, present, _, _, _, _, _ = HAMT.path(trie, key, h) - return found && present + return KeyValue.get(dict, key) !== nothing end function getindex(dict::PersistentDict{K,V}, key::K) where {K,V} - trie = dict.trie - if HAMT.islevel_empty(trie) - throw(KeyError(key)) - end - h = hash(key) - found, present, trie, i, _, _, _ = HAMT.path(trie, key, h) - if found && present - leaf = @inbounds trie.data[i]::HAMT.Leaf{K,V} - return leaf.val - end - throw(KeyError(key)) + found = KeyValue.get(dict, key) + found === nothing && throw(KeyError(key)) + return only(found) end function get(dict::PersistentDict{K,V}, key::K, default) where {K,V} - trie = dict.trie - if HAMT.islevel_empty(trie) - return default - end - h = hash(key) - found, present, trie, i, _, _, _ = HAMT.path(trie, key, h) - if found && present - leaf = @inbounds trie.data[i]::HAMT.Leaf{K,V} - return leaf.val - end - return default + found = KeyValue.get(dict, key) + found === nothing && return default + return only(found) end -function get(default::Callable, dict::PersistentDict{K,V}, key::K) where {K,V} +@noinline function KeyValue.get(dict::PersistentDict{K, V}, key) where {K, V} trie = dict.trie if HAMT.islevel_empty(trie) - return default + return nothing end - h = hash(key) + h = HAMT.HashState(key) found, present, trie, i, _, _, _ = HAMT.path(trie, key, h) if found && present leaf = @inbounds trie.data[i]::HAMT.Leaf{K,V} - return leaf.val + return (leaf.val,) end - return default() + return nothing end -iterate(dict::PersistentDict, state=nothing) = HAMT.iterate(dict.trie, state) +@noinline function KeyValue.get(default, dict::PersistentDict, key) + found = KeyValue.get(dict, key) + found === nothing && return default() + return only(found) +end + +function get(default::Callable, dict::PersistentDict{K,V}, key::K) where {K,V} + found = KeyValue.get(dict, key) + found === nothing && return default() + return only(found) +end function delete(dict::PersistentDict{K}, key::K) where K - trie = dict.trie - h = hash(key) - found, present, trie, i, bi, top, _ = HAMT.path(trie, key, h, #=persistent=# true) - if found && present - deleteat!(trie.data, i) - HAMT.unset!(trie, bi) - end - return PersistentDict(top) + return KeyValue.set(dict, key) end +iterate(dict::PersistentDict, state=nothing) = HAMT.iterate(dict.trie, state) + length(dict::PersistentDict) = HAMT.length(dict.trie) isempty(dict::PersistentDict) = HAMT.isempty(dict.trie) empty(::PersistentDict, ::Type{K}, ::Type{V}) where {K, V} = PersistentDict{K, V}() + +@propagate_inbounds Iterators.only(dict::PersistentDict) = Iterators._only(dict, first) diff --git a/base/div.jl b/base/div.jl index 9c2187e662ee9..8988f2b70f27b 100644 --- a/base/div.jl +++ b/base/div.jl @@ -22,6 +22,8 @@ See also [`fld`](@ref) and [`cld`](@ref), which are special cases of this functi # Examples: ```jldoctest +julia> div(4, 3, RoundToZero) # Matches div(4, 3) +1 julia> div(4, 3, RoundDown) # Matches fld(4, 3) 1 julia> div(4, 3, RoundUp) # Matches cld(4, 3) diff --git a/base/docs/Docs.jl b/base/docs/Docs.jl index 4ca384361c4e3..0206f867b3811 100644 --- a/base/docs/Docs.jl +++ b/base/docs/Docs.jl @@ -3,7 +3,7 @@ """ Docs -The `Docs` module provides the `@doc` macro which can be used to set and retrieve +The `Docs` module provides the [`@doc`](@ref) macro which can be used to set and retrieve documentation metadata for Julia objects. Please see the manual section on [documentation](@ref man-documentation) for more @@ -60,12 +60,12 @@ function. include("bindings.jl") -import .Base.Meta: quot, isexpr +import .Base.Meta: quot, isexpr, unblock, unescape, uncurly import .Base: Callable, with_output_color using .Base: RefValue, mapany import ..CoreDocs: lazy_iterpolate -export doc +export doc, hasdoc, undocumented_names # Basic API / Storage @@ -194,7 +194,7 @@ docexpr(__source__, __module__, args...) = Expr(:call, docstr, args...) Stores a collection of docstrings for related objects, ie. a `Function`/`DataType` and associated `Method` objects. -Each documented object in a `MultiDoc` is referred to by it's signature which is represented +Each documented object in a `MultiDoc` is referred to by its signature which is represented by a `Union` of `Tuple` types. For example, the following `Method` definition f(x, y) = ... @@ -243,7 +243,7 @@ function doc!(__module__::Module, b::Binding, str::DocStr, @nospecialize sig = U @warn "Replacing docs for `$b :: $sig` in module `$(__module__)`" else # The ordering of docstrings for each Binding is defined by the order in which they - # are initially added. Replacing a specific docstring does not change it's ordering. + # are initially added. Replacing a specific docstring does not change its ordering. push!(m.order, sig) end m.docs[sig] = str @@ -285,29 +285,6 @@ catdoc(xs...) = vcat(xs...) const keywords = Dict{Symbol, DocStr}() -function unblock(@nospecialize ex) - while isexpr(ex, :var"hygienic-scope") - isexpr(ex.args[1], :escape) || break - ex = ex.args[1].args[1] - end - isexpr(ex, :block) || return ex - exs = filter(ex -> !(isa(ex, LineNumberNode) || isexpr(ex, :line)), ex.args) - length(exs) == 1 || return ex - return unblock(exs[1]) -end - -# peek through ex to figure out what kind of expression it may eventually act like -# but ignoring scopes and line numbers -function unescape(@nospecialize ex) - ex = unblock(ex) - while isexpr(ex, :escape) || isexpr(ex, :var"hygienic-scope") - ex = unblock(ex.args[1]) - end - return ex -end - -uncurly(@nospecialize ex) = isexpr(ex, :curly) ? ex.args[1] : ex - namify(@nospecialize x) = astname(x, isexpr(x, :macro))::Union{Symbol,Expr,GlobalRef} function astname(x::Expr, ismacro::Bool) @@ -469,6 +446,52 @@ more than one expression is marked then the same docstring is applied to each ex end `@__doc__` has no effect when a macro that uses it is not documented. + +!!! compat "Julia 1.12" + + This section documents a very subtle corner case that is only relevant to + macros which themselves both define other macros and then attempt to use them + within the same expansion. Such macros were impossible to write prior to + Julia 1.12 and are still quite rare. If you are not writing such a macro, + you may ignore this note. + + In versions prior to Julia 1.12, macroexpansion would recursively expand through + `Expr(:toplevel)` blocks. This behavior was changed in Julia 1.12 to allow + macros to recursively define other macros and use them in the same returned + expression. However, to preserve backwards compatibility with existing uses of + `@__doc__`, the doc system will still expand through `Expr(:toplevel)` blocks + when looking for `@__doc__` markers. As a result, macro-defining-macros will + have an observable behavior difference when annotated with a docstring: + + ```julia + julia> macro macroception() + Expr(:toplevel, :(macro foo() 1 end), :(@foo)) + end + + julia> @macroception + 1 + + julia> "Docstring" @macroception + ERROR: LoadError: UndefVarError: `@foo` not defined in `Main` + ``` + + The supported workaround is to manually expand the `@__doc__` macro in the + defining macro, which the docsystem will recognize and suppress the recursive + expansion: + + ```julia + julia> macro macroception() + Expr(:toplevel, + macroexpand(__module__, :(@__doc__ macro foo() 1 end); recursive=false), + :(@foo)) + end + + julia> @macroception + 1 + + julia> "Docstring" @macroception + 1 + ``` """ :(Core.@__doc__) @@ -476,17 +499,23 @@ function __doc__!(source, mod, meta, def, define::Bool) @nospecialize source mod meta def # Two cases must be handled here to avoid redefining all definitions contained in `def`: if define - # `def` has not been defined yet (this is the common case, i.e. when not generating - # the Base image). We just need to convert each `@__doc__` marker to an `@doc`. - finddoc(def) do each + function replace_meta_doc(each) each.head = :macrocall each.args = Any[Symbol("@doc"), source, mod, nothing, meta, each.args[end], define] end + + # `def` has not been defined yet (this is the common case, i.e. when not generating + # the Base image). We just need to convert each `@__doc__` marker to an `@doc`. + found = finddoc(replace_meta_doc, mod, def; expand_toplevel = false) + + if !found + found = finddoc(replace_meta_doc, mod, def; expand_toplevel = true) + end else # `def` has already been defined during Base image gen so we just need to find and # document any subexpressions marked with `@__doc__`. docs = [] - found = finddoc(def) do each + found = finddoc(mod, def; expand_toplevel = true) do each push!(docs, :(@doc($source, $mod, $meta, $(each.args[end]), $define))) end # If any subexpressions have been documented then replace the entire expression with @@ -495,25 +524,30 @@ function __doc__!(source, mod, meta, def, define::Bool) def.head = :toplevel def.args = docs end - found end + return found end # Walk expression tree `def` and call `λ` when any `@__doc__` markers are found. Returns # `true` to signify that at least one `@__doc__` has been found, and `false` otherwise. -function finddoc(λ, def::Expr) +function finddoc(λ, mod::Module, def::Expr; expand_toplevel::Bool=false) if isexpr(def, :block, 2) && isexpr(def.args[1], :meta, 1) && (def.args[1]::Expr).args[1] === :doc # Found the macroexpansion of an `@__doc__` expression. λ(def) true else + if expand_toplevel && isexpr(def, :toplevel) + for i = 1:length(def.args) + def.args[i] = macroexpand(mod, def.args[i]) + end + end found = false for each in def.args - found |= finddoc(λ, each) + found |= finddoc(λ, mod, each; expand_toplevel) end found end end -finddoc(λ, @nospecialize def) = false +finddoc(λ, mod::Module, @nospecialize def; expand_toplevel::Bool=false) = false # Predicates and helpers for `docm` expression selection: @@ -551,8 +585,37 @@ iscallexpr(ex) = false function docm(source::LineNumberNode, mod::Module, meta, ex, define::Bool = true) @nospecialize meta ex # Some documented expressions may be decorated with macro calls which obscure the actual - # expression. Expand the macro calls and remove extra blocks. - x = unblock(macroexpand(mod, ex)) + # expression. Expand the macro calls. + x = macroexpand(mod, ex) + return _docm(source, mod, meta, x, define) +end + +function _docm(source::LineNumberNode, mod::Module, meta, x, define::Bool = true) + if isexpr(x, :var"hygienic-scope") + x.args[1] = _docm(source, mod, meta, x.args[1]) + return x + elseif isexpr(x, :escape) + x.args[1] = _docm(source, mod, meta, x.args[1]) + return x + elseif isexpr(x, :block) + docarg = 0 + for i = 1:length(x.args) + isa(x.args[i], LineNumberNode) && continue + if docarg == 0 + docarg = i + continue + end + # More than one documentable expression in the block, treat it as a whole + # expression, which will fall through and look for (Expr(:meta, doc)) + docarg = 0 + break + end + if docarg != 0 + x.args[docarg] = _docm(source, mod, meta, x.args[docarg], define) + return x + end + end + # Don't try to redefine expressions. This is only needed for `Base` img gen since # otherwise calling `loaddocs` would redefine all documented functions and types. def = define ? x : nothing @@ -617,7 +680,7 @@ function docm(source::LineNumberNode, mod::Module, meta, ex, define::Bool = true # All other expressions are undocumentable and should be handled on a case-by-case basis # with `@__doc__`. Unbound string literals are also undocumentable since they cannot be # retrieved from the module's metadata `IdDict` without a reference to the string. - docerror(ex) + docerror(x) return doc end @@ -650,9 +713,72 @@ function loaddocs(docs::Vector{Core.SimpleVector}) nothing end +# FIXME: formatdoc, parsedoc, apropos, and doc are defined here (but only doc is exported) +# for historical reasons (#25738), but are *implemented* in REPL/src/docview.jl, while +# apropos is *exported* by InteractiveUtils and doc is exported by Docs. Seems +# like a more sensible refactoring should be possible. + function formatdoc end function parsedoc end + +""" + apropos([io::IO=stdout], pattern::Union{AbstractString,Regex}) + +Search available docstrings for entries containing `pattern`. + +When `pattern` is a string, case is ignored. Results are printed to `io`. + +`apropos` can be called from the help mode in the REPL by wrapping the query in double quotes: +``` +help?> "pattern" +``` +""" function apropos end + +""" + Docs.doc(binding, sig) + +Return all documentation that matches both `binding` and `sig`. + +If `getdoc` returns a non-`nothing` result on the value of the binding, then a +dynamic docstring is returned instead of one based on the binding itself. +""" function doc end +""" + Docs.hasdoc(mod::Module, sym::Symbol)::Bool + +Return `true` if `sym` in `mod` has a docstring and `false` otherwise. +""" +hasdoc(mod::Module, sym::Symbol) = hasdoc(Docs.Binding(mod, sym)) +function hasdoc(binding::Docs.Binding, sig::Type = Union{}) + # this function is based on the Base.Docs.doc method implemented + # in REPL/src/docview.jl. TODO: refactor and unify these methods. + defined(binding) && !isnothing(getdoc(resolve(binding), sig)) && return true + for mod in modules + dict = meta(mod; autoinit=false) + !isnothing(dict) && haskey(dict, binding) && return true + end + alias = aliasof(binding) + return alias == binding ? false : hasdoc(alias, sig) +end + + +""" + undocumented_names(mod::Module; private=false) + +Return a sorted vector of undocumented symbols in `module` (that is, lacking docstrings). +`private=false` (the default) returns only identifiers declared with `public` and/or +`export`, whereas `private=true` returns all symbols in the module (excluding +compiler-generated hidden symbols starting with `#`). + +See also: [`names`](@ref), [`Docs.hasdoc`](@ref), [`Base.ispublic`](@ref). +""" +function undocumented_names(mod::Module; private::Bool=false) + filter!(names(mod; all=true)) do sym + !hasdoc(mod, sym) && !startswith(string(sym), '#') && + (private || Base.ispublic(mod, sym)) + end +end + end diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index b9102f01b7d0c..cc2d32f6b3deb 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -1,4 +1,5 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +# module BaseDocs @@ -63,9 +64,12 @@ kw"export" public `public` is used within modules to tell Julia which names are part of the -public API of the module . For example: `public foo` indicates that the name -`foo` is public, without making it available when [`using`](@ref) -the module. See the [manual section about modules](@ref modules) for details. +public API of the module. For example: `public foo` indicates that the name +`foo` is public, without making it available when [`using`](@ref) the module. + +As [`export`](@ref) already indicates that a name is public, it is +unnecessary and an error to declare a name both as `public` and as `export`ed. +See the [manual section about modules](@ref modules) for details. !!! compat "Julia 1.11" The public keyword was added in Julia 1.11. Prior to this the notion @@ -165,7 +169,7 @@ kw"__init__" baremodule `baremodule` declares a module that does not contain `using Base` or local definitions of -[`eval`](@ref Base.MainInclude.eval) and [`include`](@ref Base.include). It does still import `Core`. In other words, +[`eval`](@ref Main.eval) and [`include`](@ref Base.include). It does still import `Core`. In other words, ```julia module Mod @@ -217,7 +221,7 @@ kw"primitive type" A macro maps a sequence of argument expressions to a returned expression, and the resulting expression is substituted directly into the program at the point where the macro is invoked. -Macros are a way to run generated code without calling [`eval`](@ref Base.MainInclude.eval), +Macros are a way to run generated code without calling [`eval`](@ref Main.eval), since the generated code instead simply becomes part of the surrounding program. Macro arguments may include expressions, literal values, and symbols. Macros can be defined for variable number of arguments (varargs), but do not accept keyword arguments. @@ -1517,6 +1521,8 @@ Nothing The singleton instance of type [`Nothing`](@ref), used by convention when there is no value to return (as in a C `void` function) or when a variable or field holds no value. +A return value of `nothing` is not displayed by the REPL and similar interactive environments. + See also: [`isnothing`](@ref), [`something`](@ref), [`missing`](@ref). """ nothing @@ -1818,14 +1824,14 @@ In these examples, `a` is a [`Rational`](@ref), which has two fields. nfields """ - UndefVarError(var::Symbol) + UndefVarError(var::Symbol, [scope]) A symbol in the current scope is not defined. # Examples ```jldoctest julia> a -ERROR: UndefVarError: `a` not defined +ERROR: UndefVarError: `a` not defined in `Main` julia> a = 1; @@ -2027,7 +2033,21 @@ AbstractFloat """ Integer <: Real -Abstract supertype for all integers. +Abstract supertype for all integers (e.g. [`Signed`](@ref), [`Unsigned`](@ref), and [`Bool`](@ref)). + +See also [`isinteger`](@ref), [`trunc`](@ref), [`div`](@ref). + +# Examples +``` +julia> 42 isa Integer +true + +julia> 1.0 isa Integer +false + +julia> isinteger(1.0) +true +``` """ Integer @@ -2042,6 +2062,21 @@ Signed Unsigned <: Integer Abstract supertype for all unsigned integers. + +Built-in unsigned integers are printed in hexadecimal, with prefix `0x`, +and can be entered in the same way. + +# Examples +``` +julia> typemax(UInt8) +0xff + +julia> Int(0x00d) +13 + +julia> unsigned(true) +0x0000000000000001 +``` """ Unsigned @@ -2052,57 +2087,147 @@ Boolean type, containing the values `true` and `false`. `Bool` is a kind of number: `false` is numerically equal to `0` and `true` is numerically equal to `1`. -Moreover, `false` acts as a multiplicative "strong zero": +Moreover, `false` acts as a multiplicative "strong zero" +against [`NaN`](@ref) and [`Inf`](@ref): ```jldoctest -julia> false == 0 +julia> [true, false] == [1, 0] true -julia> true == 1 -true +julia> 42.0 + true +43.0 -julia> 0 * NaN -NaN +julia> 0 .* (NaN, Inf, -Inf) +(NaN, NaN, NaN) -julia> false * NaN -0.0 +julia> false .* (NaN, Inf, -Inf) +(0.0, 0.0, -0.0) ``` -See also: [`digits`](@ref), [`iszero`](@ref), [`NaN`](@ref). +Branches via [`if`](@ref) and other conditionals only accept `Bool`. +There are no "truthy" values in Julia. + +Comparisons typically return `Bool`, and broadcasted comparisons may +return [`BitArray`](@ref) instead of an `Array{Bool}`. + +```jldoctest +julia> [1 2 3 4 5] .< pi +1×5 BitMatrix: + 1 1 1 0 0 + +julia> map(>(pi), [1 2 3 4 5]) +1×5 Matrix{Bool}: + 0 0 0 1 1 +``` + +See also [`trues`](@ref), [`falses`](@ref), [`ifelse`](@ref). """ Bool -for (bit, sign, exp, frac) in ((16, 1, 5, 10), (32, 1, 8, 23), (64, 1, 11, 52)) - @eval begin - """ - Float$($bit) <: AbstractFloat +""" + Float64 <: AbstractFloat <: Real - $($bit)-bit floating point number type (IEEE 754 standard). +64-bit floating point number type (IEEE 754 standard). +Binary format is 1 sign, 11 exponent, 52 fraction bits. +See [`bitstring`](@ref), [`signbit`](@ref), [`exponent`](@ref), [`frexp`](@ref), +and [`significand`](@ref) to access various bits. - Binary format: $($sign) sign, $($exp) exponent, $($frac) fraction bits. - """ - $(Symbol("Float", bit)) - end -end +This is the default for floating point literals, `1.0 isa Float64`, +and for many operations such as `1/2, 2pi, log(2), range(0,90,length=4)`. +Unlike integers, this default does not change with `Sys.WORD_SIZE`. + +The exponent for scientific notation can be entered as `e` or `E`, +thus `2e3 === 2.0E3 === 2.0 * 10^3`. Doing so is strongly preferred over +`10^n` because integers overflow, thus `2.0 * 10^19 < 0` but `2e19 > 0`. + +See also [`Inf`](@ref), [`NaN`](@ref), [`floatmax`](@ref), [`Float32`](@ref), [`Complex`](@ref). +""" +Float64 + +""" + Float32 <: AbstractFloat <: Real + +32-bit floating point number type (IEEE 754 standard). +Binary format is 1 sign, 8 exponent, 23 fraction bits. + +The exponent for scientific notation should be entered as lower-case `f`, +thus `2f3 === 2.0f0 * 10^3 === Float32(2_000)`. +For array literals and comprehensions, the element type can be specified before +the square brackets: `Float32[1,4,9] == Float32[i^2 for i in 1:3]`. + +See also [`Inf32`](@ref), [`NaN32`](@ref), [`Float16`](@ref), [`exponent`](@ref), [`frexp`](@ref). +""" +Float32 + +""" + Float16 <: AbstractFloat <: Real + +16-bit floating point number type (IEEE 754 standard). +Binary format is 1 sign, 5 exponent, 10 fraction bits. +""" +Float16 for bit in (8, 16, 32, 64, 128) + type = Symbol(:Int, bit) + srange = bit > 31 ? "" : "Represents numbers `n ∈ " * repr(eval(:(typemin($type):typemax($type)))) * "`.\n" + unshow = repr(eval(Symbol(:UInt, bit))(bit-1)) + @eval begin """ - Int$($bit) <: Signed + Int$($bit) <: Signed <: Integer $($bit)-bit signed integer type. + + $($(srange))Note that such integers overflow without warning, + thus `typemax($($type)) + $($type)(1) < 0`. + + See also [`Int`](@ref $Int), [`widen`](@ref), [`BigInt`](@ref). """ $(Symbol("Int", bit)) """ - UInt$($bit) <: Unsigned + UInt$($bit) <: Unsigned <: Integer $($bit)-bit unsigned integer type. + + Printed in hexadecimal, thus $($(unshow)) == $($(bit-1)). """ $(Symbol("UInt", bit)) end end +""" + Int + +Sys.WORD_SIZE-bit signed integer type, `Int <: Signed <: Integer <: Real`. + +This is the default type of most integer literals and is an alias for either `Int32` +or `Int64`, depending on `Sys.WORD_SIZE`. It is the type returned by functions such as +[`length`](@ref), and the standard type for indexing arrays. + +Note that integers overflow without warning, thus `typemax(Int) + 1 < 0` and `10^19 < 0`. +Overflow can be avoided by using [`BigInt`](@ref). +Very large integer literals will use a wider type, for instance `10_000_000_000_000_000_000 isa Int128`. + +Integer division is [`div`](@ref) alias `÷`, +whereas [`/`](@ref) acting on integers returns [`Float64`](@ref). + +See also [`$(Symbol("Int", Sys.WORD_SIZE))`](@ref), [`widen`](@ref), [`typemax`](@ref), [`bitstring`](@ref). +""" +Int + +""" + UInt + +Sys.WORD_SIZE-bit unsigned integer type, `UInt <: Unsigned <: Integer`. + +Like [`Int`](@ref Int), the alias `UInt` may point to either `UInt32` or `UInt64`, +according to the value of `Sys.WORD_SIZE` on a given computer. + +Printed and parsed in hexadecimal: `UInt(15) === $(repr(UInt(15)))`. +""" +UInt + """ Symbol @@ -2235,11 +2360,14 @@ setfield! swapfield!(value, name::Symbol, x, [order::Symbol]) swapfield!(value, i::Int, x, [order::Symbol]) -These atomically perform the operations to simultaneously get and set a field: +Atomically perform the operations to simultaneously get and set a field: y = getfield(value, name) setfield!(value, name, x) return y + +!!! compat "Julia 1.7" + This function requires Julia 1.7 or later. """ swapfield! @@ -2247,7 +2375,7 @@ swapfield! 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 +Atomically perform the operations to get and set a field after applying the function `op`. y = getfield(value, name) @@ -2257,6 +2385,9 @@ the function `op`. If supported by the hardware (for example, atomic increment), this may be optimized to the appropriate hardware instruction, otherwise it'll use a loop. + +!!! compat "Julia 1.7" + This function requires Julia 1.7 or later. """ modifyfield! @@ -2266,7 +2397,7 @@ modifyfield! 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 +Atomically perform the operations to get and conditionally set a field to a given value. y = getfield(value, name, fail_order) @@ -2278,9 +2409,30 @@ a given value. If supported by the hardware, this may be optimized to the appropriate hardware instruction, otherwise it'll use a loop. + +!!! compat "Julia 1.7" + This function requires Julia 1.7 or later. """ replacefield! +""" + setfieldonce!(value, name::Union{Int,Symbol}, desired, + [success_order::Symbol, [fail_order::Symbol=success_order]) -> success::Bool + +Atomically perform the operations to set a field to +a given value, only if it was previously not set. + + ok = !isdefined(value, name, fail_order) + if ok + setfield!(value, name, desired, success_order) + end + return ok + +!!! compat "Julia 1.11" + This function requires Julia 1.11 or later. +""" +setfieldonce! + """ getglobal(module::Module, name::Symbol, [order::Symbol=:monotonic]) @@ -2321,6 +2473,7 @@ julia> getglobal(M, :a) """ getglobal + """ setglobal!(module::Module, name::Symbol, x, [order::Symbol=:monotonic]) @@ -2346,7 +2499,8 @@ See also [`setproperty!`](@ref Base.setproperty!) and [`getglobal`](@ref) julia> module M end; julia> M.a # same as `getglobal(M, :a)` -ERROR: UndefVarError: `a` not defined +ERROR: UndefVarError: `a` not defined in `M` +Suggestion: check for spelling errors or missing imports. julia> setglobal!(M, :a, 1) 1 @@ -2357,6 +2511,81 @@ julia> M.a """ setglobal! +""" + Core.get_binding_type(module::Module, name::Symbol) + +Retrieve the declared type of the binding `name` from the module `module`. + +!!! compat "Julia 1.9" + This function requires Julia 1.9 or later. +""" +Core.get_binding_type + +""" + Core.set_binding_type!(module::Module, name::Symbol, [type::Type]) + +Set the declared type of the binding `name` in the module `module` to `type`. Error if the +binding already has a type that is not equivalent to `type`. If the `type` argument is +absent, set the binding type to `Any` if unset, but do not error. + +!!! compat "Julia 1.9" + This function requires Julia 1.9 or later. +""" +Core.set_binding_type! + +""" + swapglobal!(module::Module, name::Symbol, x, [order::Symbol=:monotonic]) + +Atomically perform the operations to simultaneously get and set a global. + +!!! compat "Julia 1.11" + This function requires Julia 1.11 or later. + +See also [`swapproperty!`](@ref Base.swapproperty!) and [`setglobal!`](@ref). +""" +swapglobal! + +""" + modifyglobal!(module::Module, name::Symbol, op, x, [order::Symbol=:monotonic]) -> Pair + +Atomically perform the operations to get and set a global after applying +the function `op`. + +!!! compat "Julia 1.11" + This function requires Julia 1.11 or later. + +See also [`modifyproperty!`](@ref Base.modifyproperty!) and [`setglobal!`](@ref). +""" +modifyglobal! + +""" + replaceglobal!(module::Module, name::Symbol, expected, desired, + [success_order::Symbol, [fail_order::Symbol=success_order]) -> (; old, success::Bool) + +Atomically perform the operations to get and conditionally set a global to +a given value. + +!!! compat "Julia 1.11" + This function requires Julia 1.11 or later. + +See also [`replaceproperty!`](@ref Base.replaceproperty!) and [`setglobal!`](@ref). +""" +replaceglobal! + +""" + setglobalonce!(module::Module, name::Symbol, value, + [success_order::Symbol, [fail_order::Symbol=success_order]) -> success::Bool + +Atomically perform the operations to set a global to +a given value, only if it was previously not set. + +!!! compat "Julia 1.11" + This function requires Julia 1.11 or later. + +See also [`setpropertyonce!`](@ref Base.setpropertyonce!) and [`setglobal!`](@ref). +""" +setglobalonce! + """ typeof(x) @@ -2420,6 +2649,42 @@ false """ isdefined +""" + Memory{T}(undef, n) + +Construct an uninitialized [`Memory{T}`](@ref) of length `n`. All Memory +objects of length 0 might alias, since there is no reachable mutable content +from them. + +# Examples +```julia-repl +julia> Memory{Float64}(undef, 3) +3-element Memory{Float64}: + 6.90966e-310 + 6.90966e-310 + 6.90966e-310 +``` +""" +Memory{T}(::UndefInitializer, n) + +""" + MemoryRef(memory) + +Construct a MemoryRef from a memory object. This does not fail, but the +resulting memory may point out-of-bounds if the memory is empty. +""" +MemoryRef(::Memory) + +""" + MemoryRef(::Memory, index::Integer) + MemoryRef(::MemoryRef, index::Integer) + +Construct a MemoryRef from a memory object and an offset index (1-based) which +can also be negative. This always returns an inbounds object, and will throw an +error if that is not possible (because the index would result in a shift +out-of-bounds of the underlying memory). +""" +MemoryRef(::Union{Memory,MemoryRef}, ::Integer) """ Vector{T}(undef, n) @@ -2655,7 +2920,13 @@ Ptr{T}() """ +(x, y...) -Addition operator. `x+y+z+...` calls this function with all arguments, i.e. `+(x, y, z, ...)`. +Addition operator. + +Infix `x+y+z+...` calls this function with all arguments, i.e. `+(x, y, z, ...)`, +which by default then calls `(x+y) + z + ...` starting from the left. + +Note that overflow is possible for most integer types, including the +default `Int`, when adding large numbers. # Examples ```jldoctest @@ -2664,6 +2935,14 @@ julia> 1 + 20 + 4 julia> +(1, 20, 4) 25 + +julia> [1,2] + [3,4] +2-element Vector{Int64}: + 4 + 6 + +julia> typemax(Int) + 1 < 0 +true ``` """ (+)(x, y...) @@ -2687,6 +2966,12 @@ julia> -[1 2; 3 4] 2×2 Matrix{Int64}: -1 -2 -3 -4 + +julia> -(true) # promotes to Int +-1 + +julia> -(0x003) +0xfffd ``` """ -(x) @@ -2710,7 +2995,18 @@ julia> -(2, 4.5) """ *(x, y...) -Multiplication operator. `x*y*z*...` calls this function with all arguments, i.e. `*(x, y, z, ...)`. +Multiplication operator. + +Infix `x*y*z*...` calls this function with all arguments, i.e. `*(x, y, z, ...)`, +which by default then calls `(x*y) * z * ...` starting from the left. + +Juxtaposition such as `2pi` also calls `*(2, pi)`. Note that this operation +has higher precedence than a literal `*`. Note also that juxtaposition "0x..." +(integer zero times a variable whose name starts with `x`) is forbidden as +it clashes with unsigned integer literals: `0x01 isa UInt8`. + +Note that overflow is possible for most integer types, including the default `Int`, +when multiplying large numbers. # Examples ```jldoctest @@ -2719,6 +3015,17 @@ julia> 2 * 7 * 8 julia> *(2, 7, 8) 112 + +julia> [2 0; 0 3] * [1, 10] # matrix * vector +2-element Vector{Int64}: + 2 + 30 + +julia> 1/2pi, 1/2*pi # juxtaposition has higher precedence +(0.15915494309189535, 1.5707963267948966) + +julia> x = [1, 2]; x'x # adjoint vector * vector +5 ``` """ (*)(x, y...) @@ -2726,8 +3033,10 @@ julia> *(2, 7, 8) """ /(x, y) -Right division operator: multiplication of `x` by the inverse of `y` on the right. Gives -floating-point results for integer arguments. +Right division operator: multiplication of `x` by the inverse of `y` on the right. + +Gives floating-point results for integer arguments. +See [`÷`](@ref div) for integer division, or [`//`](@ref) for [`Rational`](@ref) results. # Examples ```jldoctest @@ -2821,23 +3130,33 @@ kw"Union{}", Base.Bottom """ Union{Types...} -A type union is an abstract type which includes all instances of any of its argument types. The empty -union [`Union{}`](@ref) is the bottom type of Julia. +A `Union` type is an abstract type which includes all instances of any of its argument types. +This means that `T <: Union{T,S}` and `S <: Union{T,S}`. + +Like other abstract types, it cannot be instantiated, even if all of its arguments are non +abstract. # Examples ```jldoctest julia> IntOrString = Union{Int,AbstractString} Union{Int64, AbstractString} -julia> 1 isa IntOrString +julia> 1 isa IntOrString # instance of Int is included in the union true -julia> "Hello!" isa IntOrString +julia> "Hello!" isa IntOrString # String is also included true -julia> 1.0 isa IntOrString +julia> 1.0 isa IntOrString # Float64 is not included because it is neither Int nor AbstractString false ``` + +# Extended Help + +Unlike most other parametric types, unions are covariant in their parameters. For example, +`Union{Real, String}` is a subtype of `Union{Number, AbstractString}`. + +The empty union [`Union{}`](@ref) is the bottom type of Julia. """ Union @@ -2846,7 +3165,7 @@ Union UnionAll A union of types over all values of a type parameter. `UnionAll` is used to describe parametric types -where the values of some parameters are not known. +where the values of some parameters are not known. See the manual section on [UnionAll Types](@ref). # Examples ```jldoctest @@ -3114,14 +3433,30 @@ Base.modifyproperty! replaceproperty!(x, f::Symbol, expected, desired, success_order::Symbol=:not_atomic, fail_order::Symbol=success_order) Perform a compare-and-swap operation on `x.f` from `expected` to `desired`, per -egal. The syntax `@atomic_replace! x.f expected => desired` can be used instead +egal. The syntax `@atomicreplace x.f expected => desired` can be used instead of the function call form. See also [`replacefield!`](@ref Core.replacefield!) -and [`setproperty!`](@ref Base.setproperty!). +[`setproperty!`](@ref Base.setproperty!), +[`setpropertyonce!`](@ref Base.setpropertyonce!). """ Base.replaceproperty! +""" + setpropertyonce!(x, f::Symbol, value, success_order::Symbol=:not_atomic, fail_order::Symbol=success_order) + +Perform a compare-and-swap operation on `x.f` to set it to `value` if previously unset. +The syntax `@atomiconce x.f = value` can be used instead of the function call form. + +See also [`setfieldonce!`](@ref Core.replacefield!), +[`setproperty!`](@ref Base.setproperty!), +[`replaceproperty!`](@ref Base.replaceproperty!). + +!!! compat "Julia 1.11" + This function requires Julia 1.11 or later. +""" +Base.setpropertyonce! + """ StridedArray{T, N} @@ -3265,7 +3600,7 @@ kw"atomic" This function prevents dead-code elimination (DCE) of itself and any arguments passed to it, but is otherwise the lightest barrier possible. In particular, -it is not a GC safepoint, does model an observable heap effect, does not expand +it is not a GC safepoint, does not model an observable heap effect, does not expand to any code itself and may be re-ordered with respect to other side effects (though the total number of executions may not change). @@ -3283,6 +3618,14 @@ unused and delete the entire benchmark code). `donotdelete(1+1)`, no add instruction needs to be executed at runtime and the code is semantically equivalent to `donotdelete(2).` +!!! note + This intrinsic does not affect the semantics of code that is dead because it is + *unreachable*. For example, the body of the function `f(x) = false && donotdelete(x)` + may be deleted in its entirety. The semantics of this intrinsic only guarantee that + *if* the intrinsic is semantically executed, then there is some program state at + which the value of the arguments of this intrinsic were available (in a register, + in memory, etc.). + # Examples ```julia @@ -3373,4 +3716,6 @@ The current differences are: """ Core.finalizer +Base.include(BaseDocs, "intrinsicsdocs.jl") + end diff --git a/base/docs/intrinsicsdocs.jl b/base/docs/intrinsicsdocs.jl new file mode 100644 index 0000000000000..ca06ad678bdbf --- /dev/null +++ b/base/docs/intrinsicsdocs.jl @@ -0,0 +1,180 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +""" + Core.IR + +The `Core.IR` module exports the IR object model. +""" +Core.IR + +""" + Core.IntrinsicFunction <: Core.Builtin <: Function + +The `Core.IntrinsicFunction` function define some basic primitives for what defines the +abilities and behaviors of a Julia program +""" +Core.IntrinsicFunction + +""" + Core.Intrinsics + +The `Core.Intrinsics` module holds the `Core.IntrinsicFunction` objects. +""" +Core.Intrinsics + +""" + Core.memoryref(::GenericMemory) + Core.memoryref(::GenericMemoryRef, index::Int, [boundscheck::Bool]) + +Return a `GenericMemoryRef` for a `GenericMemory`. See [`MemoryRef`](@ref). + +!!! compat "Julia 1.11" + This function requires Julia 1.11 or later. +""" +Core.memoryref + +""" + Core..memoryrefoffset(::GenericMemoryRef) + +Return the offset index that was used to construct the `MemoryRef`. See [`Core.memoryref`](@ref). + +!!! compat "Julia 1.11" + This function requires Julia 1.11 or later. +""" +Core.memoryrefoffset + +""" + Core.memoryrefget(::GenericMemoryRef, ordering::Symbol, boundscheck::Bool) + +Return the value stored at the `MemoryRef`, throwing a `BoundsError` if the `Memory` is empty. See `ref[]`. +The memory ordering specified must be compatible with the `isatomic` parameter. + +!!! compat "Julia 1.11" + This function requires Julia 1.11 or later. +""" +Core.memoryrefget + +""" + Core.memoryrefset!(::GenericMemoryRef, value, ordering::Symbol, boundscheck::Bool) + +Store the value to the `MemoryRef`, throwing a `BoundsError` if the `Memory` is empty. See `ref[] = value`. +The memory ordering specified must be compatible with the `isatomic` parameter. + +!!! compat "Julia 1.11" + This function requires Julia 1.11 or later. +""" +Core.memoryrefset! + +""" + Core.memoryref_isassigned(::GenericMemoryRef, ordering::Symbol, boundscheck::Bool) + +Return whether there is a value stored at the `MemoryRef`, returning false if the `Memory` +is empty. See [`isassigned(::Base.RefValue)`](@ref), [`Core.memoryrefget`](@ref). +The memory ordering specified must be compatible with the `isatomic` parameter. + +!!! compat "Julia 1.11" + This function requires Julia 1.11 or later. +""" +Core.memoryref_isassigned + +""" + Core.memoryrefswap!(::GenericMemoryRef, value, ordering::Symbol, boundscheck::Bool) + +Atomically perform the operations to simultaneously get and set a `MemoryRef` value. + +!!! compat "Julia 1.11" + This function requires Julia 1.11 or later. + +See also [`swapproperty!`](@ref Base.swapproperty!) and [`Core.memoryrefset!`](@ref). +""" +Core.memoryrefswap! + +""" + Core.memoryrefmodify!(::GenericMemoryRef, op, value, ordering::Symbol, boundscheck::Bool) -> Pair + +Atomically perform the operations to get and set a `MemoryRef` value after applying +the function `op`. + +!!! compat "Julia 1.11" + This function requires Julia 1.11 or later. + +See also [`modifyproperty!`](@ref Base.modifyproperty!) and [`Core.memoryrefset!`](@ref). +""" +Core.memoryrefmodify! + +""" + Core.memoryrefreplace!(::GenericMemoryRef, expected, desired, + success_order::Symbol, fail_order::Symbol=success_order, boundscheck::Bool) -> (; old, success::Bool) + +Atomically perform the operations to get and conditionally set a `MemoryRef` value. + +!!! compat "Julia 1.11" + This function requires Julia 1.11 or later. + +See also [`replaceproperty!`](@ref Base.replaceproperty!) and [`Core.memoryrefset!`](@ref). +""" +Core.memoryrefreplace! + +""" + Core.memoryrefsetonce!(::GenericMemoryRef, value, + success_order::Symbol, fail_order::Symbol=success_order, boundscheck::Bool) -> success::Bool + +Atomically perform the operations to set a `MemoryRef` to +a given value, only if it was previously not set. + +!!! compat "Julia 1.11" + This function requires Julia 1.11 or later. + +See also [`setpropertyonce!`](@ref Base.replaceproperty!) and [`Core.memoryrefset!`](@ref). +""" +Core.memoryrefsetonce! + +""" + Core.Intrinsics.atomic_pointerref(pointer::Ptr{T}, order::Symbol) --> T + +!!! compat "Julia 1.7" + This function requires Julia 1.7 or later. + +See [`unsafe_load`](@ref Base.unsafe_load). +""" +Core.Intrinsics.atomic_pointerref + +""" + Core.Intrinsics.atomic_pointerset(pointer::Ptr{T}, new::T, order::Symbol) --> pointer + +!!! compat "Julia 1.7" + This function requires Julia 1.7 or later. + +See [`unsafe_store!`](@ref Base.unsafe_store!). +""" +Core.Intrinsics.atomic_pointerset + +""" + Core.Intrinsics.atomic_pointerswap(pointer::Ptr{T}, new::T, order::Symbol) --> old + +!!! compat "Julia 1.7" + This function requires Julia 1.7 or later. + +See [`unsafe_swap!`](@ref Base.unsafe_swap!). +""" +Core.Intrinsics.atomic_pointerswap + +""" + Core.Intrinsics.atomic_pointermodify(pointer::Ptr{T}, function::(old::T,arg::S)->T, arg::S, order::Symbol) --> old + +!!! compat "Julia 1.7" + This function requires Julia 1.7 or later. + +See [`unsafe_modify!`](@ref Base.unsafe_modify!). +""" +Core.Intrinsics.atomic_pointermodify + +""" + Core.Intrinsics.atomic_pointerreplace(pointer::Ptr{T}, expected::Any, new::T, success_order::Symbol, failure_order::Symbol) --> (old, cmp) + +!!! compat "Julia 1.7" + This function requires Julia 1.7 or later. + +See [`unsafe_replace!`](@ref Base.unsafe_replace!). +""" +Core.Intrinsics.atomic_pointerreplace diff --git a/base/docs/temp.cpp b/base/docs/temp.cpp new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/base/docs/utils.jl b/base/docs/utils.jl index 928dfde01ccf0..1ed576c7362ff 100644 --- a/base/docs/utils.jl +++ b/base/docs/utils.jl @@ -23,7 +23,7 @@ You can also use a stream for large amounts of data: `HTML` is currently exported to maintain backwards compatibility, but this export is deprecated. It is recommended to use - this type as `Docs.HTML` or to explicitly + this type as [`Docs.HTML`](@ref) or to explicitly import it from `Docs`. """ mutable struct HTML{T} @@ -81,7 +81,7 @@ You can also use a stream for large amounts of data: `Text` is currently exported to maintain backwards compatibility, but this export is deprecated. It is recommended to use - this type as `Docs.Text` or to explicitly + this type as [`Docs.Text`](@ref) or to explicitly import it from `Docs`. """ mutable struct Text{T} diff --git a/base/env.jl b/base/env.jl index 077b3a4ed28e5..6248f1933d1ec 100644 --- a/base/env.jl +++ b/base/env.jl @@ -3,12 +3,29 @@ if Sys.iswindows() const ERROR_ENVVAR_NOT_FOUND = UInt32(203) + const env_dict = Lockable(Dict{String, Vector{Cwchar_t}}()) + + function memoized_env_lookup(str::AbstractString) + # Windows environment variables have a different format from Linux / MacOS, and previously + # incurred allocations because we had to convert a String to a Vector{Cwchar_t} each time + # an environment variable was looked up. This function memoizes that lookup process, storing + # the String => Vector{Cwchar_t} pairs in env_dict + @lock env_dict begin + var = get(env_dict[], str, nothing) + if isnothing(var) + var = cwstring(str) + env_dict[][str] = var + end + return var + end + end + _getenvlen(var::Vector{UInt16}) = ccall(:GetEnvironmentVariableW,stdcall,UInt32,(Ptr{UInt16},Ptr{UInt16},UInt32),var,C_NULL,0) _hasenv(s::Vector{UInt16}) = _getenvlen(s) != 0 || Libc.GetLastError() != ERROR_ENVVAR_NOT_FOUND - _hasenv(s::AbstractString) = _hasenv(cwstring(s)) + _hasenv(s::AbstractString) = _hasenv(memoized_env_lookup(s)) function access_env(onError::Function, str::AbstractString) - var = cwstring(str) + var = memoized_env_lookup(str) len = _getenvlen(var) if len == 0 return Libc.GetLastError() != ERROR_ENVVAR_NOT_FOUND ? "" : onError(str) @@ -21,7 +38,7 @@ if Sys.iswindows() end function _setenv(svar::AbstractString, sval::AbstractString, overwrite::Bool=true) - var = cwstring(svar) + var = memoized_env_lookup(svar) val = cwstring(sval) if overwrite || !_hasenv(var) ret = ccall(:SetEnvironmentVariableW,stdcall,Int32,(Ptr{UInt16},Ptr{UInt16}),var,val) @@ -30,7 +47,7 @@ if Sys.iswindows() end function _unsetenv(svar::AbstractString) - var = cwstring(svar) + var = memoized_env_lookup(svar) ret = ccall(:SetEnvironmentVariableW,stdcall,Int32,(Ptr{UInt16},Ptr{UInt16}),var,C_NULL) windowserror(:setenv, ret == 0 && Libc.GetLastError() != ERROR_ENVVAR_NOT_FOUND) end @@ -111,25 +128,34 @@ const get_bool_env_falsy = ( "0") """ - Base.get_bool_env(name::String, default::Bool)::Union{Bool,Nothing} + Base.get_bool_env(name::String, default::Bool; throw=false)::Union{Bool,Nothing} + Base.get_bool_env(f_default::Callable, name::String; throw=false)::Union{Bool,Nothing} -Evaluate whether the value of environnment variable `name` is a truthy or falsy string, -and return `nothing` if it is not recognized as either. If the variable is not set, or is set to "", -return `default`. +Evaluate whether the value of environment variable `name` is a truthy or falsy string, +and return `nothing` (or throw if `throw=true`) if it is not recognized as either. If +the variable is not set, or is set to "", return `default` or the result of executing `f_default()`. Recognized values are the following, and their Capitalized and UPPERCASE forms: truthy: "t", "true", "y", "yes", "1" falsy: "f", "false", "n", "no", "0" """ -function get_bool_env(name::String, default::Bool) - haskey(ENV, name) || return default - val = ENV[name] - if isempty(val) - return default - elseif val in get_bool_env_truthy +get_bool_env(name::String, default::Bool; kwargs...) = get_bool_env(Returns(default), name; kwargs...) +function get_bool_env(f_default::Callable, name::String; kwargs...) + if haskey(ENV, name) + val = ENV[name] + if !isempty(val) + return parse_bool_env(name, val; kwargs...) + end + end + return f_default() +end +function parse_bool_env(name::String, val::String = ENV[name]; throw::Bool=false) + if val in get_bool_env_truthy return true elseif val in get_bool_env_falsy return false + elseif throw + Base.throw(ArgumentError("Value for environment variable `$name` could not be parsed as Boolean: $(repr(val))")) else return nothing end diff --git a/base/error.jl b/base/error.jl index 4e9be0e172d61..10cf6baf6350d 100644 --- a/base/error.jl +++ b/base/error.jl @@ -27,6 +27,15 @@ throw ## native julia error handling ## +# This is `Experimental.@max_methods 2 function error end`, which is not available at this point in bootstrap. +# NOTE It is important to always be able to infer the return type of `error` as `Union{}`, +# but there's a hitch when a package globally sets `@max_methods 1` and it causes inference +# for `error(::Any)` to fail (JuliaLang/julia#54029). +# This definition site `@max_methods 2` setting overrides any global `@max_methods 1` settings +# on package side, guaranteeing that return type inference on `error` is successful always. +function error end +typeof(error).name.max_methods = UInt8(2) + """ error(message::AbstractString) @@ -37,7 +46,7 @@ error(s::AbstractString) = throw(ErrorException(s)) """ error(msg...) -Raise an `ErrorException` with the given message. +Raise an `ErrorException` with a message constructed by `string(msg...)`. """ function error(s::Vararg{Any,N}) where {N} @noinline @@ -162,7 +171,7 @@ end ## keyword arg lowering generates calls to this ## function kwerr(kw, args::Vararg{Any,N}) where {N} @noinline - throw(MethodError(Core.kwcall, (kw, args...))) + throw(MethodError(Core.kwcall, (kw, args...), tls_world_age())) end ## system error handling ## @@ -197,15 +206,17 @@ windowserror(p, code::UInt32=Libc.GetLastError(); extrainfo=nothing) = throw(Mai """ @assert cond [text] -Throw an [`AssertionError`](@ref) if `cond` is `false`. Preferred syntax for writing assertions. -Message `text` is optionally displayed upon assertion failure. +Throw an [`AssertionError`](@ref) if `cond` is `false`. This is the preferred syntax for +writing assertions, which are conditions that are assumed to be true, but that the user +might decide to check anyways, as an aid to debugging if they fail. +The optional message `text` is displayed upon assertion failure. !!! warning - An assert might be disabled at various optimization levels. + An assert might be disabled at some optimization levels. Assert should therefore only be used as a debugging tool - and not used for authentication verification (e.g., verifying passwords), - nor should side effects needed for the function to work correctly - be used inside of asserts. + and not used for authentication verification (e.g., verifying passwords or checking array bounds). + The code must not rely on the side effects of running `cond` for the correct behavior + of a function. # Examples ```jldoctest @@ -226,15 +237,15 @@ macro assert(ex, msgs...) msg = Main.Base.string(msg) else # string() might not be defined during bootstrap - msg = quote - msg = $(Expr(:quote,msg)) - isdefined(Main, :Base) ? Main.Base.string(msg) : - (Core.println(msg); "Error during bootstrap. See stdout.") - end + msg = :(_assert_tostring($(Expr(:quote,msg)))) end return :($(esc(ex)) ? $(nothing) : throw(AssertionError($msg))) end +# this may be overridden in contexts where `string(::Expr)` doesn't work +_assert_tostring(msg) = isdefined(Main, :Base) ? Main.Base.string(msg) : + (Core.println(msg); "Error during bootstrap. See stdout.") + struct ExponentialBackOff n::Int first_delay::Float64 diff --git a/base/errorshow.jl b/base/errorshow.jl index 39cdf14d34de8..b06052433ffc4 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -70,6 +70,8 @@ function showerror(io::IO, ex::TypeError) print(io, "TypeError: ") if ex.expected === Bool print(io, "non-boolean (", typeof(ex.got), ") used in boolean context") + elseif ex.func === :var"dict key" + print(io, "$(limitrepr(ex.got)) is not a valid key for type $(ex.expected)") else if isvarargtype(ex.got) targs = (ex.got,) @@ -80,7 +82,7 @@ function showerror(io::IO, ex::TypeError) end if ex.context == "" ctx = "in $(ex.func)" - elseif ex.func === Symbol("keyword argument") + elseif ex.func === :var"keyword argument" ctx = "in keyword argument $(ex.context)" else ctx = "in $(ex.func), in $(ex.context)" @@ -147,13 +149,7 @@ showerror(io::IO, ::DivideError) = print(io, "DivideError: integer division erro showerror(io::IO, ::StackOverflowError) = print(io, "StackOverflowError:") showerror(io::IO, ::UndefRefError) = print(io, "UndefRefError: access to undefined reference") showerror(io::IO, ::EOFError) = print(io, "EOFError: read end of file") -function showerror(io::IO, ex::ErrorException) - print(io, ex.msg) - if ex.msg == "type String has no field data" - println(io) - print(io, "Use `codeunits(str)` instead.") - end -end +showerror(io::IO, ex::ErrorException) = print(io, ex.msg) showerror(io::IO, ex::KeyError) = (print(io, "KeyError: key "); show(io, ex.key); print(io, " not found")) @@ -168,6 +164,16 @@ showerror(io::IO, ex::UndefKeywordError) = function showerror(io::IO, ex::UndefVarError) print(io, "UndefVarError: `$(ex.var)` not defined") + if isdefined(ex, :scope) + scope = ex.scope + if scope isa Module + print(io, " in `$scope`") + elseif scope === :static_parameter + print(io, " in static parameter matching") + else + print(io, " in $scope scope") + end + end Experimental.show_error_hints(io, ex) end @@ -175,7 +181,13 @@ function showerror(io::IO, ex::InexactError) print(io, "InexactError: ", ex.func, '(') T = first(ex.args) nameof(T) === ex.func || print(io, T, ", ") - join(io, ex.args[2:end], ", ") + # `join` calls `string` on its arguments, which shadows the size of e.g. Inf16 + # as `string(Inf16) == "Inf"` instead of "Inf16". Thus we cannot use `join` here. + for arg in ex.args[2:end-1] + show(io, arg) + print(io, ", ") + end + show(io, ex.args[end]) print(io, ")") Experimental.show_error_hints(io, ex) end @@ -232,35 +244,41 @@ function show_convert_error(io::IO, ex::MethodError, arg_types_param) end function showerror(io::IO, ex::MethodError) + @nospecialize io # ex.args is a tuple type if it was thrown from `invoke` and is # a tuple of the arguments otherwise. - is_arg_types = isa(ex.args, DataType) - arg_types = (is_arg_types ? ex.args : typesof(ex.args...))::DataType + is_arg_types = !isa(ex.args, Tuple) + arg_types = is_arg_types ? ex.args : typesof(ex.args...) + arg_types_param::SimpleVector = (unwrap_unionall(arg_types)::DataType).parameters + san_arg_types_param = Any[rewrap_unionall(a, arg_types) for a in arg_types_param] f = ex.f meth = methods_including_ambiguous(f, arg_types) if isa(meth, MethodList) && length(meth) > 1 return showerror_ambiguous(io, meth, f, arg_types) end - arg_types_param::SimpleVector = arg_types.parameters - show_candidates = true print(io, "MethodError: ") ft = typeof(f) f_is_function = false - kwargs = () - if f === Core.kwcall && !is_arg_types - f = (ex.args::Tuple)[2] - ft = typeof(f) + kwargs = [] + if f === Core.kwcall && length(arg_types_param) >= 2 && arg_types_param[1] <: NamedTuple && !is_arg_types + # if this is a kwcall, reformat it as a call with kwargs + # TODO: handle !is_arg_types here (aka invoke with kwargs), which needs a value for `f` + local kwt + let args = ex.args::Tuple + f = args[2] + ft = typeof(f) + kwt = typeof(args[1]) + ex = MethodError(f, args[3:end], ex.world) + end arg_types_param = arg_types_param[3:end] - kwargs = pairs(ex.args[1]) - ex = MethodError(f, ex.args[3:end::Int], ex.world) + san_arg_types_param = san_arg_types_param[3:end] + keys = kwt.parameters[1]::Tuple + kwargs = Any[(keys[i], fieldtype(kwt, i)) for i in 1:length(keys)] + arg_types = rewrap_unionall(Tuple{arg_types_param...}, arg_types) end - name = ft.name.mt.name if f === Base.convert && length(arg_types_param) == 2 && !is_arg_types f_is_function = true show_convert_error(io, ex, arg_types_param) - elseif f === mapreduce_empty || f === reduce_empty - print(io, "reducing over an empty collection is not allowed; consider supplying `init` to the reducer") - show_candidates = false elseif isempty(methods(f)) && isa(f, DataType) && isabstracttype(f) print(io, "no constructors have been defined for ", f) elseif isempty(methods(f)) && !isa(f, Function) && !isa(f, Type) @@ -269,35 +287,28 @@ function showerror(io::IO, ex::MethodError) if ft <: Function && isempty(ft.parameters) && _isself(ft) f_is_function = true end - print(io, "no method matching ") - iob = IOContext(IOBuffer(), io) # for type abbreviation as in #49795; some, like `convert(T, x)`, should not abbreviate - show_signature_function(iob, isa(f, Type) ? Type{f} : typeof(f)) - print(iob, "(") - for (i, typ) in enumerate(arg_types_param) - print(iob, "::", typ) - i == length(arg_types_param) || print(iob, ", ") - end - if !isempty(kwargs) - print(iob, "; ") - for (i, (k, v)) in enumerate(kwargs) - print(iob, k, "::", typeof(v)) - i == length(kwargs)::Int || print(iob, ", ") - end + if is_arg_types + print(io, "no method matching invoke ") + else + print(io, "no method matching ") end - print(iob, ")") - str = String(take!(unwrapcontext(iob)[1])) + buf = IOBuffer() + iob = IOContext(buf, io) # for type abbreviation as in #49795; some, like `convert(T, x)`, should not abbreviate + show_signature_function(iob, Core.Typeof(f)) + show_tuple_as_call(iob, :function, arg_types; hasfirst=false, kwargs = isempty(kwargs) ? nothing : kwargs) + str = String(take!(buf)) str = type_limited_string_from_context(io, str) print(io, str) end # catch the two common cases of element-wise addition and subtraction - if (f === Base.:+ || f === Base.:-) && length(arg_types_param) == 2 + if (f === Base.:+ || f === Base.:-) && length(san_arg_types_param) == 2 # we need one array of numbers and one number, in any order - if any(x -> x <: AbstractArray{<:Number}, arg_types_param) && - any(x -> x <: Number, arg_types_param) + if any(x -> x <: AbstractArray{<:Number}, san_arg_types_param) && + any(x -> x <: Number, san_arg_types_param) nounf = f === Base.:+ ? "addition" : "subtraction" varnames = ("scalar", "array") - first, second = arg_types_param[1] <: Number ? varnames : reverse(varnames) + first, second = san_arg_types_param[1] <: Number ? varnames : reverse(varnames) fstring = f === Base.:+ ? "+" : "-" # avoid depending on show_default for functions (invalidation) print(io, "\nFor element-wise $nounf, use broadcasting with dot syntax: $first .$fstring $second") end @@ -306,17 +317,31 @@ function showerror(io::IO, ex::MethodError) print(io, "\nUse square brackets [] for indexing an Array.") end # Check for local functions that shadow methods in Base - if f_is_function && isdefined(Base, name) - basef = getfield(Base, name) - if basef !== ex.f && hasmethod(basef, arg_types) - print(io, "\nYou may have intended to import ") - show_unquoted(io, Expr(:., :Base, QuoteNode(name))) + let name = ft.name.mt.name + if f_is_function && isdefined(Base, name) + basef = getfield(Base, name) + if basef !== f && hasmethod(basef, arg_types) + print(io, "\nYou may have intended to import ") + show_unquoted(io, Expr(:., :Base, QuoteNode(name))) + end end end - if (ex.world != typemax(UInt) && hasmethod(ex.f, arg_types) && - !hasmethod(ex.f, arg_types, world = ex.world)) + if ex.world == typemax(UInt) || hasmethod(f, arg_types, world=ex.world) + if !isempty(kwargs) + print(io, "\nThis method does not support all of the given keyword arguments (and may not support any).") + end + if ex.world == typemax(UInt) || isempty(kwargs) + print(io, "\nThis error has been manually thrown, explicitly, so the method may exist but be intentionally marked as unimplemented.") + end + elseif hasmethod(f, arg_types) && !hasmethod(f, arg_types, world=ex.world) curworld = get_world_counter() print(io, "\nThe applicable method may be too new: running in world age $(ex.world), while current world is $(curworld).") + elseif f isa Function + print(io, "\nThe function `$f` exists, but no method is defined for this combination of argument types.") + elseif f isa Type + print(io, "\nThe type `$f` exists, but no method is defined for this combination of argument types when trying to construct it.") + else + print(io, "\nThe object of type `$(typeof(f))` exists, but no method is defined for this combination of argument types when trying to treat it as a callable object.") end if !is_arg_types # Check for row vectors used where a column vector is intended. @@ -333,27 +358,24 @@ function showerror(io::IO, ex::MethodError) "\nYou can convert to a column vector with the vec() function.") end end - Experimental.show_error_hints(io, ex, arg_types_param, kwargs) - show_candidates && try + Experimental.show_error_hints(io, ex, san_arg_types_param, kwargs) + try show_method_candidates(io, ex, kwargs) catch ex @error "Error showing method candidates, aborted" exception=ex,catch_backtrace() end + nothing end striptype(::Type{T}) where {T} = T striptype(::Any) = nothing -function showerror_ambiguous(io::IO, meths, f, args) +function showerror_ambiguous(io::IO, meths, f, args::Type) + @nospecialize f args print(io, "MethodError: ") show_signature_function(io, isa(f, Type) ? Type{f} : typeof(f)) - print(io, "(") - p = args.parameters - for (i,a) in enumerate(p) - print(io, "::", a) - i < length(p) && print(io, ", ") - end - println(io, ") is ambiguous.\n\nCandidates:") + show_tuple_as_call(io, :var"", args, hasfirst=false) + println(io, " is ambiguous.\n\nCandidates:") sigfix = Any for m in meths print(io, " ") @@ -365,7 +387,7 @@ function showerror_ambiguous(io::IO, meths, f, args) let sigfix=sigfix if all(m->morespecific(sigfix, m.sig), meths) print(io, "\nPossible fix, define\n ") - Base.show_tuple_as_call(io, :function, sigfix) + show_tuple_as_call(io, :function, sigfix) else print(io, "To resolve the ambiguity, try making one of the methods more specific, or ") print(io, "adding a new method more specific than any of the existing applicable methods.") @@ -378,7 +400,7 @@ end #Show an error by directly calling jl_printf. #Useful in Base submodule __init__ functions where stderr isn't defined yet. -function showerror_nostdio(err, msg::AbstractString) +function showerror_nostdio(@nospecialize(err), msg::AbstractString) stderr_stream = ccall(:jl_stderr_stream, Ptr{Cvoid}, ()) ccall(:jl_printf, Cint, (Ptr{Cvoid},Cstring), stderr_stream, msg) ccall(:jl_printf, Cint, (Ptr{Cvoid},Cstring), stderr_stream, ":\n") @@ -390,15 +412,18 @@ stacktrace_expand_basepaths()::Bool = Base.get_bool_env("JULIA_STACKTRACE_EXPAND stacktrace_contract_userdir()::Bool = Base.get_bool_env("JULIA_STACKTRACE_CONTRACT_HOMEDIR", true) === true stacktrace_linebreaks()::Bool = Base.get_bool_env("JULIA_STACKTRACE_LINEBREAKS", false) === true -function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=()) - is_arg_types = isa(ex.args, DataType) +function show_method_candidates(io::IO, ex::MethodError, kwargs=[]) + @nospecialize io + is_arg_types = !isa(ex.args, Tuple) arg_types = is_arg_types ? ex.args : typesof(ex.args...) - arg_types_param = Any[arg_types.parameters...] + arg_types_param = Any[(unwrap_unionall(arg_types)::DataType).parameters...] + arg_types_param = Any[rewrap_unionall(a, arg_types) for a in arg_types_param] # Displays the closest candidates of the given function by looping over the # functions methods and counting the number of matching arguments. f = ex.f ft = typeof(f) - lines = [] + lines = String[] + line_score = Int[] # These functions are special cased to only show if first argument is matched. special = f === convert || f === getindex || f === setindex! funcs = Tuple{Any,Vector{Any}}[(f, arg_types_param)] @@ -489,85 +514,82 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=() end end - if right_matches > 0 || length(arg_types_param) < 2 - if length(t_i) < length(sig) - # If the methods args is longer than input then the method - # arguments is printed as not a match - for (k, sigtype) in enumerate(sig[length(t_i)+1:end]) - sigtype = isvarargtype(sigtype) ? unwrap_unionall(sigtype) : sigtype - if Base.isvarargtype(sigtype) - sigstr = (unwrapva(sigtype::Core.TypeofVararg), "...") - else - sigstr = (sigtype,) - end - if !((min(length(t_i), length(sig)) == 0) && k==1) - print(iob, ", ") - end - if k == 1 && Base.isvarargtype(sigtype) - # There wasn't actually a mismatch - the method match failed for - # some other reason, e.g. world age. Just print the sigstr. - print(iob, sigstr...) - elseif get(io, :color, false)::Bool - let sigstr=sigstr - Base.with_output_color(Base.error_color(), iob) do iob - print(iob, "::", sigstr...) - end - end - else - print(iob, "!Matched::", sigstr...) - end + if length(t_i) < length(sig) + # If the methods args is longer than input then the method + # arguments is printed as not a match + for (k, sigtype) in enumerate(sig[length(t_i)+1:end]) + sigtype = isvarargtype(sigtype) ? unwrap_unionall(sigtype) : sigtype + if Base.isvarargtype(sigtype) + sigstr = (unwrapva(sigtype::Core.TypeofVararg), "...") + else + sigstr = (sigtype,) end - end - kwords = kwarg_decl(method) - if !isempty(kwords) - print(iob, "; ") - join(iob, kwords, ", ") - end - print(iob, ")") - show_method_params(iob0, tv) - file, line = updated_methodloc(method) - if file === nothing - file = string(method.file) - end - stacktrace_contract_userdir() && (file = contractuser(file)) - - if !isempty(kwargs)::Bool - unexpected = Symbol[] - if isempty(kwords) || !(any(endswith(string(kword), "...") for kword in kwords)) - for (k, v) in kwargs - if !(k::Symbol in kwords) - push!(unexpected, k::Symbol) + if !((min(length(t_i), length(sig)) == 0) && k==1) + print(iob, ", ") + end + if k == 1 && Base.isvarargtype(sigtype) + # There wasn't actually a mismatch - the method match failed for + # some other reason, e.g. world age. Just print the sigstr. + print(iob, sigstr...) + elseif get(io, :color, false)::Bool + let sigstr=sigstr + Base.with_output_color(Base.error_color(), iob) do iob + print(iob, "::", sigstr...) end end + else + print(iob, "!Matched::", sigstr...) end - if !isempty(unexpected) - Base.with_output_color(Base.error_color(), iob) do iob - plur = length(unexpected) > 1 ? "s" : "" - print(iob, " got unsupported keyword argument$plur \"", join(unexpected, "\", \""), "\"") + end + end + kwords = kwarg_decl(method) + if !isempty(kwords) + print(iob, "; ") + join(iob, kwords, ", ") + end + print(iob, ")") + show_method_params(iob0, tv) + file, line = updated_methodloc(method) + if file === nothing + file = string(method.file) + end + stacktrace_contract_userdir() && (file = contractuser(file)) + + if !isempty(kwargs)::Bool + unexpected = Symbol[] + if isempty(kwords) || !(any(endswith(string(kword), "...") for kword in kwords)) + for (k, v) in kwargs + if !(k::Symbol in kwords) + push!(unexpected, k::Symbol) end end end - if ex.world < reinterpret(UInt, method.primary_world) - print(iob, " (method too new to be called from this world context.)") - elseif ex.world > reinterpret(UInt, method.deleted_world) - print(iob, " (method deleted before this world age.)") + if !isempty(unexpected) + Base.with_output_color(Base.error_color(), iob) do iob + plur = length(unexpected) > 1 ? "s" : "" + print(iob, " got unsupported keyword argument$plur \"", join(unexpected, "\", \""), "\"") + end end - println(iob) - - m = parentmodule_before_main(method) - modulecolor = get!(() -> popfirst!(STACKTRACE_MODULECOLORS), STACKTRACE_FIXEDCOLORS, m) - print_module_path_file(iob, m, string(file), line; modulecolor, digit_align_width = 3) - - # TODO: indicate if it's in the wrong world - push!(lines, (buf, right_matches)) end + if ex.world < reinterpret(UInt, method.primary_world) + print(iob, " (method too new to be called from this world context.)") + elseif ex.world > reinterpret(UInt, method.deleted_world) + print(iob, " (method deleted before this world age.)") + end + println(iob) + + m = parentmodule_before_main(method) + modulecolor = get!(() -> popfirst!(STACKTRACE_MODULECOLORS), STACKTRACE_FIXEDCOLORS, m) + print_module_path_file(iob, m, string(file), line; modulecolor, digit_align_width = 3) + push!(lines, String(take!(buf))) + push!(line_score, -(right_matches * 2 + (length(arg_types_param) < 2 ? 1 : 0))) end end if !isempty(lines) # Display up to three closest candidates Base.with_output_color(:normal, io) do io print(io, "\n\nClosest candidates are:") - sort!(lines, by = x -> -x[2]) + permute!(lines, sortperm(line_score)) i = 0 for line in lines println(io) @@ -576,7 +598,7 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=() break end i += 1 - print(io, String(take!(line[1]))) + print(io, line) end println(io) # extra newline for spacing to stacktrace end @@ -787,7 +809,7 @@ function show_backtrace(io::IO, t::Vector) if length(filtered) == 1 && StackTraces.is_top_level_frame(filtered[1][1]) f = filtered[1][1]::StackFrame - if f.line == 0 && f.file === Symbol("") + if f.line == 0 && f.file === :var"" # don't show a single top-level frame with no location info return end @@ -1001,6 +1023,31 @@ end Experimental.register_error_hint(noncallable_number_hint_handler, MethodError) +# handler for displaying a hint in case the user tries to call setindex! on +# something that doesn't support it: +# - a number (probably attempting to use wrong indexing) +# eg: a = [1 2; 3 4]; a[1][2] = 5 +# - a type (probably tried to initialize without parentheses) +# eg: d = Dict; d["key"] = 2 +function nonsetable_type_hint_handler(io, ex, arg_types, kwargs) + @nospecialize + if ex.f == setindex! + T = arg_types[1] + if T <: Number + print(io, "\nAre you trying to index into an array? For multi-dimensional arrays, separate the indices with commas: ") + printstyled(io, "a[1, 2]", color=:cyan) + print(io, " rather than a[1][2]") + else isType(T) + Tx = T.parameters[1] + print(io, "\nYou attempted to index the type $Tx, rather than an instance of the type. Make sure you create the type using its constructor: ") + printstyled(io, "d = $Tx([...])", color=:cyan) + print(io, " rather than d = $Tx") + end + end +end + +Experimental.register_error_hint(nonsetable_type_hint_handler, MethodError) + # Display a hint in case the user tries to use the + operator on strings # (probably attempting concatenation) function string_concatenation_hint_handler(io, ex, arg_types, kwargs) @@ -1014,6 +1061,31 @@ end Experimental.register_error_hint(string_concatenation_hint_handler, MethodError) +# Display a hint in case the user tries to use the min or max function on an iterable +# or tries to use something like `collect` on an iterator without defining either IteratorSize or length +function methods_on_iterable(io, ex, arg_types, kwargs) + @nospecialize + f = ex.f + if (f === max || f === min) && length(arg_types) == 1 && Base.isiterable(only(arg_types)) + f_correct = f === max ? "maximum" : "minimum" + print(io, "\nFinding the $f_correct of an iterable is performed with `$f_correct`.") + end + if (f === Base.length || f === Base.size) && length(arg_types) >= 1 + arg_type_tuple = Tuple{arg_types...} + if hasmethod(iterate, arg_type_tuple) + iterkind = IteratorSize(arg_types[1]) + if iterkind isa HasLength + print(io, "\nYou may need to implement the `length` method or define `IteratorSize` for this type to be `SizeUnknown`.") + elseif iterkind isa HasShape + print(io, "\nYou may need to implement the `length` and `size` methods for `IteratorSize` `HasShape`.") + end + end + end + nothing +end + +Experimental.register_error_hint(methods_on_iterable, MethodError) + # ExceptionStack implementation size(s::ExceptionStack) = size(s.stack) getindex(s::ExceptionStack, i::Int) = s.stack[i] diff --git a/base/essentials.jl b/base/essentials.jl index 4323052d540da..fdc66e94d1a00 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -1,17 +1,19 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -import Core: CodeInfo, SimpleVector, donotdelete, compilerbarrier, arrayref +using Core: CodeInfo, SimpleVector, donotdelete, compilerbarrier, memoryref, memoryrefget, memoryrefset! const Callable = Union{Function,Type} const Bottom = Union{} # Define minimal array interface here to help code used in macros: -length(a::Array) = arraylen(a) +length(a::Array{T, 0}) where {T} = 1 +length(a::Array{T, 1}) where {T} = getfield(a, :size)[1] +length(a::Array) = getfield(getfield(getfield(a, :ref), :mem), :length) +length(a::GenericMemory) = getfield(a, :length) +throw_boundserror(A, I) = (@noinline; throw(BoundsError(A, I))) -# This is more complicated than it needs to be in order to get Win64 through bootstrap -eval(:(getindex(A::Array, i1::Int) = arrayref($(Expr(:boundscheck)), A, i1))) -eval(:(getindex(A::Array, i1::Int, i2::Int, I::Int...) = (@inline; arrayref($(Expr(:boundscheck)), A, i1, i2, I...)))) +# multidimensional getindex will be defined later on ==(a::GlobalRef, b::GlobalRef) = a.mod === b.mod && a.name === b.name @@ -88,7 +90,7 @@ f(y) = [x for x in y] standard ones) on type-inference. Use [`Base.@nospecializeinfer`](@ref) together with `@nospecialize` to additionally suppress inference. -# Example +# Examples ```julia julia> f(A::AbstractArray) = g(A) @@ -209,7 +211,8 @@ macro _total_meta() #=:terminates_locally=#false, #=:notaskstate=#true, #=:inaccessiblememonly=#true, - #=:noub=#true)) + #=:noub=#true, + #=:noub_if_noinbounds=#false)) end # can be used in place of `@assume_effects :foldable` (supposed to be used for bootstrapping) macro _foldable_meta() @@ -221,31 +224,60 @@ macro _foldable_meta() #=:terminates_locally=#false, #=:notaskstate=#true, #=:inaccessiblememonly=#true, - #=:noub=#true)) + #=:noub=#true, + #=:noub_if_noinbounds=#false)) end -# can be used in place of `@assume_effects :nothrow` (supposed to be used for bootstrapping) -macro _nothrow_meta() +# can be used in place of `@assume_effects :terminates_locally` (supposed to be used for bootstrapping) +macro _terminates_locally_meta() return _is_internal(__module__) && Expr(:meta, Expr(:purity, #=:consistent=#false, #=:effect_free=#false, - #=:nothrow=#true, + #=:nothrow=#false, #=:terminates_globally=#false, - #=:terminates_locally=#false, + #=:terminates_locally=#true, #=:notaskstate=#false, #=:inaccessiblememonly=#false, - #=:noub=#false)) + #=:noub=#false, + #=:noub_if_noinbounds=#false)) end -# can be used in place of `@assume_effects :terminates_locally` (supposed to be used for bootstrapping) -macro _terminates_locally_meta() +# can be used in place of `@assume_effects :terminates_globally` (supposed to be used for bootstrapping) +macro _terminates_globally_meta() return _is_internal(__module__) && Expr(:meta, Expr(:purity, #=:consistent=#false, #=:effect_free=#false, #=:nothrow=#false, - #=:terminates_globally=#false, + #=:terminates_globally=#true, #=:terminates_locally=#true, #=:notaskstate=#false, #=:inaccessiblememonly=#false, - #=:noub=#false)) + #=:noub=#false, + #=:noub_if_noinbounds=#false)) +end +# can be used in place of `@assume_effects :terminates_globally :notaskstate` (supposed to be used for bootstrapping) +macro _terminates_globally_notaskstate_meta() + return _is_internal(__module__) && Expr(:meta, Expr(:purity, + #=:consistent=#false, + #=:effect_free=#false, + #=:nothrow=#false, + #=:terminates_globally=#true, + #=:terminates_locally=#true, + #=:notaskstate=#true, + #=:inaccessiblememonly=#false, + #=:noub=#false, + #=:noub_if_noinbounds=#false)) +end +# can be used in place of `@assume_effects :terminates_globally :noub` (supposed to be used for bootstrapping) +macro _terminates_globally_noub_meta() + return _is_internal(__module__) && Expr(:meta, Expr(:purity, + #=:consistent=#false, + #=:effect_free=#false, + #=:nothrow=#false, + #=:terminates_globally=#true, + #=:terminates_locally=#true, + #=:notaskstate=#false, + #=:inaccessiblememonly=#false, + #=:noub=#true, + #=:noub_if_noinbounds=#false)) end # can be used in place of `@assume_effects :effect_free :terminates_locally` (supposed to be used for bootstrapping) macro _effect_free_terminates_locally_meta() @@ -257,7 +289,73 @@ macro _effect_free_terminates_locally_meta() #=:terminates_locally=#true, #=:notaskstate=#false, #=:inaccessiblememonly=#false, - #=:noub=#false)) + #=:noub=#false, + #=:noub_if_noinbounds=#false)) +end +# can be used in place of `@assume_effects :nothrow :noub` (supposed to be used for bootstrapping) +macro _nothrow_noub_meta() + return _is_internal(__module__) && Expr(:meta, Expr(:purity, + #=:consistent=#false, + #=:effect_free=#false, + #=:nothrow=#true, + #=:terminates_globally=#false, + #=:terminates_locally=#false, + #=:notaskstate=#false, + #=:inaccessiblememonly=#false, + #=:noub=#true, + #=:noub_if_noinbounds=#false)) +end +# can be used in place of `@assume_effects :nothrow` (supposed to be used for bootstrapping) +macro _nothrow_meta() + return _is_internal(__module__) && Expr(:meta, Expr(:purity, + #=:consistent=#false, + #=:effect_free=#false, + #=:nothrow=#true, + #=:terminates_globally=#false, + #=:terminates_locally=#false, + #=:notaskstate=#false, + #=:inaccessiblememonly=#false, + #=:noub=#false, + #=:noub_if_noinbounds=#false)) +end +# can be used in place of `@assume_effects :nothrow` (supposed to be used for bootstrapping) +macro _noub_meta() + return _is_internal(__module__) && Expr(:meta, Expr(:purity, + #=:consistent=#false, + #=:effect_free=#false, + #=:nothrow=#false, + #=:terminates_globally=#false, + #=:terminates_locally=#false, + #=:notaskstate=#false, + #=:inaccessiblememonly=#false, + #=:noub=#true, + #=:noub_if_noinbounds=#false)) +end +# can be used in place of `@assume_effects :notaskstate` (supposed to be used for bootstrapping) +macro _notaskstate_meta() + return _is_internal(__module__) && Expr(:meta, Expr(:purity, + #=:consistent=#false, + #=:effect_free=#false, + #=:nothrow=#false, + #=:terminates_globally=#false, + #=:terminates_locally=#false, + #=:notaskstate=#true, + #=:inaccessiblememonly=#false, + #=:noub=#false, + #=:noub_if_noinbounds=#false)) +end +# can be used in place of `@assume_effects :noub_if_noinbounds` (supposed to be used for bootstrapping) +macro _noub_if_noinbounds_meta() + return _is_internal(__module__) && Expr(:meta, Expr(:purity, + #=:consistent=#false, + #=:effect_free=#false, + #=:nothrow=#false, + #=:terminates_globally=#false, + #=:terminates_locally=#false, + #=:notaskstate=#false, + #=:inaccessiblememonly=#false, + #=:noub=#false, + #=:noub_if_noinbounds=#true)) end # another version of inlining that propagates an inbounds context @@ -268,6 +366,15 @@ macro _nospecializeinfer_meta() return Expr(:meta, :nospecializeinfer) end +default_access_order(a::GenericMemory{:not_atomic}) = :not_atomic +default_access_order(a::GenericMemory{:atomic}) = :monotonic +default_access_order(a::GenericMemoryRef{:not_atomic}) = :not_atomic +default_access_order(a::GenericMemoryRef{:atomic}) = :monotonic + +getindex(A::GenericMemory, i::Int) = (@_noub_if_noinbounds_meta; + memoryrefget(memoryref(memoryref(A), i, @_boundscheck), default_access_order(A), false)) +getindex(A::GenericMemoryRef) = memoryrefget(A, default_access_order(A), @_boundscheck) + function iterate end """ @@ -422,6 +529,11 @@ function rename_unionall(@nospecialize(u)) return UnionAll(nv, body{nv}) end +# remove concrete constraint on diagonal TypeVar if it comes from troot +function widen_diagonal(@nospecialize(t), troot::UnionAll) + body = ccall(:jl_widen_diagonal, Any, (Any, Any), t, troot) +end + function isvarargtype(@nospecialize(t)) return isa(t, Core.TypeofVararg) end @@ -561,7 +673,7 @@ Change the type-interpretation of the binary data in the isbits value `x` to that of the isbits type `Out`. The size (ignoring padding) of `Out` has to be the same as that of the type of `x`. For example, `reinterpret(Float32, UInt32(7))` interprets the 4 bytes corresponding to `UInt32(7)` as a -[`Float32`](@ref). +[`Float32`](@ref). Note that `reinterpret(In, reinterpret(Out, x)) === x` ```jldoctest julia> reinterpret(Float32, UInt32(7)) @@ -577,11 +689,16 @@ julia> reinterpret(Tuple{UInt16, UInt8}, (0x01, 0x0203)) (0x0301, 0x02) ``` +!!! note + + The treatment of padding differs from reinterpret(::DataType, ::AbstractArray). + !!! warning Use caution if some combinations of bits in `Out` are not considered valid and would otherwise be prevented by the type's constructors and methods. Unexpected behavior may result without additional validation. + """ function reinterpret(::Type{Out}, x) where {Out} if isprimitivetype(Out) && isprimitivetype(typeof(x)) @@ -653,9 +770,6 @@ julia> ifelse(1 > 2, 1, 2) """ ifelse(condition::Bool, x, y) = Core.ifelse(condition, x, y) -# simple Array{Any} operations needed for bootstrap -@eval setindex!(A::Array{Any}, @nospecialize(x), i::Int) = arrayset($(Expr(:boundscheck)), A, x, i) - """ esc(e) @@ -771,6 +885,23 @@ macro goto(name::Symbol) return esc(Expr(:symbolicgoto, name)) end +# linear indexing +function getindex(A::Array, i::Int) + @_noub_if_noinbounds_meta + @boundscheck ult_int(bitcast(UInt, sub_int(i, 1)), bitcast(UInt, length(A))) || throw_boundserror(A, (i,)) + memoryrefget(memoryref(getfield(A, :ref), i, false), :not_atomic, false) +end +# simple Array{Any} operations needed for bootstrap +function setindex!(A::Array{Any}, @nospecialize(x), i::Int) + @_noub_if_noinbounds_meta + @boundscheck ult_int(bitcast(UInt, sub_int(i, 1)), bitcast(UInt, length(A))) || throw_boundserror(A, (i,)) + memoryrefset!(memoryref(getfield(A, :ref), i, false), x, :not_atomic, false) + return A +end +setindex!(A::Memory{Any}, @nospecialize(x), i::Int) = (memoryrefset!(memoryref(memoryref(A), i, @_boundscheck), x, :not_atomic, @_boundscheck); A) +setindex!(A::MemoryRef{T}, x) where {T} = (memoryrefset!(A, convert(T, x), :not_atomic, @_boundscheck); A) +setindex!(A::MemoryRef{Any}, @nospecialize(x)) = (memoryrefset!(A, x, :not_atomic, @_boundscheck); A) + # SimpleVector getindex(v::SimpleVector, i::Int) = (@_foldable_meta; Core._svec_ref(v, i)) diff --git a/base/experimental.jl b/base/experimental.jl index 133171b78271d..8dbdcacd65376 100644 --- a/base/experimental.jl +++ b/base/experimental.jl @@ -9,7 +9,7 @@ """ module Experimental -using Base: Threads, sync_varname, is_function_def +using Base: Threads, sync_varname, is_function_def, @propagate_inbounds using Base.Meta """ @@ -28,10 +28,7 @@ end Base.IndexStyle(::Type{<:Const}) = IndexLinear() Base.size(C::Const) = size(C.a) Base.axes(C::Const) = axes(C.a) -@eval Base.getindex(A::Const, i1::Int) = - (Base.@inline; Core.const_arrayref($(Expr(:boundscheck)), A.a, i1)) -@eval Base.getindex(A::Const, i1::Int, i2::Int, I::Int...) = - (Base.@inline; Core.const_arrayref($(Expr(:boundscheck)), A.a, i1, i2, I...)) +@propagate_inbounds Base.getindex(A::Const, i1::Int, I::Int...) = A.a[i1, I...] """ @aliasscope expr @@ -146,7 +143,7 @@ code to resort to runtime dispatch instead. Supported values are `1`, `2`, `3`, `4`, and `default` (currently equivalent to `3`). """ macro max_methods(n::Int) - 0 < n < 5 || error("We must have that `1 <= max_methods <= 4`, but `max_methods = $n`.") + 1 <= n <= 4 || error("We must have that `1 <= max_methods <= 4`, but `max_methods = $n`.") return Expr(:meta, :max_methods, n) end @@ -159,13 +156,13 @@ for max_methods. This setting is global for the entire generic function (or more the MethodTable). """ macro max_methods(n::Int, fdef::Expr) - 0 < n <= 255 || error("We must have that `1 <= max_methods <= 255`, but `max_methods = $n`.") + 1 <= n <= 255 || error("We must have that `1 <= max_methods <= 255`, but `max_methods = $n`.") (fdef.head === :function && length(fdef.args) == 1) || error("Second argument must be a function forward declaration") return :(typeof($(esc(fdef))).name.max_methods = $(UInt8(n))) end """ - Experimental.@compiler_options optimize={0,1,2,3} compile={yes,no,all,min} infer={yes,no} max_methods={default,1,2,3,...} + Experimental.@compiler_options optimize={0,1,2,3} compile={yes,no,all,min} infer={yes,no} max_methods={default,1,2,3,4} Set compiler options for code in the enclosing module. Options correspond directly to command-line options with the same name, where applicable. The following options @@ -198,7 +195,7 @@ macro compiler_options(args...) elseif ex.args[1] === :max_methods a = ex.args[2] a = a === :default ? 3 : - a isa Int ? ((0 < a < 5) ? a : error("We must have that `1 <= max_methods <= 4`, but `max_methods = $a`.")) : + a isa Int ? ((1 <= a <= 4) ? a : error("We must have that `1 <= max_methods <= 4`, but `max_methods = $a`.")) : error("invalid argument to \"max_methods\" option") push!(opts.args, Expr(:meta, :max_methods, a)) else @@ -257,7 +254,7 @@ When issuing a hint, the output should typically start with `\\n`. If you define custom exception types, your `showerror` method can support hints by calling [`Experimental.show_error_hints`](@ref). -# Example +# Examples ``` julia> module Hinter @@ -283,6 +280,7 @@ Then if you call `Hinter.only_int` on something that isn't an `Int` (thereby tri ``` julia> Hinter.only_int(1.0) ERROR: MethodError: no method matching only_int(::Float64) +The function `only_int` exists, but no method is defined for this combination of argument types. Did you mean to call `any_number`? Closest candidates are: ... diff --git a/base/exports.jl b/base/exports.jl index de9e2b8cf1b93..fc2ee86a8d0d4 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -65,6 +65,7 @@ export Missing, NTuple, IdDict, + IdSet, OrdinalRange, Pair, PartialQuickSort, @@ -411,6 +412,7 @@ export isperm, issorted, last, + logrange, mapslices, max, maximum!, @@ -532,6 +534,7 @@ export getkey, haskey, in, + in!, intersect!, intersect, isdisjoint, @@ -649,11 +652,6 @@ export sprint, summary, -# ScopedValue - with, - @with, - ScopedValue, - # logging @debug, @info, @@ -707,6 +705,8 @@ export yield, yieldto, wait, + waitany, + waitall, timedwait, asyncmap, asyncmap!, @@ -715,6 +715,7 @@ export # channels take!, put!, + isfull, isready, fetch, bind, @@ -754,6 +755,7 @@ export swapproperty!, modifyproperty!, replaceproperty!, + setpropertyonce!, fieldoffset, fieldname, fieldnames, @@ -930,6 +932,7 @@ export isblockdev, ischardev, isdir, + isexecutable, isfifo, isfile, islink, @@ -1034,6 +1037,7 @@ export @elapsed, @allocated, @allocations, + @lock_conflicts, # tasks @sync, @@ -1062,6 +1066,7 @@ export @atomic, @atomicswap, @atomicreplace, + @atomiconce, @__dot__, @enum, @label, @@ -1071,84 +1076,3 @@ export @static, @main - -# TODO: use normal syntax once JuliaSyntax.jl becomes available at this point in bootstrapping -eval(Expr(:public, -# Modules - :Checked, - :Filesystem, - :Order, - :Sort, - -# Types - :AbstractLock, - :AsyncCondition, - :CodeUnits, - :Event, - :Fix1, - :Fix2, - :Generator, - :ImmutableDict, - :OneTo, - :UUID, - -# Semaphores - :Semaphore, - :acquire, - :release, - -# collections - :IteratorEltype, - :IteratorSize, - :to_index, - :vect, - :isdone, - :front, - :rest, - :split_rest, - :tail, - :checked_length, - -# Loading - :DL_LOAD_PATH, - :load_path, - :active_project, - -# Reflection and introspection - :isambiguous, - :isexpr, - :isidentifier, - :issingletontype, - :identify_package, - :locate_package, - :moduleroot, - :jit_total_bytes, - :summarysize, - :isexported, - :ispublic, - -# Opperators - :operator_associativity, - :operator_precedence, - :isbinaryoperator, - :isoperator, - :isunaryoperator, - -# C interface - :cconvert, - :unsafe_convert, - -# Error handling - :exit_on_sigint, - :windowserror, - -# Macros - Symbol("@assume_effects"), - Symbol("@constprop"), - Symbol("@locals"), - Symbol("@propagate_inbounds"), - -# misc - :notnothing, - :runtests, - :text_colors)) diff --git a/base/expr.jl b/base/expr.jl index 94dd28fe2ebb5..cbeb16dd8d79b 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -33,9 +33,6 @@ macro gensym(names...) return blk end -## line numbers ## -convert(::Type{LineNumberNode}, lin::Core.LineInfoNode) = LineNumberNode(Int(lin.line), lin.file) - ## expressions ## isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head @@ -79,10 +76,8 @@ function copy(c::CodeInfo) cnew.slotnames = copy(cnew.slotnames) cnew.slotflags = copy(cnew.slotflags) if cnew.slottypes !== nothing - cnew.slottypes = copy(cnew.slottypes) + cnew.slottypes = copy(cnew.slottypes::Vector{Any}) end - cnew.codelocs = copy(cnew.codelocs) - cnew.linetable = copy(cnew.linetable::Union{Vector{Any},Vector{Core.LineInfoNode}}) cnew.ssaflags = copy(cnew.ssaflags) cnew.edges = cnew.edges === nothing ? nothing : copy(cnew.edges::Vector) ssavaluetypes = cnew.ssavaluetypes @@ -129,9 +124,10 @@ function macroexpand(m::Module, @nospecialize(x); recursive=true) end """ - @macroexpand + @macroexpand [mod,] ex Return equivalent expression with all macros removed (expanded). +If two arguments are provided, the first is the module to evaluate in. There are differences between `@macroexpand` and [`macroexpand`](@ref). @@ -166,20 +162,28 @@ julia> M.f() ``` With `@macroexpand` the expression expands where `@macroexpand` appears in the code (module `M` in the example). With `macroexpand` the expression expands in the module given as the first argument. + +!!! compat "Julia 1.11" + The two-argument form requires at least Julia 1.11. """ macro macroexpand(code) return :(macroexpand($__module__, $(QuoteNode(code)), recursive=true)) end - +macro macroexpand(mod, code) + return :(macroexpand($(esc(mod)), $(QuoteNode(code)), recursive=true)) +end """ - @macroexpand1 + @macroexpand1 [mod,] ex Non recursive version of [`@macroexpand`](@ref). """ macro macroexpand1(code) return :(macroexpand($__module__, $(QuoteNode(code)), recursive=false)) end +macro macroexpand1(mod, code) + return :(macroexpand($(esc(mod)), $(QuoteNode(code)), recursive=false)) +end ## misc syntax ## @@ -403,16 +407,16 @@ end """ Base.@assume_effects setting... [ex] -Override the compiler's effect modeling for the given method or foreign call. -`@assume_effects` can be applied immediately before a function definition or within a function body. -It can also be applied immediately before a `@ccall` expression. - -!!! compat "Julia 1.8" - Using `Base.@assume_effects` requires Julia version 1.8. +Override the compiler's effect modeling. +This macro can be used in several contexts: +1. Immediately before a method definition, to override the entire effect modeling of the applied method. +2. Within a function body without any arguments, to override the entire effect modeling of the enclosing method. +3. Applied to a code block, to override the local effect modeling of the applied code block. # Examples ```jldoctest julia> Base.@assume_effects :terminates_locally function fact(x) + # usage 1: # this :terminates_locally allows `fact` to be constant-folded res = 1 0 ≤ x < 20 || error("bad fact") @@ -433,6 +437,7 @@ CodeInfo( julia> code_typed() do map((2,3,4)) do x + # usage 2: # this :terminates_locally allows this anonymous function to be constant-folded Base.@assume_effects :terminates_locally res = 1 @@ -448,13 +453,35 @@ CodeInfo( 1 ─ return (2, 6, 24) ) => Tuple{Int64, Int64, Int64} -julia> Base.@assume_effects :total !:nothrow @ccall jl_type_intersection(Vector{Int}::Any, Vector{<:Integer}::Any)::Any -Vector{Int64} (alias for Array{Int64, 1}) +julia> code_typed() do + map((2,3,4)) do x + res = 1 + 0 ≤ x < 20 || error("bad fact") + # usage 3: + # with this :terminates_locally annotation the compiler skips tainting + # `:terminates` effect within this `while` block, allowing the parent + # anonymous function to be constant-folded + Base.@assume_effects :terminates_locally while x > 1 + res *= x + x -= 1 + end + return res + end + end |> only +CodeInfo( +1 ─ return (2, 6, 24) +) => Tuple{Int64, Int64, Int64} ``` +!!! compat "Julia 1.8" + Using `Base.@assume_effects` requires Julia version 1.8. + !!! compat "Julia 1.10" The usage within a function body requires at least Julia 1.10. +!!! compact "Julia 1.11" + The code block annotation requires at least Julia 1.11. + !!! warning Improper use of this macro causes undefined behavior (including crashes, incorrect answers, or other hard to track bugs). Use with care and only as a @@ -477,6 +504,7 @@ The following `setting`s are supported. - `:notaskstate` - `:inaccessiblememonly` - `:noub` +- `:noub_if_noinbounds` - `:foldable` - `:removable` - `:total` @@ -551,7 +579,7 @@ were not executed. --- ## `:nothrow` -The `:nothrow` settings asserts that this method does not terminate abnormally +The `:nothrow` settings asserts that this method does not throw an exception (i.e. will either always return a value or never return). !!! note @@ -560,7 +588,11 @@ The `:nothrow` settings asserts that this method does not terminate abnormally method itself. !!! note - `MethodErrors` and similar exceptions count as abnormal termination. + If the execution of a method may raise `MethodError`s and similar exceptions, then + the method is not considered as `:nothrow`. + However, note that environment-dependent errors like `StackOverflowError` or `InterruptException` + are not modeled by this effect and thus a method that may result in `StackOverflowError` + does not necessarily need to be `!:nothrow` (although it should usually be `!:terminates` too). --- ## `:terminates_globally` @@ -703,69 +735,85 @@ the call is generally total, it may however throw. """ macro assume_effects(args...) lastex = args[end] - inner = unwrap_macrocalls(lastex) - if is_function_def(inner) - ex = lastex - idx = length(args)-1 + override = compute_assumed_settings(args[begin:end-1]) + if is_function_def(unwrap_macrocalls(lastex)) + return esc(pushmeta!(lastex, form_purity_expr(override))) elseif isexpr(lastex, :macrocall) && lastex.args[1] === Symbol("@ccall") - ex = lastex - idx = length(args)-1 - else # anonymous function case - ex = nothing - idx = length(args) + lastex.args[1] = GlobalRef(Base, Symbol("@ccall_effects")) + insert!(lastex.args, 3, Core.Compiler.encode_effects_override(override)) + return esc(lastex) end - (consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly, noub) = - (false, false, false, false, false, false, false, false, false) - for org_setting in args[1:idx] - (setting, val) = compute_assumed_setting(org_setting) - if setting === :consistent - consistent = val - elseif setting === :effect_free - effect_free = val - elseif setting === :nothrow - nothrow = val - elseif setting === :terminates_globally - terminates_globally = val - elseif setting === :terminates_locally - terminates_locally = val - elseif setting === :notaskstate - notaskstate = val - elseif setting === :inaccessiblememonly - inaccessiblememonly = val - elseif setting === :noub - noub = val - elseif setting === :foldable - consistent = effect_free = terminates_globally = noub = val - elseif setting === :removable - effect_free = nothrow = terminates_globally = val - elseif setting === :total - consistent = effect_free = nothrow = terminates_globally = notaskstate = inaccessiblememonly = noub = val - else - throw(ArgumentError("@assume_effects $org_setting not supported")) - end + override′ = compute_assumed_setting(override, lastex) + if override′ !== nothing + # anonymous function case + return Expr(:meta, form_purity_expr(override′)) + else + # call site annotation case + return Expr(:block, + form_purity_expr(override), + Expr(:local, Expr(:(=), :val, esc(lastex))), + Expr(:purity), # region end token + :val) end - if is_function_def(inner) - return esc(pushmeta!(ex, :purity, - consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly, noub)) - elseif isexpr(ex, :macrocall) && ex.args[1] === Symbol("@ccall") - ex.args[1] = GlobalRef(Base, Symbol("@ccall_effects")) - insert!(ex.args, 3, Core.Compiler.encode_effects_override(Core.Compiler.EffectsOverride( - consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly, noub))) - return esc(ex) - else # anonymous function case - return Expr(:meta, Expr(:purity, - consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly, noub)) +end + +function compute_assumed_settings(settings) + override = EffectsOverride() + for setting in settings + override = compute_assumed_setting(override, setting) + override === nothing && + throw(ArgumentError("@assume_effects $setting not supported")) end + return override end -function compute_assumed_setting(@nospecialize(setting), val::Bool=true) +using Core.Compiler: EffectsOverride + +function compute_assumed_setting(override::EffectsOverride, @nospecialize(setting), val::Bool=true) if isexpr(setting, :call) && setting.args[1] === :(!) - return compute_assumed_setting(setting.args[2], !val) + return compute_assumed_setting(override, setting.args[2], !val) elseif isa(setting, QuoteNode) - return compute_assumed_setting(setting.value, val) - else - return (setting, val) + return compute_assumed_setting(override, setting.value, val) end + if setting === :consistent + return EffectsOverride(override; consistent = val) + elseif setting === :effect_free + return EffectsOverride(override; effect_free = val) + elseif setting === :nothrow + return EffectsOverride(override; nothrow = val) + elseif setting === :terminates_globally + return EffectsOverride(override; terminates_globally = val) + elseif setting === :terminates_locally + return EffectsOverride(override; terminates_locally = val) + elseif setting === :notaskstate + return EffectsOverride(override; notaskstate = val) + elseif setting === :inaccessiblememonly + return EffectsOverride(override; inaccessiblememonly = val) + elseif setting === :noub + return EffectsOverride(override; noub = val) + elseif setting === :noub_if_noinbounds + return EffectsOverride(override; noub_if_noinbounds = val) + elseif setting === :foldable + consistent = effect_free = terminates_globally = noub = val + return EffectsOverride(override; consistent, effect_free, terminates_globally, noub) + elseif setting === :removable + effect_free = nothrow = terminates_globally = val + return EffectsOverride(override; effect_free, nothrow, terminates_globally) + elseif setting === :total + consistent = effect_free = nothrow = terminates_globally = notaskstate = + inaccessiblememonly = noub = val + return EffectsOverride(override; + consistent, effect_free, nothrow, terminates_globally, notaskstate, + inaccessiblememonly, noub) + end + return nothing +end + +function form_purity_expr(override::EffectsOverride) + return Expr(:purity, + override.consistent, override.effect_free, override.nothrow, + override.terminates_globally, override.terminates_locally, override.notaskstate, + override.inaccessiblememonly, override.noub, override.noub_if_noinbounds) end """ @@ -778,7 +826,7 @@ end Tells the compiler to infer `f` using the declared types of `@nospecialize`d arguments. This can be used to limit the number of compiler-generated specializations during inference. -# Example +# Examples ```julia julia> f(A::AbstractArray) = g(A) @@ -839,23 +887,17 @@ function unwrap_macrocalls(ex::Expr) return inner end -function pushmeta!(ex::Expr, sym::Symbol, args::Any...) - if isempty(args) - tag = sym - else - tag = Expr(sym, args...)::Expr - end - +function pushmeta!(ex::Expr, tag::Union{Symbol,Expr}) inner = unwrap_macrocalls(ex) - idx, exargs = findmeta(inner) if idx != 0 - push!(exargs[idx].args, tag) + metastmt = exargs[idx]::Expr + push!(metastmt.args, tag) else body = inner.args[2]::Expr pushfirst!(body.args, Expr(:meta, tag)) end - ex + return ex end popmeta!(body, sym) = _getmeta(body, sym, true) @@ -951,26 +993,29 @@ function findmeta_block(exargs, argsmatch=args->true) return 0, [] end -remove_linenums!(ex) = ex -function remove_linenums!(ex::Expr) - if ex.head === :block || ex.head === :quote - # remove line number expressions from metadata (not argument literal or inert) position - filter!(ex.args) do x - isa(x, Expr) && x.head === :line && return false - isa(x, LineNumberNode) && return false - return true +""" + Base.remove_linenums!(ex) + +Remove all line-number metadata from expression-like object `ex`. +""" +function remove_linenums!(@nospecialize ex) + if ex isa Expr + if ex.head === :block || ex.head === :quote + # remove line number expressions from metadata (not argument literal or inert) position + filter!(ex.args) do x + isa(x, Expr) && x.head === :line && return false + isa(x, LineNumberNode) && return false + return true + end end - end - for subex in ex.args - subex isa Expr && remove_linenums!(subex) + for subex in ex.args + subex isa Expr && remove_linenums!(subex) + end + elseif ex isa CodeInfo + ex.debuginfo = Core.DebugInfo(ex.debuginfo.def) # TODO: filter partially, but keep edges end return ex end -function remove_linenums!(src::CodeInfo) - src.codelocs .= 0 - length(src.linetable) > 1 && resize!(src.linetable, 1) - return src -end replace_linenums!(ex, ln::LineNumberNode) = ex function replace_linenums!(ex::Expr, ln::LineNumberNode) @@ -1029,7 +1074,6 @@ macro generated(f) if isa(f, Expr) && (f.head === :function || is_short_function_def(f)) body = f.args[2] lno = body.args[1] - tmp = gensym("tmp") return Expr(:escape, Expr(f.head, f.args[1], Expr(:block, @@ -1278,3 +1322,60 @@ function make_atomicreplace(success_order, fail_order, ex, old_new) return :(replaceproperty!($ll, $lr, $old_new::Pair..., $success_order, $fail_order)) end end + +""" + @atomiconce a.b.x = value + @atomiconce :sequentially_consistent a.b.x = value + @atomiconce :sequentially_consistent :monotonic a.b.x = value + +Perform the conditional assignment of value atomically if it was previously +unset, returning the value `success::Bool`. Where `success` indicates whether +the assignment was completed. + +This operation translates to a `setpropertyonce!(a.b, :x, value)` call. + +See [Per-field atomics](@ref man-atomics) section in the manual for more details. + +# Examples +```jldoctest +julia> mutable struct AtomicOnce + @atomic x + AtomicOnce() = new() + end + +julia> a = AtomicOnce() +AtomicOnce(#undef) + +julia> @atomiconce a.x = 1 # set field x of a to 1, if unset, with sequential consistency +true + +julia> @atomic a.x # fetch field x of a, with sequential consistency +1 + +julia> @atomiconce a.x = 1 # set field x of a to 1, if unset, with sequential consistency +false +``` + +!!! compat "Julia 1.11" + This functionality requires at least Julia 1.11. +""" +macro atomiconce(success_order, fail_order, ex) + fail_order isa QuoteNode || (fail_order = esc(fail_order)) + success_order isa QuoteNode || (success_order = esc(success_order)) + return make_atomiconce(success_order, fail_order, ex) +end +macro atomiconce(order, ex) + order isa QuoteNode || (order = esc(order)) + return make_atomiconce(order, order, ex) +end +macro atomiconce(ex) + return make_atomiconce(QuoteNode(:sequentially_consistent), QuoteNode(:sequentially_consistent), ex) +end +function make_atomiconce(success_order, fail_order, ex) + @nospecialize + is_expr(ex, :(=), 2) || error("@atomiconce expression missing assignment") + l, val = ex.args[1], esc(ex.args[2]) + is_expr(l, :., 2) || error("@atomiconce expression missing field access") + ll, lr = esc(l.args[1]), esc(l.args[2]) + return :(setpropertyonce!($ll, $lr, $val, $success_order, $fail_order)) +end diff --git a/base/fastmath.jl b/base/fastmath.jl index 44440ebad2050..28a7852640ba9 100644 --- a/base/fastmath.jl +++ b/base/fastmath.jl @@ -6,7 +6,7 @@ # strict IEEE semantics. # This allows the following transformations. For more information see -# http://llvm.org/docs/LangRef.html#fast-math-flags: +# https://llvm.org/docs/LangRef.html#fast-math-flags: # nnan: No NaNs - Allow optimizations to assume the arguments and # result are not NaN. Such optimizations are required to retain # defined behavior over NaNs, but the value of the result is @@ -101,9 +101,12 @@ const rewrite_op = function make_fastmath(expr::Expr) if expr.head === :quote return expr - elseif expr.head === :call && expr.args[1] === :^ && expr.args[3] isa Integer - # mimic Julia's literal_pow lowering of literal integer powers - return Expr(:call, :(Base.FastMath.pow_fast), make_fastmath(expr.args[2]), Val{expr.args[3]}()) + elseif expr.head === :call && expr.args[1] === :^ + ea = expr.args + if length(ea) >= 3 && isa(ea[3], Int) + # mimic Julia's literal_pow lowering of literal integer powers + return Expr(:call, :(Base.FastMath.pow_fast), make_fastmath(ea[2]), Val(ea[3])) + end end op = get(rewrite_op, expr.head, :nothing) if op !== :nothing @@ -136,7 +139,7 @@ may violate strict IEEE semantics. This allows the fastest possible operation, but results are undefined -- be careful when doing this, as it may change numerical results. -This sets the [LLVM Fast-Math flags](http://llvm.org/docs/LangRef.html#fast-math-flags), +This sets the [LLVM Fast-Math flags](https://llvm.org/docs/LangRef.html#fast-math-flags), and corresponds to the `-ffast-math` option in clang. See [the notes on performance annotations](@ref man-performance-annotations) for more details. @@ -309,7 +312,7 @@ end Complex{T}(c, s) end - # See + # See pow_fast(x::T, y::T) where {T<:ComplexTypes} = exp(y*log(x)) pow_fast(x::T, y::Complex{T}) where {T<:FloatTypes} = exp(y*log(x)) pow_fast(x::Complex{T}, y::T) where {T<:FloatTypes} = exp(y*log(x)) @@ -364,6 +367,10 @@ for f in (:^, :atan, :hypot, :log) # fall-back implementation that applies after promotion $f_fast(x::T, y::T) where {T<:Number} = $f(x, y) end + # Issue 53886 - avoid promotion of Int128 etc to be consistent with non-fastmath + if f === :^ + @eval $f_fast(x::Number, y::Integer) = $f(x, y) + end end # Reductions diff --git a/base/file.jl b/base/file.jl index 9060b086b44bb..727b97abd36f1 100644 --- a/base/file.jl +++ b/base/file.jl @@ -249,6 +249,10 @@ function mkpath(path::AbstractString; mode::Integer = 0o777) path end +# Files that were requested to be deleted but can't be by the current process +# i.e. loaded DLLs on Windows +delayed_delete_dir() = joinpath(tempdir(), "julia_delayed_deletes") + """ rm(path::AbstractString; force::Bool=false, recursive::Bool=false) @@ -270,20 +274,26 @@ Stacktrace: [...] ``` """ -function rm(path::AbstractString; force::Bool=false, recursive::Bool=false) +function rm(path::AbstractString; force::Bool=false, recursive::Bool=false, allow_delayed_delete::Bool=true) + # allow_delayed_delete is used by Pkg.gc() but is otherwise not part of the public API if islink(path) || !isdir(path) try - @static if Sys.iswindows() - # is writable on windows actually means "is deletable" - st = lstat(path) - if ispath(st) && (filemode(st) & 0o222) == 0 - chmod(path, 0o777) - end - end unlink(path) catch err - if force && isa(err, IOError) && err.code==Base.UV_ENOENT - return + if isa(err, IOError) + force && err.code==Base.UV_ENOENT && return + @static if Sys.iswindows() + if allow_delayed_delete && err.code==Base.UV_EACCES && endswith(path, ".dll") + # Loaded DLLs cannot be deleted on Windows, even with posix delete mode + # but they can be moved. So move out to allow the dir to be deleted. + # Pkg.gc() cleans up this dir when possible + dir = mkpath(delayed_delete_dir()) + temp_path = tempname(dir, cleanup = false, suffix = string("_", basename(path))) + @debug "Could not delete DLL most likely because it is loaded, moving to tempdir" path temp_path + mv(path, temp_path) + return + end + end end rethrow() end @@ -291,12 +301,14 @@ function rm(path::AbstractString; force::Bool=false, recursive::Bool=false) if recursive try for p in readdir(path) - rm(joinpath(path, p), force=force, recursive=true) + try + rm(joinpath(path, p), force=force, recursive=true) + catch err + (isa(err, IOError) && err.code==Base.UV_EACCES) || rethrow() + end end catch err - if !(isa(err, IOError) && err.code==Base.UV_EACCES) - rethrow(err) - end + (isa(err, IOError) && err.code==Base.UV_EACCES) || rethrow() end end req = Libc.malloc(_sizeof_uv_fs) @@ -480,13 +492,26 @@ function tempdir() rc = ccall(:uv_os_tmpdir, Cint, (Ptr{UInt8}, Ptr{Csize_t}), buf, sz) if rc == 0 resize!(buf, sz[]) - return String(buf) + break elseif rc == Base.UV_ENOBUFS resize!(buf, sz[] - 1) # space for null-terminator implied by StringVector else uv_error("tempdir()", rc) end end + tempdir = String(buf) + try + s = stat(tempdir) + if !ispath(s) + @warn "tempdir path does not exist" tempdir + elseif !isdir(s) + @warn "tempdir path is not a directory" tempdir + end + catch ex + ex isa IOError || ex isa SystemError || rethrow() + @warn "accessing tempdir path failed" _exception=ex + end + return tempdir end """ @@ -504,13 +529,19 @@ function prepare_for_deletion(path::AbstractString) return end - try chmod(path, filemode(path) | 0o333) - catch; end + try + chmod(path, filemode(path) | 0o333) + catch ex + ex isa IOError || ex isa SystemError || rethrow() + end for (root, dirs, files) in walkdir(path; onerror=x->()) for dir in dirs dpath = joinpath(root, dir) - try chmod(dpath, filemode(dpath) | 0o333) - catch; end + try + chmod(dpath, filemode(dpath) | 0o333) + catch ex + ex isa IOError || ex isa SystemError || rethrow() + end end end end @@ -521,37 +552,70 @@ const TEMP_CLEANUP = Dict{String,Bool}() const TEMP_CLEANUP_LOCK = ReentrantLock() function temp_cleanup_later(path::AbstractString; asap::Bool=false) - lock(TEMP_CLEANUP_LOCK) + @lock TEMP_CLEANUP_LOCK begin # each path should only be inserted here once, but if there # is a collision, let !asap win over asap: if any user might # still be using the path, don't delete it until process exit TEMP_CLEANUP[path] = get(TEMP_CLEANUP, path, true) & asap if length(TEMP_CLEANUP) > TEMP_CLEANUP_MAX[] - temp_cleanup_purge() + temp_cleanup_purge_prelocked(false) TEMP_CLEANUP_MAX[] = max(TEMP_CLEANUP_MIN[], 2*length(TEMP_CLEANUP)) end - unlock(TEMP_CLEANUP_LOCK) - return nothing + end + nothing +end + +function temp_cleanup_forget(path::AbstractString) + @lock TEMP_CLEANUP_LOCK delete!(TEMP_CLEANUP, path) + nothing end -function temp_cleanup_purge(; force::Bool=false) - need_gc = Sys.iswindows() - for (path, asap) in TEMP_CLEANUP +function temp_cleanup_purge_prelocked(force::Bool) + filter!(TEMP_CLEANUP) do (path, asap) try - if (force || asap) && ispath(path) - need_gc && GC.gc(true) - need_gc = false + ispath(path) || return false + if force || asap prepare_for_deletion(path) rm(path, recursive=true, force=true) end - !ispath(path) && delete!(TEMP_CLEANUP, path) + return ispath(path) catch ex @warn """ Failed to clean up temporary path $(repr(path)) $ex """ _group=:file + ex isa InterruptException && rethrow() + return true end end + nothing +end + +function temp_cleanup_purge_all() + may_need_gc = false + @lock TEMP_CLEANUP_LOCK filter!(TEMP_CLEANUP) do (path, asap) + try + ispath(path) || return false + may_need_gc = true + return true + catch ex + ex isa InterruptException && rethrow() + return true + end + end + if may_need_gc + # this is only usually required on Sys.iswindows(), but may as well do it everywhere + GC.gc(true) + end + @lock TEMP_CLEANUP_LOCK temp_cleanup_purge_prelocked(true) + nothing +end + +# deprecated internal function used by some packages +temp_cleanup_purge(; force=false) = force ? temp_cleanup_purge_all() : @lock TEMP_CLEANUP_LOCK temp_cleanup_purge_prelocked(false) + +function __postinit__() + Base.atexit(temp_cleanup_purge_all) end const temp_prefix = "jl_" @@ -568,13 +632,13 @@ end # Obtain a temporary filename. -function tempname(parent::AbstractString=tempdir(); max_tries::Int = 100, cleanup::Bool=true) +function tempname(parent::AbstractString=tempdir(); max_tries::Int = 100, cleanup::Bool=true, suffix::AbstractString="") isdir(parent) || throw(ArgumentError("$(repr(parent)) is not a directory")) prefix = joinpath(parent, temp_prefix) filename = nothing for i in 1:max_tries - filename = string(prefix, _rand_filename()) + filename = string(prefix, _rand_filename(), suffix) if ispath(filename) filename = nothing else @@ -630,7 +694,7 @@ end # os-test """ - tempname(parent=tempdir(); cleanup=true) -> String + tempname(parent=tempdir(); cleanup=true, suffix="") -> String Generate a temporary file path. This function only returns a path; no file is created. The path is likely to be unique, but this cannot be guaranteed due to @@ -641,7 +705,8 @@ existing at the time of the call to `tempname`. When called with no arguments, the temporary name will be an absolute path to a temporary name in the system temporary directory as given by `tempdir()`. If a `parent` directory argument is given, the temporary path will be in that -directory instead. +directory instead. If a suffix is given the tempname will end with that suffix +and be tested for uniqueness with that suffix. The `cleanup` option controls whether the process attempts to delete the returned path automatically when the process exits. Note that the `tempname` @@ -653,6 +718,9 @@ you do and `cleanup` is `true` it will be deleted upon process termination. The `parent` and `cleanup` arguments were added in 1.4. Prior to Julia 1.4 the path `tempname` would never be cleaned up at process termination. +!!! compat "Julia 1.12" + The `suffix` keyword argument was added in Julia 1.12. + !!! warning This can lead to security holes if another process obtains the same @@ -733,10 +801,11 @@ temporary file upon completion. See also: [`mktempdir`](@ref). """ function mktemp(fn::Function, parent::AbstractString=tempdir()) - (tmp_path, tmp_io) = mktemp(parent, cleanup=false) + (tmp_path, tmp_io) = mktemp(parent) try fn(tmp_path, tmp_io) finally + temp_cleanup_forget(tmp_path) try close(tmp_io) ispath(tmp_path) && rm(tmp_path) @@ -752,7 +821,7 @@ end mktempdir(f::Function, parent=tempdir(); prefix=$(repr(temp_prefix))) Apply the function `f` to the result of [`mktempdir(parent; prefix)`](@ref) and remove the -temporary directory all of its contents upon completion. +temporary directory and all of its contents upon completion. See also: [`mktemp`](@ref), [`mkdir`](@ref). @@ -761,10 +830,11 @@ See also: [`mktemp`](@ref), [`mkdir`](@ref). """ function mktempdir(fn::Function, parent::AbstractString=tempdir(); prefix::AbstractString=temp_prefix) - tmpdir = mktempdir(parent; prefix=prefix, cleanup=false) + tmpdir = mktempdir(parent; prefix=prefix) try fn(tmpdir) finally + temp_cleanup_forget(tmpdir) try if ispath(tmpdir) prepare_for_deletion(tmpdir) @@ -859,7 +929,79 @@ julia> readdir(abspath("base"), join=true) "/home/JuliaUser/dev/julia/base/weakkeydict.jl" ``` """ -function readdir(dir::AbstractString; join::Bool=false, sort::Bool=true) +readdir(; join::Bool=false, kwargs...) = readdir(join ? pwd() : "."; join, kwargs...)::Vector{String} +readdir(dir::AbstractString; kwargs...) = _readdir(dir; return_objects=false, kwargs...)::Vector{String} + +# this might be better as an Enum but they're not available here +# UV_DIRENT_T +const UV_DIRENT_UNKNOWN = Cint(0) +const UV_DIRENT_FILE = Cint(1) +const UV_DIRENT_DIR = Cint(2) +const UV_DIRENT_LINK = Cint(3) +const UV_DIRENT_FIFO = Cint(4) +const UV_DIRENT_SOCKET = Cint(5) +const UV_DIRENT_CHAR = Cint(6) +const UV_DIRENT_BLOCK = Cint(7) + +""" + DirEntry + +A type representing a filesystem entry that contains the name of the entry, the directory, and +the raw type of the entry. The full path of the entry can be obtained lazily by accessing the +`path` field. The type of the entry can be checked for by calling [`isfile`](@ref), [`isdir`](@ref), +[`islink`](@ref), [`isfifo`](@ref), [`issocket`](@ref), [`ischardev`](@ref), and [`isblockdev`](@ref) +""" +struct DirEntry + dir::String + name::String + rawtype::Cint +end +function Base.getproperty(obj::DirEntry, p::Symbol) + if p === :path + return joinpath(obj.dir, obj.name) + else + return getfield(obj, p) + end +end +Base.propertynames(::DirEntry) = (:dir, :name, :path, :rawtype) +Base.isless(a::DirEntry, b::DirEntry) = a.dir == b.dir ? isless(a.name, b.name) : isless(a.dir, b.dir) +Base.hash(o::DirEntry, h::UInt) = hash(o.dir, hash(o.name, hash(o.rawtype, h))) +Base.:(==)(a::DirEntry, b::DirEntry) = a.name == b.name && a.dir == b.dir && a.rawtype == b.rawtype +joinpath(obj::DirEntry, args...) = joinpath(obj.path, args...) +isunknown(obj::DirEntry) = obj.rawtype == UV_DIRENT_UNKNOWN +islink(obj::DirEntry) = isunknown(obj) ? islink(obj.path) : obj.rawtype == UV_DIRENT_LINK +isfile(obj::DirEntry) = (isunknown(obj) || islink(obj)) ? isfile(obj.path) : obj.rawtype == UV_DIRENT_FILE +isdir(obj::DirEntry) = (isunknown(obj) || islink(obj)) ? isdir(obj.path) : obj.rawtype == UV_DIRENT_DIR +isfifo(obj::DirEntry) = (isunknown(obj) || islink(obj)) ? isfifo(obj.path) : obj.rawtype == UV_DIRENT_FIFO +issocket(obj::DirEntry) = (isunknown(obj) || islink(obj)) ? issocket(obj.path) : obj.rawtype == UV_DIRENT_SOCKET +ischardev(obj::DirEntry) = (isunknown(obj) || islink(obj)) ? ischardev(obj.path) : obj.rawtype == UV_DIRENT_CHAR +isblockdev(obj::DirEntry) = (isunknown(obj) || islink(obj)) ? isblockdev(obj.path) : obj.rawtype == UV_DIRENT_BLOCK +realpath(obj::DirEntry) = realpath(obj.path) + +""" + _readdirx(dir::AbstractString=pwd(); sort::Bool = true) -> Vector{DirEntry} + +Return a vector of [`DirEntry`](@ref) objects representing the contents of the directory `dir`, +or the current working directory if not given. If `sort` is true, the returned vector is +sorted by name. + +Unlike [`readdir`](@ref), `_readdirx` returns [`DirEntry`](@ref) objects, which contain the name of the +file, the directory it is in, and the type of the file which is determined during the +directory scan. This means that calls to [`isfile`](@ref), [`isdir`](@ref), [`islink`](@ref), [`isfifo`](@ref), +[`issocket`](@ref), [`ischardev`](@ref), and [`isblockdev`](@ref) can be made on the +returned objects without further stat calls. However, for some filesystems, the type of the file +cannot be determined without a stat call. In these cases the `rawtype` field of the [`DirEntry`](@ref)) +object will be 0 (`UV_DIRENT_UNKNOWN`) and [`isfile`](@ref) etc. will fall back to a `stat` call. + +```julia +for obj in _readdirx() + isfile(obj) && println("\$(obj.name) is a file with path \$(obj.path)") +end +``` +""" +_readdirx(dir::AbstractString=pwd(); sort::Bool=true) = _readdir(dir; return_objects=true, sort)::Vector{DirEntry} + +function _readdir(dir::AbstractString; return_objects::Bool=false, join::Bool=false, sort::Bool=true) # Allocate space for uv_fs_t struct req = Libc.malloc(_sizeof_uv_fs) try @@ -869,11 +1011,16 @@ function readdir(dir::AbstractString; join::Bool=false, sort::Bool=true) err < 0 && uv_error("readdir($(repr(dir)))", err) # iterate the listing into entries - entries = String[] + entries = return_objects ? DirEntry[] : String[] ent = Ref{uv_dirent_t}() while Base.UV_EOF != ccall(:uv_fs_scandir_next, Cint, (Ptr{Cvoid}, Ptr{uv_dirent_t}), req, ent) name = unsafe_string(ent[].name) - push!(entries, join ? joinpath(dir, name) : name) + if return_objects + rawtype = ent[].typ + push!(entries, DirEntry(dir, name, rawtype)) + else + push!(entries, join ? joinpath(dir, name) : name) + end end # Clean up the request string @@ -887,8 +1034,6 @@ function readdir(dir::AbstractString; join::Bool=false, sort::Bool=true) Libc.free(req) end end -readdir(; join::Bool=false, sort::Bool=true) = - readdir(join ? pwd() : ".", join=join, sort=sort) """ walkdir(dir; topdown=true, follow_symlinks=false, onerror=throw) @@ -944,18 +1089,16 @@ function walkdir(root; topdown=true, follow_symlinks=false, onerror=throw) end return end - content = tryf(readdir, root) - content === nothing && return - dirs = Vector{eltype(content)}() - files = Vector{eltype(content)}() - for name in content - path = joinpath(root, name) - + entries = tryf(_readdirx, root) + entries === nothing && return + dirs = Vector{String}() + files = Vector{String}() + for entry in entries # If we're not following symlinks, then treat all symlinks as files - if (!follow_symlinks && something(tryf(islink, path), true)) || !something(tryf(isdir, path), false) - push!(files, name) + if (!follow_symlinks && something(tryf(islink, entry), true)) || !something(tryf(isdir, entry), false) + push!(files, entry.name) else - push!(dirs, name) + push!(dirs, entry.name) end end diff --git a/base/filesystem.jl b/base/filesystem.jl index d291dd3b31630..36e4bb4596285 100644 --- a/base/filesystem.jl +++ b/base/filesystem.jl @@ -4,6 +4,45 @@ module Filesystem +""" + JL_O_APPEND + JL_O_ASYNC + JL_O_CLOEXEC + JL_O_CREAT + JL_O_DIRECT + JL_O_DIRECTORY + JL_O_DSYNC + JL_O_EXCL + JL_O_FSYNC + JL_O_LARGEFILE + JL_O_NDELAY + JL_O_NOATIME + JL_O_NOCTTY + JL_O_NOFOLLOW + JL_O_NONBLOCK + JL_O_PATH + JL_O_RANDOM + JL_O_RDONLY + JL_O_RDWR + JL_O_RSYNC + JL_O_SEQUENTIAL + JL_O_SHORT_LIVED + JL_O_SYNC + JL_O_TEMPORARY + JL_O_TMPFILE + JL_O_TRUNC + JL_O_WRONLY + +Enum constant for the `open` syscall, where `JL_O_*` corresponds to the `O_*` constant. +See [the libuv docs](https://docs.libuv.org/en/v1.x/fs.html#file-open-constants) for more details. +""" +(:JL_O_APPEND, :JL_O_ASYNC, :JL_O_CLOEXEC, :JL_O_CREAT, :JL_O_DIRECT, + :JL_O_DIRECTORY, :JL_O_DSYNC, :JL_O_EXCL, :JL_O_FSYNC, :JL_O_LARGEFILE, + :JL_O_NOATIME, :JL_O_NOCTTY, :JL_O_NDELAY, :JL_O_NOFOLLOW, :JL_O_NONBLOCK, + :JL_O_PATH, :JL_O_RANDOM, :JL_O_RDONLY, :JL_O_RDWR, :JL_O_RSYNC, + :JL_O_SEQUENTIAL, :JL_O_SHORT_LIVED, :JL_O_SYNC, :JL_O_TEMPORARY, + :JL_O_TMPFILE, :JL_O_TRUNC, :JL_O_WRONLY) + const S_IFDIR = 0o040000 # directory const S_IFCHR = 0o020000 # character device const S_IFBLK = 0o060000 # block device @@ -31,6 +70,36 @@ const S_IWOTH = 0o0002 # write by other const S_IXOTH = 0o0001 # execute by other const S_IRWXO = 0o0007 # mask for other permissions +""" + S_IRUSR + S_IWUSR + S_IXUSR + S_IRGRP + S_IWGRP + S_IXGRP + S_IROTH + S_IWOTH + S_IXOTH + +Constants for file access permission bits. +The general structure is `S_I[permission][class]` +where `permission` is `R` for read, `W` for write, and `X` for execute, +and `class` is `USR` for user/owner, `GRP` for group, and `OTH` for other. +""" +(:S_IRUSR, :S_IWUSR, :S_IXUSR, :S_IRGRP, :S_IWGRP, :S_IXGRP, :S_IROTH, :S_IWOTH, :S_IXOTH) + +""" + S_IRWXU + S_IRWXG + S_IRWXO + +Constants for file access permission masks, i.e. the combination of read, write, +and execute permissions for a class. +The general structure is `S_IRWX[class]` +where `class` is `U` for user/owner, `G` for group, and `O` for other. +""" +(:S_IRWXU, :S_IRWXG, :S_IRWXO) + export File, StatStruct, # open, @@ -48,7 +117,6 @@ export File, JL_O_SEQUENTIAL, JL_O_RANDOM, JL_O_NOCTTY, - JL_O_NOCTTY, JL_O_NONBLOCK, JL_O_NDELAY, JL_O_SYNC, @@ -72,7 +140,8 @@ import .Base: IOError, _UVError, _sizeof_uv_fs, check_open, close, eof, eventloop, fd, isopen, bytesavailable, position, read, read!, readavailable, seek, seekend, show, skip, stat, unsafe_read, unsafe_write, write, transcode, uv_error, - setup_stdio, rawhandle, OS_HANDLE, INVALID_OS_HANDLE, windowserror, filesize + setup_stdio, rawhandle, OS_HANDLE, INVALID_OS_HANDLE, windowserror, filesize, + isexecutable, isreadable, iswritable import .Base.RefValue @@ -143,6 +212,8 @@ function close(f::File) nothing end +closewrite(f::File) = nothing + # sendfile is the most efficient way to copy from a file descriptor function sendfile(dst::File, src::File, src_offset::Int64, bytes::Int) check_open(dst) @@ -193,9 +264,13 @@ end function read(f::File, ::Type{UInt8}) check_open(f) - ret = ccall(:jl_fs_read_byte, Int32, (OS_HANDLE,), f.handle) + p = Ref{UInt8}() + ret = ccall(:jl_fs_read, Int32, (OS_HANDLE, Ptr{Cvoid}, Csize_t), + f.handle, p, 1) uv_error("read", ret) - return ret % UInt8 + @assert ret <= sizeof(p) == 1 + ret < 1 && throw(EOFError()) + return p[] % UInt8 end function read(f::File, ::Type{Char}) @@ -291,5 +366,85 @@ function touch(f::File) f end +""" + isexecutable(path::String) + +Return `true` if the given `path` has executable permissions. + +!!! note + This permission may change before the user executes `path`, + so it is recommended to execute the file and handle the error if that fails, + rather than calling `isexecutable` first. + +!!! note + Prior to Julia 1.6, this did not correctly interrogate filesystem + ACLs on Windows, therefore it would return `true` for any + file. From Julia 1.6 on, it correctly determines whether the + file is marked as executable or not. + +See also [`ispath`](@ref), [`isreadable`](@ref), [`iswritable`](@ref). +""" +function isexecutable(path::String) + # We use `access()` and `X_OK` to determine if a given path is + # executable by the current user. `X_OK` comes from `unistd.h`. + X_OK = 0x01 + return ccall(:jl_fs_access, Cint, (Cstring, Cint), path, X_OK) == 0 +end +isexecutable(path::AbstractString) = isexecutable(String(path)) + +""" + isreadable(path::String) + +Return `true` if the access permissions for the given `path` permitted reading by the current user. + +!!! note + This permission may change before the user calls `open`, + so it is recommended to just call `open` alone and handle the error if that fails, + rather than calling `isreadable` first. + +!!! note + Currently this function does not correctly interrogate filesystem + ACLs on Windows, therefore it can return wrong results. + +!!! compat "Julia 1.11" + This function requires at least Julia 1.11. + +See also [`ispath`](@ref), [`isexecutable`](@ref), [`iswritable`](@ref). +""" +function isreadable(path::String) + # We use `access()` and `R_OK` to determine if a given path is + # readable by the current user. `R_OK` comes from `unistd.h`. + R_OK = 0x04 + return ccall(:jl_fs_access, Cint, (Cstring, Cint), path, R_OK) == 0 +end +isreadable(path::AbstractString) = isreadable(String(path)) + +""" + iswritable(path::String) + +Return `true` if the access permissions for the given `path` permitted writing by the current user. + +!!! note + This permission may change before the user calls `open`, + so it is recommended to just call `open` alone and handle the error if that fails, + rather than calling `iswritable` first. + +!!! note + Currently this function does not correctly interrogate filesystem + ACLs on Windows, therefore it can return wrong results. + +!!! compat "Julia 1.11" + This function requires at least Julia 1.11. + +See also [`ispath`](@ref), [`isexecutable`](@ref), [`isreadable`](@ref). +""" +function iswritable(path::String) + # We use `access()` and `W_OK` to determine if a given path is + # writeable by the current user. `W_OK` comes from `unistd.h`. + W_OK = 0x02 + return ccall(:jl_fs_access, Cint, (Cstring, Cint), path, W_OK) == 0 +end +iswritable(path::AbstractString) = iswritable(String(path)) + end diff --git a/base/float.jl b/base/float.jl index 3e6e39c5951d8..64dcb8b807550 100644 --- a/base/float.jl +++ b/base/float.jl @@ -14,6 +14,8 @@ const Inf16 = bitcast(Float16, 0x7c00) NaN16 A not-a-number value of type [`Float16`](@ref). + +See also: [`NaN`](@ref). """ const NaN16 = bitcast(Float16, 0x7e00) """ @@ -26,6 +28,8 @@ const Inf32 = bitcast(Float32, 0x7f800000) NaN32 A not-a-number value of type [`Float32`](@ref). + +See also: [`NaN`](@ref). """ const NaN32 = bitcast(Float32, 0x7fc00000) const Inf64 = bitcast(Float64, 0x7ff0000000000000) @@ -69,9 +73,23 @@ NaN julia> Inf - Inf NaN -julia> NaN == NaN, isequal(NaN, NaN), NaN === NaN +julia> NaN == NaN, isequal(NaN, NaN), isnan(NaN) (false, true, true) ``` + +!!! note + Always use [`isnan`](@ref) or [`isequal`](@ref) for checking for `NaN`. + Using `x === NaN` may give unexpected results: + ```julia-repl + julia> reinterpret(UInt32, NaN32) + 0x7fc00000 + + julia> NaN32p1 = reinterpret(Float32, 0x7fc00001) + NaN32 + + julia> NaN32p1 === NaN32, isequal(NaN32p1, NaN32), isnan(NaN32p1) + (false, true, true) + ``` """ NaN, NaN64 @@ -836,12 +854,12 @@ number of significand digits in that base. """ function precision end -_precision(::Type{Float16}) = 11 -_precision(::Type{Float32}) = 24 -_precision(::Type{Float64}) = 53 -function _precision(x, base::Integer=2) +_precision_with_base_2(::Type{Float16}) = 11 +_precision_with_base_2(::Type{Float32}) = 24 +_precision_with_base_2(::Type{Float64}) = 53 +function _precision(x, base::Integer) base > 1 || throw(DomainError(base, "`base` cannot be less than 2.")) - p = _precision(x) + p = _precision_with_base_2(x) return base == 2 ? Int(p) : floor(Int, p / log2(base)) end precision(::Type{T}; base::Integer=2) where {T<:AbstractFloat} = _precision(T, base) @@ -1014,13 +1032,24 @@ isodd(x::AbstractFloat) = isinteger(x) && abs(x) ≤ maxintfloat(x) && isodd(Int floatmax(::Type{Float32}) = $(bitcast(Float32, 0x7f7fffff)) floatmax(::Type{Float64}) = $(bitcast(Float64, 0x7fefffffffffffff)) - eps(x::AbstractFloat) = isfinite(x) ? abs(x) >= floatmin(x) ? ldexp(eps(typeof(x)), exponent(x)) : nextfloat(zero(x)) : oftype(x, NaN) eps(::Type{Float16}) = $(bitcast(Float16, 0x1400)) eps(::Type{Float32}) = $(bitcast(Float32, 0x34000000)) eps(::Type{Float64}) = $(bitcast(Float64, 0x3cb0000000000000)) eps() = eps(Float64) end +eps(x::AbstractFloat) = isfinite(x) ? abs(x) >= floatmin(x) ? ldexp(eps(typeof(x)), exponent(x)) : nextfloat(zero(x)) : oftype(x, NaN) + +function eps(x::T) where T<:IEEEFloat + # For isfinite(x), toggling the LSB will produce either prevfloat(x) or + # nextfloat(x) but will never change the sign or exponent. + # For !isfinite(x), this will map Inf to NaN and NaN to NaN or Inf. + y = reinterpret(T, reinterpret(Unsigned, x) ⊻ true) + # The absolute difference between these values is eps(x). This is true even + # for Inf/NaN values. + return abs(x - y) +end + """ floatmin(T = Float64) diff --git a/base/gcutils.jl b/base/gcutils.jl index fed30befd7d5c..3b8c4cf4ede10 100644 --- a/base/gcutils.jl +++ b/base/gcutils.jl @@ -70,7 +70,6 @@ end A finalizer may be registered at object construction. In the following example note that we implicitly rely on the finalizer returning the newly created mutable struct `x`. -# Example ```julia mutable struct MyMutableStruct bar @@ -119,9 +118,12 @@ const GC_INCREMENTAL = 2 GC.gc([full=true]) Perform garbage collection. The argument `full` determines the kind of -collection: A full collection (default) sweeps all objects, which makes the -next GC scan much slower, while an incremental collection may only sweep -so-called young objects. +collection: a full collection (default) traverses all live objects (i.e. full mark) +and should reclaim memory from all unreachable objects. An incremental collection only +reclaims memory from young objects which are not reachable. + +The GC may decide to perform a full collection even if an incremental collection was +requested. !!! warning Excessive use will likely lead to poor performance. @@ -259,4 +261,13 @@ function enable_logging(on::Bool=true) ccall(:jl_enable_gc_logging, Cvoid, (Cint,), on) end +""" + GC.logging_enabled() + +Return whether GC logging has been enabled via [`GC.enable_logging`](@ref). +""" +function logging_enabled() + ccall(:jl_is_gc_logging_enabled, Cint, ()) != 0 +end + end # module GC diff --git a/base/generator.jl b/base/generator.jl index aa4b7f67cba95..1f981de8dc788 100644 --- a/base/generator.jl +++ b/base/generator.jl @@ -5,25 +5,26 @@ Given a function `f` and an iterator `iter`, construct an iterator that yields the values of `f` applied to the elements of `iter`. -The syntax for constructing an instance of this type is `f(x) for x in iter [if cond(x)::Bool] `. -The `[if cond(x)::Bool]` expression is optional and acts as a "guard", effectively -filtering out values where the condition is false. +The syntax `f(x) for x in iter` is syntax for constructing an instance of this +type. ```jldoctest -julia> g = (abs2(x) for x in 1:5 if x != 3); +julia> g = (abs2(x) for x in 1:5); julia> for x in g println(x) end 1 4 +9 16 25 julia> collect(g) -4-element Vector{Int64}: +5-element Vector{Int64}: 1 4 + 9 16 25 ``` diff --git a/base/genericmemory.jl b/base/genericmemory.jl new file mode 100644 index 0000000000000..c1dc215a68d33 --- /dev/null +++ b/base/genericmemory.jl @@ -0,0 +1,315 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +## genericmemory.jl: Managed Memory + +""" + GenericMemory{kind::Symbol, T, addrspace=Core.CPU} <: AbstractVector{T} + +One-dimensional dense array with elements of type `T`. + +!!! compat "Julia 1.11" + This type requires Julia 1.11 or later. +""" +GenericMemory + +""" + Memory{T} == GenericMemory{:not_atomic, T, Core.CPU} + +One-dimensional dense array with elements of type `T`. + +!!! compat "Julia 1.11" + This type requires Julia 1.11 or later. +""" +Memory + +""" + AtomicMemory{T} == GenericMemory{:atomic, T, Core.CPU} + +One-dimensional dense array with elements of type `T`, where each element is +independently atomic when accessed, and cannot be set non-atomically. + +!!! compat "Julia 1.11" + This type requires Julia 1.11 or later. +""" +AtomicMemory + +## Basic functions ## + +using Core: memoryrefoffset, memoryref_isassigned # import more functions which were not essential + +size(a::GenericMemory, d::Int) = + d < 1 ? error("dimension out of range") : + d == 1 ? length(a) : + 1 +size(a::GenericMemory, d::Integer) = size(a, convert(Int, d)) +size(a::GenericMemory) = (length(a),) + +IndexStyle(::Type{<:GenericMemory}) = IndexLinear() + +pointer(mem::GenericMemoryRef) = unsafe_convert(Ptr{Cvoid}, mem) # no bounds check, even for empty array + +_unsetindex!(A::Memory, i::Int) = (@_propagate_inbounds_meta; _unsetindex!(GenericMemoryRef(A, i)); A) +function _unsetindex!(A::MemoryRef{T}) where T + @_terminates_locally_meta + @_propagate_inbounds_meta + @inline + @boundscheck GenericMemoryRef(A, 1) + mem = A.mem + MemT = typeof(mem) + arrayelem = datatype_arrayelem(MemT) + elsz = datatype_layoutsize(MemT) + isboxed = 1; isunion = 2 + t = @_gc_preserve_begin mem + p = Ptr{Ptr{Cvoid}}(@inbounds pointer(A)) + if arrayelem == isboxed + Intrinsics.atomic_pointerset(p, C_NULL, :monotonic) + elseif arrayelem != isunion + if !datatype_pointerfree(T::DataType) + for j = 1:Core.sizeof(Ptr{Cvoid}):elsz + # XXX: this violates memory ordering, since it writes more than one C_NULL to each + Intrinsics.atomic_pointerset(p + j - 1, C_NULL, :monotonic) + end + end + end + @_gc_preserve_end t + return A +end + +elsize(@nospecialize _::Type{A}) where {T,A<:GenericMemory{<:Any,T}} = aligned_sizeof(T) # XXX: probably supposed to be the stride? +sizeof(a::GenericMemory) = Core.sizeof(a) + +# multi arg case will be overwritten later. This is needed for bootstrapping +function isassigned(a::GenericMemory, i::Int) + @inline + @boundscheck (i - 1)%UInt < length(a)%UInt || return false + return @inbounds memoryref_isassigned(GenericMemoryRef(a, i), default_access_order(a), false) +end + +isassigned(a::GenericMemoryRef) = memoryref_isassigned(a, default_access_order(a), @_boundscheck) + +## copy ## +function unsafe_copyto!(dest::MemoryRef{T}, src::MemoryRef{T}, n) where {T} + @_terminates_globally_notaskstate_meta + n == 0 && return dest + @boundscheck GenericMemoryRef(dest, n), GenericMemoryRef(src, n) + ccall(:jl_genericmemory_copyto, Cvoid, (Any, Ptr{Cvoid}, Any, Ptr{Cvoid}, Int), dest.mem, dest.ptr_or_offset, src.mem, src.ptr_or_offset, Int(n)) + return dest +end + +function unsafe_copyto!(dest::GenericMemoryRef, src::GenericMemoryRef, n) + n == 0 && return dest + @boundscheck GenericMemoryRef(dest, n), GenericMemoryRef(src, n) + unsafe_copyto!(dest.mem, memoryrefoffset(dest), src.mem, memoryrefoffset(src), n) + return dest +end + +function unsafe_copyto!(dest::Memory{T}, doffs, src::Memory{T}, soffs, n) where{T} + n == 0 && return dest + unsafe_copyto!(GenericMemoryRef(dest, doffs), GenericMemoryRef(src, soffs), n) + return dest +end + +function unsafe_copyto!(dest::Memory, doffs, src::Memory, soffs, n) + @_terminates_locally_meta + n == 0 && return dest + # use pointer math to determine if they are deemed to alias + destp = pointer(dest, doffs) + srcp = pointer(src, soffs) + endp = pointer(src, soffs + n - 1) + @inbounds if destp < srcp || destp > endp + for i = 1:n + if isassigned(src, soffs + i - 1) + dest[doffs + i - 1] = src[soffs + i - 1] + else + _unsetindex!(dest, doffs + i - 1) + end + end + else + for i = n:-1:1 + if isassigned(src, soffs + i - 1) + dest[doffs + i - 1] = src[soffs + i - 1] + else + _unsetindex!(dest, doffs + i - 1) + end + end + end + return dest +end + +copy(a::T) where {T<:Memory} = ccall(:jl_genericmemory_copy, Ref{T}, (Any,), a) + +function copyto!(dest::Memory, doffs::Integer, src::Memory, soffs::Integer, n::Integer) + n < 0 && _throw_argerror("Number of elements to copy must be non-negative.") + unsafe_copyto!(dest, doffs, src, soffs, n) + return dest +end + + +## Constructors ## + +similar(a::GenericMemory) = + typeof(a)(undef, length(a)) +similar(a::GenericMemory{kind,<:Any,AS}, T::Type) where {kind,AS} = + GenericMemory{kind,T,AS}(undef, length(a)) +similar(a::GenericMemory, m::Int) = + typeof(a)(undef, m) +similar(a::GenericMemory{kind,<:Any,AS}, T::Type, dims::Dims{1}) where {kind,AS} = + GenericMemory{kind,T,AS}(undef, dims[1]) +similar(a::GenericMemory, dims::Dims{1}) = + typeof(a)(undef, dims[1]) + +function fill!(a::Union{Memory{UInt8}, Memory{Int8}}, x::Integer) + t = @_gc_preserve_begin a + p = unsafe_convert(Ptr{Cvoid}, a) + T = eltype(a) + memset(p, x isa T ? x : convert(T, x), length(a)) + @_gc_preserve_end t + return a +end + +## Conversions ## + +convert(::Type{T}, a::AbstractArray) where {T<:Memory} = a isa T ? a : T(a)::T + +promote_rule(a::Type{Memory{T}}, b::Type{Memory{S}}) where {T,S} = el_same(promote_type(T,S), a, b) + +## Constructors ## + +if nameof(@__MODULE__) === :Base # avoid method overwrite +# constructors should make copies +Memory{T}(x::AbstractArray{S,1}) where {T,S} = copyto_axcheck!(Memory{T}(undef, size(x)), x) +end + +## copying iterators to containers + +## Iteration ## + +iterate(A::Memory, i=1) = (@inline; (i - 1)%UInt < length(A)%UInt ? (@inbounds A[i], i + 1) : nothing) + +## Indexing: getindex ## + +# Faster contiguous indexing using copyto! for AbstractUnitRange and Colon +function getindex(A::Memory, I::AbstractUnitRange{<:Integer}) + @inline + @boundscheck checkbounds(A, I) + lI = length(I) + X = similar(A, axes(I)) + if lI > 0 + copyto!(X, firstindex(X), A, first(I), lI) + end + return X +end + +# getindex for carrying out logical indexing for AbstractUnitRange{Bool} as Bool <: Integer +getindex(a::Memory, r::AbstractUnitRange{Bool}) = getindex(a, to_index(r)) + +getindex(A::Memory, c::Colon) = copy(A) + +## Indexing: setindex! ## + +function setindex!(A::Memory{T}, x, i1::Int) where {T} + val = x isa T ? x : convert(T,x)::T + ref = memoryref(memoryref(A), i1, @_boundscheck) + memoryrefset!(ref, val, :not_atomic, @_boundscheck) + return A +end +function setindex!(A::Memory{T}, x, i1::Int, i2::Int, I::Int...) where {T} + @inline + @boundscheck (i2 == 1 && all(==(1), I)) || throw_boundserror(A, (i1, i2, I...)) + setindex!(A, x, i1) +end + +# Faster contiguous setindex! with copyto! +function setindex!(A::Memory{T}, X::Memory{T}, I::AbstractUnitRange{Int}) where T + @inline + @boundscheck checkbounds(A, I) + lI = length(I) + @boundscheck setindex_shape_check(X, lI) + if lI > 0 + unsafe_copyto!(A, first(I), X, 1, lI) + end + return A +end +function setindex!(A::Memory{T}, X::Memory{T}, c::Colon) where T + @inline + lI = length(A) + @boundscheck setindex_shape_check(X, lI) + if lI > 0 + unsafe_copyto!(A, 1, X, 1, lI) + end + return A +end + +# use memcmp for cmp on byte arrays +function cmp(a::Memory{UInt8}, b::Memory{UInt8}) + ta = @_gc_preserve_begin a + tb = @_gc_preserve_begin b + pa = unsafe_convert(Ptr{Cvoid}, a) + pb = unsafe_convert(Ptr{Cvoid}, b) + c = memcmp(pa, pb, min(length(a),length(b))) + @_gc_preserve_end ta + @_gc_preserve_end tb + return c < 0 ? -1 : c > 0 ? +1 : cmp(length(a),length(b)) +end + +const BitIntegerMemory{N} = Union{map(T->Memory{T}, BitInteger_types)...} +# use memcmp for == on bit integer types +function ==(a::M, b::M) where {M <: BitIntegerMemory} + if length(a) == length(b) + ta = @_gc_preserve_begin a + tb = @_gc_preserve_begin b + pa = unsafe_convert(Ptr{Cvoid}, a) + pb = unsafe_convert(Ptr{Cvoid}, b) + c = memcmp(pa, pb, sizeof(eltype(M)) * length(a)) + @_gc_preserve_end ta + @_gc_preserve_end tb + return c == 0 + else + return false + end +end + +function findall(pred::Fix2{typeof(in),<:Union{Memory{<:Real},Real}}, x::Memory{<:Real}) + if issorted(x, Sort.Forward) && issorted(pred.x, Sort.Forward) + return _sortedfindin(x, pred.x) + else + return _findin(x, pred.x) + end +end + +# Copying subregions +function indcopy(sz::Dims, I::GenericMemory) + n = length(I) + s = sz[n] + for i = n+1:length(sz) + s *= sz[i] + end + dst = eltype(I)[_findin(I[i], i < n ? (1:sz[i]) : (1:s)) for i = 1:n] + src = eltype(I)[I[i][_findin(I[i], i < n ? (1:sz[i]) : (1:s))] for i = 1:n] + dst, src +end + +# Wrapping a memory region in an Array +@eval begin # @eval for the Array construction. Block for the docstring. + function reshape(m::GenericMemory{M, T}, dims::Vararg{Int, N}) where {M, T, N} + len = Core.checked_dims(dims...) + length(m) == len || throw(DimensionMismatch("parent has $(length(m)) elements, which is incompatible with size $(dims)")) + ref = MemoryRef(m) + $(Expr(:new, :(Array{T, N}), :ref, :dims)) + end + + """ + view(m::GenericMemory{M, T}, inds::Union{UnitRange, OneTo}) + + Create a vector `v::Vector{T}` backed by the specified indices of `m`. It is only safe to + resize `v` if `m` is subseqently not used. + """ + function view(m::GenericMemory{M, T}, inds::Union{UnitRange, OneTo}) where {M, T} + isempty(inds) && return T[] # needed to allow view(Memory{T}(undef, 0), 2:1) + @boundscheck checkbounds(m, inds) + ref = MemoryRef(m, first(inds)) # @inbounds would be safe here but does not help performance. + dims = (Int(length(inds)),) + $(Expr(:new, :(Array{T, 1}), :ref, :dims)) + end +end +view(m::GenericMemory, inds::Colon) = view(m, eachindex(m)) diff --git a/base/gmp.jl b/base/gmp.jl index ead80b56541a6..a7caa8dfcdafd 100644 --- a/base/gmp.jl +++ b/base/gmp.jl @@ -320,11 +320,6 @@ function BigInt(x::Float64) unsafe_trunc(BigInt,x) end -function round(::Type{BigInt}, x::Union{Float16,Float32,Float64}, r::RoundingMode{:ToZero}) - isfinite(x) || throw(InexactError(:trunc, BigInt, x)) - unsafe_trunc(BigInt,x) -end - BigInt(x::Float16) = BigInt(Float64(x)) BigInt(x::Float32) = BigInt(Float64(x)) @@ -611,8 +606,8 @@ function top_set_bit(x::BigInt) x.size * sizeof(Limb) << 3 - leading_zeros(GC.@preserve x unsafe_load(x.d, x.size)) end -divrem(x::BigInt, y::BigInt) = MPZ.tdiv_qr(x, y) -divrem(x::BigInt, y::Integer) = MPZ.tdiv_qr(x, big(y)) +divrem(x::BigInt, y::BigInt, ::typeof(RoundToZero) = RoundToZero) = MPZ.tdiv_qr(x, y) +divrem(x::BigInt, y::Integer, ::typeof(RoundToZero) = RoundToZero) = MPZ.tdiv_qr(x, BigInt(y)) cmp(x::BigInt, y::BigInt) = sign(MPZ.cmp(x, y)) cmp(x::BigInt, y::ClongMax) = sign(MPZ.cmp_si(x, y)) @@ -705,7 +700,7 @@ function prod(arr::AbstractArray{BigInt}) foldl(MPZ.mul!, arr; init) end -factorial(x::BigInt) = isneg(x) ? BigInt(0) : MPZ.fac_ui(x) +factorial(n::BigInt) = !isneg(n) ? MPZ.fac_ui(n) : throw(DomainError(n, "`n` must not be negative.")) function binomial(n::BigInt, k::Integer) k < 0 && return BigInt(0) @@ -759,7 +754,7 @@ function string(n::BigInt; base::Integer = 10, pad::Integer = 1) iszero(n) && pad < 1 && return "" nd1 = ndigits(n, base=base) nd = max(nd1, pad) - sv = Base.StringVector(nd + isneg(n)) + sv = Base.StringMemory(nd + isneg(n)) GC.@preserve sv MPZ.get_str!(pointer(sv) + nd - nd1, base, n) @inbounds for i = (1:nd-nd1) .+ isneg(n) sv[i] = '0' % UInt8 diff --git a/base/hamt.jl b/base/hamt.jl index 3c95d974800fa..e3e4b4bd03ba9 100644 --- a/base/hamt.jl +++ b/base/hamt.jl @@ -29,11 +29,11 @@ export HAMT # # At each level we use a 32bit bitmap to store which elements are occupied. # Since our storage is "sparse" we need to map from index in [0,31] to -# the actual storage index. We mask the bitmap wiht (1 << i) - 1 and count +# the actual storage index. We mask the bitmap with (1 << i) - 1 and count # the ones in the result. The number of set ones (+1) gives us the index # into the storage array. # -# HAMT can be both persitent and non-persistent. +# HAMT can be both persistent and non-persistent. # The `path` function searches for a matching entries, and for persistency # optionally copies the path so that it can be safely mutated. @@ -62,36 +62,54 @@ A HashArrayMappedTrie that optionally supports persistence. mutable struct HAMT{K, V} const data::Vector{Union{Leaf{K, V}, HAMT{K, V}}} bitmap::BITMAP + HAMT{K,V}(data, bitmap) where {K,V} = new{K,V}(data, bitmap) + HAMT{K, V}() where {K, V} = new{K,V}(Vector{Union{Leaf{K, V}, HAMT{K, V}}}(undef, 0), zero(BITMAP)) end -HAMT{K, V}() where {K, V} = HAMT(Vector{Union{Leaf{K, V}, HAMT{K, V}}}(undef, 0), zero(BITMAP)) -function HAMT{K,V}(k::K, v) where {K, V} - v = convert(V, v) - # For a single element we can't have a hash-collision - trie = HAMT(Vector{Union{Leaf{K, V}, HAMT{K, V}}}(undef, 1), zero(BITMAP)) + +Base.@assume_effects :nothrow :effect_free function init_hamt(K, V, k, v) + # For a single element we can't have a 'hash-collision + trie = HAMT{K,V}(Vector{Union{Leaf{K, V}, HAMT{K, V}}}(undef, 1), zero(BITMAP)) trie.data[1] = Leaf{K,V}(k,v) + return trie +end + +Base.@assume_effects :effect_free function HAMT{K,V}((k,v)::Pair{K,V}) where {K, V} + trie = init_hamt(K, V, k, v) bi = BitmapIndex(HashState(k)) set!(trie, bi) return trie end -HAMT(k::K, v::V) where {K, V} = HAMT{K,V}(K, V) +HAMT{K,V}(kv::Pair) where {K, V} = HAMT{K,V}(convert(Pair{K,V}, kv)) +HAMT(pair::Pair{K,V}) where {K, V} = HAMT{K,V}(pair) + +# TODO: Parameterize by hash function struct HashState{K} key::K hash::UInt depth::Int shift::Int end -HashState(key)= HashState(key, hash(key), 0, 0) +HashState(key) = HashState(key, objectid(key), 0, 0) # Reconstruct -HashState(key, depth, shift) = HashState(key, hash(key, UInt(depth ÷ BITS_PER_LEVEL)), depth, shift) +Base.@assume_effects :terminates_locally function HashState(other::HashState, key) + h = HashState(key) + while h.depth !== other.depth + h = next(h) + end + return h +end function next(h::HashState) depth = h.depth + 1 shift = h.shift + BITS_PER_LEVEL + # Assert disabled for effect precision + # @assert h.shift <= MAX_SHIFT if shift > MAX_SHIFT # Note we use `UInt(depth ÷ BITS_PER_LEVEL)` to seed the hash function # the hash docs, do we need to hash `UInt(depth ÷ BITS_PER_LEVEL)` first? - h_hash = hash(h.key, UInt(depth ÷ BITS_PER_LEVEL)) + h_hash = hash(objectid(h.key), UInt(depth ÷ BITS_PER_LEVEL)) + shift = 0 else h_hash = h.hash end @@ -137,8 +155,7 @@ as the current `level`. If a copy function is provided `copyf` use the return `top` for the new persistent tree. """ -@inline function path(trie::HAMT{K,V}, key, _h, copy=false) where {K, V} - h = HashState(key, _h, 0, 0) +@inline @Base.assume_effects :noub :terminates_locally function path(trie::HAMT{K,V}, key, h::HashState, copy=false) where {K, V} if copy trie = top = HAMT{K,V}(Base.copy(trie.data), trie.bitmap) else @@ -151,11 +168,12 @@ new persistent tree. next = @inbounds trie.data[i] if next isa Leaf{K,V} # Check if key match if not we will need to grow. - found = (next.key === h.key || isequal(next.key, h.key)) + found = next.key === h.key return found, true, trie, i, bi, top, h end if copy next = HAMT{K,V}(Base.copy(next.data), next.bitmap) + # :noub because entry_index is guaranteed to be inbounds for trie.data @inbounds trie.data[i] = next end trie = next::HAMT{K,V} @@ -171,7 +189,7 @@ end Internal function that given an obtained path, either set the value or grows the HAMT by inserting a new trie instead. """ -@inline function insert!(found, present, trie::HAMT{K,V}, i, bi, h, val) where {K,V} +@inline @Base.assume_effects :terminates_locally function insert!(found, present, trie::HAMT{K,V}, i, bi, h, val) where {K,V} if found # we found a slot, just set it to the new leaf # replace or insert if present # replace @@ -184,7 +202,7 @@ or grows the HAMT by inserting a new trie instead. @assert present # collision -> grow leaf = @inbounds trie.data[i]::Leaf{K,V} - leaf_h = HashState(leaf.key, h.depth, h.shift) + leaf_h = HashState(h, leaf.key) if leaf_h.hash == h.hash error("Perfect hash collision") end diff --git a/base/hashing.jl b/base/hashing.jl index 5dbae09123bd6..7de9f47de3182 100644 --- a/base/hashing.jl +++ b/base/hashing.jl @@ -29,7 +29,9 @@ See also: [`objectid`](@ref), [`Dict`](@ref), [`Set`](@ref). """ hash(x::Any) = hash(x, zero(UInt)) hash(w::WeakRef, h::UInt) = hash(w.value, h) -hash(T::Type, h::UInt) = hash_uint(3h - ccall(:jl_type_hash, UInt, (Any,), T)) + +# Types can't be deleted, so marking as total allows the compiler to look up the hash +hash(T::Type, h::UInt) = hash_uint(3h - @assume_effects :total ccall(:jl_type_hash, UInt, (Any,), T)) ## hashing general objects ## diff --git a/base/iddict.jl b/base/iddict.jl index 01ff213305d7b..9c133d5ba23c6 100644 --- a/base/iddict.jl +++ b/base/iddict.jl @@ -4,11 +4,12 @@ IdDict([itr]) `IdDict{K,V}()` constructs a hash table using [`objectid`](@ref) as hash and -`===` as equality with keys of type `K` and values of type `V`. +`===` as equality with keys of type `K` and values of type `V`. See [`Dict`](@ref) +for further help and [`IdSet`](@ref) for the set version of this. -See [`Dict`](@ref) for further help. In the example below, The `Dict` -keys are all `isequal` and therefore get hashed the same, so they get overwritten. -The `IdDict` hashes by object-id, and thus preserves the 3 different keys. +In the example below, the `Dict` keys are all `isequal` and therefore get hashed +the same, so they get overwritten. The `IdDict` hashes by object-id, and thus +preserves the 3 different keys. # Examples ```julia-repl @@ -24,10 +25,10 @@ IdDict{Any, String} with 3 entries: ``` """ mutable struct IdDict{K,V} <: AbstractDict{K,V} - ht::Vector{Any} + ht::Memory{Any} count::Int ndel::Int - IdDict{K,V}() where {K, V} = new{K,V}(Vector{Any}(undef, 32), 0, 0) + IdDict{K,V}() where {K, V} = new{K,V}(Memory{Any}(undef, 32), 0, 0) function IdDict{K,V}(itr) where {K, V} d = IdDict{K,V}() @@ -53,23 +54,12 @@ IdDict(ps::Pair{K}...) where {K} = IdDict{K,Any}(ps) IdDict(ps::(Pair{K,V} where K)...) where {V} = IdDict{Any,V}(ps) IdDict(ps::Pair...) = IdDict{Any,Any}(ps) -function IdDict(kv) - try - dict_with_eltype((K, V) -> IdDict{K, V}, kv, eltype(kv)) - catch - if !applicable(iterate, kv) || !all(x->isa(x,Union{Tuple,Pair}),kv) - throw(ArgumentError( - "IdDict(kv): kv needs to be an iterator of tuples or pairs")) - else - rethrow() - end - end -end +IdDict(kv) = dict_with_eltype((K, V) -> IdDict{K, V}, kv, eltype(kv)) empty(d::IdDict, ::Type{K}, ::Type{V}) where {K, V} = IdDict{K,V}() function rehash!(d::IdDict, newsz = length(d.ht)%UInt) - d.ht = ccall(:jl_idtable_rehash, Vector{Any}, (Any, Csize_t), d.ht, newsz) + d.ht = ccall(:jl_idtable_rehash, Memory{Any}, (Any, Csize_t), d.ht, newsz) d end @@ -84,7 +74,7 @@ function sizehint!(d::IdDict, newsz) end function setindex!(d::IdDict{K,V}, @nospecialize(val), @nospecialize(key)) where {K, V} - !isa(key, K) && throw(ArgumentError("$(limitrepr(key)) is not a valid key for type $K")) + !isa(key, K) && throw(KeyTypeError(K, key)) if !(val isa V) # avoid a dynamic call val = convert(V, val)::V end @@ -93,7 +83,7 @@ function setindex!(d::IdDict{K,V}, @nospecialize(val), @nospecialize(key)) where d.ndel = 0 end inserted = RefValue{Cint}(0) - d.ht = ccall(:jl_eqtable_put, Array{Any,1}, (Any, Any, Any, Ptr{Cint}), d.ht, key, val, inserted) + d.ht = ccall(:jl_eqtable_put, Memory{Any}, (Any, Any, Any, Ptr{Cint}), d.ht, key, val, inserted) d.count += inserted[] return d end @@ -133,7 +123,7 @@ function delete!(d::IdDict{K}, @nospecialize(key)) where K end function empty!(d::IdDict) - resize!(d.ht, 32) + d.ht = Memory{Any}(undef, 32) ht = d.ht t = @_gc_preserve_begin ht memset(unsafe_convert(Ptr{Cvoid}, ht), 0, sizeof(ht)) diff --git a/base/idset.jl b/base/idset.jl index 0a4d4275b4231..c46d49968ff73 100644 --- a/base/idset.jl +++ b/base/idset.jl @@ -1,13 +1,36 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -# Like Set, but using IdDict -mutable struct IdSet{T} <: AbstractSet{T} - dict::IdDict{T,Nothing} +""" + IdSet{T}([itr]) + IdSet() - IdSet{T}() where {T} = new(IdDict{T,Nothing}()) - IdSet{T}(s::IdSet{T}) where {T} = new(copy(s.dict)) -end +IdSet{T}() constructs a set (see [`Set`](@ref)) using +`===` as equality with values of type `T`. + +In the example below, the values are all `isequal` so they get overwritten in the ordinary `Set`. +The `IdSet` compares by `===` and so preserves the 3 different values. + +# Examples +```jldoctest; filter = r"\\n\\s*(1|1\\.0|true)" +julia> Set(Any[true, 1, 1.0]) +Set{Any} with 1 element: + 1.0 +julia> IdSet{Any}(Any[true, 1, 1.0]) +IdSet{Any} with 3 elements: + 1.0 + 1 + true +``` +""" +mutable struct IdSet{K} <: AbstractSet{K} + list::Memory{Any} + idxs::Union{Memory{UInt8}, Memory{UInt16}, Memory{UInt32}} + count::Int + max::Int # n.b. always <= length(list) + IdSet{T}() where {T} = new(Memory{Any}(undef, 0), Memory{UInt8}(undef, 0), 0, 0) + IdSet{T}(s::IdSet{T}) where {T} = new(copy(s.list), copy(s.idxs), s.count, s.max) +end IdSet{T}(itr) where {T} = union!(IdSet{T}(), itr) IdSet() = IdSet{Any}() @@ -15,22 +38,77 @@ copymutable(s::IdSet) = typeof(s)(s) emptymutable(s::IdSet{T}, ::Type{U}=T) where {T,U} = IdSet{U}() copy(s::IdSet) = typeof(s)(s) -isempty(s::IdSet) = isempty(s.dict) -length(s::IdSet) = length(s.dict) -in(@nospecialize(x), s::IdSet) = haskey(s.dict, x) -push!(s::IdSet, @nospecialize(x)) = (s.dict[x] = nothing; s) -pop!(s::IdSet, @nospecialize(x)) = (pop!(s.dict, x); x) -pop!(s::IdSet, @nospecialize(x), @nospecialize(default)) = (x in s ? pop!(s, x) : default) -delete!(s::IdSet, @nospecialize(x)) = (delete!(s.dict, x); s) +haskey(s::IdSet, @nospecialize(key)) = ccall(:jl_idset_peek_bp, Int, (Any, Any, Any), s.list, s.idxs, key) != -1 +isempty(s::IdSet) = s.count == 0 +length(s::IdSet) = s.count +in(@nospecialize(x), s::IdSet) = haskey(s, x) +function push!(s::IdSet, @nospecialize(x)) + idx = ccall(:jl_idset_peek_bp, Int, (Any, Any, Any), s.list, s.idxs, x) + if idx >= 0 + s.list[idx + 1] = x + else + if s.max < length(s.list) + idx = s.max + @assert !isassigned(s.list, idx + 1) + s.list[idx + 1] = x + s.max = idx + 1 + else + newidx = RefValue{Int}(0) + setfield!(s, :list, ccall(:jl_idset_put_key, Any, (Any, Any, Ptr{Int}), s.list, x, newidx)) + idx = newidx[] + s.max = idx < 0 ? -idx : idx + 1 + end + @assert s.list[s.max] === x + setfield!(s, :idxs, ccall(:jl_idset_put_idx, Any, (Any, Any, Int), s.list, s.idxs, idx)) + s.count += 1 + end + s +end +function _pop!(s::IdSet, @nospecialize(x)) + removed = ccall(:jl_idset_pop, Int, (Any, Any, Any), s.list, s.idxs, x) + if removed != -1 + s.count -= 1 + while s.max > 0 && !isassigned(s.list, s.max) + s.max -= 1 + end + end + removed +end +pop!(s::IdSet, @nospecialize(x)) = _pop!(s, x) == -1 ? throw(KeyError(x)) : x +pop!(s::IdSet, @nospecialize(x), @nospecialize(default)) = _pop!(s, x) == -1 ? default : x +delete!(s::IdSet, @nospecialize(x)) = (_pop!(s, x); s) + +function sizehint!(s::IdSet, newsz) + # TODO: grow/compact list and perform rehash, if profitable? + # TODO: shrink? + # s.list = resize(s.list, newsz) + # newsz = _tablesz(newsz) + # oldsz = length(s.idxs) + # #grow at least 25% + # if newsz < (oldsz*5)>>2 + # return s + # end + # rehash!(s, newsz) + nothing +end -sizehint!(s::IdSet, newsz) = (sizehint!(s.dict, newsz); s) -empty!(s::IdSet) = (empty!(s.dict); s) +function empty!(s::IdSet) + fill!(s.idxs, 0x00) + list = s.list + for i = 1:s.max + _unsetindex!(list, i) + end + s.count = 0 + s.max = 0 + s +end filter!(f, d::IdSet) = unsafe_filter!(f, d) -function iterate(s::IdSet, state...) - y = iterate(s.dict, state...) - y === nothing && return nothing - ((k, _), i) = y - return (k, i) +function iterate(s::IdSet{S}, state=0) where {S} + while true + state += 1 + state > s.max && return nothing + isassigned(s.list, state) && return s.list[state]::S, state + end end diff --git a/base/indices.jl b/base/indices.jl index 810b9fd5b8627..e629d2d6a03ff 100644 --- a/base/indices.jl +++ b/base/indices.jl @@ -106,26 +106,49 @@ IndexStyle(::IndexStyle, ::IndexStyle) = IndexCartesian() promote_shape(::Tuple{}, ::Tuple{}) = () -function promote_shape(a::Tuple{Int,}, b::Tuple{Int,}) - if a[1] != b[1] - throw(DimensionMismatch("dimensions must match: a has dims $a, b has dims $b")) +# Consistent error message for promote_shape mismatch, hiding type details like +# OneTo. When b ≡ nothing, it is omitted; i can be supplied for an index. +function throw_promote_shape_mismatch(a::Tuple, b::Union{Nothing,Tuple}, i = nothing) + if a isa Tuple{Vararg{Base.OneTo}} && (b === nothing || b isa Tuple{Vararg{Base.OneTo}}) + a = map(lastindex, a)::Dims + b === nothing || (b = map(lastindex, b)::Dims) + end + _has_axes = !(a isa Dims && (b === nothing || b isa Dims)) + if _has_axes + _normalize(d) = map(x -> firstindex(x):lastindex(x), d) + a = _normalize(a) + b === nothing || (b = _normalize(b)) + _things = "axes " + else + _things = "size " + end + msg = IOBuffer() + print(msg, "a has ", _things) + print(msg, a) + if b ≢ nothing + print(msg, ", b has ", _things) + print(msg, b) end + if i ≢ nothing + print(msg, ", mismatch at dim ", i) + end + throw(DimensionMismatch(String(take!(msg)))) +end + +function promote_shape(a::Tuple{Int,}, b::Tuple{Int,}) + a[1] != b[1] && throw_promote_shape_mismatch(a, b) return a end function promote_shape(a::Tuple{Int,Int}, b::Tuple{Int,}) - if a[1] != b[1] || a[2] != 1 - throw(DimensionMismatch("dimensions must match: a has dims $a, b has dims $b")) - end + (a[1] != b[1] || a[2] != 1) && throw_promote_shape_mismatch(a, b) return a end promote_shape(a::Tuple{Int,}, b::Tuple{Int,Int}) = promote_shape(b, a) function promote_shape(a::Tuple{Int, Int}, b::Tuple{Int, Int}) - if a[1] != b[1] || a[2] != b[2] - throw(DimensionMismatch("dimensions must match: a has dims $a, b has dims $b")) - end + (a[1] != b[1] || a[2] != b[2]) && throw_promote_shape_mismatch(a, b) return a end @@ -153,14 +176,10 @@ function promote_shape(a::Dims, b::Dims) return promote_shape(b, a) end for i=1:length(b) - if a[i] != b[i] - throw(DimensionMismatch("dimensions must match: a has dims $a, b has dims $b, mismatch at $i")) - end + a[i] != b[i] && throw_promote_shape_mismatch(a, b, i) end for i=length(b)+1:length(a) - if a[i] != 1 - throw(DimensionMismatch("dimensions must match: a has dims $a, must have singleton at dim $i")) - end + a[i] != 1 && throw_promote_shape_mismatch(a, nothing, i) end return a end @@ -174,14 +193,10 @@ function promote_shape(a::Indices, b::Indices) return promote_shape(b, a) end for i=1:length(b) - if a[i] != b[i] - throw(DimensionMismatch("dimensions must match: a has dims $a, b has dims $b, mismatch at $i")) - end + a[i] != b[i] && throw_promote_shape_mismatch(a, b, i) end for i=length(b)+1:length(a) - if a[i] != 1:1 - throw(DimensionMismatch("dimensions must match: a has dims $a, must have singleton at dim $i")) - end + a[i] != 1:1 && throw_promote_shape_mismatch(a, nothing, i) end return a end @@ -349,15 +364,8 @@ to_indices(A, I::Tuple{}) = () to_indices(A, I::Tuple{Vararg{Int}}) = I to_indices(A, I::Tuple{Vararg{Integer}}) = (@inline; to_indices(A, (), I)) to_indices(A, inds, ::Tuple{}) = () -function to_indices(A, inds, I::Tuple{Any, Vararg{Any}}) - @inline - head = _to_indices1(A, inds, I[1]) - rest = to_indices(A, _cutdim(inds, I[1]), tail(I)) - (head..., rest...) -end - -_to_indices1(A, inds, I1) = (to_index(A, I1),) -_cutdim(inds, I1) = safe_tail(inds) +to_indices(A, inds, I::Tuple{Any, Vararg}) = + (@inline; (to_index(A, I[1]), to_indices(A, safe_tail(inds), tail(I))...)) """ Slice(indices) @@ -415,15 +423,57 @@ first(S::IdentityUnitRange) = first(S.indices) last(S::IdentityUnitRange) = last(S.indices) size(S::IdentityUnitRange) = (length(S.indices),) length(S::IdentityUnitRange) = length(S.indices) -getindex(S::IdentityUnitRange, i::Int) = (@inline; @boundscheck checkbounds(S, i); i) -getindex(S::IdentityUnitRange, i::AbstractUnitRange{<:Integer}) = (@inline; @boundscheck checkbounds(S, i); i) -getindex(S::IdentityUnitRange, i::StepRange{<:Integer}) = (@inline; @boundscheck checkbounds(S, i); i) +unsafe_length(S::IdentityUnitRange) = unsafe_length(S.indices) +getindex(S::IdentityUnitRange, i::Integer) = (@inline; @boundscheck checkbounds(S, i); convert(eltype(S), i)) +getindex(S::IdentityUnitRange, i::Bool) = throw(ArgumentError("invalid index: $i of type Bool")) +function getindex(S::IdentityUnitRange, i::AbstractUnitRange{<:Integer}) + @inline + @boundscheck checkbounds(S, i) + return convert(AbstractUnitRange{eltype(S)}, i) +end +function getindex(S::IdentityUnitRange, i::AbstractUnitRange{Bool}) + @inline + @boundscheck checkbounds(S, i) + range(first(i) ? first(S) : last(S), length = last(i)) +end +function getindex(S::IdentityUnitRange, i::StepRange{<:Integer}) + @inline + @boundscheck checkbounds(S, i) + return convert(AbstractRange{eltype(S)}, i) +end +function getindex(S::IdentityUnitRange, i::StepRange{Bool}) + @inline + @boundscheck checkbounds(S, i) + range(first(i) ? first(S) : last(S), length = last(i), step = Int(step(i))) +end +# Indexing with offset ranges should preserve the axes of the indices +# however, this is only really possible in general with OffsetArrays. +# In some cases, though, we may obtain correct results using Base ranges +# the following methods are added to allow OffsetArrays to dispatch on the first argument without ambiguities +function getindex(S::IdentityUnitRange{<:AbstractUnitRange{<:Integer}}, + i::IdentityUnitRange{<:AbstractUnitRange{<:Integer}}) + @inline + @boundscheck checkbounds(S, i) + return i +end +function getindex(S::Slice{<:AbstractUnitRange{<:Integer}}, + i::IdentityUnitRange{<:AbstractUnitRange{<:Integer}}) + @inline + @boundscheck checkbounds(S, i) + return i +end show(io::IO, r::IdentityUnitRange) = print(io, "Base.IdentityUnitRange(", r.indices, ")") iterate(S::IdentityUnitRange, s...) = iterate(S.indices, s...) # For OneTo, the values and indices of the values are identical, so this may be defined in Base. # In general such an indexing operation would produce offset ranges -getindex(S::OneTo, I::IdentityUnitRange{<:AbstractUnitRange{<:Integer}}) = (@inline; @boundscheck checkbounds(S, I); I) +# This should also ideally return an AbstractUnitRange{eltype(S)}, but currently +# we're restricted to eltype(::IdentityUnitRange) == Int by definition +function getindex(S::OneTo, I::IdentityUnitRange{<:AbstractUnitRange{<:Integer}}) + @inline + @boundscheck checkbounds(S, I) + return I +end """ LinearIndices(A::AbstractArray) @@ -456,7 +506,7 @@ julia> extrema(b) Return a `LinearIndices` array with the specified shape or [`axes`](@ref). -# Example +# Examples The main purpose of this constructor is intuitive conversion from cartesian to linear indexing: @@ -515,6 +565,7 @@ function getindex(iter::LinearIndices, i::AbstractRange{<:Integer}) @boundscheck checkbounds(iter, i) @inbounds isa(iter, LinearIndices{1}) ? iter.indices[1][i] : (first(iter):last(iter))[i] end +copy(iter::LinearIndices) = iter # More efficient iteration — predominantly for non-vector LinearIndices # but one-dimensional LinearIndices must be special-cased to support OffsetArrays iterate(iter::LinearIndices{1}, s...) = iterate(axes1(iter.indices[1]), s...) diff --git a/base/initdefs.jl b/base/initdefs.jl index c04a97971eff2..182984ae01d56 100644 --- a/base/initdefs.jl +++ b/base/initdefs.jl @@ -9,7 +9,7 @@ A string containing the script name passed to Julia from the command line. Note script name remains unchanged from within included files. Alternatively see [`@__FILE__`](@ref). """ -global PROGRAM_FILE = "" +global PROGRAM_FILE::String = "" """ ARGS @@ -25,7 +25,7 @@ Stop the program with an exit code. The default exit code is zero, indicating th program completed successfully. In an interactive session, `exit()` can be called with the keyboard shortcut `^D`. """ -exit(n) = ccall(:jl_exit, Cvoid, (Int32,), n) +exit(n) = ccall(:jl_exit, Union{}, (Int32,), n) exit() = exit(0) const roottask = current_task() @@ -73,22 +73,28 @@ environment variable if set. Each entry in `DEPOT_PATH` is a path to a directory which contains subdirectories used by Julia for various purposes. Here is an overview of some of the subdirectories that may exist in a depot: +* `artifacts`: Contains content that packages use for which Pkg manages the installation of. * `clones`: Contains full clones of package repos. Maintained by `Pkg.jl` and used as a cache. +* `config`: Contains julia-level configuration such as a `startup.jl`. * `compiled`: Contains precompiled `*.ji` files for packages. Maintained by Julia. * `dev`: Default directory for `Pkg.develop`. Maintained by `Pkg.jl` and the user. * `environments`: Default package environments. For instance the global environment for a specific julia version. Maintained by `Pkg.jl`. -* `logs`: Contains logs of `Pkg` and `REPL` operations. Maintained by `Pkg.jl` and `Julia`. +* `logs`: Contains logs of `Pkg` and `REPL` operations. Maintained by `Pkg.jl` and Julia. * `packages`: Contains packages, some of which were explicitly installed and some which are implicit dependencies. Maintained by `Pkg.jl`. * `registries`: Contains package registries. By default only `General`. Maintained by `Pkg.jl`. +* `scratchspaces`: Contains content that a package itself installs via the [`Scratch.jl`](https://github.com/JuliaPackaging/Scratch.jl) package. `Pkg.gc()` will delete content that is known to be unused. + +!!! note + Packages that want to store content should use the `scratchspaces` subdirectory via + [`Scratch.jl`](https://github.com/JuliaPackaging/Scratch.jl) instead of creating new + subdirectories in the depot root. See also [`JULIA_DEPOT_PATH`](@ref JULIA_DEPOT_PATH), and [Code Loading](@ref code-loading). """ const DEPOT_PATH = String[] -function append_default_depot_path!(DEPOT_PATH) - path = joinpath(homedir(), ".julia") - path in DEPOT_PATH || push!(DEPOT_PATH, path) +function append_bundled_depot_path!(DEPOT_PATH) path = abspath(Sys.BINDIR, "..", "local", "share", "julia") path in DEPOT_PATH || push!(DEPOT_PATH, path) path = abspath(Sys.BINDIR, "..", "share", "julia") @@ -100,17 +106,31 @@ function init_depot_path() empty!(DEPOT_PATH) if haskey(ENV, "JULIA_DEPOT_PATH") str = ENV["JULIA_DEPOT_PATH"] + + # explicitly setting JULIA_DEPOT_PATH to the empty string means using no depot isempty(str) && return + + # otherwise, populate the depot path with the entries in JULIA_DEPOT_PATH, + # expanding empty strings to the bundled depot + populated = false for path in eachsplit(str, Sys.iswindows() ? ';' : ':') if isempty(path) - append_default_depot_path!(DEPOT_PATH) + append_bundled_depot_path!(DEPOT_PATH) else path = expanduser(path) path in DEPOT_PATH || push!(DEPOT_PATH, path) + populated = true end end + + # backwards compatibility: if JULIA_DEPOT_PATH only contains empty entries + # (e.g., JULIA_DEPOT_PATH=':'), make sure to use the default depot + if !populated + pushfirst!(DEPOT_PATH, joinpath(homedir(), ".julia")) + end else - append_default_depot_path!(DEPOT_PATH) + push!(DEPOT_PATH, joinpath(homedir(), ".julia")) + append_bundled_depot_path!(DEPOT_PATH) end nothing end @@ -367,9 +387,7 @@ end ## atexit: register exit hooks ## -const atexit_hooks = Callable[ - () -> Filesystem.temp_cleanup_purge(force=true) -] +const atexit_hooks = Callable[] const _atexit_hooks_lock = ReentrantLock() global _atexit_hooks_finished::Bool = false @@ -413,7 +431,7 @@ function _atexit(exitcode::Cint) # will immediately run here. while true local f - Base.@lock _atexit_hooks_lock begin + @lock _atexit_hooks_lock begin # If this is the last iteration, atomically disable atexit hooks to prevent # someone from registering a hook that will never be run. # (We do this inside the loop, so that it is atomic: no one can have registered @@ -434,7 +452,7 @@ function _atexit(exitcode::Cint) end catch ex showerror(stderr, ex) - Base.show_backtrace(stderr, catch_backtrace()) + show_backtrace(stderr, catch_backtrace()) println(stderr) end end @@ -454,7 +472,7 @@ function _postoutput() f() catch ex showerror(stderr, ex) - Base.show_backtrace(stderr, catch_backtrace()) + show_backtrace(stderr, catch_backtrace()) println(stderr) end end @@ -462,7 +480,7 @@ end ## hook for disabling threaded libraries ## -library_threading_enabled = true +library_threading_enabled::Bool = true const disable_library_threading_hooks = [] function at_disable_library_threading(f) diff --git a/base/int.jl b/base/int.jl index 61576d4360835..a25b17e2cc958 100644 --- a/base/int.jl +++ b/base/int.jl @@ -843,166 +843,14 @@ widemul(x::Bool,y::Number) = x * y widemul(x::Number,y::Bool) = x * y -## wide multiplication, Int128 multiply and divide ## - -if Core.sizeof(Int) == 4 - function widemul(u::Int64, v::Int64) - local u0::UInt64, v0::UInt64, w0::UInt64 - local u1::Int64, v1::Int64, w1::UInt64, w2::Int64, t::UInt64 - - u0 = u & 0xffffffff; u1 = u >> 32 - v0 = v & 0xffffffff; v1 = v >> 32 - w0 = u0 * v0 - t = reinterpret(UInt64, u1) * v0 + (w0 >>> 32) - w2 = reinterpret(Int64, t) >> 32 - w1 = u0 * reinterpret(UInt64, v1) + (t & 0xffffffff) - hi = u1 * v1 + w2 + (reinterpret(Int64, w1) >> 32) - lo = w0 & 0xffffffff + (w1 << 32) - return Int128(hi) << 64 + Int128(lo) - end - - function widemul(u::UInt64, v::UInt64) - local u0::UInt64, v0::UInt64, w0::UInt64 - local u1::UInt64, v1::UInt64, w1::UInt64, w2::UInt64, t::UInt64 - - u0 = u & 0xffffffff; u1 = u >>> 32 - v0 = v & 0xffffffff; v1 = v >>> 32 - w0 = u0 * v0 - t = u1 * v0 + (w0 >>> 32) - w2 = t >>> 32 - w1 = u0 * v1 + (t & 0xffffffff) - hi = u1 * v1 + w2 + (w1 >>> 32) - lo = w0 & 0xffffffff + (w1 << 32) - return UInt128(hi) << 64 + UInt128(lo) - end - - function *(u::Int128, v::Int128) - u0 = u % UInt64; u1 = Int64(u >> 64) - v0 = v % UInt64; v1 = Int64(v >> 64) - lolo = widemul(u0, v0) - lohi = widemul(reinterpret(Int64, u0), v1) - hilo = widemul(u1, reinterpret(Int64, v0)) - t = reinterpret(UInt128, hilo) + (lolo >>> 64) - w1 = reinterpret(UInt128, lohi) + (t & 0xffffffffffffffff) - return Int128(lolo & 0xffffffffffffffff) + reinterpret(Int128, w1) << 64 - end - - function *(u::UInt128, v::UInt128) - u0 = u % UInt64; u1 = UInt64(u>>>64) - v0 = v % UInt64; v1 = UInt64(v>>>64) - lolo = widemul(u0, v0) - lohi = widemul(u0, v1) - hilo = widemul(u1, v0) - t = hilo + (lolo >>> 64) - w1 = lohi + (t & 0xffffffffffffffff) - return (lolo & 0xffffffffffffffff) + UInt128(w1) << 64 - end - - function _setbit(x::UInt128, i) - # faster version of `return x | (UInt128(1) << i)` - j = i >> 5 - y = UInt128(one(UInt32) << (i & 0x1f)) - if j == 0 - return x | y - elseif j == 1 - return x | (y << 32) - elseif j == 2 - return x | (y << 64) - elseif j == 3 - return x | (y << 96) - end - return x - end +# Int128 multiply and divide +*(x::T, y::T) where {T<:Union{Int128,UInt128}} = mul_int(x, y) - function divrem(x::UInt128, y::UInt128) - iszero(y) && throw(DivideError()) - if (x >> 64) % UInt64 == 0 - if (y >> 64) % UInt64 == 0 - # fast path: upper 64 bits are zero, so we can fallback to UInt64 division - q64, x64 = divrem(x % UInt64, y % UInt64) - return UInt128(q64), UInt128(x64) - else - # this implies y>x, so - return zero(UInt128), x - end - end - n = leading_zeros(y) - leading_zeros(x) - q = zero(UInt128) - ys = y << n - while n >= 0 - # ys == y * 2^n - if ys <= x - x -= ys - q = _setbit(q, n) - if (x >> 64) % UInt64 == 0 - # exit early, similar to above fast path - if (y >> 64) % UInt64 == 0 - q64, x64 = divrem(x % UInt64, y % UInt64) - q |= q64 - x = UInt128(x64) - end - return q, x - end - end - ys >>>= 1 - n -= 1 - end - return q, x - end +div(x::Int128, y::Int128) = checked_sdiv_int(x, y) +div(x::UInt128, y::UInt128) = checked_udiv_int(x, y) - function div(x::Int128, y::Int128) - (x == typemin(Int128)) & (y == -1) && throw(DivideError()) - return Int128(div(BigInt(x), BigInt(y)))::Int128 - end - div(x::UInt128, y::UInt128) = divrem(x, y)[1] - - function rem(x::Int128, y::Int128) - return Int128(rem(BigInt(x), BigInt(y)))::Int128 - end - - function rem(x::UInt128, y::UInt128) - iszero(y) && throw(DivideError()) - if (x >> 64) % UInt64 == 0 - if (y >> 64) % UInt64 == 0 - # fast path: upper 64 bits are zero, so we can fallback to UInt64 division - return UInt128(rem(x % UInt64, y % UInt64)) - else - # this implies y>x, so - return x - end - end - n = leading_zeros(y) - leading_zeros(x) - ys = y << n - while n >= 0 - # ys == y * 2^n - if ys <= x - x -= ys - if (x >> 64) % UInt64 == 0 - # exit early, similar to above fast path - if (y >> 64) % UInt64 == 0 - x = UInt128(rem(x % UInt64, y % UInt64)) - end - return x - end - end - ys >>>= 1 - n -= 1 - end - return x - end - - function mod(x::Int128, y::Int128) - return Int128(mod(BigInt(x), BigInt(y)))::Int128 - end -else - *(x::T, y::T) where {T<:Union{Int128,UInt128}} = mul_int(x, y) - - div(x::Int128, y::Int128) = checked_sdiv_int(x, y) - div(x::UInt128, y::UInt128) = checked_udiv_int(x, y) - - rem(x::Int128, y::Int128) = checked_srem_int(x, y) - rem(x::UInt128, y::UInt128) = checked_urem_int(x, y) -end +rem(x::Int128, y::Int128) = checked_srem_int(x, y) +rem(x::UInt128, y::UInt128) = checked_urem_int(x, y) # issue #15489: since integer ops are unchecked, they shouldn't check promotion for op in (:+, :-, :*, :&, :|, :xor) diff --git a/base/intfuncs.jl b/base/intfuncs.jl index 558d55c118b34..a89d45eb55fc1 100644 --- a/base/intfuncs.jl +++ b/base/intfuncs.jl @@ -218,7 +218,7 @@ gcdx(a::T, b::T) where T<:Real = throw(MethodError(gcdx, (a,b))) # multiplicative inverse of n mod m, error if none """ - invmod(n, m) + invmod(n::Integer, m::Integer) Take the inverse of `n` modulo `m`: `y` such that ``n y = 1 \\pmod m``, and ``div(y,m) = 0``. This will throw an error if ``m = 0``, or if @@ -257,6 +257,43 @@ function invmod(n::Integer, m::Integer) return mod(x, m) end +""" + invmod(n::Integer, T) where {T <: Base.BitInteger} + invmod(n::T) where {T <: Base.BitInteger} + +Compute the modular inverse of `n` in the integer ring of type `T`, i.e. modulo +`2^N` where `N = 8*sizeof(T)` (e.g. `N = 32` for `Int32`). In other words these +methods satisfy the following identities: +``` +n * invmod(n) == 1 +(n * invmod(n, T)) % T == 1 +(n % T) * invmod(n, T) == 1 +``` +Note that `*` here is modular multiplication in the integer ring, `T`. + +Specifying the modulus implied by an integer type as an explicit value is often +inconvenient since the modulus is by definition too big to be represented by the +type. + +The modular inverse is computed much more efficiently than the general case +using the algorithm described in https://arxiv.org/pdf/2204.04342.pdf. + +!!! compat "Julia 1.11" + The `invmod(n)` and `invmod(n, T)` methods require Julia 1.11 or later. +""" +invmod(n::Integer, ::Type{T}) where {T<:BitInteger} = invmod(n % T) + +function invmod(n::T) where {T<:BitInteger} + isodd(n) || throw(DomainError(n, "Argument must be odd.")) + x = (3*n ⊻ 2) % T + y = (1 - n*x) % T + for _ = 1:trailing_zeros(2*sizeof(T)) + x *= y + true + y *= y + end + return x +end + # ^ for any x supporting * to_power_type(x) = convert(Base._return_type(*, Tuple{typeof(x), typeof(x)}), x) @noinline throw_domerr_powbysq(::Any, p) = throw(DomainError(p, LazyString( @@ -265,21 +302,22 @@ to_power_type(x) = convert(Base._return_type(*, Tuple{typeof(x), typeof(x)}), x) @noinline throw_domerr_powbysq(::Integer, p) = throw(DomainError(p, LazyString( "Cannot raise an integer x to a negative power ", p, ".", "\nMake x or ", p, " a float by adding a zero decimal ", - "(e.g., 2.0^", p, " or 2^", float(p), " instead of 2^", p, ")", + "(e.g., 2.0^", p, " or 2^", float(p), " instead of 2^", p, ") ", "or write 1/x^", -p, ", float(x)^", p, ", x^float(", p, ") or (x//1)^", p, "."))) @noinline throw_domerr_powbysq(::AbstractMatrix, p) = throw(DomainError(p, LazyString( "Cannot raise an integer matrix x to a negative power ", p, ".", "\nMake x a float matrix by adding a zero decimal ", - "(e.g., [2.0 1.0;1.0 0.0]^", p, " instead of [2 1;1 0]^", p, ")", + "(e.g., [2.0 1.0;1.0 0.0]^", p, " instead of [2 1;1 0]^", p, ") ", "or write float(x)^", p, " or Rational.(x)^", p, "."))) -@assume_effects :terminates_locally function power_by_squaring(x_, p::Integer) +# The * keyword supports `*=checked_mul` for `checked_pow` +@assume_effects :terminates_locally function power_by_squaring(x_, p::Integer; mul=*) x = to_power_type(x_) if p == 1 return copy(x) elseif p == 0 return one(x) elseif p == 2 - return x*x + return mul(x, x) elseif p < 0 isone(x) && return copy(x) isone(-x) && return iseven(p) ? one(x) : copy(x) @@ -288,16 +326,16 @@ to_power_type(x) = convert(Base._return_type(*, Tuple{typeof(x), typeof(x)}), x) t = trailing_zeros(p) + 1 p >>= t while (t -= 1) > 0 - x *= x + x = mul(x, x) end y = x while p > 0 t = trailing_zeros(p) + 1 p >>= t while (t -= 1) >= 0 - x *= x + x = mul(x, x) end - y *= x + y = mul(y, x) end return y end @@ -704,7 +742,7 @@ ndigits(x::Integer; base::Integer=10, pad::Integer=1) = max(pad, ndigits0z(x, ba function bin(x::Unsigned, pad::Int, neg::Bool) m = top_set_bit(x) n = neg + max(pad, m) - a = StringVector(n) + a = StringMemory(n) # for i in 0x0:UInt(n-1) # automatic vectorization produces redundant codes # @inbounds a[n - i] = 0x30 + (((x >> i) % UInt8)::UInt8 & 0x1) # end @@ -731,7 +769,7 @@ end function oct(x::Unsigned, pad::Int, neg::Bool) m = div(top_set_bit(x) + 2, 3) n = neg + max(pad, m) - a = StringVector(n) + a = StringMemory(n) i = n while i > neg @inbounds a[i] = 0x30 + ((x % UInt8)::UInt8 & 0x7) @@ -806,7 +844,7 @@ end function dec(x::Unsigned, pad::Int, neg::Bool) n = neg + ndigits(x, pad=pad) - a = StringVector(n) + a = StringMemory(n) append_c_digits_fast(n, x, a, 1) neg && (@inbounds a[1] = 0x2d) # UInt8('-') String(a) @@ -815,7 +853,7 @@ end function hex(x::Unsigned, pad::Int, neg::Bool) m = 2 * sizeof(x) - (leading_zeros(x) >> 2) n = neg + max(pad, m) - a = StringVector(n) + a = StringMemory(n) i = n while i >= 2 b = (x % UInt8)::UInt8 @@ -842,7 +880,7 @@ function _base(base::Integer, x::Integer, pad::Int, neg::Bool) b = (base % Int)::Int digits = abs(b) <= 36 ? base36digits : base62digits n = neg + ndigits(x, base=b, pad=pad) - a = StringVector(n) + a = StringMemory(n) i = n @inbounds while i > neg if b > 0 @@ -918,7 +956,7 @@ julia> bitstring(2.2) function bitstring(x::T) where {T} isprimitivetype(T) || throw(ArgumentError("$T not a primitive type")) sz = sizeof(T) * 8 - str = StringVector(sz) + str = StringMemory(sz) i = sz @inbounds while i >= 4 b = UInt32(sizeof(T) == 1 ? bitcast(UInt8, x) : trunc_int(UInt8, x)) diff --git a/base/io.jl b/base/io.jl index be43a954c621e..fb883234be4df 100644 --- a/base/io.jl +++ b/base/io.jl @@ -25,6 +25,14 @@ end lock(::IO) = nothing unlock(::IO) = nothing + +""" + reseteof(io) + +Clear the EOF flag from IO so that further reads (and possibly writes) are +again allowed. Note that it may immediately get re-set, if the underlying +stream object is at EOF and cannot be resumed. +""" reseteof(x::IO) = nothing const SZ_UNBUFFERED_IO = 65536 @@ -68,6 +76,10 @@ Shutdown the write half of a full-duplex I/O stream. Performs a [`flush`](@ref) first. Notify the other end that no more data will be written to the underlying file. This is not supported by all IO types. +If implemented, `closewrite` causes subsequent `read` or `eof` calls that would +block to instead throw EOF or return true, respectively. If the stream is +already closed, this is idempotent. + # Examples ```jldoctest julia> io = Base.BufferStream(); # this never blocks, so we can read and write on the same Task @@ -119,6 +131,8 @@ data has already been buffered. The result is a `Vector{UInt8}`. """ function readavailable end +function isexecutable end + """ isreadable(io) -> Bool @@ -402,7 +416,14 @@ end """ AbstractPipe -`AbstractPipe` is the abstract supertype for IO pipes that provide for communication between processes. +`AbstractPipe` is an abstract supertype that exists for the convenience of creating +pass-through wrappers for other IO objects, so that you only need to implement the +additional methods relevant to your type. A subtype only needs to implement one or both of +these methods: + + struct P <: AbstractPipe; ...; end + pipe_reader(io::P) = io.out + pipe_writer(io::P) = io.in If `pipe isa AbstractPipe`, it must obey the following interface: @@ -780,10 +801,17 @@ end @noinline unsafe_write(s::IO, p::Ref{T}, n::Integer) where {T} = unsafe_write(s, unsafe_convert(Ref{T}, p)::Ptr, n) # mark noinline to ensure ref is gc-rooted somewhere (by the caller) unsafe_write(s::IO, p::Ptr, n::Integer) = unsafe_write(s, convert(Ptr{UInt8}, p), convert(UInt, n)) -write(s::IO, x::Ref{T}) where {T} = unsafe_write(s, x, Core.sizeof(T)) +function write(s::IO, x::Ref{T}) where {T} + x isa Ptr && error("write cannot copy from a Ptr") + if isbitstype(T) + unsafe_write(s, x, Core.sizeof(T)) + else + write(s, x[]) + end +end write(s::IO, x::Int8) = write(s, reinterpret(UInt8, x)) function write(s::IO, x::Union{Int16,UInt16,Int32,UInt32,Int64,UInt64,Int128,UInt128,Float16,Float32,Float64}) - return write(s, Ref(x)) + return unsafe_write(s, Ref(x), Core.sizeof(x)) end write(s::IO, x::Bool) = write(s, UInt8(x)) @@ -794,38 +822,43 @@ function write(s::IO, A::AbstractArray) error("`write` is not supported on non-isbits arrays") end nb = 0 + r = Ref{eltype(A)}() for a in A - nb += write(s, a) + r[] = a + nb += @noinline unsafe_write(s, r, Core.sizeof(r)) # r must be heap-allocated end return nb end -function write(s::IO, a::Array) - if isbitstype(eltype(a)) - return GC.@preserve a unsafe_write(s, pointer(a), sizeof(a)) - else +function write(s::IO, A::StridedArray) + if !isbitstype(eltype(A)) error("`write` is not supported on non-isbits arrays") end -end - -function write(s::IO, a::SubArray{T,N,<:Array}) where {T,N} - if !isbitstype(T) || !isa(a, StridedArray) - return invoke(write, Tuple{IO, AbstractArray}, s, a) + _checkcontiguous(Bool, A) && + return GC.@preserve A unsafe_write(s, pointer(A), elsize(A) * length(A)) + sz::Dims = size(A) + st::Dims = strides(A) + msz, mst, n = merge_adjacent_dim(sz, st) + mst == 1 || return invoke(write, Tuple{IO, AbstractArray}, s, A) + n == ndims(A) && + return GC.@preserve A unsafe_write(s, pointer(A), elsize(A) * length(A)) + sz′, st′ = tail(sz), tail(st) + while n > 1 + sz′ = (tail(sz′)..., 1) + st′ = (tail(st′)..., 0) + n -= 1 end - elsz = elsize(a) - colsz = size(a,1) * elsz - GC.@preserve a if stride(a,1) != 1 - for idxs in CartesianIndices(size(a)) - unsafe_write(s, pointer(a, idxs), elsz) - end - return elsz * length(a) - elseif N <= 1 - return unsafe_write(s, pointer(a, 1), colsz) - else - for colstart in CartesianIndices((1, size(a)[2:end]...)) - unsafe_write(s, pointer(a, colstart), colsz) + GC.@preserve A begin + nb = 0 + iter = CartesianIndices(sz′) + for I in iter + p = pointer(A) + for i in 1:length(sz′) + p += elsize(A) * st′[i] * (I[i] - 1) + end + nb += unsafe_write(s, p, elsize(A) * msz) end - return colsz * trailingsize(a,2) + return nb end end @@ -856,30 +889,74 @@ end @noinline unsafe_read(s::IO, p::Ref{T}, n::Integer) where {T} = unsafe_read(s, unsafe_convert(Ref{T}, p)::Ptr, n) # mark noinline to ensure ref is gc-rooted somewhere (by the caller) unsafe_read(s::IO, p::Ptr, n::Integer) = unsafe_read(s, convert(Ptr{UInt8}, p), convert(UInt, n)) -read!(s::IO, x::Ref{T}) where {T} = (unsafe_read(s, x, Core.sizeof(T)); x) +function read!(s::IO, x::Ref{T}) where {T} + x isa Ptr && error("read! cannot copy into a Ptr") + if isbitstype(T) + unsafe_read(s, x, Core.sizeof(T)) + else + x[] = read(s, T) + end + return x +end read(s::IO, ::Type{Int8}) = reinterpret(Int8, read(s, UInt8)) function read(s::IO, T::Union{Type{Int16},Type{UInt16},Type{Int32},Type{UInt32},Type{Int64},Type{UInt64},Type{Int128},Type{UInt128},Type{Float16},Type{Float32},Type{Float64}}) - return read!(s, Ref{T}(0))[]::T + r = Ref{T}(0) + unsafe_read(s, r, Core.sizeof(T)) + return r[] end read(s::IO, ::Type{Bool}) = (read(s, UInt8) != 0) read(s::IO, ::Type{Ptr{T}}) where {T} = convert(Ptr{T}, read(s, UInt)) -function read!(s::IO, a::Array{UInt8}) - GC.@preserve a unsafe_read(s, pointer(a), sizeof(a)) - return a +function read!(s::IO, A::AbstractArray{T}) where {T} + if isbitstype(T) && _checkcontiguous(Bool, A) + GC.@preserve A unsafe_read(s, pointer(A), elsize(A) * length(A)) + else + if isbitstype(T) + r = Ref{T}() + for i in eachindex(A) + @noinline unsafe_read(s, r, Core.sizeof(r)) # r must be heap-allocated + A[i] = r[] + end + else + for i in eachindex(A) + A[i] = read(s, T) + end + end + end + return A end -function read!(s::IO, a::AbstractArray{T}) where T - if isbitstype(T) && (a isa Array || a isa FastContiguousSubArray{T,<:Any,<:Array{T}}) - GC.@preserve a unsafe_read(s, pointer(a), sizeof(a)) +function read!(s::IO, A::StridedArray{T}) where {T} + if !isbitstype(T) || _checkcontiguous(Bool, A) + return invoke(read!, Tuple{IO, AbstractArray}, s, A) + end + sz::Dims = size(A) + st::Dims = strides(A) + msz, mst, n = merge_adjacent_dim(sz, st) + mst == 1 || return invoke(read!, Tuple{IO, AbstractArray}, s, A) + if n == ndims(A) + GC.@preserve A unsafe_read(s, pointer(A), elsize(A) * length(A)) else - for i in eachindex(a) - a[i] = read(s, T) + sz′, st′ = tail(sz), tail(st) + while n > 1 + sz′ = (tail(sz′)..., 1) + st′ = (tail(st′)..., 0) + n -= 1 + end + GC.@preserve A begin + iter = CartesianIndices(sz′) + for I in iter + p = pointer(A) + for i in 1:length(sz′) + p += elsize(A) * st′[i] * (I[i] - 1) + end + unsafe_read(s, p, elsize(A) * msz) + end end end - return a + return A end function read(io::IO, ::Type{Char}) diff --git a/base/iobuffer.jl b/base/iobuffer.jl index deb86e774f4e4..f9585b0599919 100644 --- a/base/iobuffer.jl +++ b/base/iobuffer.jl @@ -4,32 +4,45 @@ # Stateful string mutable struct GenericIOBuffer{T<:AbstractVector{UInt8}} <: IO - data::T # T should support: getindex, setindex!, length, copyto!, and resize! + data::T # T should support: getindex, setindex!, length, copyto!, similar, and (optionally) resize! reinit::Bool # if true, data needs to be re-allocated (after take!) readable::Bool writable::Bool seekable::Bool # if not seekable, implementation is free to destroy (compact) past read data append::Bool # add data at end instead of at pointer - size::Int # end pointer (and write pointer if append == true) + size::Int # end pointer (and write pointer if append == true) + offset maxsize::Int # fixed array size (typically pre-allocated) - ptr::Int # read (and maybe write) pointer + ptr::Int # read (and maybe write) pointer + offset + offset::Int # offset of ptr and size from actual start of data and actual size mark::Int # reset mark location for ptr (or <0 for no mark) function GenericIOBuffer{T}(data::T, readable::Bool, writable::Bool, seekable::Bool, append::Bool, maxsize::Integer) where T<:AbstractVector{UInt8} require_one_based_indexing(data) - new(data,false,readable,writable,seekable,append,length(data),maxsize,1,-1) + return new(data, false, readable, writable, seekable, append, length(data), maxsize, 1, 0, -1) end end -const IOBuffer = GenericIOBuffer{Vector{UInt8}} + +const IOBuffer = GenericIOBuffer{Memory{UInt8}} function GenericIOBuffer(data::T, readable::Bool, writable::Bool, seekable::Bool, append::Bool, maxsize::Integer) where T<:AbstractVector{UInt8} GenericIOBuffer{T}(data, readable, writable, seekable, append, maxsize) end +function GenericIOBuffer(data::Vector{UInt8}, readable::Bool, writable::Bool, seekable::Bool, append::Bool, + maxsize::Integer) + ref = data.ref + buf = GenericIOBuffer(ref.mem, readable, writable, seekable, append, maxsize) + offset = memoryrefoffset(ref) - 1 + buf.ptr += offset + buf.size = length(data) + offset + buf.offset = offset + return buf +end # allocate Vector{UInt8}s for IOBuffer storage that can efficiently become Strings -StringVector(n::Integer) = unsafe_wrap(Vector{UInt8}, _string_n(n)) +StringMemory(n::Integer) = unsafe_wrap(Memory{UInt8}, _string_n(n)) +StringVector(n::Integer) = view(StringMemory(n), 1:n)::Vector{UInt8} # IOBuffers behave like Files. They are typically readable and writable. They are seekable. (They can be appendable). @@ -98,7 +111,7 @@ function IOBuffer( flags = open_flags(read=read, write=write, append=append, truncate=truncate) buf = GenericIOBuffer(data, flags.read, flags.write, true, flags.append, Int(maxsize)) if flags.truncate - buf.size = 0 + buf.size = buf.offset end return buf end @@ -113,7 +126,7 @@ function IOBuffer(; size = sizehint !== nothing ? Int(sizehint) : maxsize != typemax(Int) ? Int(maxsize) : 32 flags = open_flags(read=read, write=write, append=append, truncate=truncate) buf = IOBuffer( - StringVector(size), + StringMemory(size), read=flags.read, write=flags.write, append=flags.append, @@ -123,10 +136,11 @@ function IOBuffer(; return buf end -# PipeBuffers behave like Unix Pipes. They are typically readable and writable, they act appendable, and are not seekable. +# PipeBuffers behave somewhat more like Unix Pipes (than Files). They are typically readable and writable, they act appendable, and are not seekable. +# However, they do not support stream notification, so for that there is the BufferStream wrapper around this. """ - PipeBuffer(data::Vector{UInt8}=UInt8[]; maxsize::Integer = typemax(Int)) + PipeBuffer(data::AbstractVector{UInt8}=UInt8[]; maxsize::Integer = typemax(Int)) An [`IOBuffer`](@ref) that allows reading and performs writes by appending. Seeking and truncating are not supported. @@ -134,12 +148,12 @@ See [`IOBuffer`](@ref) for the available constructors. If `data` is given, creates a `PipeBuffer` to operate on a data vector, optionally specifying a size beyond which the underlying `Array` may not be grown. """ -PipeBuffer(data::Vector{UInt8}=UInt8[]; maxsize::Int = typemax(Int)) = - GenericIOBuffer(data,true,true,false,true,maxsize) -PipeBuffer(maxsize::Integer) = (x = PipeBuffer(StringVector(maxsize), maxsize = maxsize); x.size=0; x) +PipeBuffer(data::AbstractVector{UInt8}=Memory{UInt8}(); maxsize::Int = typemax(Int)) = + GenericIOBuffer(data, true, true, false, true, maxsize) +PipeBuffer(maxsize::Integer) = (x = PipeBuffer(StringMemory(maxsize), maxsize = maxsize); x.size = 0; x) _similar_data(b::GenericIOBuffer, len::Int) = similar(b.data, len) -_similar_data(b::IOBuffer, len::Int) = StringVector(len) +_similar_data(b::IOBuffer, len::Int) = StringMemory(len) function copy(b::GenericIOBuffer) ret = typeof(b)(b.reinit ? _similar_data(b, 0) : b.writable ? @@ -147,6 +161,8 @@ function copy(b::GenericIOBuffer) b.readable, b.writable, b.seekable, b.append, b.maxsize) ret.size = b.size ret.ptr = b.ptr + ret.mark = b.mark + ret.offset = b.offset return ret end @@ -155,9 +171,9 @@ show(io::IO, b::GenericIOBuffer) = print(io, "IOBuffer(data=UInt8[...], ", "writable=", b.writable, ", ", "seekable=", b.seekable, ", ", "append=", b.append, ", ", - "size=", b.size, ", ", + "size=", b.size - b.offset, ", ", "maxsize=", b.maxsize == typemax(Int) ? "Inf" : b.maxsize, ", ", - "ptr=", b.ptr, ", ", + "ptr=", b.ptr - b.offset, ", ", "mark=", b.mark, ")") @noinline function _throw_not_readable() @@ -239,29 +255,37 @@ read(from::GenericIOBuffer, ::Type{Ptr{T}}) where {T} = convert(Ptr{T}, read(fro isreadable(io::GenericIOBuffer) = io.readable iswritable(io::GenericIOBuffer) = io.writable -# TODO: GenericIOBuffer is not iterable, so doesn't really have a length. -# This should maybe be sizeof() instead. -#length(io::GenericIOBuffer) = (io.seekable ? io.size : bytesavailable(io)) +filesize(io::GenericIOBuffer) = (io.seekable ? io.size - io.offset : bytesavailable(io)) bytesavailable(io::GenericIOBuffer) = io.size - io.ptr + 1 -position(io::GenericIOBuffer) = io.ptr-1 +position(io::GenericIOBuffer) = io.ptr - io.offset - 1 function skip(io::GenericIOBuffer, n::Integer) - seekto = io.ptr + n - n < 0 && return seek(io, seekto-1) # Does error checking - io.ptr = min(seekto, io.size+1) - return io + skip(io, clamp(n, Int)) +end +function skip(io::GenericIOBuffer, n::Int) + if signbit(n) + seekto = clamp(widen(position(io)) + widen(n), Int) + seek(io, seekto) # Does error checking + else + n_max = io.size + 1 - io.ptr + io.ptr += min(n, n_max) + io + end end function seek(io::GenericIOBuffer, n::Integer) + seek(io, clamp(n, Int)) +end +function seek(io::GenericIOBuffer, n::Int) if !io.seekable ismarked(io) || throw(ArgumentError("seek failed, IOBuffer is not seekable and is not marked")) n == io.mark || throw(ArgumentError("seek failed, IOBuffer is not seekable and n != mark")) end # TODO: REPL.jl relies on the fact that this does not throw (by seeking past the beginning or end # of an GenericIOBuffer), so that would need to be fixed in order to throw an error here - #(n < 0 || n > io.size) && throw(ArgumentError("Attempted to seek outside IOBuffer boundaries.")) - #io.ptr = n+1 - io.ptr = max(min(n+1, io.size+1), 1) + #(n < 0 || n > io.size - io.offset) && throw(ArgumentError("Attempted to seek outside IOBuffer boundaries.")) + #io.ptr = n + io.offset + 1 + io.ptr = clamp(n, 0, io.size - io.offset) + io.offset + 1 return io end @@ -270,32 +294,66 @@ function seekend(io::GenericIOBuffer) return io end +# choose a resize strategy based on whether `resize!` is defined: +# for a Vector, we use `resize!`, but for most other types, +# this calls `similar`+copy +function _resize!(io::GenericIOBuffer, sz::Int) + a = io.data + offset = io.offset + if applicable(resize!, a, sz) + if offset != 0 + size = io.size + size > offset && copyto!(a, 1, a, offset + 1, min(sz, size - offset)) + io.ptr -= offset + io.size -= offset + io.offset = 0 + end + resize!(a, sz) + else + size = io.size + if size >= sz && sz != 0 + b = a + else + b = _similar_data(io, sz == 0 ? 0 : max(overallocation(size - io.offset), sz)) + end + size > offset && copyto!(b, 1, a, offset + 1, min(sz, size - offset)) + io.data = b + io.ptr -= offset + io.size -= offset + io.offset = 0 + end + return io +end + function truncate(io::GenericIOBuffer, n::Integer) io.writable || throw(ArgumentError("truncate failed, IOBuffer is not writeable")) io.seekable || throw(ArgumentError("truncate failed, IOBuffer is not seekable")) n < 0 && throw(ArgumentError("truncate failed, n bytes must be ≥ 0, got $n")) n > io.maxsize && throw(ArgumentError("truncate failed, $(n) bytes is exceeds IOBuffer maxsize $(io.maxsize)")) + n = Int(n) if io.reinit io.data = _similar_data(io, n) io.reinit = false - elseif n > length(io.data) - resize!(io.data, n) + elseif n > length(io.data) + io.offset + _resize!(io, n) end + ismarked(io) && io.mark > n && unmark(io) + n += io.offset io.data[io.size+1:n] .= 0 io.size = n io.ptr = min(io.ptr, n+1) - ismarked(io) && io.mark > n && unmark(io) return io end function compact(io::GenericIOBuffer) io.writable || throw(ArgumentError("compact failed, IOBuffer is not writeable")) io.seekable && throw(ArgumentError("compact failed, IOBuffer is seekable")) + io.reinit && return local ptr::Int, bytes_to_move::Int - if ismarked(io) && io.mark < io.ptr - if io.mark == 0 return end - ptr = io.mark - bytes_to_move = bytesavailable(io) + (io.ptr-io.mark) + if ismarked(io) && io.mark < position(io) + io.mark == 0 && return + ptr = io.mark + io.offset + bytes_to_move = bytesavailable(io) + (io.ptr - ptr) else ptr = io.ptr bytes_to_move = bytesavailable(io) @@ -303,19 +361,24 @@ function compact(io::GenericIOBuffer) copyto!(io.data, 1, io.data, ptr, bytes_to_move) io.size -= ptr - 1 io.ptr -= ptr - 1 - io.mark -= ptr - 1 - return io + io.offset = 0 + return end @noinline function ensureroom_slowpath(io::GenericIOBuffer, nshort::UInt) io.writable || throw(ArgumentError("ensureroom failed, IOBuffer is not writeable")) + if io.reinit + io.data = _similar_data(io, nshort % Int) + io.reinit = false + end if !io.seekable - if !ismarked(io) && io.ptr > 1 && io.size <= io.ptr - 1 + if !ismarked(io) && io.ptr > io.offset+1 && io.size <= io.ptr - 1 io.ptr = 1 io.size = 0 + io.offset = 0 else - datastart = ismarked(io) ? io.mark : io.ptr - if (io.size+nshort > io.maxsize) || + datastart = (ismarked(io) ? io.mark : io.ptr - io.offset) + if (io.size-io.offset+nshort > io.maxsize) || (datastart > 4096 && datastart > io.size - io.ptr) || (datastart > 262144) # apply somewhat arbitrary heuristics to decide when to destroy @@ -329,27 +392,21 @@ end @inline ensureroom(io::GenericIOBuffer, nshort::Int) = ensureroom(io, UInt(nshort)) @inline function ensureroom(io::GenericIOBuffer, nshort::UInt) - if !io.writable || (!io.seekable && io.ptr > 1) + if !io.writable || (!io.seekable && io.ptr > io.offset+1) || io.reinit ensureroom_slowpath(io, nshort) end - n = min((nshort % Int) + (io.append ? io.size : io.ptr-1), io.maxsize) - if io.reinit - io.data = _similar_data(io, n) - io.reinit = false - else - l = length(io.data) - if n > l - _growend!(io.data, (n - l) % UInt) - end + n = min((nshort % Int) + (io.append ? io.size : io.ptr-1) - io.offset, io.maxsize) + l = length(io.data) + io.offset + if n > l + _resize!(io, Int(n)) end return io end -eof(io::GenericIOBuffer) = (io.ptr-1 == io.size) +eof(io::GenericIOBuffer) = (io.ptr - 1 >= io.size) function closewrite(io::GenericIOBuffer) io.writable = false - # OR throw(_UVError("closewrite", UV_ENOTSOCK)) nothing end @@ -358,11 +415,12 @@ end io.writable = false io.seekable = false io.size = 0 + io.offset = 0 io.maxsize = 0 io.ptr = 1 io.mark = -1 - if io.writable - resize!(io.data, 0) + if io.writable && !io.reinit + io.data = _resize!(io, 0) end nothing end @@ -388,45 +446,45 @@ julia> String(take!(io)) function take!(io::GenericIOBuffer) ismarked(io) && unmark(io) if io.seekable - nbytes = io.size - data = copyto!(StringVector(nbytes), 1, io.data, 1, nbytes) + nbytes = io.size - io.offset + data = copyto!(StringVector(nbytes), 1, io.data, io.offset + 1, nbytes) else nbytes = bytesavailable(io) - data = read!(io,StringVector(nbytes)) + data = read!(io, StringVector(nbytes)) end if io.writable io.ptr = 1 io.size = 0 + io.offset = 0 end return data end function take!(io::IOBuffer) ismarked(io) && unmark(io) if io.seekable - if io.writable - if io.reinit - data = StringVector(0) - else - data = resize!(io.data, io.size) - io.reinit = true - end + nbytes = filesize(io) + if nbytes == 0 || io.reinit + data = StringVector(0) + elseif io.writable + data = view(io.data, io.offset+1:nbytes+io.offset) else - data = copyto!(StringVector(io.size), 1, io.data, 1, io.size) + data = copyto!(StringVector(nbytes), 1, io.data, io.offset + 1, nbytes) end else nbytes = bytesavailable(io) - if io.writable - data = io.data - io.reinit = true - _deletebeg!(data, io.ptr-1) - resize!(data, nbytes) + if nbytes == 0 + data = StringVector(0) + elseif io.writable + data = view(io.data, io.ptr:io.ptr+nbytes-1) else - data = read!(io, StringVector(nbytes)) + data = read!(io, data) end end if io.writable + io.reinit = true io.ptr = 1 io.size = 0 + io.offset = 0 end return data end @@ -440,17 +498,19 @@ state. This should only be used internally for performance-critical `String` routines that immediately discard `io` afterwards, and it *assumes* that `io` is writable and seekable. -It saves no allocations compared to `take!`, it just omits some checks. +It might save an allocation compared to `take!` (if the compiler elides the +Array allocation), as well as omits some checks. """ -_unsafe_take!(io::IOBuffer) = resize!(io.data, io.size) +_unsafe_take!(io::IOBuffer) = view(io.data, io.offset+1:io.size) function write(to::IO, from::GenericIOBuffer) + written::Int = bytesavailable(from) if to === from from.ptr = from.size + 1 - return 0 + else + written = GC.@preserve from unsafe_write(to, pointer(from.data, from.ptr), UInt(written)) + from.ptr += written end - written::Int = GC.@preserve from unsafe_write(to, pointer(from.data, from.ptr), UInt(bytesavailable(from))) - from.ptr += written return written end @@ -497,13 +557,13 @@ function readbytes!(io::GenericIOBuffer, b::Array{UInt8}, nb::Int) read_sub(io, b, 1, nr) return nr end -read(io::GenericIOBuffer) = read!(io,StringVector(bytesavailable(io))) +read(io::GenericIOBuffer) = read!(io, StringVector(bytesavailable(io))) readavailable(io::GenericIOBuffer) = read(io) -read(io::GenericIOBuffer, nb::Integer) = read!(io,StringVector(min(nb, bytesavailable(io)))) +read(io::GenericIOBuffer, nb::Integer) = read!(io, StringVector(min(nb, bytesavailable(io)))) function occursin(delim::UInt8, buf::IOBuffer) p = pointer(buf.data, buf.ptr) - q = GC.@preserve buf ccall(:memchr,Ptr{UInt8},(Ptr{UInt8},Int32,Csize_t),p,delim,bytesavailable(buf)) + q = GC.@preserve buf ccall(:memchr, Ptr{UInt8}, (Ptr{UInt8}, Int32, Csize_t), p, delim, bytesavailable(buf)) return q != C_NULL end @@ -532,8 +592,8 @@ end function copyline(out::GenericIOBuffer, s::IO; keep::Bool=false) copyuntil(out, s, 0x0a, keep=true) line = out.data - i = out.size - if keep || i == 0 || line[i] != 0x0a + i = out.size # XXX: this is only correct for appended data. if the data was inserted, only ptr should change + if keep || i == out.offset || line[i] != 0x0a return out elseif i < 2 || line[i-1] != 0x0d i -= 1 diff --git a/base/iostream.jl b/base/iostream.jl index b048d8a5f1d01..0ee51b9b98efd 100644 --- a/base/iostream.jl +++ b/base/iostream.jl @@ -63,6 +63,8 @@ function close(s::IOStream) systemerror("close", bad) end +closewrite(s::IOStream) = nothing + function flush(s::IOStream) sigatomic_begin() bad = @_lock_ios s ccall(:ios_flush, Cint, (Ptr{Cvoid},), s.ios) != 0 @@ -290,12 +292,15 @@ function open(fname::String; lock = true, if !lock s._dolock = false end - systemerror("opening file $(repr(fname))", - ccall(:ios_file, Ptr{Cvoid}, - (Ptr{UInt8}, Cstring, Cint, Cint, Cint, Cint), - s.ios, fname, flags.read, flags.write, flags.create, flags.truncate) == C_NULL) + if ccall(:ios_file, Ptr{Cvoid}, + (Ptr{UInt8}, Cstring, Cint, Cint, Cint, Cint), + s.ios, fname, flags.read, flags.write, flags.create, flags.truncate) == C_NULL + systemerror("opening file $(repr(fname))") + end if flags.append - systemerror("seeking to end of file $fname", ccall(:ios_seek_end, Int64, (Ptr{Cvoid},), s.ios) != 0) + if ccall(:ios_seek_end, Int64, (Ptr{Cvoid},), s.ios) != 0 + systemerror("seeking to end of file $fname") + end end return s end @@ -453,26 +458,24 @@ end function copyuntil(out::IOBuffer, s::IOStream, delim::UInt8; keep::Bool=false) ensureroom(out, 1) # make sure we can read at least 1 byte, for iszero(n) check below - ptr = (out.append ? out.size+1 : out.ptr) - d = out.data - len = length(d) while true + d = out.data + len = length(d) + ptr = (out.append ? out.size+1 : out.ptr) GC.@preserve d @_lock_ios s n= Int(ccall(:jl_readuntil_buf, Csize_t, (Ptr{Cvoid}, UInt8, Ptr{UInt8}, Csize_t), s.ios, delim, pointer(d, ptr), (len - ptr + 1) % Csize_t)) iszero(n) && break ptr += n - if d[ptr-1] == delim - keep || (ptr -= 1) - break - end + found = (d[ptr - 1] == delim) + found && !keep && (ptr -= 1) + out.size = max(out.size, ptr - 1) + out.append || (out.ptr = ptr) + found && break (eof(s) || len == out.maxsize) && break len = min(2len + 64, out.maxsize) - resize!(d, len) - end - out.size = max(out.size, ptr - 1) - if !out.append - out.ptr = ptr + ensureroom(out, len) + @assert length(out.data) >= len end return out end diff --git a/base/irrationals.jl b/base/irrationals.jl index 6513e3269a4d7..eafe388162353 100644 --- a/base/irrationals.jl +++ b/base/irrationals.jl @@ -28,6 +28,9 @@ See also [`AbstractIrrational`](@ref). """ struct Irrational{sym} <: AbstractIrrational end +typemin(::Type{T}) where {T<:Irrational} = T() +typemax(::Type{T}) where {T<:Irrational} = T() + show(io::IO, x::Irrational{sym}) where {sym} = print(io, sym) function show(io::IO, ::MIME"text/plain", x::Irrational{sym}) where {sym} diff --git a/base/iterators.jl b/base/iterators.jl index 937d61f156514..a0b3a6cd4672d 100644 --- a/base/iterators.jl +++ b/base/iterators.jl @@ -11,7 +11,7 @@ const Base = parentmodule(@__MODULE__) using .Base: @inline, Pair, Pairs, AbstractDict, IndexLinear, IndexStyle, AbstractVector, Vector, SizeUnknown, HasLength, HasShape, IsInfinite, EltypeUnknown, HasEltype, OneTo, - @propagate_inbounds, @isdefined, @boundscheck, @inbounds, Generator, + @propagate_inbounds, @isdefined, @boundscheck, @inbounds, Generator, IdDict, AbstractRange, AbstractUnitRange, UnitRange, LinearIndices, TupleOrBottom, (:), |, +, -, *, !==, !, ==, !=, <=, <, >, >=, missing, any, _counttuple, eachindex, ntuple, zero, prod, reduce, in, firstindex, lastindex, @@ -59,7 +59,7 @@ julia> collect(Iterators.map(x -> x^2, 1:3)) 9 ``` """ -map(f, args...) = Base.Generator(f, args...) +map(f, arg, args...) = Base.Generator(f, arg, args...) tail_if_any(::Tuple{}) = () tail_if_any(x::Tuple) = tail(x) @@ -387,6 +387,22 @@ function _zip_min_length(is) end end _zip_min_length(is::Tuple{}) = nothing + +# For a collection of iterators `is`, returns a tuple (b, n), where +# `b` is true when every component of `is` has a statically-known finite +# length and all such lengths are equal. Otherwise, `b` is false. +# `n` is an implementation detail, and will be the `length` of the first +# iterator if it is statically-known and finite. Otherwise, `n` is `nothing`. +function _zip_lengths_finite_equal(is) + i = is[1] + if IteratorSize(i) isa Union{IsInfinite, SizeUnknown} + return (false, nothing) + else + b, n = _zip_lengths_finite_equal(tail(is)) + return (b && (n === nothing || n == length(i)), length(i)) + end +end +_zip_lengths_finite_equal(is::Tuple{}) = (true, nothing) size(z::Zip) = _promote_tuple_shape(Base.map(size, z.is)...) axes(z::Zip) = _promote_tuple_shape(Base.map(axes, z.is)...) _promote_tuple_shape((a,)::Tuple{OneTo}, (b,)::Tuple{OneTo}) = (intersect(a, b),) @@ -468,8 +484,13 @@ zip_iteratoreltype() = HasEltype() zip_iteratoreltype(a) = a zip_iteratoreltype(a, tail...) = and_iteratoreltype(a, zip_iteratoreltype(tail...)) -reverse(z::Zip) = Zip(Base.map(reverse, z.is)) # n.b. we assume all iterators are the same length last(z::Zip) = getindex.(z.is, minimum(Base.map(lastindex, z.is))) +function reverse(z::Zip) + if !first(_zip_lengths_finite_equal(z.is)) + throw(ArgumentError("Cannot reverse zipped iterators of unknown, infinite, or unequal lengths")) + end + Zip(Base.map(reverse, z.is)) +end # filter @@ -932,12 +953,17 @@ struct Cycle{I} end """ - cycle(iter) + cycle(iter[, n::Int]) An iterator that cycles through `iter` forever. -If `iter` is empty, so is `cycle(iter)`. +If `n` is specified, then it cycles through `iter` that many times. +When `iter` is empty, so are `cycle(iter)` and `cycle(iter, n)`. + +`Iterators.cycle(iter, n)` is the lazy equivalent of [`Base.repeat`](@ref)`(vector, n)`, +while [`Iterators.repeated`](@ref)`(iter, n)` is the lazy [`Base.fill`](@ref)`(item, n)`. -See also: [`Iterators.repeated`](@ref), [`Base.repeat`](@ref). +!!! compat "Julia 1.11" + The method `cycle(iter, n)` was added in Julia 1.11. # Examples ```jldoctest @@ -946,13 +972,23 @@ julia> for (i, v) in enumerate(Iterators.cycle("hello")) i > 10 && break end hellohelloh + +julia> foreach(print, Iterators.cycle(['j', 'u', 'l', 'i', 'a'], 3)) +juliajuliajulia + +julia> repeat([1,2,3], 4) == collect(Iterators.cycle([1,2,3], 4)) +true + +julia> fill([1,2,3], 4) == collect(Iterators.repeated([1,2,3], 4)) +true ``` """ cycle(xs) = Cycle(xs) +cycle(xs, n::Integer) = flatten(repeated(xs, n)) eltype(::Type{Cycle{I}}) where {I} = eltype(I) IteratorEltype(::Type{Cycle{I}}) where {I} = IteratorEltype(I) -IteratorSize(::Type{Cycle{I}}) where {I} = IsInfinite() +IteratorSize(::Type{Cycle{I}}) where {I} = IsInfinite() # XXX: this is false if iterator ever becomes empty iterate(it::Cycle) = iterate(it.xs) isdone(it::Cycle) = isdone(it.xs) @@ -979,7 +1015,7 @@ repeated(x) = Repeated(x) An iterator that generates the value `x` forever. If `n` is specified, generates `x` that many times (equivalent to `take(repeated(x), n)`). -See also: [`Iterators.cycle`](@ref), [`Base.repeat`](@ref). +See also [`fill`](@ref Base.fill), and compare [`Iterators.cycle`](@ref). # Examples ```jldoctest @@ -991,6 +1027,12 @@ julia> collect(a) [1 2] [1 2] [1 2] + +julia> ans == fill([1 2], 4) +true + +julia> Iterators.cycle([1 2], 4) |> collect |> println +[1, 2, 1, 2, 1, 2, 1, 2] ``` """ repeated(x, n::Integer) = take(repeated(x), Int(n)) @@ -1401,43 +1443,30 @@ julia> sum(a) # Sum the remaining elements 7 ``` """ -mutable struct Stateful{T, VS, N<:Integer} +mutable struct Stateful{T, VS} itr::T # A bit awkward right now, but adapted to the new iteration protocol nextvalstate::Union{VS, Nothing} - - # Number of remaining elements, if itr is HasLength or HasShape. - # if not, store -1 - number_of_consumed_elements. - # This allows us to defer calculating length until asked for. - # See PR #45924 - remaining::N @inline function Stateful{<:Any, Any}(itr::T) where {T} - itl = iterlength(itr) - new{T, Any, typeof(itl)}(itr, iterate(itr), itl) + return new{T, Any}(itr, iterate(itr)) end @inline function Stateful(itr::T) where {T} VS = approx_iter_type(T) - itl = iterlength(itr) - return new{T, VS, typeof(itl)}(itr, iterate(itr)::VS, itl) + return new{T, VS}(itr, iterate(itr)::VS) end end -function iterlength(it)::Signed - if IteratorSize(it) isa Union{HasShape, HasLength} - return length(it) - else - -1 - end +function reset!(s::Stateful) + setfield!(s, :nextvalstate, iterate(s.itr)) # bypass convert call of setproperty! + return s end - -function reset!(s::Stateful{T,VS}, itr::T=s.itr) where {T,VS} +function reset!(s::Stateful{T}, itr::T) where {T} s.itr = itr - itl = iterlength(itr) - setfield!(s, :nextvalstate, iterate(itr)) - s.remaining = itl - s + reset!(s) + return s end + # Try to find an appropriate type for the (value, state tuple), # by doing a recursive unrolling of the iteration protocol up to # fixpoint. @@ -1459,7 +1488,6 @@ end Stateful(x::Stateful) = x convert(::Type{Stateful}, itr) = Stateful(itr) - @inline isdone(s::Stateful, st=nothing) = s.nextvalstate === nothing @inline function popfirst!(s::Stateful) @@ -1469,8 +1497,6 @@ convert(::Type{Stateful}, itr) = Stateful(itr) else val, state = vs Core.setfield!(s, :nextvalstate, iterate(s.itr, state)) - rem = s.remaining - s.remaining = rem - typeof(rem)(1) return val end end @@ -1480,20 +1506,10 @@ end return ns !== nothing ? ns[1] : sentinel end @inline iterate(s::Stateful, state=nothing) = s.nextvalstate === nothing ? nothing : (popfirst!(s), nothing) -IteratorSize(::Type{<:Stateful{T}}) where {T} = IteratorSize(T) isa HasShape ? HasLength() : IteratorSize(T) +IteratorSize(::Type{<:Stateful{T}}) where {T} = IteratorSize(T) isa IsInfinite ? IsInfinite() : SizeUnknown() eltype(::Type{<:Stateful{T}}) where {T} = eltype(T) IteratorEltype(::Type{<:Stateful{T}}) where {T} = IteratorEltype(T) -function length(s::Stateful) - rem = s.remaining - # If rem is actually remaining length, return it. - # else, rem is number of consumed elements. - if rem >= 0 - rem - else - length(s.itr) - (typeof(rem)(1) - rem) - end -end end # if statement several hundred lines above """ @@ -1526,7 +1542,9 @@ Stacktrace: [...] ``` """ -@propagate_inbounds function only(x) +@propagate_inbounds only(x) = _only(x, iterate) + +@propagate_inbounds function _only(x, ::typeof(iterate)) i = iterate(x) @boundscheck if i === nothing throw(ArgumentError("Collection is empty, must contain exactly 1 element")) @@ -1538,15 +1556,20 @@ Stacktrace: return ret end -# Collections of known size -only(x::Ref) = x[] -only(x::Number) = x -only(x::Char) = x +@inline function _only(x, ::typeof(first)) + @boundscheck if length(x) != 1 + throw(ArgumentError("Collection must contain exactly 1 element")) + end + @inbounds first(x) +end + +@propagate_inbounds only(x::IdDict) = _only(x, first) + +# Specific error messages for tuples and named tuples only(x::Tuple{Any}) = x[1] only(x::Tuple) = throw( ArgumentError("Tuple contains $(length(x)) elements, must contain exactly 1 element") ) -only(a::AbstractArray{<:Any, 0}) = @inbounds return a[] only(x::NamedTuple{<:Any, <:Tuple{Any}}) = first(x) only(x::NamedTuple) = throw( ArgumentError("NamedTuple contains $(length(x)) elements, must contain exactly 1 element") diff --git a/base/libc.jl b/base/libc.jl index 25d1e4c495804..c4a93eae6f3a3 100644 --- a/base/libc.jl +++ b/base/libc.jl @@ -6,7 +6,7 @@ Interface to libc, the C standard library. """ Libc import Base: transcode, windowserror, show -# these need to be defined seperately for bootstrapping but belong to Libc +# these need to be defined separately for bootstrapping but belong to Libc import Base: memcpy, memmove, memset, memcmp import Core.Intrinsics: bitcast @@ -41,10 +41,15 @@ dup(src::RawFD, target::RawFD) = systemerror("dup", -1 == ccall((@static Sys.iswindows() ? :_dup2 : :dup2), Int32, (RawFD, RawFD), src, target)) -show(io::IO, fd::RawFD) = print(io, "RawFD(", bitcast(UInt32, fd), ')') # avoids invalidation via show_default +show(io::IO, fd::RawFD) = print(io, "RawFD(", bitcast(Int32, fd), ')') # avoids invalidation via show_default # Wrapper for an OS file descriptor (for Windows) if Sys.iswindows() + @doc """ + WindowsRawSocket + + Primitive type which wraps the native Windows file `HANDLE`. + """ primitive type WindowsRawSocket sizeof(Ptr) * 8 end # On Windows file descriptors are HANDLE's and 64-bit on 64-bit Windows WindowsRawSocket(handle::Ptr{Cvoid}) = bitcast(WindowsRawSocket, handle) WindowsRawSocket(handle::WindowsRawSocket) = handle @@ -493,6 +498,26 @@ struct Group mem::Vector{String} end +# Gets password-file entry for default user, or a subset thereof +# (e.g., uid and guid are set to -1 on Windows) +function getpw() + ref_pd = Ref(Cpasswd()) + ret = ccall(:uv_os_get_passwd, Cint, (Ref{Cpasswd},), ref_pd) + Base.uv_error("getpw", ret) + + pd = ref_pd[] + pd = Passwd( + pd.username == C_NULL ? "" : unsafe_string(pd.username), + pd.uid, + pd.gid, + pd.shell == C_NULL ? "" : unsafe_string(pd.shell), + pd.homedir == C_NULL ? "" : unsafe_string(pd.homedir), + pd.gecos == C_NULL ? "" : unsafe_string(pd.gecos), + ) + ccall(:uv_os_free_passwd, Cvoid, (Ref{Cpasswd},), ref_pd) + return pd +end + function getpwuid(uid::Unsigned, throw_error::Bool=true) ref_pd = Ref(Cpasswd()) ret = ccall(:uv_os_get_passwd2, Cint, (Ref{Cpasswd}, Culong), ref_pd, uid) @@ -512,6 +537,7 @@ function getpwuid(uid::Unsigned, throw_error::Bool=true) ccall(:uv_os_free_passwd, Cvoid, (Ref{Cpasswd},), ref_pd) return pd end + function getgrgid(gid::Unsigned, throw_error::Bool=true) ref_gp = Ref(Cgroup()) ret = ccall(:uv_os_get_group, Cint, (Ref{Cgroup}, Culong), ref_gp, gid) diff --git a/base/libdl.jl b/base/libdl.jl index 09f4ad4ea2159..199d847572ca4 100644 --- a/base/libdl.jl +++ b/base/libdl.jl @@ -118,7 +118,7 @@ dlopen(s::Symbol, flags::Integer = default_rtld_flags; kwargs...) = function dlopen(s::AbstractString, flags::Integer = default_rtld_flags; throw_error::Bool = true) ret = ccall(:jl_load_dynamic_library, Ptr{Cvoid}, (Cstring,UInt32,Cint), s, flags, Cint(throw_error)) - if ret == C_NULL + if !throw_error && ret == C_NULL return nothing end return ret @@ -130,7 +130,7 @@ end Wrapper for usage with `do` blocks to automatically close the dynamic library once control flow leaves the `do` block scope. -# Example +# Examples ```julia vendor = dlopen("libblas") do lib if Libdl.dlsym(lib, :openblas_set_num_threads; throw_error=false) !== nothing @@ -234,7 +234,7 @@ end Get the full path of the library `libname`. -# Example +# Examples ```julia-repl julia> dlpath("libjulia") ``` @@ -334,7 +334,7 @@ struct LazyLibraryPath LazyLibraryPath(pieces::Vector) = new(pieces) end LazyLibraryPath(args...) = LazyLibraryPath(collect(args)) -Base.string(llp::LazyLibraryPath) = joinpath(string.(llp.pieces)...) +Base.string(llp::LazyLibraryPath) = joinpath(string.(llp.pieces)...)::String Base.cconvert(::Type{Cstring}, llp::LazyLibraryPath) = Base.cconvert(Cstring, string(llp)) # Define `print` so that we can wrap this in a `LazyString` Base.print(io::IO, llp::LazyLibraryPath) = print(io, string(llp)) diff --git a/base/libuv.jl b/base/libuv.jl index 4c56af29e7e60..35424ad3eda3e 100644 --- a/base/libuv.jl +++ b/base/libuv.jl @@ -82,7 +82,13 @@ struct IOError <: Exception IOError(msg::AbstractString, code::Integer) = new(msg, code) end -showerror(io::IO, e::IOError) = print(io, "IOError: ", e.msg) +function showerror(io::IO, e::IOError) + print(io, "IOError: ", e.msg) + if e.code == UV_ENOENT && '~' in e.msg + print(io, "\nMany shells expand '~' to the home directory in unquoted strings. To replicate this behavior, call", + " `expanduser` to expand the '~' character to the user’s home directory.") + end +end function _UVError(pfx::AbstractString, code::Integer) code = Int32(code) @@ -128,9 +134,9 @@ function uv_asynccb end function uv_timercb end function reinit_stdio() - global stdin = init_stdio(ccall(:jl_stdin_stream, Ptr{Cvoid}, ())) - global stdout = init_stdio(ccall(:jl_stdout_stream, Ptr{Cvoid}, ())) - global stderr = init_stdio(ccall(:jl_stderr_stream, Ptr{Cvoid}, ())) + global stdin = init_stdio(ccall(:jl_stdin_stream, Ptr{Cvoid}, ()))::IO + global stdout = init_stdio(ccall(:jl_stdout_stream, Ptr{Cvoid}, ()))::IO + global stderr = init_stdio(ccall(:jl_stderr_stream, Ptr{Cvoid}, ()))::IO opts = JLOptions() if opts.color != 0 have_color = (opts.color == 1) diff --git a/base/linking.jl b/base/linking.jl index fd21ce74c9268..2d68ea730c0fb 100644 --- a/base/linking.jl +++ b/base/linking.jl @@ -150,16 +150,16 @@ else end function link_image_cmd(path, out) - LIBDIR = "-L$(libdir())" PRIVATE_LIBDIR = "-L$(private_libdir())" SHLIBDIR = "-L$(shlibdir())" - LIBS = is_debug() ? ("-ljulia-debug", "-ljulia-internal-debug") : ("-ljulia", "-ljulia-internal") + LIBS = is_debug() ? ("-ljulia-debug", "-ljulia-internal-debug") : + ("-ljulia", "-ljulia-internal") @static if Sys.iswindows() LIBS = (LIBS..., "-lopenlibm", "-lssp", "-lgcc_s", "-lgcc", "-lmsvcrt") end V = VERBOSE[] ? "--verbose" : "" - `$(ld()) $V $SHARED -o $out $WHOLE_ARCHIVE $path $NO_WHOLE_ARCHIVE $LIBDIR $PRIVATE_LIBDIR $SHLIBDIR $LIBS` + `$(ld()) $V $SHARED -o $out $WHOLE_ARCHIVE $path $NO_WHOLE_ARCHIVE $PRIVATE_LIBDIR $SHLIBDIR $LIBS` end function link_image(path, out, internal_stderr::IO=stderr, internal_stdout::IO=stdout) diff --git a/base/loading.jl b/base/loading.jl index a10c7b35d0e18..86cad6f2ed94d 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -256,9 +256,9 @@ struct LoadingCache env_project_file::Dict{String, Union{Bool, String}} project_file_manifest_path::Dict{String, Union{Nothing, String}} require_parsed::Set{String} - identified_where::Dict{Tuple{PkgId, String}, Union{Nothing, Tuple{PkgId, Union{Nothing, String}}}} - identified::Dict{String, Union{Nothing, Tuple{PkgId, Union{Nothing, String}}}} - located::Dict{Tuple{PkgId, Union{String, Nothing}}, Union{Tuple{Union{String, Nothing}, Union{String, Nothing}}, Nothing}} + identified_where::Dict{Tuple{PkgId, String}, Union{Nothing, Tuple{PkgId, String}}} + identified::Dict{String, Union{Nothing, Tuple{PkgId, String}}} + located::Dict{Tuple{PkgId, Union{String, Nothing}}, Union{Tuple{String, String}, Nothing}} end const LOADING_CACHE = Ref{Union{LoadingCache, Nothing}}(nothing) LoadingCache() = LoadingCache(load_path(), Dict(), Dict(), Dict(), Set(), Dict(), Dict(), Dict()) @@ -298,7 +298,7 @@ end ## package identification: determine unique identity of package to be loaded ## # Used by Pkg but not used in loading itself -function find_package(arg) +function find_package(arg) # ::Union{Nothing,String} pkgenv = identify_package_env(arg) pkgenv === nothing && return nothing pkg, env = pkgenv @@ -307,21 +307,21 @@ end """ Base.identify_package_env(name::String)::Union{Tuple{PkgId, String}, Nothing} - Base.identify_package_env(where::Union{Module,PkgId}, name::String)::Union{Tuple{PkgId, String} Nothing} + Base.identify_package_env(where::Union{Module,PkgId}, name::String)::Union{Tuple{PkgId, Union{String, Nothing}}, Nothing} Same as [`Base.identify_package`](@ref) except that the path to the environment where the package is identified -is also returned. +is also returned, except when the identity is not identified. """ identify_package_env(where::Module, name::String) = identify_package_env(PkgId(where), name) function identify_package_env(where::PkgId, name::String) cache = LOADING_CACHE[] if cache !== nothing - pkg_env = get(cache.identified_where, (where, name), nothing) - pkg_env === nothing || return pkg_env + pkg_env = get(cache.identified_where, (where, name), missing) + pkg_env === missing || return pkg_env end pkg_env = nothing if where.name === name - pkg_env = where, nothing + return (where, nothing) elseif where.uuid === nothing pkg_env = identify_package_env(name) # ignore `where` else @@ -342,8 +342,8 @@ end function identify_package_env(name::String) cache = LOADING_CACHE[] if cache !== nothing - pkg_env = get(cache.identified, name, nothing) - pkg_env === nothing || return pkg_env + pkg_env = get(cache.identified, name, missing) + pkg_env === missing || return pkg_env end pkg_env = nothing for env in load_path() @@ -371,7 +371,7 @@ its `PkgId`, or `nothing` if it cannot be found. If only the `name` argument is provided, it searches each environment in the stack and its named direct dependencies. -There `where` argument provides the context from where to search for the +The `where` argument provides the context from where to search for the package: in this case it first checks if the name matches the context itself, otherwise it searches all recursive dependencies (from the resolved manifest of each environment) until it locates the context `where`, and from there @@ -390,17 +390,16 @@ identify_package(where::Module, name::String) = _nothing_or_first(identify_packa identify_package(where::PkgId, name::String) = _nothing_or_first(identify_package_env(where, name)) identify_package(name::String) = _nothing_or_first(identify_package_env(name)) -function locate_package_env(pkg::PkgId, stopenv::Union{String, Nothing}=nothing) +function locate_package_env(pkg::PkgId, stopenv::Union{String, Nothing}=nothing)::Union{Nothing,Tuple{String,String}} cache = LOADING_CACHE[] if cache !== nothing - pathenv = get(cache.located, (pkg, stopenv), nothing) - pathenv === nothing || return pathenv + pathenv = get(cache.located, (pkg, stopenv), missing) + pathenv === missing || return pathenv end path = nothing env′ = nothing if pkg.uuid === nothing for env in load_path() - env′ = env # look for the toplevel pkg `pkg.name` in this entry found = project_deps_get(env, pkg.name) if found !== nothing @@ -410,6 +409,7 @@ function locate_package_env(pkg::PkgId, stopenv::Union{String, Nothing}=nothing) # return the path the entry point for the code, if it could be found # otherwise, signal failure path = implicit_manifest_uuid_path(env, pkg) + env′ = env @goto done end end @@ -419,7 +419,6 @@ function locate_package_env(pkg::PkgId, stopenv::Union{String, Nothing}=nothing) end else for env in load_path() - env′ = env path = manifest_uuid_path(env, pkg) # missing is used as a sentinel to stop looking further down in envs if path === missing @@ -427,7 +426,7 @@ function locate_package_env(pkg::PkgId, stopenv::Union{String, Nothing}=nothing) @goto done end if path !== nothing - path = entry_path(path, pkg.name) + env′ = env @goto done end if !(loading_extension || precompiling_extension) @@ -438,15 +437,20 @@ function locate_package_env(pkg::PkgId, stopenv::Union{String, Nothing}=nothing) # e.g. if they have been explicitly added to the project/manifest mbypath = manifest_uuid_path(Sys.STDLIB, pkg) if mbypath isa String - path = entry_path(mbypath, pkg.name) + path = mbypath + env′ = Sys.STDLIB @goto done end end @label done + if path !== nothing && !isfile_casesensitive(path) + path = nothing + end if cache !== nothing - cache.located[(pkg, stopenv)] = path, env′ + cache.located[(pkg, stopenv)] = path === nothing ? nothing : (path, something(env′)) end - return path, env′ + path === nothing && return nothing + return path, something(env′) end """ @@ -566,7 +570,12 @@ end ## generic project & manifest API ## const project_names = ("JuliaProject.toml", "Project.toml") -const manifest_names = ("JuliaManifest.toml", "Manifest.toml") +const manifest_names = ( + "JuliaManifest-v$(VERSION.major).$(VERSION.minor).toml", + "Manifest-v$(VERSION.major).$(VERSION.minor).toml", + "JuliaManifest.toml", + "Manifest.toml", +) const preferences_names = ("JuliaLocalPreferences.toml", "LocalPreferences.toml") function locate_project_file(env::String) @@ -604,6 +613,23 @@ function env_project_file(env::String)::Union{Bool,String} end end +function base_project(project_file) + base_dir = abspath(joinpath(dirname(project_file), "..")) + base_project_file = env_project_file(base_dir) + base_project_file isa String || return nothing + d = parsed_toml(base_project_file) + workspace = get(d, "workspace", nothing)::Union{Dict{String, Any}, Nothing} + if workspace === nothing + return nothing + end + projects = get(workspace, "projects", nothing)::Union{Vector{String}, Nothing, String} + projects === nothing && return nothing + if projects isa Vector && basename(dirname(project_file)) in projects + return base_project_file + end + return nothing +end + function project_deps_get(env::String, name::String)::Union{Nothing,PkgId} project_file = env_project_file(env) if project_file isa String @@ -615,21 +641,27 @@ function project_deps_get(env::String, name::String)::Union{Nothing,PkgId} return nothing end +function package_get(project_file, where::PkgId, name::String) + proj = project_file_name_uuid(project_file, where.name) + if proj == where + # if `where` matches the project, use [deps] section as manifest, and stop searching + pkg_uuid = explicit_project_deps_get(project_file, name) + return PkgId(pkg_uuid, name) + end + return nothing +end + function manifest_deps_get(env::String, where::PkgId, name::String)::Union{Nothing,PkgId} uuid = where.uuid @assert uuid !== nothing project_file = env_project_file(env) if project_file isa String - # first check if `where` names the Project itself - proj = project_file_name_uuid(project_file, where.name) - if proj == where - # if `where` matches the project, use [deps] section as manifest, and stop searching - pkg_uuid = explicit_project_deps_get(project_file, name) - return PkgId(pkg_uuid, name) - end + pkg = package_get(project_file, where, name) + pkg === nothing || return pkg d = parsed_toml(project_file) exts = get(d, "extensions", nothing)::Union{Dict{String, Any}, Nothing} if exts !== nothing + proj = project_file_name_uuid(project_file, where.name) # Check if `where` is an extension of the project if where.name in keys(exts) && where.uuid == uuid5(proj.uuid::UUID, where.name) # Extensions can load weak deps... @@ -660,7 +692,7 @@ function manifest_uuid_path(env::String, pkg::PkgId)::Union{Nothing,String,Missi proj = project_file_name_uuid(project_file, pkg.name) if proj == pkg # if `pkg` matches the project, return the project itself - return project_file_path(project_file) + return project_file_path(project_file, pkg.name) end mby_ext = project_file_ext_path(project_file, pkg.name) mby_ext === nothing || return mby_ext @@ -668,7 +700,20 @@ function manifest_uuid_path(env::String, pkg::PkgId)::Union{Nothing,String,Missi return explicit_manifest_uuid_path(project_file, pkg) elseif project_file # if env names a directory, search it - return implicit_manifest_uuid_path(env, pkg) + proj = implicit_manifest_uuid_path(env, pkg) + proj === nothing || return proj + # if not found + parentid = get(EXT_PRIMED, pkg, nothing) + if parentid !== nothing + _, parent_project_file = entry_point_and_project_file(env, parentid.name) + if parent_project_file !== nothing + parentproj = project_file_name_uuid(parent_project_file, parentid.name) + if parentproj == parentid + mby_ext = project_file_ext_path(parent_project_file, pkg.name) + mby_ext === nothing || return mby_ext + end + end + end end return nothing end @@ -682,7 +727,7 @@ end function project_file_ext_path(project_file::String, name::String) d = parsed_toml(project_file) - p = project_file_path(project_file) + p = dirname(project_file) exts = get(d, "extensions", nothing)::Union{Dict{String, Any}, Nothing} if exts !== nothing if name in keys(exts) @@ -701,9 +746,22 @@ function project_file_name_uuid(project_file::String, name::String)::PkgId return PkgId(uuid, name) end -function project_file_path(project_file::String) +function project_file_path(project_file::String, name::String) d = parsed_toml(project_file) - joinpath(dirname(project_file), get(d, "path", "")::String) + entryfile = get(d, "path", nothing)::Union{String, Nothing} + # "path" entry in project file is soft deprecated + if entryfile === nothing + entryfile = get(d, "entryfile", nothing)::Union{String, Nothing} + end + return entry_path(dirname(project_file), name, entryfile) +end + +function workspace_manifest(project_file) + base = base_project(project_file) + if base !== nothing + return project_file_manifest_path(base) + end + return nothing end # find project file's corresponding manifest file @@ -716,6 +774,10 @@ function project_file_manifest_path(project_file::String)::Union{Nothing,String} end dir = abspath(dirname(project_file)) d = parsed_toml(project_file) + base_manifest = workspace_manifest(project_file) + if base_manifest !== nothing + return base_manifest + end explicit_manifest = get(d, "manifest", nothing)::Union{String, Nothing} manifest_path = nothing if explicit_manifest !== nothing @@ -758,31 +820,46 @@ end function entry_point_and_project_file(dir::String, name::String)::Union{Tuple{Nothing,Nothing},Tuple{String,Nothing},Tuple{String,String}} path = normpath(joinpath(dir, "$name.jl")) isfile_casesensitive(path) && return path, nothing - dir = joinpath(dir, name) - path, project_file = entry_point_and_project_file_inside(dir, name) + dir_name = joinpath(dir, name) + path, project_file = entry_point_and_project_file_inside(dir_name, name) path === nothing || return path, project_file - dir = dir * ".jl" - path, project_file = entry_point_and_project_file_inside(dir, name) + dir_jl = dir_name * ".jl" + path, project_file = entry_point_and_project_file_inside(dir_jl, name) path === nothing || return path, project_file return nothing, nothing end -# given a path and a name, return the entry point -function entry_path(path::String, name::String)::Union{Nothing,String} +# Find the project file for the extension `ext` in the implicit env `dir`` +function implicit_env_project_file_extension(dir::String, ext::PkgId) + for pkg in readdir(dir; join=true) + project_file = env_project_file(pkg) + project_file isa String || continue + proj = project_file_name_uuid(project_file, "") + uuid5(proj.uuid, ext.name) == ext.uuid || continue + path = project_file_ext_path(project_file, ext.name) + if path !== nothing + return path, project_file + end + end + return nothing, nothing +end + +# given a path, name, and possibly an entryfile, return the entry point +function entry_path(path::String, name::String, entryfile::Union{Nothing,String})::String isfile_casesensitive(path) && return normpath(path) - path = normpath(joinpath(path, "src", "$name.jl")) - isfile_casesensitive(path) && return path - return nothing # source not found + entrypoint = entryfile === nothing ? joinpath("src", "$name.jl") : entryfile + return normpath(joinpath(path, entrypoint)) end ## explicit project & manifest API ## # find project file root or deps `name => uuid` mapping +# `ext` is the name of the extension if `name` is loaded from one # return `nothing` if `name` is not found -function explicit_project_deps_get(project_file::String, name::String)::Union{Nothing,UUID} +function explicit_project_deps_get(project_file::String, name::String, ext::Union{String,Nothing}=nothing)::Union{Nothing,UUID} d = parsed_toml(project_file) - root_uuid = dummy_uuid(project_file) if get(d, "name", nothing)::Union{String, Nothing} === name + root_uuid = dummy_uuid(project_file) uuid = get(d, "uuid", nothing)::Union{String, Nothing} return uuid === nothing ? root_uuid : UUID(uuid) end @@ -791,6 +868,19 @@ function explicit_project_deps_get(project_file::String, name::String)::Union{No uuid = get(deps, name, nothing)::Union{String, Nothing} uuid === nothing || return UUID(uuid) end + if ext !== nothing + extensions = get(d, "extensions", nothing) + extensions === nothing && return nothing + ext_data = get(extensions, ext, nothing) + ext_data === nothing && return nothing + if (ext_data isa String && name == ext_data) || (ext_data isa Vector{String} && name in ext_data) + weakdeps = get(d, "weakdeps", nothing)::Union{Dict{String, Any}, Nothing} + weakdeps === nothing && return nothing + wuuid = get(weakdeps, name, nothing)::Union{String, Nothing} + wuuid === nothing && return nothing + return UUID(wuuid) + end + end return nothing end @@ -932,19 +1022,26 @@ end function explicit_manifest_entry_path(manifest_file::String, pkg::PkgId, entry::Dict{String,Any}) path = get(entry, "path", nothing)::Union{Nothing, String} + entryfile = get(entry, "entryfile", nothing)::Union{Nothing, String} if path !== nothing - path = normpath(abspath(dirname(manifest_file), path)) + path = entry_path(normpath(abspath(dirname(manifest_file), path)), pkg.name, entryfile) return path end hash = get(entry, "git-tree-sha1", nothing)::Union{Nothing, String} - hash === nothing && return nothing + if hash === nothing + mbypath = manifest_uuid_path(Sys.STDLIB, pkg) + if mbypath isa String && isfile(mbypath) + return mbypath + end + return nothing + end hash = SHA1(hash) # Keep the 4 since it used to be the default uuid = pkg.uuid::UUID # checked within `explicit_manifest_uuid_path` for slug in (version_slug(uuid, hash), version_slug(uuid, hash, 4)) for depot in DEPOT_PATH path = joinpath(depot, "packages", pkg.name, slug) - ispath(path) && return abspath(path) + ispath(path) && return entry_path(abspath(path), pkg.name, entryfile) end end # no depot contains the package, return missing to stop looking @@ -972,11 +1069,28 @@ end function implicit_manifest_deps_get(dir::String, where::PkgId, name::String)::Union{Nothing,PkgId} @assert where.uuid !== nothing project_file = entry_point_and_project_file(dir, where.name)[2] - project_file === nothing && return nothing # a project file is mandatory for a package with a uuid + if project_file === nothing + # `where` could be an extension + project_file = implicit_env_project_file_extension(dir, where)[2] + project_file === nothing && return nothing + end proj = project_file_name_uuid(project_file, where.name) - proj == where || return nothing # verify that this is the correct project file + ext = nothing + if proj !== where + # `where` could be an extension in `proj` + d = parsed_toml(project_file) + exts = get(d, "extensions", nothing)::Union{Dict{String, Any}, Nothing} + if exts !== nothing && where.name in keys(exts) + if where.uuid !== uuid5(proj.uuid, where.name) + return nothing + end + ext = where.name + else + return nothing + end + end # this is the correct project, so stop searching here - pkg_uuid = explicit_project_deps_get(project_file, name) + pkg_uuid = explicit_project_deps_get(project_file, name, ext) return PkgId(pkg_uuid, name) end @@ -1009,10 +1123,16 @@ function cache_file_entry(pkg::PkgId) uuid === nothing ? pkg.name : package_slug(uuid) end -function find_all_in_cache_path(pkg::PkgId) +# for use during running the REPL precompilation subprocess script, given we don't +# want it to pick up caches that already exist for other optimization levels +const ignore_compiled_cache = PkgId[] + +function find_all_in_cache_path(pkg::PkgId, DEPOT_PATH::typeof(DEPOT_PATH)=DEPOT_PATH) paths = String[] + pkg in ignore_compiled_cache && return paths entrypath, entryfile = cache_file_entry(pkg) - for path in joinpath.(DEPOT_PATH, entrypath) + for path in DEPOT_PATH + path = joinpath(path, entrypath) isdir(path) || continue for file in readdir(path, sort = false) # no sort given we sort later if !((pkg.uuid === nothing && file == entryfile * ".ji") || @@ -1025,9 +1145,35 @@ function find_all_in_cache_path(pkg::PkgId) end end if length(paths) > 1 - # allocating the sort vector is less expensive than using sort!(.. by=mtime), which would - # call the relatively slow mtime multiple times per path - p = sortperm(mtime.(paths), rev = true) + function sort_by(path) + # when using pkgimages, consider those cache files first + pkgimage = if JLOptions().use_pkgimages != 0 + io = open(path, "r") + try + if iszero(isvalid_cache_header(io)) + false + else + _, _, _, _, _, _, _, flags = parse_cache_header(io, path) + CacheFlags(flags).use_pkgimages + end + finally + close(io) + end + else + false + end + (; pkgimage, mtime=mtime(path)) + end + function sort_lt(a, b) + if a.pkgimage != b.pkgimage + return a.pkgimage < b.pkgimage + end + return a.mtime < b.mtime + end + + # allocating the sort vector is less expensive than using sort!(.. by=sort_by), + # which would call the relatively slow mtime multiple times per path + p = sortperm(sort_by.(paths), lt=sort_lt, rev=true) return paths[p] else return paths @@ -1041,10 +1187,26 @@ cachefile_from_ocachefile(cachefile) = string(chopsuffix(cachefile, ".$(Libc.Lib # use an Int counter so that nested @time_imports calls all remain open const TIMING_IMPORTS = Threads.Atomic{Int}(0) +# loads a precompile cache file, ignoring stale_cachefile tests +# assuming all depmods are already loaded and everything is valid # these return either the array of modules loaded from the path / content given # or an Exception that describes why it couldn't be loaded # and it reconnects the Base.Docs.META -function _include_from_serialized(pkg::PkgId, path::String, ocachepath::Union{Nothing, String}, depmods::Vector{Any}) +function _include_from_serialized(pkg::PkgId, path::String, ocachepath::Union{Nothing, String}, depmods::Vector{Any}, ignore_native::Union{Nothing,Bool}=nothing) + if isnothing(ignore_native) + if JLOptions().code_coverage == 0 && JLOptions().malloc_log == 0 + ignore_native = false + else + io = open(path, "r") + try + iszero(isvalid_cache_header(io)) && return ArgumentError("Invalid header in cache file $path.") + _, (includes, _, _), _, _, _, _, _, _ = parse_cache_header(io, path) + ignore_native = pkg_tracked(includes) + finally + close(io) + end + end + end assert_havelock(require_lock) timing_imports = TIMING_IMPORTS[] > 0 try @@ -1054,11 +1216,20 @@ function _include_from_serialized(pkg::PkgId, path::String, ocachepath::Union{No t_comp_before = cumulative_compile_time_ns() end + for i in 1:length(depmods) + dep = depmods[i] + dep isa Module && continue + _, depkey, depbuild_id = dep::Tuple{String, PkgId, UInt128} + @assert root_module_exists(depkey) + dep = root_module(depkey) + depmods[i] = dep + end + if ocachepath !== nothing - @debug "Loading object cache file $ocachepath for $pkg" - sv = ccall(:jl_restore_package_image_from_file, Any, (Cstring, Any, Cint, Cstring), ocachepath, depmods, false, pkg.name) + @debug "Loading object cache file $ocachepath for $(repr("text/plain", pkg))" + sv = ccall(:jl_restore_package_image_from_file, Any, (Cstring, Any, Cint, Cstring, Cint), ocachepath, depmods, false, pkg.name, ignore_native) else - @debug "Loading cache file $path for $pkg" + @debug "Loading cache file $path for $(repr("text/plain", pkg))" sv = ccall(:jl_restore_incremental, Any, (Cstring, Any, Cint, Cstring), path, depmods, false, pkg.name) end if isa(sv, Exception) @@ -1091,7 +1262,7 @@ function _include_from_serialized(pkg::PkgId, path::String, ocachepath::Union{No return M end end - return ErrorException("Required dependency $pkg failed to load from a cache file.") + return ErrorException("Required dependency $(repr("text/plain", pkg)) failed to load from a cache file.") finally timing_imports && cumulative_compile_timing(false) @@ -1148,13 +1319,13 @@ function run_module_init(mod::Module, i::Int=1) cumulative_compile_timing(false); comp_time, recomp_time = (cumulative_compile_time_ns() .- compile_elapsedtimes) ./ 1e6 - print(round(elapsedtime, digits=1), " ms $mod.__init__() ") + print("$(round(elapsedtime, digits=1)) ms $mod.__init__() ") if comp_time > 0 printstyled(Ryu.writefixed(Float64(100 * comp_time / elapsedtime), 2), "% compilation time", color = Base.info_color()) end if recomp_time > 0 perc = Float64(100 * recomp_time / comp_time) - printstyled(" (", perc < 1 ? "<1" : Ryu.writefixed(perc, 0), "% recompilation)", color = Base.warn_color()) + printstyled(" ($(perc < 1 ? "<1" : Ryu.writefixed(perc, 0))% recompilation)", color = Base.warn_color()) end println() end @@ -1199,29 +1370,34 @@ function insert_extension_triggers(pkg::PkgId) path_env_loc = locate_package_env(pkg) path_env_loc === nothing && return path, env_loc = path_env_loc - if path === nothing || env_loc === nothing - return - end insert_extension_triggers(env_loc, pkg) end function insert_extension_triggers(env::String, pkg::PkgId)::Union{Nothing,Missing} project_file = env_project_file(env) - if project_file isa String + if project_file isa String || project_file + implicit_project_file = project_file + if !(implicit_project_file isa String) + # if env names a directory, search it for an implicit project file (for stdlibs) + path, implicit_project_file = entry_point_and_project_file(env, pkg.name) + if !(implicit_project_file isa String) + return nothing + end + end # Look in project for extensions to insert - proj_pkg = project_file_name_uuid(project_file, pkg.name) + proj_pkg = project_file_name_uuid(implicit_project_file, pkg.name) if pkg == proj_pkg - d_proj = parsed_toml(project_file) - weakdeps = get(d_proj, "weakdeps", nothing)::Union{Nothing, Vector{String}, Dict{String,Any}} + d_proj = parsed_toml(implicit_project_file) extensions = get(d_proj, "extensions", nothing)::Union{Nothing, Dict{String, Any}} extensions === nothing && return - weakdeps === nothing && return - if weakdeps isa Dict{String, Any} - return _insert_extension_triggers(pkg, extensions, weakdeps) - end + weakdeps = get(Dict{String, Any}, d_proj, "weakdeps")::Dict{String,Any} + deps = get(Dict{String, Any}, d_proj, "deps")::Dict{String,Any} + total_deps = merge(weakdeps, deps) + return _insert_extension_triggers(pkg, extensions, total_deps) end # Now look in manifest + project_file isa String || return nothing manifest_file = project_file_manifest_path(project_file) manifest_file === nothing && return d = get_deps(parsed_toml(manifest_file)) @@ -1232,27 +1408,35 @@ function insert_extension_triggers(env::String, pkg::PkgId)::Union{Nothing,Missi uuid = get(entry, "uuid", nothing)::Union{String, Nothing} uuid === nothing && continue if UUID(uuid) == pkg.uuid - weakdeps = get(entry, "weakdeps", nothing)::Union{Nothing, Vector{String}, Dict{String,Any}} extensions = get(entry, "extensions", nothing)::Union{Nothing, Dict{String, Any}} extensions === nothing && return - weakdeps === nothing && return - if weakdeps isa Dict{String, Any} - return _insert_extension_triggers(pkg, extensions, weakdeps) + weakdeps = get(Dict{String, Any}, entry, "weakdeps")::Union{Vector{String}, Dict{String,Any}} + deps = get(Dict{String, Any}, entry, "deps")::Union{Vector{String}, Dict{String,Any}} + + function expand_deps_list(deps′::Vector{String}) + deps′_expanded = Dict{String, Any}() + for (dep_name, entries) in d + dep_name in deps′ || continue + entries::Vector{Any} + if length(entries) != 1 + error("expected a single entry for $(repr(dep_name)) in $(repr(project_file))") + end + entry = first(entries)::Dict{String, Any} + uuid = entry["uuid"]::String + deps′_expanded[dep_name] = uuid + end + return deps′_expanded end - d_weakdeps = Dict{String, Any}() - for (dep_name, entries) in d - dep_name in weakdeps || continue - entries::Vector{Any} - if length(entries) != 1 - error("expected a single entry for $(repr(dep_name)) in $(repr(project_file))") - end - entry = first(entries)::Dict{String, Any} - uuid = entry["uuid"]::String - d_weakdeps[dep_name] = uuid + if weakdeps isa Vector{String} + weakdeps = expand_deps_list(weakdeps) end - @assert length(d_weakdeps) == length(weakdeps) - return _insert_extension_triggers(pkg, extensions, d_weakdeps) + if deps isa Vector{String} + deps = expand_deps_list(deps) + end + + total_deps = merge(weakdeps, deps) + return _insert_extension_triggers(pkg, extensions, total_deps) end end end @@ -1260,11 +1444,11 @@ function insert_extension_triggers(env::String, pkg::PkgId)::Union{Nothing,Missi return nothing end -function _insert_extension_triggers(parent::PkgId, extensions::Dict{String, Any}, weakdeps::Dict{String, Any}) +function _insert_extension_triggers(parent::PkgId, extensions::Dict{String, Any}, totaldeps::Dict{String, Any}) for (ext, triggers) in extensions triggers = triggers::Union{String, Vector{String}} triggers isa String && (triggers = [triggers]) - id = PkgId(uuid5(parent.uuid, ext), ext) + id = PkgId(uuid5(parent.uuid::UUID, ext), ext) if id in keys(EXT_PRIMED) || haskey(Base.loaded_modules, id) continue # extension is already primed or loaded, don't add it again end @@ -1274,9 +1458,9 @@ function _insert_extension_triggers(parent::PkgId, extensions::Dict{String, Any} push!(trigger1, gid) for trigger in triggers # TODO: Better error message if this lookup fails? - uuid_trigger = UUID(weakdeps[trigger]::String) + uuid_trigger = UUID(totaldeps[trigger]::String) trigger_id = PkgId(uuid_trigger, trigger) - if !haskey(Base.loaded_modules, trigger_id) || haskey(package_locks, trigger_id) + if !haskey(explicit_loaded_modules, trigger_id) || haskey(package_locks, trigger_id) trigger1 = get!(Vector{ExtensionId}, EXT_DORMITORY, trigger_id) push!(trigger1, gid) else @@ -1371,31 +1555,153 @@ end # End extensions -# should sync with the types of arguments of `stale_cachefile` -const StaleCacheKey = Tuple{Base.PkgId, UInt128, String, String} -""" - Base.isprecompiled(pkg::PkgId; ignore_loaded::Bool=false) +struct CacheFlags + # OOICCDDP - see jl_cache_flags + use_pkgimages::Bool + debug_level::Int + check_bounds::Int + inline::Bool + opt_level::Int +end +function CacheFlags(f::UInt8) + use_pkgimages = Bool(f & 1) + debug_level = Int((f >> 1) & 3) + check_bounds = Int((f >> 3) & 3) + inline = Bool((f >> 5) & 1) + opt_level = Int((f >> 6) & 3) # define OPT_LEVEL in statiddata_utils + CacheFlags(use_pkgimages, debug_level, check_bounds, inline, opt_level) +end +CacheFlags(f::Int) = CacheFlags(UInt8(f)) +function CacheFlags(cf::CacheFlags=CacheFlags(ccall(:jl_cache_flags, UInt8, ())); + use_pkgimages::Union{Nothing,Bool}=nothing, + debug_level::Union{Nothing,Int}=nothing, + check_bounds::Union{Nothing,Int}=nothing, + inline::Union{Nothing,Bool}=nothing, + opt_level::Union{Nothing,Int}=nothing + ) + return CacheFlags( + use_pkgimages === nothing ? cf.use_pkgimages : use_pkgimages, + debug_level === nothing ? cf.debug_level : debug_level, + check_bounds === nothing ? cf.check_bounds : check_bounds, + inline === nothing ? cf.inline : inline, + opt_level === nothing ? cf.opt_level : opt_level + ) +end -Returns whether a given PkgId within the active project is precompiled. +function _cacheflag_to_uint8(cf::CacheFlags)::UInt8 + f = UInt8(0) + f |= cf.use_pkgimages << 0 + f |= cf.debug_level << 1 + f |= cf.check_bounds << 3 + f |= cf.inline << 5 + f |= cf.opt_level << 6 + return f +end -By default this check observes the same approach that code loading takes -with respect to when different versions of dependencies are currently loaded -to that which is expected. To ignore loaded modules and answer as if in a -fresh julia session specify `ignore_loaded=true`. +function show(io::IO, cf::CacheFlags) + print(io, "use_pkgimages = ", cf.use_pkgimages) + print(io, ", debug_level = ", cf.debug_level) + print(io, ", check_bounds = ", cf.check_bounds) + print(io, ", inline = ", cf.inline) + print(io, ", opt_level = ", cf.opt_level) +end -!!! compat "Julia 1.10" - This function requires at least Julia 1.10. -""" -function isprecompiled(pkg::PkgId; +struct ImageTarget + name::String + flags::Int32 + ext_features::String + features_en::Vector{UInt8} + features_dis::Vector{UInt8} +end + +function parse_image_target(io::IO) + flags = read(io, Int32) + nfeature = read(io, Int32) + feature_en = read(io, 4*nfeature) + feature_dis = read(io, 4*nfeature) + name_len = read(io, Int32) + name = String(read(io, name_len)) + ext_features_len = read(io, Int32) + ext_features = String(read(io, ext_features_len)) + ImageTarget(name, flags, ext_features, feature_en, feature_dis) +end + +function parse_image_targets(targets::Vector{UInt8}) + io = IOBuffer(targets) + ntargets = read(io, Int32) + targets = Vector{ImageTarget}(undef, ntargets) + for i in 1:ntargets + targets[i] = parse_image_target(io) + end + return targets +end + +function current_image_targets() + targets = @ccall jl_reflect_clone_targets()::Vector{UInt8} + return parse_image_targets(targets) +end + +struct FeatureName + name::Cstring + bit::UInt32 # bit index into a `uint32_t` array; + llvmver::UInt32 # 0 if it is available on the oldest LLVM version we support +end + +function feature_names() + fnames = Ref{Ptr{FeatureName}}() + nf = Ref{Csize_t}() + @ccall jl_reflect_feature_names(fnames::Ptr{Ptr{FeatureName}}, nf::Ptr{Csize_t})::Cvoid + if fnames[] == C_NULL + @assert nf[] == 0 + return Vector{FeatureName}(undef, 0) + end + Base.unsafe_wrap(Array, fnames[], nf[], own=false) +end + +function test_feature(features::Vector{UInt8}, feat::FeatureName) + bitidx = feat.bit + u8idx = div(bitidx, 8) + 1 + bit = bitidx % 8 + return (features[u8idx] & (1 << bit)) != 0 +end + +function show(io::IO, it::ImageTarget) + print(io, it.name) + if !isempty(it.ext_features) + print(io, ",", it.ext_features) + end + print(io, "; flags=", it.flags) + print(io, "; features_en=(") + first = true + for feat in feature_names() + if test_feature(it.features_en, feat) + name = Base.unsafe_string(feat.name) + if first + first = false + print(io, name) + else + print(io, ", ", name) + end + end + end + print(io, ")") + # Is feature_dis useful? +end + +# should sync with the types of arguments of `stale_cachefile` +const StaleCacheKey = Tuple{Base.PkgId, UInt128, String, String} + +function compilecache_path(pkg::PkgId; ignore_loaded::Bool=false, stale_cache::Dict{StaleCacheKey,Bool}=Dict{StaleCacheKey, Bool}(), cachepaths::Vector{String}=Base.find_all_in_cache_path(pkg), - sourcepath::Union{String,Nothing}=Base.locate_package(pkg) - ) - isnothing(sourcepath) && error("Cannot locate source for $(repr(pkg))") + sourcepath::Union{String,Nothing}=Base.locate_package(pkg), + flags::CacheFlags=CacheFlags()) + path = nothing + isnothing(sourcepath) && error("Cannot locate source for $(repr("text/plain", pkg))") for path_to_try in cachepaths - staledeps = stale_cachefile(sourcepath, path_to_try, ignore_loaded = true) + staledeps = stale_cachefile(sourcepath, path_to_try, ignore_loaded = true, requested_flags=flags) if staledeps === true continue end @@ -1408,7 +1714,7 @@ function isprecompiled(pkg::PkgId; modpaths = find_all_in_cache_path(modkey) for modpath_to_try in modpaths::Vector{String} stale_cache_key = (modkey, modbuild_id, modpath, modpath_to_try)::StaleCacheKey - if get!(() -> stale_cachefile(stale_cache_key...; ignore_loaded) === true, + if get!(() -> stale_cachefile(stale_cache_key...; ignore_loaded, requested_flags=flags) === true, stale_cache, stale_cache_key) continue end @@ -1424,16 +1730,69 @@ function isprecompiled(pkg::PkgId; # file might be read-only and then we fail to update timestamp, which is fine ex isa IOError || rethrow() end - return true + path = path_to_try + break @label check_next_path end - return false + return path +end + +""" + Base.isprecompiled(pkg::PkgId; ignore_loaded::Bool=false) + +Returns whether a given PkgId within the active project is precompiled. + +By default this check observes the same approach that code loading takes +with respect to when different versions of dependencies are currently loaded +to that which is expected. To ignore loaded modules and answer as if in a +fresh julia session specify `ignore_loaded=true`. + +!!! compat "Julia 1.10" + This function requires at least Julia 1.10. +""" +function isprecompiled(pkg::PkgId; + ignore_loaded::Bool=false, + stale_cache::Dict{StaleCacheKey,Bool}=Dict{StaleCacheKey, Bool}(), + cachepaths::Vector{String}=Base.find_all_in_cache_path(pkg), + sourcepath::Union{String,Nothing}=Base.locate_package(pkg), + flags::CacheFlags=CacheFlags()) + path = compilecache_path(pkg; ignore_loaded, stale_cache, cachepaths, sourcepath, flags) + return !isnothing(path) end -# loads a precompile cache file, after checking stale_cachefile tests +""" + Base.isrelocatable(pkg::PkgId) + +Returns whether a given PkgId within the active project is precompiled and the +associated cache is relocatable. + +!!! compat "Julia 1.11" + This function requires at least Julia 1.11. +""" +function isrelocatable(pkg::PkgId) + path = compilecache_path(pkg) + isnothing(path) && return false + io = open(path, "r") + try + iszero(isvalid_cache_header(io)) && throw(ArgumentError("Invalid header in cache file $cachefile.")) + _, (includes, includes_srcfiles, _), _... = _parse_cache_header(io, path) + for inc in includes + !startswith(inc.filename, "@depot") && return false + if inc ∉ includes_srcfiles + # its an include_dependency + track_content = inc.mtime == -1.0 + track_content || return false + end + end + finally + close(io) + end + return true +end + +# search for a precompile cache file to load, after some various checks function _tryrequire_from_serialized(modkey::PkgId, build_id::UInt128) assert_havelock(require_lock) - loaded = nothing if root_module_exists(modkey) loaded = root_module(modkey) else @@ -1443,7 +1802,7 @@ function _tryrequire_from_serialized(modkey::PkgId, build_id::UInt128) modpath = locate_package(modkey) modpath === nothing && return nothing set_pkgorigin_version_path(modkey, String(modpath)) - loaded = _require_search_from_serialized(modkey, String(modpath), build_id) + loaded = _require_search_from_serialized(modkey, String(modpath), build_id, true) finally end_loading(modkey, loaded) end @@ -1453,57 +1812,51 @@ function _tryrequire_from_serialized(modkey::PkgId, build_id::UInt128) end end end - if !(loaded isa Module) || PkgId(loaded) != modkey - return ErrorException("Required dependency $modkey failed to load from a cache file.") + if loaded isa Module && PkgId(loaded) == modkey && module_build_id(loaded) === build_id + return loaded end - return loaded + return ErrorException("Required dependency $modkey failed to load from a cache file.") end -# loads a precompile cache file, ignoring stale_cachefile tests -# assuming all depmods are already loaded and everything is valid -function _tryrequire_from_serialized(modkey::PkgId, path::String, ocachepath::Union{Nothing, String}, sourcepath::String, depmods::Vector{Any}) - assert_havelock(require_lock) - loaded = nothing - if root_module_exists(modkey) - loaded = root_module(modkey) - else - loaded = start_loading(modkey) - if loaded === nothing - try - for i in 1:length(depmods) - dep = depmods[i] - dep isa Module && continue - _, depkey, depbuild_id = dep::Tuple{String, PkgId, UInt128} - @assert root_module_exists(depkey) - dep = root_module(depkey) - depmods[i] = dep +# returns whether the package is tracked in coverage or malloc tracking based on +# JLOptions and includes +function pkg_tracked(includes) + if JLOptions().code_coverage == 0 && JLOptions().malloc_log == 0 + return false + elseif JLOptions().code_coverage == 1 || JLOptions().malloc_log == 1 # user + # Just say true. Pkgimages aren't in Base + return true + elseif JLOptions().code_coverage == 2 || JLOptions().malloc_log == 2 # all + return true + elseif JLOptions().code_coverage == 3 || JLOptions().malloc_log == 3 # tracked path + if JLOptions().tracked_path == C_NULL + return false + else + tracked_path = unsafe_string(JLOptions().tracked_path) + if isempty(tracked_path) + return false + else + return any(includes) do inc + startswith(inc.filename, tracked_path) end - set_pkgorigin_version_path(modkey, sourcepath) - loaded = _include_from_serialized(modkey, path, ocachepath, depmods) - finally - end_loading(modkey, loaded) - end - if loaded isa Module - insert_extension_triggers(modkey) - run_package_callbacks(modkey) end end end - if !(loaded isa Module) || PkgId(loaded) != modkey - return ErrorException("Required dependency $modkey failed to load from a cache file.") - end - return loaded end # loads a precompile cache file, ignoring stale_cachefile tests -# load the best available (non-stale) version of all dependent modules first +# load all dependent modules first function _tryrequire_from_serialized(pkg::PkgId, path::String, ocachepath::Union{Nothing, String}) assert_havelock(require_lock) local depmodnames io = open(path, "r") + ignore_native = false try iszero(isvalid_cache_header(io)) && return ArgumentError("Invalid header in cache file $path.") - _, _, depmodnames, _, _, _, clone_targets, _ = parse_cache_header(io) + _, (includes, _, _), depmodnames, _, _, _, clone_targets, _ = parse_cache_header(io, path) + + ignore_native = pkg_tracked(includes) + pkgimage = !isempty(clone_targets) if pkgimage ocachepath !== nothing || return ArgumentError("Expected ocachepath to be provided") @@ -1529,61 +1882,97 @@ function _tryrequire_from_serialized(pkg::PkgId, path::String, ocachepath::Union depmods[i] = dep end # then load the file - return _include_from_serialized(pkg, path, ocachepath, depmods) + return _include_from_serialized(pkg, path, ocachepath, depmods, ignore_native) end # returns `nothing` if require found a precompile cache for this sourcepath, but couldn't load it # returns the set of modules restored if the cache load succeeded -@constprop :none function _require_search_from_serialized(pkg::PkgId, sourcepath::String, build_id::UInt128) +@constprop :none function _require_search_from_serialized(pkg::PkgId, sourcepath::String, build_id::UInt128, stalecheck::Bool; reasons=nothing, DEPOT_PATH::typeof(DEPOT_PATH)=DEPOT_PATH) assert_havelock(require_lock) - paths = find_all_in_cache_path(pkg) + paths = find_all_in_cache_path(pkg, DEPOT_PATH) + newdeps = PkgId[] for path_to_try in paths::Vector{String} - staledeps = stale_cachefile(pkg, build_id, sourcepath, path_to_try) + staledeps = stale_cachefile(pkg, build_id, sourcepath, path_to_try; reasons, stalecheck) if staledeps === true continue end - staledeps, ocachefile = staledeps::Tuple{Vector{Any}, Union{Nothing, String}} - # finish checking staledeps module graph - for i in 1:length(staledeps) - dep = staledeps[i] - dep isa Module && continue - modpath, modkey, modbuild_id = dep::Tuple{String, PkgId, UInt128} - modpaths = find_all_in_cache_path(modkey) - for modpath_to_try in modpaths - modstaledeps = stale_cachefile(modkey, modbuild_id, modpath, modpath_to_try) - if modstaledeps === true - continue - end - modstaledeps, modocachepath = modstaledeps::Tuple{Vector{Any}, Union{Nothing, String}} - staledeps[i] = (modpath, modkey, modpath_to_try, modstaledeps, modocachepath) - @goto check_next_dep - end - @debug "Rejecting cache file $path_to_try because required dependency $modkey with build ID $(UUID(modbuild_id)) is missing from the cache." - @goto check_next_path - @label check_next_dep - end try - touch(path_to_try) # update timestamp of precompilation file - catch ex # file might be read-only and then we fail to update timestamp, which is fine - ex isa IOError || rethrow() - end - # finish loading module graph into staledeps - for i in 1:length(staledeps) - dep = staledeps[i] - dep isa Module && continue - modpath, modkey, modcachepath, modstaledeps, modocachepath = dep::Tuple{String, PkgId, String, Vector{Any}, Union{Nothing, String}} - dep = _tryrequire_from_serialized(modkey, modcachepath, modocachepath, modpath, modstaledeps) - if !isa(dep, Module) - @debug "Rejecting cache file $path_to_try because required dependency $modkey failed to load from cache file for $modcachepath." exception=dep + staledeps, ocachefile = staledeps::Tuple{Vector{Any}, Union{Nothing, String}} + # finish checking staledeps module graph + for i in 1:length(staledeps) + dep = staledeps[i] + dep isa Module && continue + modpath, modkey, modbuild_id = dep::Tuple{String, PkgId, UInt128} + modpaths = find_all_in_cache_path(modkey, DEPOT_PATH) + for modpath_to_try in modpaths + modstaledeps = stale_cachefile(modkey, modbuild_id, modpath, modpath_to_try; stalecheck) + if modstaledeps === true + continue + end + modstaledeps, modocachepath = modstaledeps::Tuple{Vector{Any}, Union{Nothing, String}} + staledeps[i] = (modpath, modkey, modbuild_id, modpath_to_try, modstaledeps, modocachepath) + @goto check_next_dep + end + @debug "Rejecting cache file $path_to_try because required dependency $modkey with build ID $(UUID(modbuild_id)) is missing from the cache." @goto check_next_path + @label check_next_dep + end + if stalecheck + try + touch(path_to_try) # update timestamp of precompilation file + catch ex # file might be read-only and then we fail to update timestamp, which is fine + ex isa IOError || rethrow() + end end - staledeps[i] = dep + # finish loading module graph into staledeps + for i in 1:length(staledeps) + dep = staledeps[i] + dep isa Module && continue + modpath, modkey, modbuild_id, modcachepath, modstaledeps, modocachepath = dep::Tuple{String, PkgId, UInt128, String, Vector{Any}, Union{Nothing, String}} + dep = nothing + if root_module_exists(modkey) + dep = root_module(modkey) + end + while true + if dep isa Module + if PkgId(dep) == modkey && module_build_id(dep) === modbuild_id + break + else + if stalecheck + @debug "Rejecting cache file $path_to_try because module $modkey is already loaded and incompatible." + @goto check_next_path + end + end + end + dep = start_loading(modkey) + if dep === nothing + try + set_pkgorigin_version_path(modkey, modpath) + dep = _include_from_serialized(modkey, modcachepath, modocachepath, modstaledeps) + finally + end_loading(modkey, dep) + end + if !isa(dep, Module) + @debug "Rejecting cache file $path_to_try because required dependency $modkey failed to load from cache file for $modcachepath." exception=dep + @goto check_next_path + else + push!(newdeps, modkey) + end + end + end + staledeps[i] = dep + end + restored = _include_from_serialized(pkg, path_to_try, ocachefile, staledeps) + isa(restored, Module) && return restored + @debug "Deserialization checks failed while attempting to load cache from $path_to_try" exception=restored + @label check_next_path + finally + for modkey in newdeps + insert_extension_triggers(modkey) + run_package_callbacks(modkey) + end + empty!(newdeps) end - restored = _include_from_serialized(pkg, path_to_try, ocachefile, staledeps) - isa(restored, Module) && return restored - @debug "Deserialization checks failed while attempting to load cache from $path_to_try" exception=restored - continue - @label check_next_path end return nothing end @@ -1660,9 +2049,9 @@ const include_callbacks = Any[] # used to optionally track dependencies when requiring a module: const _concrete_dependencies = Pair{PkgId,UInt128}[] # these dependency versions are "set in stone", and the process should try to avoid invalidating them -const _require_dependencies = Any[] # a list of (mod, path, mtime) tuples that are the file dependencies of the module currently being precompiled +const _require_dependencies = Any[] # a list of (mod, abspath, fsize, hash, mtime) tuples that are the file dependencies of the module currently being precompiled const _track_dependencies = Ref(false) # set this to true to track the list of file dependencies -function _include_dependency(mod::Module, _path::AbstractString) +function _include_dependency(mod::Module, _path::AbstractString; track_content=true) prev = source_path(nothing) if prev === nothing path = abspath(_path) @@ -1671,31 +2060,42 @@ function _include_dependency(mod::Module, _path::AbstractString) end if _track_dependencies[] @lock require_lock begin - push!(_require_dependencies, (mod, path, mtime(path))) + if track_content + hash = isdir(path) ? _crc32c(join(readdir(path))) : open(_crc32c, path, "r") + # use mtime=-1.0 here so that fsize==0 && mtime==0.0 corresponds to a missing include_dependency + push!(_require_dependencies, (mod, path, filesize(path), hash, -1.0)) + else + push!(_require_dependencies, (mod, path, UInt64(0), UInt32(0), mtime(path))) + end end end return path, prev end """ - include_dependency(path::AbstractString) + include_dependency(path::AbstractString; track_content::Bool=false) In a module, declare that the file, directory, or symbolic link specified by `path` (relative or absolute) is a dependency for precompilation; that is, the module will need -to be recompiled if the modification time of `path` changes. +to be recompiled if the modification time `mtime` of `path` changes. +If `track_content=true` recompilation is triggered when the content of `path` changes +(if `path` is a directory the content equals `join(readdir(path))`). This is only needed if your module depends on a path that is not used via [`include`](@ref). It has no effect outside of compilation. + +!!! compat "Julia 1.11" + Keyword argument `track_content` requires at least Julia 1.11. """ -function include_dependency(path::AbstractString) - _include_dependency(Main, path) +function include_dependency(path::AbstractString; track_content::Bool=false) + _include_dependency(Main, path, track_content=track_content) return nothing end # we throw PrecompilableError when a module doesn't want to be precompiled -struct PrecompilableError <: Exception end +import Core: PrecompilableError function show(io::IO, ex::PrecompilableError) - print(io, "Declaring __precompile__(false) is not allowed in files that are being precompiled.") + print(io, "Error when precompiling module, potentially caused by a __precompile__(false) declaration in the module.") end precompilableerror(ex::PrecompilableError) = true precompilableerror(ex::WrappedException) = precompilableerror(ex.error) @@ -1769,10 +2169,14 @@ function __require(into::Module, mod::Symbol) end end hint_message = hint ? ", maybe you meant `import/using $(dots)$(mod)`" : "" - start_sentence = hint ? "Otherwise, run" : "Run" - throw(ArgumentError(""" - Package $mod not found in current path$hint_message. - - $start_sentence `import Pkg; Pkg.add($(repr(String(mod))))` to install the $mod package.""")) + install_message = if mod != :Pkg + start_sentence = hint ? "Otherwise, run" : "Run" + "\n- $start_sentence `import Pkg; Pkg.add($(repr(String(mod))))` to install the $mod package." + else # for some reason Pkg itself isn't availability so do not tell them to use Pkg to install it. + "" + end + + throw(ArgumentError("Package $mod not found in current path$hint_message.$install_message")) else manifest_warnings = collect_manifest_warnings() throw(ArgumentError(""" @@ -1787,7 +2191,8 @@ function __require(into::Module, mod::Symbol) end uuidkey, env = uuidkey_env if _track_dependencies[] - push!(_require_dependencies, (into, binpack(uuidkey), 0.0)) + path = binpack(uuidkey) + push!(_require_dependencies, (into, path, UInt64(0), UInt32(0), 0.0)) end return _require_prelocked(uuidkey, env) finally @@ -1847,8 +2252,6 @@ end require(uuidkey::PkgId) = @lock require_lock _require_prelocked(uuidkey) -const REPL_PKGID = PkgId(UUID("3fa0cd96-eef1-5676-8a61-b3b8758bbffb"), "REPL") - function _require_prelocked(uuidkey::PkgId, env=nothing) if _require_world_age[] != typemax(UInt) Base.invoke_in_world(_require_world_age[], __require_prelocked, uuidkey, env) @@ -1869,6 +2272,11 @@ function __require_prelocked(uuidkey::PkgId, env=nothing) # After successfully loading, notify downstream consumers run_package_callbacks(uuidkey) else + m = get(loaded_modules, uuidkey, nothing) + if m !== nothing + explicit_loaded_modules[uuidkey] = m + run_package_callbacks(uuidkey) + end newm = root_module(uuidkey) end return newm @@ -1883,10 +2291,11 @@ PkgOrigin() = PkgOrigin(nothing, nothing, nothing) const pkgorigins = Dict{PkgId,PkgOrigin}() const loaded_modules = Dict{PkgId,Module}() +# Emptied on Julia start +const explicit_loaded_modules = Dict{PkgId,Module}() const loaded_modules_order = Vector{Module}() const module_keys = IdDict{Module,PkgId}() # the reverse -is_root_module(m::Module) = @lock require_lock haskey(module_keys, m) root_module_key(m::Module) = @lock require_lock module_keys[m] @constprop :none function register_root_module(m::Module) @@ -1906,6 +2315,7 @@ root_module_key(m::Module) = @lock require_lock module_keys[m] end push!(loaded_modules_order, m) loaded_modules[key] = m + explicit_loaded_modules[key] = m module_keys[m] = key end nothing @@ -1943,21 +2353,20 @@ function unreference_module(key::PkgId) end # whoever takes the package_locks[pkg] must call this function immediately -function set_pkgorigin_version_path(pkg::PkgId, path::Union{String,Nothing}) +function set_pkgorigin_version_path(pkg::PkgId, path::String) assert_havelock(require_lock) pkgorigin = get!(PkgOrigin, pkgorigins, pkg) - if path !== nothing - # Pkg needs access to the version of packages in the sysimage. - if generating_output(#=incremental=#false) - pkgorigin.version = get_pkgversion_from_path(joinpath(dirname(path), "..")) - end + # Pkg needs access to the version of packages in the sysimage. + if generating_output(#=incremental=#false) + pkgorigin.version = get_pkgversion_from_path(joinpath(dirname(path), "..")) end pkgorigin.path = path nothing end -# A hook to allow code load to use Pkg.precompile +# Unused const PKG_PRECOMPILE_HOOK = Ref{Function}() +disable_parallel_precompile::Bool = false # Returns `nothing` or the new(ish) module function _require(pkg::PkgId, env=nothing) @@ -1972,29 +2381,33 @@ function _require(pkg::PkgId, env=nothing) path = locate_package(pkg, env) if path === nothing throw(ArgumentError(""" - Package $pkg is required but does not seem to be installed: + Package $(repr("text/plain", pkg)) is required but does not seem to be installed: - Run `Pkg.instantiate()` to install all recorded dependencies. """)) end set_pkgorigin_version_path(pkg, path) - pkg_precompile_attempted = false # being safe to avoid getting stuck in a Pkg.precompile loop - + parallel_precompile_attempted = false # being safe to avoid getting stuck in a precompilepkgs loop + reasons = Dict{String,Int}() # attempt to load the module file via the precompile cache locations if JLOptions().use_compiled_modules != 0 @label load_from_cache - m = _require_search_from_serialized(pkg, path, UInt128(0)) + m = _require_search_from_serialized(pkg, path, UInt128(0), true; reasons) if m isa Module return m end end + if JLOptions().use_compiled_modules == 3 + error("Precompiled image $pkg not available with flags $(CacheFlags())") + end + # if the module being required was supposed to have a particular version # but it was not handled by the precompile loader, complain for (concrete_pkg, concrete_build_id) in _concrete_dependencies if pkg == concrete_pkg @warn """Module $(pkg.name) with build ID $((UUID(concrete_build_id))) is missing from the cache. - This may mean $pkg does not support precompilation but is imported by a module that does.""" + This may mean $(repr("text/plain", pkg)) does not support precompilation but is imported by a module that does.""" if JLOptions().incremental != 0 # during incremental precompilation, this should be fail-fast throw(PrecompilableError()) @@ -2004,11 +2417,13 @@ function _require(pkg::PkgId, env=nothing) if JLOptions().use_compiled_modules == 1 if !generating_output(#=incremental=#false) - if !pkg_precompile_attempted && isinteractive() && isassigned(PKG_PRECOMPILE_HOOK) - pkg_precompile_attempted = true + project = active_project() + if !generating_output() && !parallel_precompile_attempted && !disable_parallel_precompile && @isdefined(Precompilation) && project !== nothing && + isfile(project) && project_file_manifest_path(project) !== nothing + parallel_precompile_attempted = true unlock(require_lock) try - @invokelatest PKG_PRECOMPILE_HOOK[](pkg.name, _from_loading = true) + Precompilation.precompilepkgs([pkg.name]; _from_loading=true) finally lock(require_lock) end @@ -2017,9 +2432,9 @@ function _require(pkg::PkgId, env=nothing) # spawn off a new incremental pre-compile task for recursive `require` calls cachefile_or_module = maybe_cachefile_lock(pkg, path) do # double-check now that we have lock - m = _require_search_from_serialized(pkg, path, UInt128(0)) + m = _require_search_from_serialized(pkg, path, UInt128(0), true) m isa Module && return m - compilecache(pkg, path) + compilecache(pkg, path; reasons) end cachefile_or_module isa Module && return cachefile_or_module::Module cachefile = cachefile_or_module @@ -2028,16 +2443,16 @@ function _require(pkg::PkgId, env=nothing) elseif isa(cachefile, Exception) if precompilableerror(cachefile) verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug - @logmsg verbosity "Skipping precompilation since __precompile__(false). Importing $pkg." + @logmsg verbosity "Skipping precompilation due to precompilable error. Importing $(repr("text/plain", pkg))." exception=m else - @warn "The call to compilecache failed to create a usable precompiled cache file for $pkg" exception=m + @warn "The call to compilecache failed to create a usable precompiled cache file for $(repr("text/plain", pkg))" exception=m end # fall-through to loading the file locally if not incremental else cachefile, ocachefile = cachefile::Tuple{String, Union{Nothing, String}} m = _tryrequire_from_serialized(pkg, cachefile, ocachefile) if !isa(m, Module) - @warn "The call to compilecache failed to create a usable precompiled cache file for $pkg" exception=m + @warn "The call to compilecache failed to create a usable precompiled cache file for $(repr("text/plain", pkg))" exception=m else return m end @@ -2074,10 +2489,10 @@ function _require(pkg::PkgId, env=nothing) return loaded end -# Only used from test/precompile.jl -function _require_from_serialized(uuidkey::PkgId, path::String, ocachepath::Union{String, Nothing}) +# load a serialized file directly +function _require_from_serialized(uuidkey::PkgId, path::String, ocachepath::Union{String, Nothing}, sourcepath::String) @lock require_lock begin - set_pkgorigin_version_path(uuidkey, nothing) + set_pkgorigin_version_path(uuidkey, sourcepath) newm = _tryrequire_from_serialized(uuidkey, path, ocachepath) newm isa Module || throw(newm) insert_extension_triggers(uuidkey) @@ -2087,6 +2502,58 @@ function _require_from_serialized(uuidkey::PkgId, path::String, ocachepath::Unio end end +# load a serialized file directly from append_bundled_depot_path for uuidkey without stalechecks +function require_stdlib(uuidkey::PkgId, ext::Union{Nothing, String}=nothing) + @lock require_lock begin + if root_module_exists(uuidkey) + return loaded_modules[uuidkey] + end + # first since this is a stdlib, try to look there directly first + env = Sys.STDLIB + #sourcepath = "" + if ext === nothing + sourcepath = normpath(env, uuidkey.name, "src", uuidkey.name * ".jl") + else + sourcepath = find_ext_path(normpath(joinpath(env, uuidkey.name)), ext) + uuidkey = PkgId(uuid5(uuidkey.uuid, ext), ext) + end + #mbypath = manifest_uuid_path(env, uuidkey) + #if mbypath isa String && isfile_casesensitive(mbypath) + # sourcepath = mbypath + #else + # # if the user deleted the stdlib folder, we next try using their environment + # sourcepath = locate_package_env(uuidkey) + # if sourcepath !== nothing + # sourcepath, env = sourcepath + # end + #end + #if sourcepath === nothing + # throw(ArgumentError(""" + # Package $(repr("text/plain", uuidkey)) is required but does not seem to be installed. + # """)) + #end + set_pkgorigin_version_path(uuidkey, sourcepath) + depot_path = append_bundled_depot_path!(empty(DEPOT_PATH)) + newm = start_loading(uuidkey) + newm === nothing || return newm + try + newm = _require_search_from_serialized(uuidkey, sourcepath, UInt128(0), false; DEPOT_PATH=depot_path) + finally + end_loading(uuidkey, newm) + end + if newm isa Module + # After successfully loading, notify downstream consumers + insert_extension_triggers(env, uuidkey) + run_package_callbacks(uuidkey) + else + # if the user deleted their bundled depot, next try to load it completely normally + newm = _require(uuidkey) + end + return newm + end +end + + # relative-path load @@ -2199,7 +2666,7 @@ and return the value of the last expression. The optional `args` argument can be used to set the input arguments of the script (i.e. the global `ARGS` variable). Note that definitions (e.g. methods, globals) are evaluated in the anonymous module and do not affect the current module. -# Example +# Examples ```jldoctest julia> write("testfile.jl", \"\"\" @@ -2252,7 +2719,7 @@ end Checks that a package entry file `srcpath` has a module declaration, and that it is before any using/import statements. """ function check_src_module_wrap(pkg::PkgId, srcpath::String) - module_rgx = r"^\s*(?:@\w*\s*)*(?:bare)?module\s" + module_rgx = r"^(|end |\"\"\" )\s*(?:@)*(?:bare)?module\s" load_rgx = r"\b(?:using|import)\s" load_seen = false inside_string = false @@ -2262,9 +2729,9 @@ function check_src_module_wrap(pkg::PkgId, srcpath::String) inside_string = !inside_string end inside_string && continue - if startswith(s, module_rgx) + if contains(s, module_rgx) if load_seen - throw(ErrorException("Package $pkg source file $srcpath has a using/import before a module declaration.")) + throw(ErrorException("Package $(repr("text/plain", pkg)) source file $srcpath has a using/import before a module declaration.")) end return true end @@ -2272,7 +2739,7 @@ function check_src_module_wrap(pkg::PkgId, srcpath::String) load_seen = true end end - throw(ErrorException("Package $pkg source file $srcpath does not contain a module declaration.")) + throw(ErrorException("Package $(repr("text/plain", pkg)) source file $srcpath does not contain a module declaration.")) end # this is called in the external process that generates precompiled package files @@ -2311,18 +2778,35 @@ end const PRECOMPILE_TRACE_COMPILE = Ref{String}() function create_expr_cache(pkg::PkgId, input::String, output::String, output_o::Union{Nothing, String}, - concrete_deps::typeof(_concrete_dependencies), internal_stderr::IO = stderr, internal_stdout::IO = stdout) + concrete_deps::typeof(_concrete_dependencies), flags::Cmd=``, internal_stderr::IO = stderr, internal_stdout::IO = stdout) @nospecialize internal_stderr internal_stdout rm(output, force=true) # Remove file if it exists output_o === nothing || rm(output_o, force=true) depot_path = map(abspath, DEPOT_PATH) dl_load_path = map(abspath, DL_LOAD_PATH) load_path = map(abspath, Base.load_path()) + # if pkg is a stdlib, append its parent Project.toml to the load path + parentid = get(EXT_PRIMED, pkg, nothing) + if parentid !== nothing + for env in load_path + project_file = env_project_file(env) + if project_file === true + _, parent_project_file = entry_point_and_project_file(env, parentid.name) + if parent_project_file !== nothing + parentproj = project_file_name_uuid(parent_project_file, parentid.name) + if parentproj == parentid + push!(load_path, parent_project_file) + end + end + end + end + end path_sep = Sys.iswindows() ? ';' : ':' any(path -> path_sep in path, load_path) && error("LOAD_PATH entries cannot contain $(repr(path_sep))") deps_strs = String[] + # protects against PkgId and UUID being imported and losing Base prefix function pkg_str(_pkg::PkgId) if _pkg.uuid === nothing "Base.PkgId($(repr(_pkg.name)))" @@ -2333,20 +2817,24 @@ function create_expr_cache(pkg::PkgId, input::String, output::String, output_o:: for (pkg, build_id) in concrete_deps push!(deps_strs, "$(pkg_str(pkg)) => $(repr(build_id))") end + deps_eltype = sprint(show, eltype(concrete_deps); context = :module=>nothing) + deps = deps_eltype * "[" * join(deps_strs, ",") * "]" + precomp_stack = "Base.PkgId[$(join(map(pkg_str, vcat(Base.precompilation_stack, pkg)), ", "))]" if output_o !== nothing + @debug "Generating object cache file for $(repr("text/plain", pkg))" cpu_target = get(ENV, "JULIA_CPU_TARGET", nothing) - opt_level = Base.JLOptions().opt_level - opts = `-O$(opt_level) --output-o $(output_o) --output-ji $(output) --output-incremental=yes` + opts = `--output-o $(output_o) --output-ji $(output) --output-incremental=yes` else + @debug "Generating cache file for $(repr("text/plain", pkg))" cpu_target = nothing opts = `-O0 --output-ji $(output) --output-incremental=yes` end - deps_eltype = sprint(show, eltype(concrete_deps); context = :module=>nothing) - deps = deps_eltype * "[" * join(deps_strs, ",") * "]" trace = isassigned(PRECOMPILE_TRACE_COMPILE) ? `--trace-compile=$(PRECOMPILE_TRACE_COMPILE[])` : `` - io = open(pipeline(addenv(`$(julia_cmd(;cpu_target)::Cmd) $(opts) + io = open(pipeline(addenv(`$(julia_cmd(;cpu_target)::Cmd) + $(flags) + $(opts) --startup-file=no --history-file=no --warn-overwrite=yes --color=$(have_color === nothing ? "auto" : have_color ? "yes" : "no") $trace @@ -2358,6 +2846,7 @@ function create_expr_cache(pkg::PkgId, input::String, output::String, output_o:: # write data over stdin to avoid the (unlikely) case of exceeding max command line size write(io.in, """ empty!(Base.EXT_DORMITORY) # If we have a custom sysimage with `EXT_DORMITORY` prepopulated + Base.track_nested_precomp($precomp_stack) Base.precompiling_extension = $(loading_extension) Base.include_package_for_output($(pkg_str(pkg)), $(repr(abspath(input))), $(repr(depot_path)), $(repr(dl_load_path)), $(repr(load_path)), $deps, $(repr(source_path(nothing)))) @@ -2366,12 +2855,24 @@ function create_expr_cache(pkg::PkgId, input::String, output::String, output_o:: return io end +const precompilation_stack = Vector{PkgId}() +# Helpful for debugging when precompilation is unexpectedly nested. +# Enable with `JULIA_DEBUG=nested_precomp`. Note that it expected to be nested in classical code-load precompilation +# TODO: Add detection if extension precompilation is nested and error / return early? +function track_nested_precomp(pkgs::Vector{PkgId}) + append!(precompilation_stack, pkgs) + if length(precompilation_stack) > 1 + list() = join(map(p->p.name, precompilation_stack), " > ") + @debug "Nested precompilation: $(list())" _group=:nested_precomp + end +end + function compilecache_dir(pkg::PkgId) entrypath, entryfile = cache_file_entry(pkg) return joinpath(DEPOT_PATH[1], entrypath) end -function compilecache_path(pkg::PkgId, prefs_hash::UInt64; project::String=something(Base.active_project(), ""))::String +function compilecache_path(pkg::PkgId, prefs_hash::UInt64; flags::CacheFlags=CacheFlags(), project::String=something(Base.active_project(), ""))::String entrypath, entryfile = cache_file_entry(pkg) cachepath = joinpath(DEPOT_PATH[1], entrypath) isdir(cachepath) || mkpath(cachepath) @@ -2381,7 +2882,7 @@ function compilecache_path(pkg::PkgId, prefs_hash::UInt64; project::String=somet crc = _crc32c(project) crc = _crc32c(unsafe_string(JLOptions().image_file), crc) crc = _crc32c(unsafe_string(JLOptions().julia_bin), crc) - crc = _crc32c(ccall(:jl_cache_flags, UInt8, ()), crc) + crc = _crc32c(_cacheflag_to_uint8(flags), crc) cpu_target = get(ENV, "JULIA_CPU_TARGET", nothing) if cpu_target === nothing @@ -2403,17 +2904,18 @@ This can be used to reduce package load times. Cache files are stored in `DEPOT_PATH[1]/compiled`. See [Module initialization and precompilation](@ref) for important notes. """ -function compilecache(pkg::PkgId, internal_stderr::IO = stderr, internal_stdout::IO = stdout) +function compilecache(pkg::PkgId, internal_stderr::IO = stderr, internal_stdout::IO = stdout; flags::Cmd=``, reasons::Union{Dict{String,Int},Nothing}=Dict{String,Int}()) @nospecialize internal_stderr internal_stdout path = locate_package(pkg) - path === nothing && throw(ArgumentError("$pkg not found during precompilation")) - return compilecache(pkg, path, internal_stderr, internal_stdout) + path === nothing && throw(ArgumentError("$(repr("text/plain", pkg)) not found during precompilation")) + return compilecache(pkg, path, internal_stderr, internal_stdout; flags, reasons) end const MAX_NUM_PRECOMPILE_FILES = Ref(10) function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, internal_stdout::IO = stdout, - keep_loaded_modules::Bool = true) + keep_loaded_modules::Bool = true; flags::Cmd=``, cacheflags::CacheFlags=CacheFlags(), + reasons::Union{Dict{String,Int},Nothing}=Dict{String,Int}()) @nospecialize internal_stderr internal_stdout # decide where to put the resulting cache file @@ -2430,12 +2932,12 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in end # run the expression and cache the result verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug - @logmsg verbosity "Precompiling $pkg" + @logmsg verbosity "Precompiling $(repr("text/plain", pkg)) $(list_reasons(reasons))" # create a temporary file in `cachepath` directory, write the cache in it, # write the checksum, _and then_ atomically move the file to `cachefile`. mkpath(cachepath) - cache_objects = JLOptions().use_pkgimages != 0 + cache_objects = JLOptions().use_pkgimages == 1 tmppath, tmpio = mktemp(cachepath) if cache_objects @@ -2451,7 +2953,7 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in close(tmpio_o) close(tmpio_so) end - p = create_expr_cache(pkg, path, tmppath, tmppath_o, concrete_deps, internal_stderr, internal_stdout) + p = create_expr_cache(pkg, path, tmppath, tmppath_o, concrete_deps, flags, internal_stderr, internal_stdout) if success(p) if cache_objects @@ -2462,7 +2964,7 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in # Read preferences hash back from .ji file (we can't precompute because # we don't actually know what the list of compile-time preferences are without compiling) prefs_hash = preferences_hash(tmppath) - cachefile = compilecache_path(pkg, prefs_hash) + cachefile = compilecache_path(pkg, prefs_hash; flags=cacheflags) ocachefile = cache_objects ? ocachefile_from_cachefile(cachefile) : nothing # append checksum for so to the end of the .ji file: @@ -2474,7 +2976,7 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in # append extra crc to the end of the .ji file: open(tmppath, "r+") do f if iszero(isvalid_cache_header(f)) - error("Invalid header for $pkg in new cache file $(repr(tmppath)).") + error("Invalid header for $(repr("text/plain", pkg)) in new cache file $(repr(tmppath)).") end seekend(f) write(f, crc_so) @@ -2484,12 +2986,6 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in # inherit permission from the source file (and make them writable) chmod(tmppath, filemode(path) & 0o777 | 0o200) - if cache_objects - # Ensure that the user can execute the `.so` we're generating - # Note that on windows, `filemode(path)` typically returns `0o666`, so this - # addition of the execute bit for the user is doubly needed. - chmod(tmppath_so, filemode(path) & 0o777 | 0o333) - end # prune the directory with cache files if pkg.uuid !== nothing @@ -2512,26 +3008,10 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in end if cache_objects - try - rename(tmppath_so, ocachefile::String; force=true) - catch e - e isa IOError || rethrow() - isfile(ocachefile::String) || rethrow() - # Windows prevents renaming a file that is in use so if there is a Julia session started - # with a package image loaded, we cannot rename that file. - # The code belows append a `_i` to the name of the cache file where `i` is the smallest number such that - # that cache file does not exist. - ocachename, ocacheext = splitext(ocachefile::String) - old_cachefiles = Set(readdir(cachepath)) - num = 1 - while true - ocachefile = ocachename * "_$num" * ocacheext - in(basename(ocachefile), old_cachefiles) || break - num += 1 - end - # TODO: Risk for a race here if some other process grabs this name before us - cachefile = cachefile_from_ocachefile(ocachefile) - rename(tmppath_so, ocachefile::String; force=true) + ocachefile_new = rename_unique_ocachefile(tmppath_so, ocachefile) + if ocachefile_new != ocachefile + cachefile = cachefile_from_ocachefile(ocachefile_new) + ocachefile = ocachefile_new end @static if Sys.isapple() run(`$(Linking.dsymutil()) $ocachefile`, Base.DevNull(), Base.DevNull(), Base.DevNull()) @@ -2551,8 +3031,29 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in if p.exitcode == 125 return PrecompilableError() else - error("Failed to precompile $pkg to $(repr(tmppath)).") + error("Failed to precompile $(repr("text/plain", pkg)) to $(repr(tmppath)).") + end +end + +function rename_unique_ocachefile(tmppath_so::String, ocachefile_orig::String, ocachefile::String = ocachefile_orig, num = 0) + try + rename(tmppath_so, ocachefile; force=true) + catch e + e isa IOError || rethrow() + # If `rm` was called on a dir containing a loaded DLL, we moved it to temp for cleanup + # on restart. However the old path cannot be used (UV_EACCES) while the DLL is loaded + if !isfile(ocachefile) && e.code != Base.UV_EACCES + rethrow() + end + # Windows prevents renaming a file that is in use so if there is a Julia session started + # with a package image loaded, we cannot rename that file. + # The code belows append a `_i` to the name of the cache file where `i` is the smallest number such that + # that cache file does not exist. + ocachename, ocacheext = splitext(ocachefile_orig) + ocachefile_unique = ocachename * "_$num" * ocacheext + ocachefile = rename_unique_ocachefile(tmppath_so, ocachefile_orig, ocachefile_unique, num + 1) end + return ocachefile end function module_build_id(m::Module) @@ -2560,6 +3061,14 @@ function module_build_id(m::Module) return (UInt128(hi) << 64) | lo end +function object_build_id(obj) + mod = ccall(:jl_object_top_module, Any, (Any,), obj) + if mod === nothing + return nothing + end + return module_build_id(mod::Module) +end + function isvalid_cache_header(f::IOStream) pkgimage = Ref{UInt8}() checksum = ccall(:jl_read_verify_header, UInt64, (Ptr{Cvoid}, Ptr{UInt8}, Ptr{Int64}, Ptr{Int64}), f.ios, pkgimage, Ref{Int64}(), Ref{Int64}()) # returns checksum id or zero @@ -2580,14 +3089,46 @@ function isvalid_pkgimage_crc(f::IOStream, ocachefile::String) expected_crc_so == crc_so end -struct CacheHeaderIncludes - id::PkgId +mutable struct CacheHeaderIncludes + const id::PkgId filename::String - mtime::Float64 - modpath::Vector{String} # seemingly not needed in Base, but used by Revise + const fsize::UInt64 + const hash::UInt32 + const mtime::Float64 + const modpath::Vector{String} # seemingly not needed in Base, but used by Revise end -function parse_cache_header(f::IO) +function replace_depot_path(path::AbstractString) + for depot in DEPOT_PATH + !isdir(depot) && continue + + # Strip extraneous pathseps through normalization. + if isdirpath(depot) + depot = dirname(depot) + end + + if startswith(path, depot) + path = replace(path, depot => "@depot"; count=1) + break + end + end + return path +end + +function restore_depot_path(path::AbstractString, depot::AbstractString) + replace(path, r"^@depot" => depot; count=1) +end + +function resolve_depot(inc::AbstractString) + startswith(inc, string("@depot", Filesystem.pathsep())) || return :not_relocatable + for depot in DEPOT_PATH + ispath(restore_depot_path(inc, depot)) && return depot + end + return :no_depot_found +end + + +function _parse_cache_header(f::IO, cachefile::AbstractString) flags = read(f, UInt8) modules = Vector{Pair{PkgId, UInt64}}() while true @@ -2598,7 +3139,7 @@ function parse_cache_header(f::IO) build_id = read(f, UInt64) # build UUID (mostly just a timestamp) push!(modules, PkgId(uuid, sym) => build_id) end - totbytes = read(f, Int64) # total bytes for file dependencies + preferences + totbytes = Int64(read(f, UInt64)) # total bytes for file dependencies + preferences # read the list of requirements # and split the list into include and requires statements includes = CacheHeaderIncludes[] @@ -2611,6 +3152,10 @@ function parse_cache_header(f::IO) end depname = String(read(f, n2)) totbytes -= n2 + fsize = read(f, UInt64) + totbytes -= 8 + hash = read(f, UInt32) + totbytes -= 4 mtime = read(f, Float64) totbytes -= 8 n1 = read(f, Int32) @@ -2633,7 +3178,7 @@ function parse_cache_header(f::IO) if depname[1] == '\0' push!(requires, modkey => binunpack(depname)) else - push!(includes, CacheHeaderIncludes(modkey, depname, mtime, modpath)) + push!(includes, CacheHeaderIncludes(modkey, depname, fsize, hash, mtime, modpath)) end end prefs = String[] @@ -2665,69 +3210,151 @@ function parse_cache_header(f::IO) l = read(f, Int32) clone_targets = read(f, l) - return modules, (includes, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags + srcfiles = srctext_files(f, srctextpos, includes) + + return modules, (includes, srcfiles, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags end -function parse_cache_header(cachefile::String; srcfiles_only::Bool=false) +function parse_cache_header(f::IO, cachefile::AbstractString) + modules, (includes, srcfiles, requires), required_modules, + srctextpos, prefs, prefs_hash, clone_targets, flags = _parse_cache_header(f, cachefile) + + includes_srcfiles = CacheHeaderIncludes[] + includes_depfiles = CacheHeaderIncludes[] + for inc in includes + if inc.filename ∈ srcfiles + push!(includes_srcfiles, inc) + else + push!(includes_depfiles, inc) + end + end + + + # The @depot resolution logic for include() files: + # 1. If the cache is not relocatable because of an absolute path, + # we ignore that path for the depot search. + # Recompilation will be triggered by stale_cachefile() if that absolute path does not exist. + # 2. If we can't find a depot for a relocatable path, + # we still replace it with the depot we found from other files. + # Recompilation will be triggered by stale_cachefile() because the resolved path does not exist. + # 3. We require that relocatable paths all resolve to the same depot. + # 4. We explicitly check that all relocatable paths resolve to the same depot. This has two reasons: + # - We want to scan all source files in order to provide logs for 1. and 2. above. + # - It is possible that a depot might be missing source files. + # Assume that we have two depots on DEPOT_PATH, depot_complete and depot_incomplete. + # If DEPOT_PATH=["depot_complete","depot_incomplete"] then no recompilation shall happen, + # because depot_complete will be picked. + # If DEPOT_PATH=["depot_incomplete","depot_complete"] we trigger recompilation and + # hopefully a meaningful error about missing files is thrown. + # If we were to just select the first depot we find, then whether recompilation happens would + # depend on whether the first relocatable file resolves to depot_complete or depot_incomplete. + srcdepot = nothing + any_not_relocatable = false + any_no_depot_found = false + multiple_depots_found = false + for src in srcfiles + depot = resolve_depot(src) + if depot === :not_relocatable + any_not_relocatable = true + elseif depot === :no_depot_found + any_no_depot_found = true + elseif isnothing(srcdepot) + srcdepot = depot + elseif depot != srcdepot + multiple_depots_found = true + end + end + if any_no_depot_found + @debug("Unable to resolve @depot tag for at least one include() file from cache file $cachefile", srcfiles, _group=:relocatable) + end + if any_not_relocatable + @debug("At least one include() file from $cachefile is not relocatable", srcfiles, _group=:relocatable) + end + if multiple_depots_found + @debug("Some include() files from $cachefile are distributed over multiple depots", srcfiles, _group=:relocatable) + elseif !isnothing(srcdepot) + for inc in includes_srcfiles + inc.filename = restore_depot_path(inc.filename, srcdepot) + end + end + + # unlike include() files, we allow each relocatable include_dependency() file to resolve + # to a separate depot, #52161 + for inc in includes_depfiles + depot = resolve_depot(inc.filename) + if depot === :no_depot_found + @debug("Unable to resolve @depot tag for include_dependency() file $(inc.filename) from cache file $cachefile", _group=:relocatable) + elseif depot === :not_relocatable + @debug("include_dependency() file $(inc.filename) from $cachefile is not relocatable", _group=:relocatable) + else + inc.filename = restore_depot_path(inc.filename, depot) + end + end + + return modules, (includes, includes_srcfiles, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags +end + +function parse_cache_header(cachefile::String) io = open(cachefile, "r") try iszero(isvalid_cache_header(io)) && throw(ArgumentError("Invalid header in cache file $cachefile.")) - ret = parse_cache_header(io) - srcfiles_only || return ret - _, (includes, _), _, srctextpos, _... = ret - srcfiles = srctext_files(io, srctextpos) - delidx = Int[] - for (i, chi) in enumerate(includes) - chi.filename ∈ srcfiles || push!(delidx, i) - end - deleteat!(includes, delidx) + ret = parse_cache_header(io, cachefile) return ret finally close(io) end end -preferences_hash(f::IO) = parse_cache_header(f)[6] +preferences_hash(f::IO, cachefile::AbstractString) = parse_cache_header(f, cachefile)[6] function preferences_hash(cachefile::String) io = open(cachefile, "r") try if iszero(isvalid_cache_header(io)) throw(ArgumentError("Invalid header in cache file $cachefile.")) end - return preferences_hash(io) + return preferences_hash(io, cachefile) finally close(io) end end -function cache_dependencies(f::IO) - _, (includes, _), modules, _... = parse_cache_header(f) - return modules, map(chi -> (chi.filename, chi.mtime), includes) # return just filename and mtime +function cache_dependencies(f::IO, cachefile::AbstractString) + _, (includes, _, _), modules, _... = parse_cache_header(f, cachefile) + return modules, map(chi -> chi.filename, includes) # return just filename end function cache_dependencies(cachefile::String) io = open(cachefile, "r") try iszero(isvalid_cache_header(io)) && throw(ArgumentError("Invalid header in cache file $cachefile.")) - return cache_dependencies(io) + return cache_dependencies(io, cachefile) finally close(io) end end -function read_dependency_src(io::IO, filename::AbstractString) - srctextpos = parse_cache_header(io)[4] +function read_dependency_src(io::IO, cachefile::AbstractString, filename::AbstractString) + _, (includes, _, _), _, srctextpos, _, _, _, _ = parse_cache_header(io, cachefile) srctextpos == 0 && error("no source-text stored in cache file") seek(io, srctextpos) - return _read_dependency_src(io, filename) + return _read_dependency_src(io, filename, includes) end -function _read_dependency_src(io::IO, filename::AbstractString) +function _read_dependency_src(io::IO, filename::AbstractString, includes::Vector{CacheHeaderIncludes}=CacheHeaderIncludes[]) while !eof(io) filenamelen = read(io, Int32) filenamelen == 0 && break - fn = String(read(io, filenamelen)) + depotfn = String(read(io, filenamelen)) len = read(io, UInt64) + fn = if !startswith(depotfn, string("@depot", Filesystem.pathsep())) + depotfn + else + basefn = restore_depot_path(depotfn, "") + idx = findfirst(includes) do inc + endswith(inc.filename, basefn) + end + isnothing(idx) ? depotfn : includes[idx].filename + end if fn == filename return String(read(io, len)) end @@ -2740,22 +3367,22 @@ function read_dependency_src(cachefile::String, filename::AbstractString) io = open(cachefile, "r") try iszero(isvalid_cache_header(io)) && throw(ArgumentError("Invalid header in cache file $cachefile.")) - return read_dependency_src(io, filename) + return read_dependency_src(io, cachefile, filename) finally close(io) end end -function srctext_files(f::IO, srctextpos::Int64) +function srctext_files(f::IO, srctextpos::Int64, includes::Vector{CacheHeaderIncludes}) files = Set{String}() srctextpos == 0 && return files seek(f, srctextpos) while !eof(f) filenamelen = read(f, Int32) filenamelen == 0 && break - fn = String(read(f, filenamelen)) + filename = String(read(f, filenamelen)) len = read(f, UInt64) - push!(files, fn) + push!(files, filename) seek(f, position(f) + len) end return files @@ -2874,9 +3501,27 @@ function recursive_prefs_merge(base::Dict{String, Any}, overrides::Dict{String, return new_base end +function get_projects_workspace_to_root(project_file) + projects = String[project_file] + while true + project_file = base_project(project_file) + if project_file === nothing + return projects + end + push!(projects, project_file) + end +end + function get_preferences(uuid::Union{UUID,Nothing} = nothing) merged_prefs = Dict{String,Any}() - for env in reverse(load_path()) + loadpath = load_path() + projects_to_merge_prefs = String[] + append!(projects_to_merge_prefs, Iterators.drop(loadpath, 1)) + if length(loadpath) >= 1 + prepend!(projects_to_merge_prefs, get_projects_workspace_to_root(first(loadpath))) + end + + for env in reverse(projects_to_merge_prefs) project_toml = env_project_file(env) if !isa(project_toml, String) continue @@ -2933,134 +3578,27 @@ function check_clone_targets(clone_targets) end end -struct CacheFlags - # OOICCDDP - see jl_cache_flags - use_pkgimages::Bool - debug_level::Int - check_bounds::Int - inline::Bool - opt_level::Int - - function CacheFlags(f::UInt8) - use_pkgimages = Bool(f & 1) - debug_level = Int((f >> 1) & 3) - check_bounds = Int((f >> 3) & 3) - inline = Bool((f >> 5) & 1) - opt_level = Int((f >> 6) & 3) # define OPT_LEVEL in statiddata_utils - new(use_pkgimages, debug_level, check_bounds, inline, opt_level) - end -end -CacheFlags(f::Int) = CacheFlags(UInt8(f)) -CacheFlags() = CacheFlags(ccall(:jl_cache_flags, UInt8, ())) - -function show(io::IO, cf::CacheFlags) - print(io, "use_pkgimages = ", cf.use_pkgimages) - print(io, ", debug_level = ", cf.debug_level) - print(io, ", check_bounds = ", cf.check_bounds) - print(io, ", inline = ", cf.inline) - print(io, ", opt_level = ", cf.opt_level) -end - -struct ImageTarget - name::String - flags::Int32 - ext_features::String - features_en::Vector{UInt8} - features_dis::Vector{UInt8} -end - -function parse_image_target(io::IO) - flags = read(io, Int32) - nfeature = read(io, Int32) - feature_en = read(io, 4*nfeature) - feature_dis = read(io, 4*nfeature) - name_len = read(io, Int32) - name = String(read(io, name_len)) - ext_features_len = read(io, Int32) - ext_features = String(read(io, ext_features_len)) - ImageTarget(name, flags, ext_features, feature_en, feature_dis) -end - -function parse_image_targets(targets::Vector{UInt8}) - io = IOBuffer(targets) - ntargets = read(io, Int32) - targets = Vector{ImageTarget}(undef, ntargets) - for i in 1:ntargets - targets[i] = parse_image_target(io) - end - return targets -end - -function current_image_targets() - targets = @ccall jl_reflect_clone_targets()::Vector{UInt8} - return parse_image_targets(targets) -end - -struct FeatureName - name::Cstring - bit::UInt32 # bit index into a `uint32_t` array; - llvmver::UInt32 # 0 if it is available on the oldest LLVM version we support -end - -function feature_names() - fnames = Ref{Ptr{FeatureName}}() - nf = Ref{Csize_t}() - @ccall jl_reflect_feature_names(fnames::Ptr{Ptr{FeatureName}}, nf::Ptr{Csize_t})::Cvoid - if fnames[] == C_NULL - @assert nf[] == 0 - return Vector{FeatureName}(undef, 0) - end - Base.unsafe_wrap(Array, fnames[], nf[], own=false) -end - -function test_feature(features::Vector{UInt8}, feat::FeatureName) - bitidx = feat.bit - u8idx = div(bitidx, 8) + 1 - bit = bitidx % 8 - return (features[u8idx] & (1 << bit)) != 0 -end - -function show(io::IO, it::ImageTarget) - print(io, it.name) - if !isempty(it.ext_features) - print(io, ",", it.ext_features) - end - print(io, "; flags=", it.flags) - print(io, "; features_en=(") - first = true - for feat in feature_names() - if test_feature(it.features_en, feat) - name = Base.unsafe_string(feat.name) - if first - first = false - print(io, name) - else - print(io, ", ", name) - end - end - end - print(io, ")") - # Is feature_dis useful? -end - # Set by FileWatching.__init__() -global mkpidlock_hook -global trymkpidlock_hook -global parse_pidfile_hook +global mkpidlock_hook::Any +global trymkpidlock_hook::Any +global parse_pidfile_hook::Any # The preferences hash is only known after precompilation so just assume no preferences. # Also ignore the active project, which means that if all other conditions are equal, # the same package cannot be precompiled from different projects and/or different preferences at the same time. -compilecache_pidfile_path(pkg::PkgId) = compilecache_path(pkg, UInt64(0); project="") * ".pidfile" +compilecache_pidfile_path(pkg::PkgId; flags::CacheFlags=CacheFlags()) = compilecache_path(pkg, UInt64(0); project="", flags) * ".pidfile" + +const compilecache_pidlock_stale_age = 10 # Allows processes to wait if another process is precompiling a given source already. -# The lock file mtime will be updated when held every `stale_age/2` seconds. +# The lock file mtime will be updated when held at most every `stale_age/2` seconds, with expected +# variance of 10 seconds or more being infrequent but not unusual. # After `stale_age` seconds beyond the mtime of the lock file, the lock file is deleted and -# precompilation will proceed if -# - the locking process no longer exists -# - the lock is held by another host, since processes cannot be checked remotely -# or after `stale_age * 25` seconds if the process does still exist. -function maybe_cachefile_lock(f, pkg::PkgId, srcpath::String; stale_age=10) +# precompilation will proceed if the locking process no longer exists or after `stale_age * 5` +# seconds if the process does still exist. +# If the lock is held by another host, it will conservatively wait `stale_age * 5` +# seconds since processes cannot be checked remotely +function maybe_cachefile_lock(f, pkg::PkgId, srcpath::String; stale_age=compilecache_pidlock_stale_age) if @isdefined(mkpidlock_hook) && @isdefined(trymkpidlock_hook) && @isdefined(parse_pidfile_hook) pidfile = compilecache_pidfile_path(pkg) cachefile = invokelatest(trymkpidlock_hook, f, pidfile; stale_age) @@ -3068,9 +3606,9 @@ function maybe_cachefile_lock(f, pkg::PkgId, srcpath::String; stale_age=10) pid, hostname, age = invokelatest(parse_pidfile_hook, pidfile) verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug if isempty(hostname) || hostname == gethostname() - @logmsg verbosity "Waiting for another process (pid: $pid) to finish precompiling $pkg" + @logmsg verbosity "Waiting for another process (pid: $pid) to finish precompiling $(repr("text/plain", pkg)). Pidfile: $pidfile" else - @logmsg verbosity "Waiting for another machine (hostname: $hostname, pid: $pid) to finish precompiling $pkg" + @logmsg verbosity "Waiting for another machine (hostname: $hostname, pid: $pid) to finish precompiling $(repr("text/plain", pkg)). Pidfile: $pidfile" end # wait until the lock is available, but don't actually acquire it # returning nothing indicates a process waited for another @@ -3082,29 +3620,45 @@ function maybe_cachefile_lock(f, pkg::PkgId, srcpath::String; stale_age=10) f() end end + +function record_reason(reasons::Dict{String,Int}, reason::String) + reasons[reason] = get(reasons, reason, 0) + 1 +end +record_reason(::Nothing, ::String) = nothing +function list_reasons(reasons::Dict{String,Int}) + isempty(reasons) && return "" + return "(cache misses: $(join(("$k ($v)" for (k,v) in reasons), ", ")))" +end +list_reasons(::Nothing) = "" + # returns true if it "cachefile.ji" is stale relative to "modpath.jl" and build_id for modkey # otherwise returns the list of dependencies to also check -@constprop :none function stale_cachefile(modpath::String, cachefile::String; ignore_loaded::Bool = false) - return stale_cachefile(PkgId(""), UInt128(0), modpath, cachefile; ignore_loaded) +@constprop :none function stale_cachefile(modpath::String, cachefile::String; ignore_loaded::Bool = false, requested_flags::CacheFlags=CacheFlags(), reasons=nothing) + return stale_cachefile(PkgId(""), UInt128(0), modpath, cachefile; ignore_loaded, requested_flags, reasons) end -@constprop :none function stale_cachefile(modkey::PkgId, build_id::UInt128, modpath::String, cachefile::String; ignore_loaded::Bool = false) +@constprop :none function stale_cachefile(modkey::PkgId, build_id::UInt128, modpath::String, cachefile::String; + ignore_loaded::Bool=false, requested_flags::CacheFlags=CacheFlags(), + reasons::Union{Dict{String,Int},Nothing}=nothing, stalecheck::Bool=true) + # XXX: this function appears to dl all of the file validation, not just those checks related to stale io = open(cachefile, "r") try checksum = isvalid_cache_header(io) if iszero(checksum) @debug "Rejecting cache file $cachefile due to it containing an invalid cache header" + record_reason(reasons, "invalid header") return true # invalid cache file end - modules, (includes, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags = parse_cache_header(io) + modules, (includes, _, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, actual_flags = parse_cache_header(io, cachefile) if isempty(modules) return true # ignore empty file end - if ccall(:jl_match_cache_flags, UInt8, (UInt8,), flags) == 0 + if @ccall(jl_match_cache_flags(_cacheflag_to_uint8(requested_flags)::UInt8, actual_flags::UInt8)::UInt8) == 0 @debug """ Rejecting cache file $cachefile for $modkey since the flags are mismatched - current session: $(CacheFlags()) - cache file: $(CacheFlags(flags)) + requested flags: $(requested_flags) [$(_cacheflag_to_uint8(requested_flags))] + cache file: $(CacheFlags(actual_flags)) [$actual_flags] """ + record_reason(reasons, "mismatched flags") return true end pkgimage = !isempty(clone_targets) @@ -3113,6 +3667,7 @@ end if JLOptions().use_pkgimages == 0 # presence of clone_targets means native code cache @debug "Rejecting cache file $cachefile for $modkey since it would require usage of pkgimage" + record_reason(reasons, "requires pkgimages") return true end rejection_reasons = check_clone_targets(clone_targets) @@ -3121,10 +3676,12 @@ end Reasons=rejection_reasons, var"Image Targets"=parse_image_targets(clone_targets), var"Current Targets"=current_image_targets()) + record_reason(reasons, "target mismatch") return true end if !isfile(ocachefile) @debug "Rejecting cache file $cachefile for $modkey since pkgimage $ocachefile was not found" + record_reason(reasons, "missing ocachefile") return true end else @@ -3133,12 +3690,14 @@ end id = first(modules) if id.first != modkey && modkey != PkgId("") @debug "Rejecting cache file $cachefile for $modkey since it is for $id instead" + record_reason(reasons, "for different pkgid") return true end if build_id != UInt128(0) id_build = (UInt128(checksum) << 64) | id.second if id_build != build_id - @debug "Ignoring cache file $cachefile for $modkey ($((UUID(id_build)))) since it is does not provide desired build_id ($((UUID(build_id))))" + @debug "Ignoring cache file $cachefile for $modkey ($((UUID(id_build)))) since it does not provide desired build_id ($((UUID(build_id))))" + record_reason(reasons, "for different buildid") return true end end @@ -3155,18 +3714,24 @@ end M = root_module(req_key) if PkgId(M) == req_key && module_build_id(M) === req_build_id depmods[i] = M - elseif ignore_loaded + elseif M == Core + @debug "Rejecting cache file $cachefile because it was made with a different julia version" + record_reason(reasons, "wrong julia version") + return true # Won't be able to fulfill dependency + elseif ignore_loaded || !stalecheck # Used by Pkg.precompile given that there it's ok to precompile different versions of loaded packages @goto locate_branch else @debug "Rejecting cache file $cachefile because module $req_key is already loaded and incompatible." + record_reason(reasons, "wrong dep version loaded") return true # Won't be able to fulfill dependency end else @label locate_branch - path = locate_package(req_key) + path = locate_package(req_key) # TODO: add env and/or skip this when stalecheck is false if path === nothing @debug "Rejecting cache file $cachefile because dependency $req_key not found." + record_reason(reasons, "dep missing source") return true # Won't be able to fulfill dependency end depmods[i] = (path, req_key, req_build_id) @@ -3176,66 +3741,93 @@ end # check if this file is going to provide one of our concrete dependencies # or if it provides a version that conflicts with our concrete dependencies # or neither - skip_timecheck = false for (req_key, req_build_id) in _concrete_dependencies build_id = get(modules, req_key, UInt64(0)) if build_id !== UInt64(0) build_id |= UInt128(checksum) << 64 if build_id === req_build_id - skip_timecheck = true + stalecheck = false break end @debug "Rejecting cache file $cachefile because it provides the wrong build_id (got $((UUID(build_id)))) for $req_key (want $(UUID(req_build_id)))" + record_reason(reasons, "wrong dep buildid") return true # cachefile doesn't provide the required version of the dependency end end - # now check if this file is fresh relative to its source files - if !skip_timecheck + # now check if this file's content hash has changed relative to its source files + if stalecheck if !samefile(includes[1].filename, modpath) && !samefile(fixup_stdlib_path(includes[1].filename), modpath) @debug "Rejecting cache file $cachefile because it is for file $(includes[1].filename) not file $modpath" + record_reason(reasons, "wrong source") return true # cache file was compiled from a different path end for (modkey, req_modkey) in requires # verify that `require(modkey, name(req_modkey))` ==> `req_modkey` - if identify_package(modkey, req_modkey.name) != req_modkey - @debug "Rejecting cache file $cachefile because uuid mapping for $modkey => $req_modkey has changed" + pkg = identify_package(modkey, req_modkey.name) + if pkg != req_modkey + @debug "Rejecting cache file $cachefile because uuid mapping for $modkey => $req_modkey has changed, expected $modkey => $(repr("text/plain", pkg))" + record_reason(reasons, "dep uuid changed") return true end end for chi in includes - f, ftime_req = chi.filename, chi.mtime + f, fsize_req, hash_req, ftime_req = chi.filename, chi.fsize, chi.hash, chi.mtime + if startswith(f, string("@depot", Filesystem.pathsep())) + @debug("Rejecting stale cache file $cachefile because its depot could not be resolved") + record_reason(reasons, "nonresolveable depot") + return true + end if !ispath(f) _f = fixup_stdlib_path(f) if isfile(_f) && startswith(_f, Sys.STDLIB) - # mtime is changed by extraction continue end @debug "Rejecting stale cache file $cachefile because file $f does not exist" + record_reason(reasons, "missing sourcefile") return true end - ftime = mtime(f) - is_stale = ( ftime != ftime_req ) && - ( ftime != floor(ftime_req) ) && # Issue #13606, PR #13613: compensate for Docker images rounding mtimes - ( ftime != ceil(ftime_req) ) && # PR: #47433 Compensate for CirceCI's truncating of timestamps in its caching - ( ftime != trunc(ftime_req, digits=6) ) && # Issue #20837, PR #20840: compensate for GlusterFS truncating mtimes to microseconds - ( ftime != 1.0 ) && # PR #43090: provide compatibility with Nix mtime. - !( 0 < (ftime_req - ftime) < 1e-6 ) # PR #45552: Compensate for Windows tar giving mtimes that may be incorrect by up to one microsecond - if is_stale - @debug "Rejecting stale cache file $cachefile (mtime $ftime_req) because file $f (mtime $ftime) has changed" - return true + if ftime_req >= 0.0 + # this is an include_dependency for which we only recorded the mtime + ftime = mtime(f) + is_stale = ( ftime != ftime_req ) && + ( ftime != floor(ftime_req) ) && # Issue #13606, PR #13613: compensate for Docker images rounding mtimes + ( ftime != ceil(ftime_req) ) && # PR: #47433 Compensate for CirceCI's truncating of timestamps in its caching + ( ftime != trunc(ftime_req, digits=6) ) && # Issue #20837, PR #20840: compensate for GlusterFS truncating mtimes to microseconds + ( ftime != 1.0 ) && # PR #43090: provide compatibility with Nix mtime. + !( 0 < (ftime_req - ftime) < 1e-6 ) # PR #45552: Compensate for Windows tar giving mtimes that may be incorrect by up to one microsecond + if is_stale + @debug "Rejecting stale cache file $cachefile because mtime of include_dependency $f has changed (mtime $ftime, before $ftime_req)" + record_reason(reasons, "include_dependency mtime change") + return true + end + else + fsize = filesize(f) + if fsize != fsize_req + @debug "Rejecting stale cache file $cachefile because file size of $f has changed (file size $fsize, before $fsize_req)" + record_reason(reasons, "include_dependency fsize change") + return true + end + hash = isdir(f) ? _crc32c(join(readdir(f))) : open(_crc32c, f, "r") + if hash != hash_req + @debug "Rejecting stale cache file $cachefile because hash of $f has changed (hash $hash, before $hash_req)" + record_reason(reasons, "include_dependency fhash change") + return true + end end end end if !isvalid_file_crc(io) @debug "Rejecting cache file $cachefile because it has an invalid checksum" + record_reason(reasons, "invalid checksum") return true end if pkgimage if !isvalid_pkgimage_crc(io, ocachefile::String) @debug "Rejecting cache file $cachefile because $ocachefile has an invalid checksum" + record_reason(reasons, "ocachefile invalid checksum") return true end end @@ -3243,6 +3835,7 @@ end curr_prefs_hash = get_preferences_hash(id.uuid, prefs) if prefs_hash != curr_prefs_hash @debug "Rejecting cache file $cachefile because preferences hash does not match 0x$(string(prefs_hash, base=16)) != 0x$(string(curr_prefs_hash, base=16))" + record_reason(reasons, "preferences hash mismatch") return true end @@ -3268,9 +3861,32 @@ end """ @__DIR__ -> String -Expand to a string with the absolute path to the directory of the file -containing the macrocall. -Return the current working directory if run from a REPL or if evaluated by `julia -e `. +Macro to obtain the absolute path of the current directory as a string. + +If in a script, returns the directory of the script containing the `@__DIR__` macrocall. If run from a +REPL or if evaluated by `julia -e `, returns the current working directory. + +# Examples + +The example illustrates the difference in the behaviors of `@__DIR__` and `pwd()`, by creating +a simple script in a different directory than the current working one and executing both commands: + +```julia-repl +julia> cd("/home/JuliaUser") # working directory + +julia> # create script at /home/JuliaUser/Projects + open("/home/JuliaUser/Projects/test.jl","w") do io + print(io, \"\"\" + println("@__DIR__ = ", @__DIR__) + println("pwd() = ", pwd()) + \"\"\") + end + +julia> # outputs script directory and current working directory + include("/home/JuliaUser/Projects/test.jl") +@__DIR__ = /home/JuliaUser/Projects +pwd() = /home/JuliaUser +``` """ macro __DIR__() __source__.file === nothing && return nothing @@ -3317,7 +3933,7 @@ function precompile(@nospecialize(argt::Type), m::Method) return precompile(mi) end -precompile(include_package_for_output, (PkgId, String, Vector{String}, Vector{String}, Vector{String}, typeof(_concrete_dependencies), Nothing)) -precompile(include_package_for_output, (PkgId, String, Vector{String}, Vector{String}, Vector{String}, typeof(_concrete_dependencies), String)) -precompile(create_expr_cache, (PkgId, String, String, String, typeof(_concrete_dependencies), IO, IO)) -precompile(create_expr_cache, (PkgId, String, String, Nothing, typeof(_concrete_dependencies), IO, IO)) +precompile(include_package_for_output, (PkgId, String, Vector{String}, Vector{String}, Vector{String}, typeof(_concrete_dependencies), Nothing)) || @assert false +precompile(include_package_for_output, (PkgId, String, Vector{String}, Vector{String}, Vector{String}, typeof(_concrete_dependencies), String)) || @assert false +precompile(create_expr_cache, (PkgId, String, String, String, typeof(_concrete_dependencies), Cmd, IO, IO)) || @assert false +precompile(create_expr_cache, (PkgId, String, String, Nothing, typeof(_concrete_dependencies), Cmd, IO, IO)) || @assert false diff --git a/base/lock.jl b/base/lock.jl index b473b4033e2de..7cbb023a78ee4 100644 --- a/base/lock.jl +++ b/base/lock.jl @@ -145,6 +145,7 @@ Each `lock` must be matched by an [`unlock`](@ref). """ @inline function lock(rl::ReentrantLock) trylock(rl) || (@noinline function slowlock(rl::ReentrantLock) + Threads.lock_profiling() && Threads.inc_lock_conflict_count() c = rl.cond_wait lock(c.lock) try @@ -220,6 +221,8 @@ available. When this function returns, the `lock` has been released, so the caller should not attempt to `unlock` it. +See also: [`@lock`](@ref). + !!! compat "Julia 1.7" Using a [`Channel`](@ref) as the second argument requires Julia 1.7 or later. """ @@ -258,6 +261,9 @@ end ``` This is similar to using [`lock`](@ref) with a `do` block, but avoids creating a closure and thus can improve the performance. + +!!! compat + `@lock` was added in Julia 1.3, and exported in Julia 1.10. """ macro lock(l, expr) quote @@ -288,6 +294,63 @@ macro lock_nofail(l, expr) end end +""" + Lockable(value, lock = ReentrantLock()) + +Creates a `Lockable` object that wraps `value` and +associates it with the provided `lock`. This object +supports [`@lock`](@ref), [`lock`](@ref), [`trylock`](@ref), +[`unlock`](@ref). To access the value, index the lockable object while +holding the lock. + +!!! compat "Julia 1.11" + Requires at least Julia 1.11. + +## Example + +```jldoctest +julia> locked_list = Base.Lockable(Int[]); + +julia> @lock(locked_list, push!(locked_list[], 1)) # must hold the lock to access the value +1-element Vector{Int64}: + 1 + +julia> lock(summary, locked_list) +"1-element Vector{Int64}" +``` +""" +struct Lockable{T, L <: AbstractLock} + value::T + lock::L +end + +Lockable(value) = Lockable(value, ReentrantLock()) +getindex(l::Lockable) = (assert_havelock(l.lock); l.value) + +""" + lock(f::Function, l::Lockable) + +Acquire the lock associated with `l`, execute `f` with the lock held, +and release the lock when `f` returns. `f` will receive one positional +argument: the value wrapped by `l`. If the lock is already locked by a +different task/thread, wait for it to become available. +When this function returns, the `lock` has been released, so the caller should +not attempt to `unlock` it. + +!!! compat "Julia 1.11" + Requires at least Julia 1.11. +""" +function lock(f, l::Lockable) + lock(l.lock) do + f(l.value) + end +end + +# implement the rest of the Lock interface on Lockable +lock(l::Lockable) = lock(l.lock) +trylock(l::Lockable) = trylock(l.lock) +unlock(l::Lockable) = unlock(l.lock) + @eval Threads begin """ Threads.Condition([lock]) diff --git a/base/logging.jl b/base/logging.jl index 208774bcecf38..75b8eea5051d2 100644 --- a/base/logging.jl +++ b/base/logging.jl @@ -3,6 +3,7 @@ module CoreLogging import Base: isless, +, -, convert, show +import Base: ScopedValue, with, @with export AbstractLogger, @@ -132,6 +133,11 @@ isless(a::LogLevel, b::LogLevel) = isless(a.level, b.level) -(level::LogLevel, inc::Integer) = LogLevel(level.level-inc) convert(::Type{LogLevel}, level::Integer) = LogLevel(level) +""" + BelowMinLevel + +Alias for [`LogLevel(-1_000_001)`](@ref LogLevel). +""" const BelowMinLevel = LogLevel(-1000001) """ Debug @@ -157,8 +163,16 @@ const Warn = LogLevel( 1000) Alias for [`LogLevel(2000)`](@ref LogLevel). """ const Error = LogLevel( 2000) +""" + AboveMaxLevel + +Alias for [`LogLevel(1_000_001)`](@ref LogLevel). +""" const AboveMaxLevel = LogLevel( 1000001) +# Global log limiting mechanism for super fast but inflexible global log limiting. +const _min_enabled_level = Ref{LogLevel}(Debug) + function show(io::IO, level::LogLevel) if level == BelowMinLevel print(io, "BelowMinLevel") elseif level == Debug print(io, "Debug") @@ -319,6 +333,15 @@ function issimplekw(@nospecialize val) return false end +# helper function to get the current logger, if enabled for the specified message type +@noinline Base.@constprop :none function current_logger_for_env(std_level::LogLevel, group, _module) + logstate = @inline current_logstate() + if std_level >= logstate.min_enabled_level || env_override_minlevel(group, _module) + return logstate.logger + end + return nothing +end + # Generate code for logging macros function logmsg_code(_module, file, line, level, message, exs...) @nospecialize @@ -335,12 +358,12 @@ function logmsg_code(_module, file, line, level, message, exs...) checkerrors = nothing for kwarg in reverse(log_data.kwargs) if isa(kwarg.args[2].args[1], Symbol) - checkerrors = Expr(:if, Expr(:isdefined, kwarg.args[2]), checkerrors, Expr(:call, Expr(:core, :UndefVarError), QuoteNode(kwarg.args[2].args[1]))) + checkerrors = Expr(:if, Expr(:isdefined, kwarg.args[2]), checkerrors, Expr(:call, Expr(:core, :UndefVarError), QuoteNode(kwarg.args[2].args[1]), QuoteNode(:local))) end end if isa(message, Symbol) message = esc(message) - checkerrors = Expr(:if, Expr(:isdefined, message), checkerrors, Expr(:call, Expr(:core, :UndefVarError), QuoteNode(message.args[1]))) + checkerrors = Expr(:if, Expr(:isdefined, message), checkerrors, Expr(:call, Expr(:core, :UndefVarError), QuoteNode(message.args[1]), QuoteNode(:local))) end logrecord = quote let err = $checkerrors @@ -370,23 +393,23 @@ function logmsg_code(_module, file, line, level, message, exs...) let level = $level # simplify std_level code emitted, if we know it is one of our global constants - std_level = $(level isa Symbol ? :level : :(level isa LogLevel ? level : convert(LogLevel, level)::LogLevel)) - if std_level >= _min_enabled_level[] + std_level = $(level isa Symbol ? :level : :(level isa $LogLevel ? level : convert($LogLevel, level)::$LogLevel)) + if std_level >= $(_min_enabled_level)[] group = $(log_data._group) _module = $(log_data._module) - logger = current_logger_for_env(std_level, group, _module) + logger = $(current_logger_for_env)(std_level, group, _module) if !(logger === nothing) id = $(log_data._id) # Second chance at an early bail-out (before computing the message), # based on arbitrary logger-specific logic. - if invokelatest(shouldlog, logger, level, _module, group, id) + if invokelatest($shouldlog, logger, level, _module, group, id) file = $(log_data._file) if file isa String file = Base.fixup_stdlib_path(file) end line = $(log_data._line) local msg, kwargs - $(logrecord) && invokelatest(handle_message, + $(logrecord) && invokelatest($handle_message, logger, level, msg, _module, group, id, file, line; kwargs...) end @@ -481,9 +504,6 @@ function logmsg_shim(level, message, _module, group, id, file, line, kwargs) nothing end -# Global log limiting mechanism for super fast but inflexible global log limiting. -const _min_enabled_level = Ref{LogLevel}(Debug) - # LogState - a cache of data extracted from the logger, plus the logger itself. struct LogState min_enabled_level::LogLevel @@ -499,15 +519,6 @@ function current_logstate() return something(maybe, _global_logstate)::LogState end -# helper function to get the current logger, if enabled for the specified message type -@noinline Base.@constprop :none function current_logger_for_env(std_level::LogLevel, group, _module) - logstate = @inline current_logstate() - if std_level >= logstate.min_enabled_level || env_override_minlevel(group, _module) - return logstate.logger - end - return nothing -end - with_logstate(f::Function, logstate) = @with(CURRENT_LOGSTATE => logstate, f()) #------------------------------------------------------------------------------- @@ -604,7 +615,7 @@ end Execute `function`, directing all log messages to `logger`. -# Example +# Examples ```julia function test(x) diff --git a/base/math.jl b/base/math.jl index 0f917dce7c99c..14242ba381cde 100644 --- a/base/math.jl +++ b/base/math.jl @@ -95,11 +95,10 @@ julia> clamp.([11, 8, 5], 10, 6) # an example where lo > hi 10 ``` """ -clamp(x::X, lo::L, hi::H) where {X,L,H} = - ifelse(x > hi, convert(promote_type(X,L,H), hi), - ifelse(x < lo, - convert(promote_type(X,L,H), lo), - convert(promote_type(X,L,H), x))) +function clamp(x::X, lo::L, hi::H) where {X,L,H} + T = promote_type(X, L, H) + return (x > hi) ? convert(T, hi) : (x < lo) ? convert(T, lo) : convert(T, x) +end """ clamp(x, T)::T @@ -120,7 +119,14 @@ julia> trunc(Int, 4pi^2) 39 ``` """ -clamp(x, ::Type{T}) where {T<:Integer} = clamp(x, typemin(T), typemax(T)) % T +function clamp(x, ::Type{T}) where {T<:Integer} + # delegating to clamp(x, typemin(T), typemax(T)) would promote types + # this way, we avoid unnecessary conversions + # think of, e.g., clamp(big(2) ^ 200, Int16) + lo = typemin(T) + hi = typemax(T) + return (x > hi) ? hi : (x < lo) ? lo : convert(T, x) +end """ @@ -177,7 +183,7 @@ a Goertzel-like [^DK62] algorithm if `x` is complex. !!! compat "Julia 1.4" This function requires Julia 1.4 or later. -# Example +# Examples ```jldoctest julia> evalpoly(2, (1, 2, 3)) 17 @@ -304,14 +310,19 @@ end # polynomial evaluation using compensated summation. # much more accurate, especially when lo can be combined with other rounding errors -Base.@assume_effects :terminates_locally @inline function exthorner(x, p::Tuple) - hi, lo = p[end], zero(x) - for i in length(p)-1:-1:1 - pi = getfield(p, i) # needed to prove consistency - prod, err = two_mul(hi,x) - hi = pi+prod - lo = fma(lo, x, prod - (hi - pi) + err) - end +@inline function exthorner(x::T, p::Tuple{T,T,T}) where T<:Union{Float32,Float64} + hi, lo = p[lastindex(p)], zero(x) + hi, lo = _exthorner(2, x, p, hi, lo) + hi, lo = _exthorner(1, x, p, hi, lo) + return hi, lo +end + +@inline function _exthorner(i::Int, x::T, p::Tuple{T,T,T}, hi::T, lo::T) where T<:Union{Float32,Float64} + i == 2 || i == 1 || error("unexpected index") + pi = p[i] + prod, err = two_mul(hi,x) + hi = pi+prod + lo = fma(lo, x, prod - (hi - pi) + err) return hi, lo end @@ -354,7 +365,7 @@ log(b::T, x::T) where {T<:Number} = log(x)/log(b) """ log(b,x) -Compute the base `b` logarithm of `x`. Throws [`DomainError`](@ref) for negative +Compute the base `b` logarithm of `x`. Throw a [`DomainError`](@ref) for negative [`Real`](@ref) arguments. # Examples @@ -403,6 +414,8 @@ const libm = Base.libm_name sinh(x) Compute hyperbolic sine of `x`. + +See also [`sin`](@ref). """ sinh(x::Number) @@ -410,6 +423,8 @@ sinh(x::Number) cosh(x) Compute hyperbolic cosine of `x`. + +See also [`cos`](@ref). """ cosh(x::Number) @@ -448,7 +463,7 @@ tanh(x::Number) Compute the inverse tangent of `y` or `y/x`, respectively. -For one argument, this is the angle in radians between the positive *x*-axis and the point +For one real argument, this is the angle in radians between the positive *x*-axis and the point (1, *y*), returning a value in the interval ``[-\\pi/2, \\pi/2]``. For two arguments, this is the angle in radians between the positive *x*-axis and the @@ -488,10 +503,12 @@ asinh(x::Number) # functions that return NaN on non-NaN argument for domain error """ - sin(x) + sin(x::T) where {T <: Number} -> float(T) Compute sine of `x`, where `x` is in radians. +Throw a [`DomainError`](@ref) if `isinf(x)`, return a `T(NaN)` if `isnan(x)`. + See also [`sind`](@ref), [`sinpi`](@ref), [`sincos`](@ref), [`cis`](@ref), [`asin`](@ref). # Examples @@ -519,26 +536,34 @@ julia> round(exp(im*pi/6), digits=3) sin(x::Number) """ - cos(x) + cos(x::T) where {T <: Number} -> float(T) Compute cosine of `x`, where `x` is in radians. +Throw a [`DomainError`](@ref) if `isinf(x)`, return a `T(NaN)` if `isnan(x)`. + See also [`cosd`](@ref), [`cospi`](@ref), [`sincos`](@ref), [`cis`](@ref). """ cos(x::Number) """ - tan(x) + tan(x::T) where {T <: Number} -> float(T) Compute tangent of `x`, where `x` is in radians. + +Throw a [`DomainError`](@ref) if `isinf(x)`, return a `T(NaN)` if `isnan(x)`. + +See also [`tanh`](@ref). """ tan(x::Number) """ - asin(x) + asin(x::T) where {T <: Number} -> float(T) Compute the inverse sine of `x`, where the output is in radians. +Return a `T(NaN)` if `isnan(x)`. + See also [`asind`](@ref) for output in degrees. # Examples @@ -553,9 +578,11 @@ julia> asind.((0, 1/2, 1)) asin(x::Number) """ - acos(x) + acos(x::T) where {T <: Number} -> float(T) Compute the inverse cosine of `x`, where the output is in radians + +Return a `T(NaN)` if `isnan(x)`. """ acos(x::Number) @@ -576,8 +603,14 @@ atanh(x::Number) """ log(x) -Compute the natural logarithm of `x`. Throws [`DomainError`](@ref) for negative -[`Real`](@ref) arguments. Use complex negative arguments to obtain complex results. +Compute the natural logarithm of `x`. + +Throw a [`DomainError`](@ref) for negative [`Real`](@ref) arguments. +Use [`Complex`](@ref) arguments to obtain [`Complex`](@ref) results. + +!!! note "Branch cut" + `log` has a branch cut along the negative real axis; `-0.0im` is taken + to be below the axis. See also [`ℯ`](@ref), [`log1p`](@ref), [`log2`](@ref), [`log10`](@ref). @@ -593,6 +626,12 @@ Stacktrace: [1] throw_complex_domainerror(::Symbol, ::Float64) at ./math.jl:31 [...] +julia> log(-3 + 0im) +1.0986122886681098 + 3.141592653589793im + +julia> log(-3 - 0.0im) +1.0986122886681098 - 3.141592653589793im + julia> log.(exp.(-1:1)) 3-element Vector{Float64}: -1.0 @@ -605,7 +644,7 @@ log(x::Number) """ log2(x) -Compute the logarithm of `x` to base 2. Throws [`DomainError`](@ref) for negative +Compute the logarithm of `x` to base 2. Throw a [`DomainError`](@ref) for negative [`Real`](@ref) arguments. See also: [`exp2`](@ref), [`ldexp`](@ref), [`ispow2`](@ref). @@ -638,7 +677,7 @@ log2(x) log10(x) Compute the logarithm of `x` to base 10. -Throws [`DomainError`](@ref) for negative [`Real`](@ref) arguments. +Throw a [`DomainError`](@ref) for negative [`Real`](@ref) arguments. # Examples ```jldoctest; filter = r"Stacktrace:(\\n \\[[0-9]+\\].*)*" @@ -661,7 +700,7 @@ log10(x) """ log1p(x) -Accurate natural logarithm of `1+x`. Throws [`DomainError`](@ref) for [`Real`](@ref) +Accurate natural logarithm of `1+x`. Throw a [`DomainError`](@ref) for [`Real`](@ref) arguments less than -1. # Examples @@ -690,8 +729,16 @@ end """ sqrt(x) -Return ``\\sqrt{x}``. Throws [`DomainError`](@ref) for negative [`Real`](@ref) arguments. -Use complex negative arguments instead. The prefix operator `√` is equivalent to `sqrt`. +Return ``\\sqrt{x}``. + +Throw a [`DomainError`](@ref) for negative [`Real`](@ref) arguments. +Use [`Complex`](@ref) negative arguments instead to obtain a [`Complex`](@ref) result. + +The prefix operator `√` is equivalent to `sqrt`. + +!!! note "Branch cut" + `sqrt` has a branch cut along the negative real axis; `-0.0im` is taken + to be below the axis. See also: [`hypot`](@ref). @@ -710,6 +757,9 @@ Stacktrace: julia> sqrt(big(complex(-81))) 0.0 + 9.0im +julia> sqrt(-81 - 0.0im) # -0.0im is below the branch cut +0.0 - 9.0im + julia> .√(1:4) 4-element Vector{Float64}: 1.0 @@ -773,8 +823,8 @@ true ``` """ hypot(x::Number) = abs(float(x)) -hypot(x::Number, y::Number) = _hypot(promote(float(x), y)...) -hypot(x::Number, y::Number, xs::Number...) = _hypot(promote(float(x), y, xs...)) +hypot(x::Number, y::Number) = _hypot(float.(promote(x, y))...) +hypot(x::Number, y::Number, xs::Number...) = _hypot(float.(promote(x, y, xs...))) function _hypot(x, y) # preserves unit axu = abs(x) @@ -927,9 +977,11 @@ end Compute ``x \\times 2^n``. +See also [`frexp`](@ref), [`exponent`](@ref). + # Examples ```jldoctest -julia> ldexp(5., 2) +julia> ldexp(5.0, 2) 20.0 ``` """ @@ -979,27 +1031,36 @@ end ldexp(x::Float16, q::Integer) = Float16(ldexp(Float32(x), q)) """ - exponent(x) -> Int + exponent(x::Real) -> Int -Returns the largest integer `y` such that `2^y ≤ abs(x)`. +Return the largest integer `y` such that `2^y ≤ abs(x)`. For a normalized floating-point number `x`, this corresponds to the exponent of `x`. +Throws a `DomainError` when `x` is zero, infinite, or [`NaN`](@ref). +For any other non-subnormal floating-point number `x`, this corresponds to the exponent bits of `x`. + +See also [`signbit`](@ref), [`significand`](@ref), [`frexp`](@ref), [`issubnormal`](@ref), [`log2`](@ref), [`ldexp`](@ref). # Examples ```jldoctest julia> exponent(8) 3 -julia> exponent(64//1) -6 - julia> exponent(6.5) 2 -julia> exponent(16.0) -4 +julia> exponent(-1//4) +-2 julia> exponent(3.142e-4) -12 + +julia> exponent(floatmin(Float32)), exponent(nextfloat(0.0f0)) +(-126, -149) + +julia> exponent(0.0) +ERROR: DomainError with 0.0: +Cannot be ±0.0. +[...] ``` """ function exponent(x::T) where T<:IEEEFloat @@ -1039,6 +1100,8 @@ a non-zero finite number, then the result will be a number of the same type and sign as `x`, and whose absolute value is on the interval ``[1,2)``. Otherwise `x` is returned. +See also [`frexp`](@ref), [`exponent`](@ref). + # Examples ```jldoctest julia> significand(15.2) @@ -1073,10 +1136,19 @@ end Return `(x,exp)` such that `x` has a magnitude in the interval ``[1/2, 1)`` or 0, and `val` is equal to ``x \\times 2^{exp}``. + +See also [`significand`](@ref), [`exponent`](@ref), [`ldexp`](@ref). + # Examples ```jldoctest -julia> frexp(12.8) -(0.8, 4) +julia> frexp(6.0) +(0.75, 3) + +julia> significand(6.0), exponent(6.0) # interval [1, 2) instead +(1.5, 2) + +julia> frexp(0.0), frexp(NaN), frexp(-Inf) # exponent would give an error +((0.0, 0), (NaN, 0), (-Inf, 0)) ``` """ function frexp(x::T) where T<:IEEEFloat @@ -1189,6 +1261,10 @@ function modf(x::T) where T<:IEEEFloat return (rx, ix) end +@inline function use_power_by_squaring(n::Integer) + -2^12 <= n <= 3 * 2^13 +end + # @constprop aggressive to help the compiler see the switch between the integer and float # variants for callers with constant `y` @constprop :aggressive function ^(x::Float64, y::Float64) @@ -1201,24 +1277,33 @@ end y = sign(y)*0x1.8p62 end yint = unsafe_trunc(Int64, y) # This is actually safe since julia freezes the result - y == yint && return @noinline x^yint - 2*xu==0 && return abs(y)*Inf*(!(y>0)) # if x==0 - x<0 && throw_exp_domainerror(x) # |y| is small enough that y isn't an integer - !isfinite(x) && return x*(y>0 || isnan(x)) # x is inf or NaN + yisint = y == yint + if yisint + yint == 0 && return 1.0 + use_power_by_squaring(yint) && return @noinline pow_body(x, yint) + end + 2*xu==0 && return abs(y)*Inf*(!(y>0)) # if x === +0.0 or -0.0 (Inf * false === 0.0) + s = 1 + if x < 0 + !yisint && throw_exp_domainerror(x) # y isn't an integer + s = ifelse(isodd(yint), -1, 1) + end + !isfinite(x) && return copysign(x,s)*(y>0 || isnan(x)) # x is inf or NaN + return copysign(pow_body(abs(x), y), s) +end + +@assume_effects :foldable @noinline function pow_body(x::Float64, y::Float64) + xu = reinterpret(UInt64, x) if xu < (UInt64(1)<<52) # x is subnormal xu = reinterpret(UInt64, x * 0x1p52) # normalize x xu &= ~sign_mask(Float64) xu -= UInt64(52) << 52 # mess with the exponent end - return pow_body(xu, y) -end - -@inline function pow_body(xu::UInt64, y::Float64) - logxhi,logxlo = Base.Math._log_ext(xu) + logxhi,logxlo = _log_ext(xu) xyhi, xylo = two_mul(logxhi,y) xylo = muladd(logxlo, y, xylo) hi = xyhi+xylo - return Base.Math.exp_impl(hi, xylo-(hi-xyhi), Val(:ℯ)) + return @inline Base.Math.exp_impl(hi, xylo-(hi-xyhi), Val(:ℯ)) end @constprop :aggressive function ^(x::T, y::T) where T <: Union{Float16, Float32} @@ -1242,12 +1327,29 @@ end return T(exp2(log2(abs(widen(x))) * y)) end -# compensated power by squaring @constprop :aggressive @inline function ^(x::Float64, n::Integer) + x^clamp(n, Int64) +end +@constprop :aggressive @inline function ^(x::Float64, n::Int64) n == 0 && return one(x) - return pow_body(x, n) + if use_power_by_squaring(n) + return pow_body(x, n) + else + s = ifelse(x < 0 && isodd(n), -1.0, 1.0) + x = abs(x) + y = float(n) + if y == n + return copysign(pow_body(x, y), s) + else + n2 = n % 1024 + y = float(n - n2) + return pow_body(x, y) * copysign(pow_body(x, n2), s) + end + end end +# compensated power by squaring +# this method is only reliable for -2^20 < n < 2^20 (cf. #53881 #53886) @assume_effects :terminates_locally @noinline function pow_body(x::Float64, n::Integer) y = 1.0 xnlo = ynlo = 0.0 @@ -1287,7 +1389,7 @@ end function add22condh(xh::Float64, xl::Float64, yh::Float64, yl::Float64) # This algorithm, due to Dekker, computes the sum of two - # double-double numbers and returns the high double. References: + # double-double numbers and return the high double. References: # [1] http://www.digizeitschriften.de/en/dms/img/?PID=GDZPPN001170007 # [2] https://doi.org/10.1007/BF01397083 r = xh+yh @@ -1463,9 +1565,11 @@ end rem2pi(x::Float32, r::RoundingMode) = Float32(rem2pi(Float64(x), r)) rem2pi(x::Float16, r::RoundingMode) = Float16(rem2pi(Float64(x), r)) rem2pi(x::Int32, r::RoundingMode) = rem2pi(Float64(x), r) -function rem2pi(x::Int64, r::RoundingMode) - fx = Float64(x) - fx == x || throw(ArgumentError("Int64 argument to rem2pi is too large: $x")) + +# general fallback +function rem2pi(x::Integer, r::RoundingMode) + fx = float(x) + fx == x || throw(ArgumentError(LazyString(typeof(x), " argument to rem2pi is too large: ", x))) rem2pi(fx, r) end diff --git a/base/meta.jl b/base/meta.jl index 31fef1b9697e3..bc07a7efa6c74 100644 --- a/base/meta.jl +++ b/base/meta.jl @@ -377,6 +377,9 @@ function _partially_inline!(@nospecialize(x), slot_replacements::Vector{Any}, x.dest + statement_offset, ) end + if isa(x, Core.EnterNode) + return Core.EnterNode(x, x.catch_dest + statement_offset) + end if isa(x, Expr) head = x.head if head === :static_parameter @@ -424,8 +427,6 @@ function _partially_inline!(@nospecialize(x), slot_replacements::Vector{Any}, static_param_values, slot_offset, statement_offset, boundscheck) x.args[2] += statement_offset - elseif head === :enter - x.args[1] += statement_offset elseif head === :isdefined arg = x.args[1] # inlining a QuoteNode or literal into `Expr(:isdefined, x)` is invalid, replace with true @@ -460,4 +461,45 @@ end _instantiate_type_in_env(x, spsig, spvals) = ccall(:jl_instantiate_type_in_env, Any, (Any, Any, Ptr{Any}), x, spsig, spvals) +""" + Meta.unblock(expr) + +Peel away redundant block expressions. + +Specifically, the following expressions are stripped by this function: +- `:block` expressions with a single non-line-number argument. +- Pairs of `:var"hygienic-scope"` / `:escape` expressions. +""" +function unblock(@nospecialize ex) + while isexpr(ex, :var"hygienic-scope") + isexpr(ex.args[1], :escape) || break + ex = ex.args[1].args[1] + end + isexpr(ex, :block) || return ex + exs = filter(ex -> !(isa(ex, LineNumberNode) || isexpr(ex, :line)), ex.args) + length(exs) == 1 || return ex + return unblock(exs[1]) +end + +""" + Meta.unescape(expr) + +Peel away `:escape` expressions and redundant block expressions (see +[`unblock`](@ref)). +""" +function unescape(@nospecialize ex) + ex = unblock(ex) + while isexpr(ex, :escape) || isexpr(ex, :var"hygienic-scope") + ex = unblock(ex.args[1]) + end + return ex +end + +""" + Meta.uncurly(expr) + +Turn `T{P...}` into just `T`. +""" +uncurly(@nospecialize ex) = isexpr(ex, :curly) ? ex.args[1] : ex + end # module diff --git a/base/methodshow.jl b/base/methodshow.jl index 0eb99dc88303f..477acd2960c48 100644 --- a/base/methodshow.jl +++ b/base/methodshow.jl @@ -286,6 +286,29 @@ function show_method_list_header(io::IO, ms::MethodList, namefmt::Function) !iszero(n) && print(io, ":") end +# Determine the `modulecolor` value to pass to `show_method` +function _modulecolor(method::Method) + mmt = get_methodtable(method) + if mmt === nothing || mmt.module === parentmodule(method) + return nothing + end + # `mmt` is only particularly relevant for external method tables. Since the primary + # method table is shared, we now need to distinguish "primary" methods by trying to + # check if there is a primary `DataType` to identify it with. c.f. how `jl_method_def` + # would derive this same information (for the name). + ft = argument_datatype((unwrap_unionall(method.sig)::DataType).parameters[1]) + # `ft` should be the type associated with the first argument in the method signature. + # If it's `Type`, try to unwrap it again. + if isType(ft) + ft = argument_datatype(ft.parameters[1]) + end + if ft === nothing || parentmodule(method) === parentmodule(ft) !== Core + return nothing + end + m = parentmodule_before_main(method) + return get!(() -> popfirst!(STACKTRACE_MODULECOLORS), STACKTRACE_FIXEDCOLORS, m) +end + function show_method_table(io::IO, ms::MethodList, max::Int=-1, header::Bool=true) mt = ms.mt name = mt.name @@ -300,12 +323,6 @@ function show_method_table(io::IO, ms::MethodList, max::Int=-1, header::Bool=tru last_shown_line_infos = get(io, :last_shown_line_infos, nothing) last_shown_line_infos === nothing || empty!(last_shown_line_infos) - modul = if mt === _TYPE_NAME.mt && length(ms) > 0 # type constructor - which(ms.ms[1].module, ms.ms[1].name) - else - mt.module - end - digit_align_width = length(string(max > 0 ? max : length(ms))) for meth in ms @@ -315,13 +332,7 @@ function show_method_table(io::IO, ms::MethodList, max::Int=-1, header::Bool=tru print(io, " ", lpad("[$n]", digit_align_width + 2), " ") - modulecolor = if parentmodule(meth) == modul - nothing - else - m = parentmodule_before_main(meth) - get!(() -> popfirst!(STACKTRACE_MODULECOLORS), STACKTRACE_FIXEDCOLORS, m) - end - show_method(io, meth; modulecolor) + show_method(io, meth; modulecolor=_modulecolor(meth)) file, line = updated_methodloc(meth) if last_shown_line_infos !== nothing diff --git a/base/missing.jl b/base/missing.jl index 35e1b4034643c..ce174edc297e3 100644 --- a/base/missing.jl +++ b/base/missing.jl @@ -241,7 +241,7 @@ function iterate(itr::SkipMissing, state...) y = iterate(itr.x, state...) y === nothing && return nothing item, state = y - while item === missing + while ismissing(item) y = iterate(itr.x, state) y === nothing && return nothing item, state = y @@ -251,12 +251,12 @@ end IndexStyle(::Type{<:SkipMissing{T}}) where {T} = IndexStyle(T) eachindex(itr::SkipMissing) = - Iterators.filter(i -> @inbounds(itr.x[i]) !== missing, eachindex(itr.x)) + Iterators.filter(i -> !ismissing(@inbounds(itr.x[i])), eachindex(itr.x)) keys(itr::SkipMissing) = - Iterators.filter(i -> @inbounds(itr.x[i]) !== missing, keys(itr.x)) + Iterators.filter(i -> !ismissing(@inbounds(itr.x[i])), keys(itr.x)) @propagate_inbounds function getindex(itr::SkipMissing, I...) v = itr.x[I...] - v === missing && throw(MissingException(LazyString("the value at index ", I, " is missing"))) + ismissing(v) && throw(MissingException(LazyString("the value at index ", I, " is missing"))) v end @@ -280,18 +280,18 @@ function _mapreduce(f, op, ::IndexLinear, itr::SkipMissing{<:AbstractArray}) ilast = last(inds) for outer i in i:ilast @inbounds ai = A[i] - ai !== missing && break + !ismissing(ai) && break end - ai === missing && return mapreduce_empty(f, op, eltype(itr)) + ismissing(ai) && return mapreduce_empty(f, op, eltype(itr)) a1::eltype(itr) = ai i == typemax(typeof(i)) && return mapreduce_first(f, op, a1) i += 1 ai = missing for outer i in i:ilast @inbounds ai = A[i] - ai !== missing && break + !ismissing(ai) && break end - ai === missing && return mapreduce_first(f, op, a1) + ismissing(ai) && return mapreduce_first(f, op, a1) # We know A contains at least two non-missing entries: the result cannot be nothing something(mapreduce_impl(f, op, itr, first(inds), last(inds))) end @@ -309,7 +309,7 @@ mapreduce_impl(f, op, A::SkipMissing, ifirst::Integer, ilast::Integer) = return nothing elseif ifirst == ilast @inbounds a1 = A[ifirst] - if a1 === missing + if ismissing(a1) return nothing else return Some(mapreduce_first(f, op, a1)) @@ -320,25 +320,25 @@ mapreduce_impl(f, op, A::SkipMissing, ifirst::Integer, ilast::Integer) = i = ifirst for outer i in i:ilast @inbounds ai = A[i] - ai !== missing && break + !ismissing(ai) && break end - ai === missing && return nothing + ismissing(ai) && return nothing a1 = ai::eltype(itr) i == typemax(typeof(i)) && return Some(mapreduce_first(f, op, a1)) i += 1 ai = missing for outer i in i:ilast @inbounds ai = A[i] - ai !== missing && break + !ismissing(ai) && break end - ai === missing && return Some(mapreduce_first(f, op, a1)) + ismissing(ai) && return Some(mapreduce_first(f, op, a1)) a2 = ai::eltype(itr) i == typemax(typeof(i)) && return Some(op(f(a1), f(a2))) i += 1 v = op(f(a1), f(a2)) @simd for i = i:ilast @inbounds ai = A[i] - if ai !== missing + if !ismissing(ai) v = op(v, f(ai)) end end @@ -384,7 +384,7 @@ julia> filter(isodd, skipmissing(x)) function filter(f, itr::SkipMissing{<:AbstractArray}) y = similar(itr.x, eltype(itr), 0) for xi in itr.x - if xi !== missing && f(xi) + if !ismissing(xi) && f(xi) push!(y, xi) end end @@ -450,7 +450,7 @@ ERROR: `b` is still missing macro coalesce(args...) expr = :(missing) for arg in reverse(args) - expr = :((val = $arg) !== missing ? val : $expr) + expr = :(!ismissing((val = $(esc(arg));)) ? val : $expr) end - return esc(:(let val; $expr; end)) + return :(let val; $expr; end) end diff --git a/base/mpfr.jl b/base/mpfr.jl index 276fd430ff1e0..788ecf4f99790 100644 --- a/base/mpfr.jl +++ b/base/mpfr.jl @@ -16,7 +16,7 @@ import cosh, sinh, tanh, sech, csch, coth, acosh, asinh, atanh, lerpi, cbrt, typemax, typemin, unsafe_trunc, floatmin, floatmax, rounding, setrounding, maxintfloat, widen, significand, frexp, tryparse, iszero, - isone, big, _string_n, decompose, minmax, + isone, big, _string_n, decompose, minmax, _precision_with_base_2, sinpi, cospi, sincospi, tanpi, sind, cosd, tand, asind, acosd, atand, uinttype, exponent_max, exponent_min, ieee754_representation, significand_mask, RawBigIntRoundingIncrementHelper, truncated, RawBigInt @@ -109,8 +109,9 @@ end tie_breaker_is_to_even(::MPFRRoundingMode) = true const ROUNDING_MODE = Ref{MPFRRoundingMode}(MPFRRoundNearest) +const CURRENT_ROUNDING_MODE = Base.ScopedValue{MPFRRoundingMode}() const DEFAULT_PRECISION = Ref{Clong}(256) - +const CURRENT_PRECISION = Base.ScopedValue{Clong}() # Basic type and initialization definitions # Warning: the constants are MPFR implementation details from @@ -144,7 +145,7 @@ mutable struct BigFloat <: AbstractFloat return new(prec, sign, exp, pointer(d), d) end - function BigFloat(; precision::Integer=DEFAULT_PRECISION[]) + function BigFloat(; precision::Integer=_precision_with_base_2(BigFloat)) precision < 1 && throw(DomainError(precision, "`precision` cannot be less than 1.")) nb = ccall((:mpfr_custom_get_size,libmpfr), Csize_t, (Clong,), precision) nb = (nb + Core.sizeof(Limb) - 1) ÷ Core.sizeof(Limb) # align to number of Limb allocations required for this @@ -158,11 +159,17 @@ end # The rounding mode here shouldn't matter. significand_limb_count(x::BigFloat) = div(sizeof(x._d), sizeof(Limb), RoundToZero) -rounding_raw(::Type{BigFloat}) = ROUNDING_MODE[] +rounding_raw(::Type{BigFloat}) = something(Base.ScopedValues.get(CURRENT_ROUNDING_MODE), ROUNDING_MODE[]) setrounding_raw(::Type{BigFloat}, r::MPFRRoundingMode) = ROUNDING_MODE[]=r +function setrounding_raw(f::Function, ::Type{BigFloat}, r::MPFRRoundingMode) + Base.@with(CURRENT_ROUNDING_MODE => r, f()) +end + rounding(::Type{BigFloat}) = convert(RoundingMode, rounding_raw(BigFloat)) setrounding(::Type{BigFloat}, r::RoundingMode) = setrounding_raw(BigFloat, convert(MPFRRoundingMode, r)) +setrounding(f::Function, ::Type{BigFloat}, r::RoundingMode) = + setrounding_raw(f, BigFloat, convert(MPFRRoundingMode, r)) # overload the definition of unsafe_convert to ensure that `x.d` is assigned @@ -220,8 +227,8 @@ BigFloat(x, r::RoundingMode) widen(::Type{Float64}) = BigFloat widen(::Type{BigFloat}) = BigFloat -function BigFloat(x::BigFloat, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) - if precision == _precision(x) +function BigFloat(x::BigFloat, r::MPFRRoundingMode=rounding_raw(BigFloat); precision::Integer=_precision_with_base_2(BigFloat)) + if precision == _precision_with_base_2(x) return x else z = BigFloat(;precision=precision) @@ -232,7 +239,7 @@ function BigFloat(x::BigFloat, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::I end function _duplicate(x::BigFloat) - z = BigFloat(;precision=_precision(x)) + z = BigFloat(;precision=_precision_with_base_2(x)) ccall((:mpfr_set, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Int32), z, x, 0) return z end @@ -240,7 +247,7 @@ end # convert to BigFloat for (fJ, fC) in ((:si,:Clong), (:ui,:Culong)) @eval begin - function BigFloat(x::($fC), r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) + function BigFloat(x::($fC), r::MPFRRoundingMode=rounding_raw(BigFloat); precision::Integer=_precision_with_base_2(BigFloat)) z = BigFloat(;precision=precision) ccall(($(string(:mpfr_set_,fJ)), libmpfr), Int32, (Ref{BigFloat}, $fC, MPFRRoundingMode), z, x, r) return z @@ -248,7 +255,7 @@ for (fJ, fC) in ((:si,:Clong), (:ui,:Culong)) end end -function BigFloat(x::Float64, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) +function BigFloat(x::Float64, r::MPFRRoundingMode=rounding_raw(BigFloat); precision::Integer=_precision_with_base_2(BigFloat)) z = BigFloat(;precision) # punt on the hard case where we might have to deal with rounding # we could use this path in all cases, but mpfr_set_d has a lot of overhead. @@ -291,26 +298,26 @@ function BigFloat(x::Float64, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::In z end -function BigFloat(x::BigInt, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) +function BigFloat(x::BigInt, r::MPFRRoundingMode=rounding_raw(BigFloat); precision::Integer=_precision_with_base_2(BigFloat)) z = BigFloat(;precision=precision) ccall((:mpfr_set_z, libmpfr), Int32, (Ref{BigFloat}, Ref{BigInt}, MPFRRoundingMode), z, x, r) return z end -BigFloat(x::Integer; precision::Integer=DEFAULT_PRECISION[]) = - BigFloat(BigInt(x)::BigInt, ROUNDING_MODE[]; precision=precision) -BigFloat(x::Integer, r::MPFRRoundingMode; precision::Integer=DEFAULT_PRECISION[]) = +BigFloat(x::Integer; precision::Integer=_precision_with_base_2(BigFloat)) = + BigFloat(BigInt(x)::BigInt, rounding_raw(BigFloat); precision=precision) +BigFloat(x::Integer, r::MPFRRoundingMode; precision::Integer=_precision_with_base_2(BigFloat)) = BigFloat(BigInt(x)::BigInt, r; precision=precision) -BigFloat(x::Union{Bool,Int8,Int16,Int32}, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) = +BigFloat(x::Union{Bool,Int8,Int16,Int32}, r::MPFRRoundingMode=rounding_raw(BigFloat); precision::Integer=_precision_with_base_2(BigFloat)) = BigFloat(convert(Clong, x), r; precision=precision) -BigFloat(x::Union{UInt8,UInt16,UInt32}, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) = +BigFloat(x::Union{UInt8,UInt16,UInt32}, r::MPFRRoundingMode=rounding_raw(BigFloat); precision::Integer=_precision_with_base_2(BigFloat)) = BigFloat(convert(Culong, x), r; precision=precision) -BigFloat(x::Union{Float16,Float32}, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) = +BigFloat(x::Union{Float16,Float32}, r::MPFRRoundingMode=rounding_raw(BigFloat); precision::Integer=_precision_with_base_2(BigFloat)) = BigFloat(Float64(x), r; precision=precision) -function BigFloat(x::Rational, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) +function BigFloat(x::Rational, r::MPFRRoundingMode=rounding_raw(BigFloat); precision::Integer=_precision_with_base_2(BigFloat)) setprecision(BigFloat, precision) do setrounding_raw(BigFloat, r) do BigFloat(numerator(x))::BigFloat / BigFloat(denominator(x))::BigFloat @@ -318,14 +325,14 @@ function BigFloat(x::Rational, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::I end end -function tryparse(::Type{BigFloat}, s::AbstractString; base::Integer=0, precision::Integer=DEFAULT_PRECISION[], rounding::MPFRRoundingMode=ROUNDING_MODE[]) +function tryparse(::Type{BigFloat}, s::AbstractString; base::Integer=0, precision::Integer=_precision_with_base_2(BigFloat), rounding::MPFRRoundingMode=rounding_raw(BigFloat)) !isempty(s) && isspace(s[end]) && return tryparse(BigFloat, rstrip(s), base = base) z = BigFloat(precision=precision) err = ccall((:mpfr_set_str, libmpfr), Int32, (Ref{BigFloat}, Cstring, Int32, MPFRRoundingMode), z, s, base, rounding) err == 0 ? z : nothing end -BigFloat(x::AbstractString, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) = +BigFloat(x::AbstractString, r::MPFRRoundingMode=rounding_raw(BigFloat); precision::Integer=_precision_with_base_2(BigFloat)) = parse(BigFloat, x; precision=precision, rounding=r) Rational(x::BigFloat) = convert(Rational{BigInt}, x) @@ -333,9 +340,9 @@ AbstractFloat(x::BigInt) = BigFloat(x) float(::Type{BigInt}) = BigFloat -BigFloat(x::Real, r::RoundingMode; precision::Integer=DEFAULT_PRECISION[]) = +BigFloat(x::Real, r::RoundingMode; precision::Integer=_precision_with_base_2(BigFloat)) = BigFloat(x, convert(MPFRRoundingMode, r); precision=precision)::BigFloat -BigFloat(x::AbstractString, r::RoundingMode; precision::Integer=DEFAULT_PRECISION[]) = +BigFloat(x::AbstractString, r::RoundingMode; precision::Integer=_precision_with_base_2(BigFloat)) = BigFloat(x, convert(MPFRRoundingMode, r); precision=precision) ## BigFloat -> Integer @@ -385,7 +392,7 @@ round(::Type{BigInt}, x::BigFloat, r::RoundingMode) = unsafe_trunc(::Type{T}, x::BigFloat) where {T<:Integer} = unsafe_trunc(T, _unchecked_cast(T, x, RoundToZero)) unsafe_trunc(::Type{BigInt}, x::BigFloat) = _unchecked_cast(BigInt, x, RoundToZero) -round(::Type{T}, x::BigFloat) where T<:Integer = round(T, x, ROUNDING_MODE[]) +round(::Type{T}, x::BigFloat) where T<:Integer = round(T, x, rounding_raw(BigFloat)) # these two methods are split to increase their precedence in disambiguation: round(::Type{Integer}, x::BigFloat, r::RoundingMode) = round(BigInt, x, r) round(::Type{Integer}, x::BigFloat, r::MPFRRoundingMode) = round(BigInt, x, r) @@ -461,9 +468,9 @@ function to_ieee754(::Type{T}, x::BigFloat, rm) where {T<:AbstractFloat} reinterpret(T, ret_u) end -Float16(x::BigFloat, r::MPFRRoundingMode=ROUNDING_MODE[]) = to_ieee754(Float16, x, r) -Float32(x::BigFloat, r::MPFRRoundingMode=ROUNDING_MODE[]) = to_ieee754(Float32, x, r) -Float64(x::BigFloat, r::MPFRRoundingMode=ROUNDING_MODE[]) = to_ieee754(Float64, x, r) +Float16(x::BigFloat, r::MPFRRoundingMode=rounding_raw(BigFloat)) = to_ieee754(Float16, x, r) +Float32(x::BigFloat, r::MPFRRoundingMode=rounding_raw(BigFloat)) = to_ieee754(Float32, x, r) +Float64(x::BigFloat, r::MPFRRoundingMode=rounding_raw(BigFloat)) = to_ieee754(Float64, x, r) Float16(x::BigFloat, r::RoundingMode) = to_ieee754(Float16, x, r) Float32(x::BigFloat, r::RoundingMode) = to_ieee754(Float32, x, r) Float64(x::BigFloat, r::RoundingMode) = to_ieee754(Float64, x, r) @@ -490,14 +497,14 @@ for (fJ, fC) in ((:+,:add), (:*,:mul)) # BigFloat function ($fJ)(x::BigFloat, y::BigFloat) z = BigFloat() - ccall(($(string(:mpfr_,fC)),libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,fC)),libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, rounding_raw(BigFloat)) return z end # Unsigned Integer function ($fJ)(x::BigFloat, c::CulongMax) z = BigFloat() - ccall(($(string(:mpfr_,fC,:_ui)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, c, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,fC,:_ui)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, c, rounding_raw(BigFloat)) return z end ($fJ)(c::CulongMax, x::BigFloat) = ($fJ)(x,c) @@ -505,7 +512,7 @@ for (fJ, fC) in ((:+,:add), (:*,:mul)) # Signed Integer function ($fJ)(x::BigFloat, c::ClongMax) z = BigFloat() - ccall(($(string(:mpfr_,fC,:_si)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Clong, MPFRRoundingMode), z, x, c, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,fC,:_si)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Clong, MPFRRoundingMode), z, x, c, rounding_raw(BigFloat)) return z end ($fJ)(c::ClongMax, x::BigFloat) = ($fJ)(x,c) @@ -513,7 +520,7 @@ for (fJ, fC) in ((:+,:add), (:*,:mul)) # Float32/Float64 function ($fJ)(x::BigFloat, c::CdoubleMax) z = BigFloat() - ccall(($(string(:mpfr_,fC,:_d)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Cdouble, MPFRRoundingMode), z, x, c, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,fC,:_d)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Cdouble, MPFRRoundingMode), z, x, c, rounding_raw(BigFloat)) return z end ($fJ)(c::CdoubleMax, x::BigFloat) = ($fJ)(x,c) @@ -521,7 +528,7 @@ for (fJ, fC) in ((:+,:add), (:*,:mul)) # BigInt function ($fJ)(x::BigFloat, c::BigInt) z = BigFloat() - ccall(($(string(:mpfr_,fC,:_z)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigInt}, MPFRRoundingMode), z, x, c, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,fC,:_z)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigInt}, MPFRRoundingMode), z, x, c, rounding_raw(BigFloat)) return z end ($fJ)(c::BigInt, x::BigFloat) = ($fJ)(x,c) @@ -533,50 +540,50 @@ for (fJ, fC) in ((:-,:sub), (:/,:div)) # BigFloat function ($fJ)(x::BigFloat, y::BigFloat) z = BigFloat() - ccall(($(string(:mpfr_,fC)),libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,fC)),libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, rounding_raw(BigFloat)) return z end # Unsigned Int function ($fJ)(x::BigFloat, c::CulongMax) z = BigFloat() - ccall(($(string(:mpfr_,fC,:_ui)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, c, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,fC,:_ui)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, c, rounding_raw(BigFloat)) return z end function ($fJ)(c::CulongMax, x::BigFloat) z = BigFloat() - ccall(($(string(:mpfr_,:ui_,fC)), libmpfr), Int32, (Ref{BigFloat}, Culong, Ref{BigFloat}, MPFRRoundingMode), z, c, x, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,:ui_,fC)), libmpfr), Int32, (Ref{BigFloat}, Culong, Ref{BigFloat}, MPFRRoundingMode), z, c, x, rounding_raw(BigFloat)) return z end # Signed Integer function ($fJ)(x::BigFloat, c::ClongMax) z = BigFloat() - ccall(($(string(:mpfr_,fC,:_si)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Clong, MPFRRoundingMode), z, x, c, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,fC,:_si)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Clong, MPFRRoundingMode), z, x, c, rounding_raw(BigFloat)) return z end function ($fJ)(c::ClongMax, x::BigFloat) z = BigFloat() - ccall(($(string(:mpfr_,:si_,fC)), libmpfr), Int32, (Ref{BigFloat}, Clong, Ref{BigFloat}, MPFRRoundingMode), z, c, x, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,:si_,fC)), libmpfr), Int32, (Ref{BigFloat}, Clong, Ref{BigFloat}, MPFRRoundingMode), z, c, x, rounding_raw(BigFloat)) return z end # Float32/Float64 function ($fJ)(x::BigFloat, c::CdoubleMax) z = BigFloat() - ccall(($(string(:mpfr_,fC,:_d)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Cdouble, MPFRRoundingMode), z, x, c, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,fC,:_d)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Cdouble, MPFRRoundingMode), z, x, c, rounding_raw(BigFloat)) return z end function ($fJ)(c::CdoubleMax, x::BigFloat) z = BigFloat() - ccall(($(string(:mpfr_,:d_,fC)), libmpfr), Int32, (Ref{BigFloat}, Cdouble, Ref{BigFloat}, MPFRRoundingMode), z, c, x, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,:d_,fC)), libmpfr), Int32, (Ref{BigFloat}, Cdouble, Ref{BigFloat}, MPFRRoundingMode), z, c, x, rounding_raw(BigFloat)) return z end # BigInt function ($fJ)(x::BigFloat, c::BigInt) z = BigFloat() - ccall(($(string(:mpfr_,fC,:_z)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigInt}, MPFRRoundingMode), z, x, c, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,fC,:_z)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigInt}, MPFRRoundingMode), z, x, c, rounding_raw(BigFloat)) return z end # no :mpfr_z_div function @@ -585,7 +592,7 @@ end function -(c::BigInt, x::BigFloat) z = BigFloat() - ccall((:mpfr_z_sub, libmpfr), Int32, (Ref{BigFloat}, Ref{BigInt}, Ref{BigFloat}, MPFRRoundingMode), z, c, x, ROUNDING_MODE[]) + ccall((:mpfr_z_sub, libmpfr), Int32, (Ref{BigFloat}, Ref{BigInt}, Ref{BigFloat}, MPFRRoundingMode), z, c, x, rounding_raw(BigFloat)) return z end @@ -593,7 +600,7 @@ inv(x::BigFloat) = one(Clong) / x # faster than fallback one(x)/x function fma(x::BigFloat, y::BigFloat, z::BigFloat) r = BigFloat() - ccall(("mpfr_fma",libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), r, x, y, z, ROUNDING_MODE[]) + ccall(("mpfr_fma",libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), r, x, y, z, rounding_raw(BigFloat)) return r end @@ -664,23 +671,23 @@ for (fJ, fC, fI) in ((:+, :add, 0), (:*, :mul, 1)) @eval begin function ($fJ)(a::BigFloat, b::BigFloat, c::BigFloat) z = BigFloat() - ccall(($(string(:mpfr_,fC)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, a, b, ROUNDING_MODE[]) - ccall(($(string(:mpfr_,fC)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, c, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,fC)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, a, b, rounding_raw(BigFloat)) + ccall(($(string(:mpfr_,fC)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, c, rounding_raw(BigFloat)) return z end function ($fJ)(a::BigFloat, b::BigFloat, c::BigFloat, d::BigFloat) z = BigFloat() - ccall(($(string(:mpfr_,fC)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, a, b, ROUNDING_MODE[]) - ccall(($(string(:mpfr_,fC)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, c, ROUNDING_MODE[]) - ccall(($(string(:mpfr_,fC)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, d, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,fC)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, a, b, rounding_raw(BigFloat)) + ccall(($(string(:mpfr_,fC)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, c, rounding_raw(BigFloat)) + ccall(($(string(:mpfr_,fC)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, d, rounding_raw(BigFloat)) return z end function ($fJ)(a::BigFloat, b::BigFloat, c::BigFloat, d::BigFloat, e::BigFloat) z = BigFloat() - ccall(($(string(:mpfr_,fC)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, a, b, ROUNDING_MODE[]) - ccall(($(string(:mpfr_,fC)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, c, ROUNDING_MODE[]) - ccall(($(string(:mpfr_,fC)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, d, ROUNDING_MODE[]) - ccall(($(string(:mpfr_,fC)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, e, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,fC)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, a, b, rounding_raw(BigFloat)) + ccall(($(string(:mpfr_,fC)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, c, rounding_raw(BigFloat)) + ccall(($(string(:mpfr_,fC)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, d, rounding_raw(BigFloat)) + ccall(($(string(:mpfr_,fC)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, e, rounding_raw(BigFloat)) return z end end @@ -688,14 +695,14 @@ end function -(x::BigFloat) z = BigFloat() - ccall((:mpfr_neg, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, ROUNDING_MODE[]) + ccall((:mpfr_neg, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, rounding_raw(BigFloat)) return z end function sqrt(x::BigFloat) isnan(x) && return x z = BigFloat() - ccall((:mpfr_sqrt, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, ROUNDING_MODE[]) + ccall((:mpfr_sqrt, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, rounding_raw(BigFloat)) isnan(z) && throw(DomainError(x, "NaN result for non-NaN input.")) return z end @@ -704,25 +711,25 @@ sqrt(x::BigInt) = sqrt(BigFloat(x)) function ^(x::BigFloat, y::BigFloat) z = BigFloat() - ccall((:mpfr_pow, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[]) + ccall((:mpfr_pow, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, rounding_raw(BigFloat)) return z end function ^(x::BigFloat, y::CulongMax) z = BigFloat() - ccall((:mpfr_pow_ui, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, y, ROUNDING_MODE[]) + ccall((:mpfr_pow_ui, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, y, rounding_raw(BigFloat)) return z end function ^(x::BigFloat, y::ClongMax) z = BigFloat() - ccall((:mpfr_pow_si, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Clong, MPFRRoundingMode), z, x, y, ROUNDING_MODE[]) + ccall((:mpfr_pow_si, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Clong, MPFRRoundingMode), z, x, y, rounding_raw(BigFloat)) return z end function ^(x::BigFloat, y::BigInt) z = BigFloat() - ccall((:mpfr_pow_z, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigInt}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[]) + ccall((:mpfr_pow_z, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigInt}, MPFRRoundingMode), z, x, y, rounding_raw(BigFloat)) return z end @@ -732,7 +739,7 @@ end for f in (:exp, :exp2, :exp10, :expm1, :cosh, :sinh, :tanh, :sech, :csch, :coth, :cbrt) @eval function $f(x::BigFloat) z = BigFloat() - ccall(($(string(:mpfr_,f)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,f)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, rounding_raw(BigFloat)) return z end end @@ -740,7 +747,7 @@ end function sincos_fast(v::BigFloat) s = BigFloat() c = BigFloat() - ccall((:mpfr_sin_cos, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), s, c, v, ROUNDING_MODE[]) + ccall((:mpfr_sin_cos, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), s, c, v, rounding_raw(BigFloat)) return (s, c) end sincos(v::BigFloat) = sincos_fast(v) @@ -748,18 +755,18 @@ sincos(v::BigFloat) = sincos_fast(v) # return log(2) function big_ln2() c = BigFloat() - ccall((:mpfr_const_log2, libmpfr), Cint, (Ref{BigFloat}, MPFRRoundingMode), c, MPFR.ROUNDING_MODE[]) + ccall((:mpfr_const_log2, libmpfr), Cint, (Ref{BigFloat}, MPFRRoundingMode), c, MPFR.rounding_raw(BigFloat)) return c end function ldexp(x::BigFloat, n::Clong) z = BigFloat() - ccall((:mpfr_mul_2si, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Clong, MPFRRoundingMode), z, x, n, ROUNDING_MODE[]) + ccall((:mpfr_mul_2si, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Clong, MPFRRoundingMode), z, x, n, rounding_raw(BigFloat)) return z end function ldexp(x::BigFloat, n::Culong) z = BigFloat() - ccall((:mpfr_mul_2ui, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, n, ROUNDING_MODE[]) + ccall((:mpfr_mul_2ui, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, n, rounding_raw(BigFloat)) return z end ldexp(x::BigFloat, n::ClongMax) = ldexp(x, convert(Clong, n)) @@ -772,13 +779,13 @@ function factorial(x::BigFloat) end ui = convert(Culong, x) z = BigFloat() - ccall((:mpfr_fac_ui, libmpfr), Int32, (Ref{BigFloat}, Culong, MPFRRoundingMode), z, ui, ROUNDING_MODE[]) + ccall((:mpfr_fac_ui, libmpfr), Int32, (Ref{BigFloat}, Culong, MPFRRoundingMode), z, ui, rounding_raw(BigFloat)) return z end function hypot(x::BigFloat, y::BigFloat) z = BigFloat() - ccall((:mpfr_hypot, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[]) + ccall((:mpfr_hypot, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, rounding_raw(BigFloat)) return z end @@ -790,7 +797,7 @@ for f in (:log, :log2, :log10) "with a complex argument. Try ", $f, "(complex(x))."))) end z = BigFloat() - ccall(($(string(:mpfr_,f)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,f)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, rounding_raw(BigFloat)) return z end end @@ -802,7 +809,7 @@ function log1p(x::BigFloat) "with a complex argument. Try log1p(complex(x))."))) end z = BigFloat() - ccall((:mpfr_log1p, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, ROUNDING_MODE[]) + ccall((:mpfr_log1p, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, rounding_raw(BigFloat)) return z end @@ -826,19 +833,19 @@ end function modf(x::BigFloat) zint = BigFloat() zfloat = BigFloat() - ccall((:mpfr_modf, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), zint, zfloat, x, ROUNDING_MODE[]) + ccall((:mpfr_modf, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), zint, zfloat, x, rounding_raw(BigFloat)) return (zfloat, zint) end function rem(x::BigFloat, y::BigFloat) z = BigFloat() - ccall((:mpfr_fmod, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[]) + ccall((:mpfr_fmod, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, rounding_raw(BigFloat)) return z end function rem(x::BigFloat, y::BigFloat, ::RoundingMode{:Nearest}) z = BigFloat() - ccall((:mpfr_remainder, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[]) + ccall((:mpfr_remainder, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, rounding_raw(BigFloat)) return z end @@ -849,7 +856,7 @@ function sum(arr::AbstractArray{BigFloat}) z = BigFloat(0) for i in arr ccall((:mpfr_add, libmpfr), Int32, - (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, i, ROUNDING_MODE[]) + (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, i, rounding_raw(BigFloat)) end return z end @@ -860,7 +867,7 @@ for f in (:sin, :cos, :tan, :sec, :csc, :acos, :asin, :atan, :acosh, :asinh, :at function ($f)(x::BigFloat) isnan(x) && return x z = BigFloat() - ccall(($(string(:mpfr_,f)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,f)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, rounding_raw(BigFloat)) isnan(z) && throw(DomainError(x, "NaN result for non-NaN input.")) return z end @@ -870,7 +877,7 @@ sincospi(x::BigFloat) = (sinpi(x), cospi(x)) function atan(y::BigFloat, x::BigFloat) z = BigFloat() - ccall((:mpfr_atan2, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, y, x, ROUNDING_MODE[]) + ccall((:mpfr_atan2, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, y, x, rounding_raw(BigFloat)) return z end @@ -880,14 +887,14 @@ for f in (:sin, :cos, :tan) function ($(Symbol(f,:d)))(x::BigFloat) isnan(x) && return x z = BigFloat() - ccall(($(string(:mpfr_,f,:u)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, 360, ROUNDING_MODE[]) + ccall(($(string(:mpfr_,f,:u)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, 360, rounding_raw(BigFloat)) isnan(z) && throw(DomainError(x, "NaN result for non-NaN input.")) return z end function ($(Symbol(:a,f,:d)))(x::BigFloat) isnan(x) && return x z = BigFloat() - ccall(($(string(:mpfr_a,f,:u)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, 360, ROUNDING_MODE[]) + ccall(($(string(:mpfr_a,f,:u)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, 360, rounding_raw(BigFloat)) isnan(z) && throw(DomainError(x, "NaN result for non-NaN input.")) return z end @@ -895,7 +902,7 @@ for f in (:sin, :cos, :tan) end function atand(y::BigFloat, x::BigFloat) z = BigFloat() - ccall((:mpfr_atan2u, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, y, x, 360, ROUNDING_MODE[]) + ccall((:mpfr_atan2u, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, y, x, 360, rounding_raw(BigFloat)) return z end @@ -954,12 +961,17 @@ function sign(x::BigFloat) return c < 0 ? -one(x) : one(x) end -function _precision(x::BigFloat) # precision of an object of type BigFloat +function _precision_with_base_2(x::BigFloat) # precision of an object of type BigFloat return ccall((:mpfr_get_prec, libmpfr), Clong, (Ref{BigFloat},), x) end precision(x::BigFloat; base::Integer=2) = _precision(x, base) -_precision(::Type{BigFloat}) = Int(DEFAULT_PRECISION[]) # default precision of the type BigFloat itself + +_convert_precision_from_base(precision::Integer, base::Integer) = + base == 2 ? precision : ceil(Int, precision * log2(base)) + +_precision_with_base_2(::Type{BigFloat}) = + Int(something(Base.ScopedValues.get(CURRENT_PRECISION), DEFAULT_PRECISION[])) # default precision of the type BigFloat itself """ setprecision([T=BigFloat,] precision::Int; base=2) @@ -980,7 +992,7 @@ at least `precision` digits in the given `base`. function setprecision(::Type{BigFloat}, precision::Integer; base::Integer=2) base > 1 || throw(DomainError(base, "`base` cannot be less than 2.")) precision > 0 || throw(DomainError(precision, "`precision` cannot be less than 1.")) - DEFAULT_PRECISION[] = base == 2 ? precision : ceil(Int, precision * log2(base)) + DEFAULT_PRECISION[] = _convert_precision_from_base(precision, base) return precision end @@ -991,7 +1003,7 @@ maxintfloat(::Type{BigFloat}) = BigFloat(2)^precision(BigFloat) function copysign(x::BigFloat, y::BigFloat) z = BigFloat() - ccall((:mpfr_copysign, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[]) + ccall((:mpfr_copysign, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, rounding_raw(BigFloat)) return z end @@ -1006,16 +1018,16 @@ end function frexp(x::BigFloat) z = BigFloat() c = Ref{Clong}() - ccall((:mpfr_frexp, libmpfr), Int32, (Ptr{Clong}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), c, z, x, ROUNDING_MODE[]) + ccall((:mpfr_frexp, libmpfr), Int32, (Ptr{Clong}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), c, z, x, rounding_raw(BigFloat)) return (z, c[]) end function significand(x::BigFloat) z = BigFloat() c = Ref{Clong}() - ccall((:mpfr_frexp, libmpfr), Int32, (Ptr{Clong}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), c, z, x, ROUNDING_MODE[]) + ccall((:mpfr_frexp, libmpfr), Int32, (Ptr{Clong}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), c, z, x, rounding_raw(BigFloat)) # Double the significand to make it work as Base.significand - ccall((:mpfr_mul_si, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Clong, MPFRRoundingMode), z, z, 2, ROUNDING_MODE[]) + ccall((:mpfr_mul_si, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Clong, MPFRRoundingMode), z, z, 2, rounding_raw(BigFloat)) return z end @@ -1096,14 +1108,8 @@ Note: `nextfloat()`, `prevfloat()` do not use the precision mentioned by !!! compat "Julia 1.8" The `base` keyword requires at least Julia 1.8. """ -function setprecision(f::Function, ::Type{T}, prec::Integer; kws...) where T - old_prec = precision(T) - setprecision(T, prec; kws...) - try - return f() - finally - setprecision(T, old_prec) - end +function setprecision(f::Function, ::Type{BigFloat}, prec::Integer; base::Integer=2) + Base.@with(CURRENT_PRECISION => _convert_precision_from_base(prec, base), f()) end setprecision(f::Function, prec::Integer; base::Integer=2) = setprecision(f, BigFloat, prec; base) diff --git a/base/multidimensional.jl b/base/multidimensional.jl index 93fe4fedde039..5fb2bf8cad582 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -4,12 +4,12 @@ module IteratorsMD import .Base: eltype, length, size, first, last, in, getindex, setindex!, min, max, zero, oneunit, isless, eachindex, - convert, show, iterate, promote_rule + convert, show, iterate, promote_rule, to_indices, copy import .Base: +, -, *, (:) import .Base: simd_outer_range, simd_inner_length, simd_index, setindex - import .Base: to_indices, to_index, _to_indices1, _cutdim - using .Base: IndexLinear, IndexCartesian, AbstractCartesianIndex, fill_to_length, tail, + using .Base: to_index, fill_to_length, tail, safe_tail + using .Base: IndexLinear, IndexCartesian, AbstractCartesianIndex, ReshapedArray, ReshapedArrayLF, OneTo, Fix1 using .Base.Iterators: Reverse, PartitionIterator using .Base: @propagate_inbounds @@ -83,6 +83,7 @@ module IteratorsMD CartesianIndex{N}(index::Integer...) where {N} = CartesianIndex{N}(index) CartesianIndex{N}() where {N} = CartesianIndex{N}(()) # Un-nest passed CartesianIndexes + CartesianIndex{N}(index::CartesianIndex{N}) where {N} = index CartesianIndex(index::Union{Integer, CartesianIndex}...) = CartesianIndex(flatten(index)) flatten(::Tuple{}) = () flatten(I::Tuple{Any}) = Tuple(I[1]) @@ -166,6 +167,19 @@ module IteratorsMD Base.iterate(::CartesianIndex) = error("iteration is deliberately unsupported for CartesianIndex. Use `I` rather than `I...`, or use `Tuple(I)...`") + # ranges are deliberately disabled to prevent ambiguities with the colon constructor + Base.range_start_step_length(start::CartesianIndex, step::CartesianIndex, len::Integer) = + error("range with a specified length is deliberately unsupported for CartesianIndex arguments."* + " Use StepRangeLen($start, $step, $len) to construct this range") + + # show is special-cased to avoid the start:stop:step display, + # which constructs a CartesianIndices + # See #50784 + function show(io::IO, r::StepRangeLen{<:CartesianIndex}) + print(io, "StepRangeLen(", first(r), ", ", + step(r), ", ", length(r), ")") + end + # Iteration const OrdinalRangeInt = OrdinalRange{Int, Int} """ @@ -351,7 +365,7 @@ module IteratorsMD end # getindex for a 0D CartesianIndices is necessary for disambiguation - @propagate_inbounds function Base.getindex(iter::CartesianIndices{0,R}) where {R} + @inline function Base.getindex(iter::CartesianIndices{0,R}) where {R} CartesianIndex() end @inline function Base.getindex(iter::CartesianIndices{N,R}, I::Vararg{Int, N}) where {N,R} @@ -451,15 +465,19 @@ module IteratorsMD last(iter::CartesianIndices) = CartesianIndex(map(last, iter.indices)) # When used as indices themselves, CartesianIndices can simply become its tuple of ranges - _to_indices1(A, inds, I1::CartesianIndices) = map(Fix1(to_index, A), I1.indices) - _cutdim(inds::Tuple, I1::CartesianIndices) = split(inds, Val(ndims(I1)))[2] - + @inline function to_indices(A, inds, I::Tuple{CartesianIndices{N}, Vararg}) where N + _, indstail = split(inds, Val(N)) + (map(Fix1(to_index, A), I[1].indices)..., to_indices(A, indstail, tail(I))...) + end # but preserve CartesianIndices{0} as they consume a dimension. - _to_indices1(A, inds, I1::CartesianIndices{0}) = (I1,) + @inline to_indices(A, inds, I::Tuple{CartesianIndices{0}, Vararg}) = + (first(I), to_indices(A, inds, tail(I))...) @inline in(i::CartesianIndex, r::CartesianIndices) = false @inline in(i::CartesianIndex{N}, r::CartesianIndices{N}) where {N} = all(map(in, i.I, r.indices)) + copy(iter::CartesianIndices) = iter + simd_outer_range(iter::CartesianIndices{0}) = iter function simd_outer_range(iter::CartesianIndices) CartesianIndices(tail(iter.indices)) @@ -668,17 +686,15 @@ using .IteratorsMD ## Bounds-checking with CartesianIndex # Disallow linear indexing with CartesianIndex -function checkbounds(::Type{Bool}, A::AbstractArray, i::Union{CartesianIndex, AbstractArray{<:CartesianIndex}}) - @inline +@inline checkbounds(::Type{Bool}, A::AbstractArray, i::CartesianIndex) = checkbounds_indices(Bool, axes(A), (i,)) +# Here we try to consume N of the indices (if there are that many available) +@inline function checkbounds_indices(::Type{Bool}, inds::Tuple, I::Tuple{CartesianIndex,Vararg}) + inds1, rest = IteratorsMD.split(inds, Val(length(I[1]))) + checkindex(Bool, inds1, I[1]) & checkbounds_indices(Bool, rest, tail(I)) end - -@inline checkbounds_indices(::Type{Bool}, ::Tuple{}, I::Tuple{CartesianIndex,Vararg{Any}}) = - checkbounds_indices(Bool, (), (I[1].I..., tail(I)...)) -@inline checkbounds_indices(::Type{Bool}, IA::Tuple{Any}, I::Tuple{CartesianIndex,Vararg{Any}}) = - checkbounds_indices(Bool, IA, (I[1].I..., tail(I)...)) -@inline checkbounds_indices(::Type{Bool}, IA::Tuple, I::Tuple{CartesianIndex,Vararg{Any}}) = - checkbounds_indices(Bool, IA, (I[1].I..., tail(I)...)) +@inline checkindex(::Type{Bool}, inds::Tuple, I::CartesianIndex) = + checkbounds_indices(Bool, inds, I.I) # Indexing into Array with mixtures of Integers and CartesianIndices is # extremely performance-sensitive. While the abstract fallbacks support this, @@ -688,45 +704,17 @@ end @propagate_inbounds setindex!(A::Array, v, i1::Union{Integer, CartesianIndex}, I::Union{Integer, CartesianIndex}...) = (A[to_indices(A, (i1, I...))...] = v; A) -# Support indexing with an array of CartesianIndex{N}s +## Bounds-checking with arrays of CartesianIndex{N} +# Disallow linear indexing with an array of CartesianIndex{N} +@inline checkbounds(::Type{Bool}, A::AbstractArray, i::AbstractArray{CartesianIndex{N}}) where {N} = + checkbounds_indices(Bool, axes(A), (i,)) # Here we try to consume N of the indices (if there are that many available) -# The first two simply handle ambiguities -@inline function checkbounds_indices(::Type{Bool}, ::Tuple{}, - I::Tuple{AbstractArray{CartesianIndex{N}},Vararg{Any}}) where N - checkindex(Bool, (), I[1]) & checkbounds_indices(Bool, (), tail(I)) -end -@inline function checkbounds_indices(::Type{Bool}, IA::Tuple{Any}, - I::Tuple{AbstractArray{CartesianIndex{0}},Vararg{Any}}) - checkbounds_indices(Bool, IA, tail(I)) -end -@inline function checkbounds_indices(::Type{Bool}, IA::Tuple{Any}, - I::Tuple{AbstractArray{CartesianIndex{N}},Vararg{Any}}) where N - checkindex(Bool, IA, I[1]) & checkbounds_indices(Bool, (), tail(I)) -end -@inline function checkbounds_indices(::Type{Bool}, IA::Tuple, - I::Tuple{AbstractArray{CartesianIndex{N}},Vararg{Any}}) where N - IA1, IArest = IteratorsMD.split(IA, Val(N)) - checkindex(Bool, IA1, I[1]) & checkbounds_indices(Bool, IArest, tail(I)) -end - - -@inline function checkbounds_indices(::Type{Bool}, IA::Tuple{}, - I::Tuple{AbstractArray{Bool,N},Vararg{Any}}) where N - return checkbounds_indices(Bool, IA, (LogicalIndex(I[1]), tail(I)...)) -end -@inline function checkbounds_indices(::Type{Bool}, IA::Tuple, - I::Tuple{AbstractArray{Bool,N},Vararg{Any}}) where N - return checkbounds_indices(Bool, IA, (LogicalIndex(I[1]), tail(I)...)) +@inline function checkbounds_indices(::Type{Bool}, inds::Tuple, I::Tuple{AbstractArray{CartesianIndex{N}},Vararg}) where N + inds1, rest = IteratorsMD.split(inds, Val(N)) + checkindex(Bool, inds1, I[1]) & checkbounds_indices(Bool, rest, tail(I)) end - -function checkindex(::Type{Bool}, inds::Tuple, I::AbstractArray{<:CartesianIndex}) - b = true - for i in I - b &= checkbounds_indices(Bool, inds, (i,)) - end - b -end -checkindex(::Type{Bool}, inds::Tuple, I::CartesianIndices) = all(checkindex.(Bool, inds, I.indices)) +@inline checkindex(::Type{Bool}, inds::Tuple, I::CartesianIndices) = + checkbounds_indices(Bool, inds, I.indices) # combined count of all indices, including CartesianIndex and # AbstractArray{CartesianIndex} @@ -800,11 +788,11 @@ end n = s[1] n > length(L) && return nothing #unroll once to help inference, cf issue #29418 - idx, i = iterate(tail(s)...) + idx, i = iterate(tail(s)...)::Tuple{Any,Any} s = (n+1, s[2], i) L.mask[idx] && return (idx, s) while true - idx, i = iterate(tail(s)...) + idx, i = iterate(tail(s)...)::Tuple{Any,Any} s = (n+1, s[2], i) L.mask[idx] && return (idx, s) end @@ -834,11 +822,29 @@ end return eltype(L)(i1, irest...), (i1 - tz, Bi, irest, c) end -@inline checkbounds(::Type{Bool}, A::AbstractArray, I::LogicalIndex{<:Any,<:AbstractArray{Bool,1}}) = - eachindex(IndexLinear(), A) == eachindex(IndexLinear(), I.mask) -@inline checkbounds(::Type{Bool}, A::AbstractArray, I::LogicalIndex) = axes(A) == axes(I.mask) -@inline checkindex(::Type{Bool}, indx::AbstractUnitRange, I::LogicalIndex) = (indx,) == axes(I.mask) -checkindex(::Type{Bool}, inds::Tuple, I::LogicalIndex) = checkbounds_indices(Bool, inds, axes(I.mask)) +## Boundscheck for Logicalindex +# LogicalIndex: map all calls to mask +checkbounds(::Type{Bool}, A::AbstractArray, i::LogicalIndex) = checkbounds(Bool, A, i.mask) +# `checkbounds_indices` has been handled via `I::AbstractArray` fallback +checkindex(::Type{Bool}, inds::AbstractUnitRange, i::LogicalIndex) = checkindex(Bool, inds, i.mask) +checkindex(::Type{Bool}, inds::Tuple, i::LogicalIndex) = checkindex(Bool, inds, i.mask) + +## Boundscheck for AbstractArray{Bool} +# Disallow linear indexing with AbstractArray{Bool} +checkbounds(::Type{Bool}, A::AbstractArray, i::AbstractArray{Bool}) = + checkbounds_indices(Bool, axes(A), (i,)) +# But allow linear indexing with AbstractVector{Bool} +checkbounds(::Type{Bool}, A::AbstractArray, i::AbstractVector{Bool}) = + checkindex(Bool, eachindex(IndexLinear(), A), i) +@inline function checkbounds_indices(::Type{Bool}, inds::Tuple, I::Tuple{AbstractArray{Bool},Vararg}) + inds1, rest = IteratorsMD.split(inds, Val(ndims(I[1]))) + checkindex(Bool, inds1, I[1]) & checkbounds_indices(Bool, rest, tail(I)) +end +checkindex(::Type{Bool}, inds::AbstractUnitRange, I::AbstractVector{Bool}) = axes1(I) == inds +checkindex(::Type{Bool}, inds::AbstractUnitRange, I::AbstractRange{Bool}) = axes1(I) == inds +checkindex(::Type{Bool}, inds::Tuple, I::AbstractArray{Bool}) = _check_boolean_axes(inds, axes(I)) +_check_boolean_axes(inds::Tuple, axes::Tuple) = (inds[1] == axes[1]) & _check_boolean_axes(tail(inds), tail(axes)) +_check_boolean_axes(::Tuple{}, axes::Tuple) = all(==(OneTo(1)), axes) ensure_indexable(I::Tuple{}) = () @inline ensure_indexable(I::Tuple{Any, Vararg{Any}}) = (I[1], ensure_indexable(tail(I))...) @@ -849,24 +855,54 @@ ensure_indexable(I::Tuple{}) = () @inline to_indices(A, I::Tuple{Vararg{Union{Integer, CartesianIndex}}}) = to_indices(A, (), I) # But some index types require more context spanning multiple indices # CartesianIndex is unfolded outside the inner to_indices for better inference -_to_indices1(A, inds, I1::CartesianIndex) = map(Fix1(to_index, A), I1.I) -_cutdim(inds, I1::CartesianIndex) = IteratorsMD.split(inds, Val(length(I1)))[2] +@inline function to_indices(A, inds, I::Tuple{CartesianIndex{N}, Vararg}) where N + _, indstail = IteratorsMD.split(inds, Val(N)) + (map(Fix1(to_index, A), I[1].I)..., to_indices(A, indstail, tail(I))...) +end # For arrays of CartesianIndex, we just skip the appropriate number of inds -_cutdim(inds, I1::AbstractArray{CartesianIndex{N}}) where {N} = IteratorsMD.split(inds, Val(N))[2] +@inline function to_indices(A, inds, I::Tuple{AbstractArray{CartesianIndex{N}}, Vararg}) where N + _, indstail = IteratorsMD.split(inds, Val(N)) + (to_index(A, I[1]), to_indices(A, indstail, tail(I))...) +end # And boolean arrays behave similarly; they also skip their number of dimensions -_cutdim(inds::Tuple, I1::AbstractArray{Bool}) = IteratorsMD.split(inds, Val(ndims(I1)))[2] -# As an optimization, we allow trailing Array{Bool} and BitArray to be linear over trailing dimensions -@inline to_indices(A, inds, I::Tuple{Union{Array{Bool,N}, BitArray{N}}}) where {N} = - (_maybe_linear_logical_index(IndexStyle(A), A, I[1]),) +@inline function to_indices(A, inds, I::Tuple{AbstractArray{Bool, N}, Vararg}) where N + _, indstail = IteratorsMD.split(inds, Val(N)) + (to_index(A, I[1]), to_indices(A, indstail, tail(I))...) +end +# As an optimization, we allow the only `AbstractArray{Bool}` to be linear-iterated +@inline to_indices(A, I::Tuple{AbstractArray{Bool}}) = (_maybe_linear_logical_index(IndexStyle(A), A, I[1]),) _maybe_linear_logical_index(::IndexStyle, A, i) = to_index(A, i) _maybe_linear_logical_index(::IndexLinear, A, i) = LogicalIndex{Int}(i) # Colons get converted to slices by `uncolon` -_to_indices1(A, inds, I1::Colon) = (uncolon(inds),) +@inline to_indices(A, inds, I::Tuple{Colon, Vararg}) = + (uncolon(inds), to_indices(A, Base.safe_tail(inds), tail(I))...) uncolon(::Tuple{}) = Slice(OneTo(1)) uncolon(inds::Tuple) = Slice(inds[1]) +""" + _prechecked_iterate(iter[, state]) + +Internal function used to eliminate the dead branch in `iterate`. +Fallback to `iterate` by default, but optimized for indices type in `Base`. +""" +@propagate_inbounds _prechecked_iterate(iter) = iterate(iter) +@propagate_inbounds _prechecked_iterate(iter, state) = iterate(iter, state) + +_prechecked_iterate(iter::AbstractUnitRange, i = first(iter)) = i, convert(eltype(iter), i + step(iter)) +_prechecked_iterate(iter::LinearIndices, i = first(iter)) = i, i + 1 +_prechecked_iterate(iter::CartesianIndices) = first(iter), first(iter) +function _prechecked_iterate(iter::CartesianIndices, i) + i′ = IteratorsMD.inc(i.I, iter.indices) + return i′, i′ +end +_prechecked_iterate(iter::SCartesianIndices2) = first(iter), first(iter) +function _prechecked_iterate(iter::SCartesianIndices2{K}, (;i, j)) where {K} + I = i < K ? SCartesianIndex2{K}(i + 1, j) : SCartesianIndex2{K}(1, j + 1) + return I, I +end + ### From abstractarray.jl: Internal multidimensional indexing definitions ### getindex(x::Union{Number,AbstractChar}, ::CartesianIndex{0}) = x getindex(t::Tuple, i::CartesianIndex{1}) = getindex(t, i.I[1]) @@ -898,14 +934,11 @@ function _generate_unsafe_getindex!_body(N::Int) quote @inline D = eachindex(dest) - Dy = iterate(D) + Dy = _prechecked_iterate(D) @inbounds @nloops $N j d->I[d] begin - # This condition is never hit, but at the moment - # the optimizer is not clever enough to split the union without it - Dy === nothing && return dest - (idx, state) = Dy + (idx, state) = Dy::NTuple{2,Any} dest[idx] = @ncall $N getindex src j - Dy = iterate(D, state) + Dy = _prechecked_iterate(D, state) end return dest end @@ -941,14 +974,12 @@ function _generate_unsafe_setindex!_body(N::Int) @nexprs $N d->(I_d = unalias(A, I[d])) idxlens = @ncall $N index_lengths I @ncall $N setindex_shape_check x′ (d->idxlens[d]) - Xy = iterate(x′) + X = eachindex(x′) + Xy = _prechecked_iterate(X) @inbounds @nloops $N i d->I_d begin - # This is never reached, but serves as an assumption for - # the optimizer that it does not need to emit error paths - Xy === nothing && break - (val, state) = Xy - @ncall $N setindex! A val i - Xy = iterate(x′, state) + (idx, state) = Xy::NTuple{2,Any} + @ncall $N setindex! A x′[idx] i + Xy = _prechecked_iterate(X, state) end A end @@ -1169,8 +1200,7 @@ circshift!(dest::AbstractArray, src, ::Tuple{}) = copyto!(dest, src) Circularly shift, i.e. rotate, the data in `src`, storing the result in `dest`. `shifts` specifies the amount to shift in each dimension. -The `dest` array must be distinct from the `src` array (they cannot -alias each other). +$(_DOCS_ALIASING_WARNING) See also [`circshift`](@ref). """ @@ -1228,6 +1258,8 @@ their indices; any offset results in a (circular) wraparound. If the arrays have overlapping indices, then on the domain of the overlap `dest` agrees with `src`. +$(_DOCS_ALIASING_WARNING) + See also: [`circshift`](@ref). # Examples @@ -1552,19 +1584,23 @@ end end end -isassigned(a::AbstractArray, i::CartesianIndex) = isassigned(a, Tuple(i)...) -function isassigned(A::AbstractArray, i::Union{Integer, CartesianIndex}...) - isa(i, Tuple{Vararg{Int}}) || return isassigned(A, CartesianIndex(to_indices(A, i))) - @boundscheck checkbounds(Bool, A, i...) || return false +@propagate_inbounds isassigned(A::AbstractArray, i::CartesianIndex) = isassigned(A, Tuple(i)...) +@propagate_inbounds function isassigned(A::AbstractArray, i::Union{Integer, CartesianIndex}...) + return isassigned(A, CartesianIndex(to_indices(A, i))) +end +@inline function isassigned(A::AbstractArray, i::Integer...) + # convert to valid indices, checking for Bool + inds = to_indices(A, i) + @boundscheck checkbounds(Bool, A, inds...) || return false S = IndexStyle(A) - ninds = length(i) + ninds = length(inds) if (isa(S, IndexLinear) && ninds != 1) - return @inbounds isassigned(A, _to_linear_index(A, i...)) + return @inbounds isassigned(A, _to_linear_index(A, inds...)) elseif (!isa(S, IndexLinear) && ninds != ndims(A)) - return @inbounds isassigned(A, _to_subscript_indices(A, i...)...) + return @inbounds isassigned(A, _to_subscript_indices(A, inds...)...) else try - A[i...] + A[inds...] true catch e if isa(e, BoundsError) || isa(e, UndefRefError) @@ -1576,6 +1612,12 @@ function isassigned(A::AbstractArray, i::Union{Integer, CartesianIndex}...) end end +# _unsetindex +@propagate_inbounds function Base._unsetindex!(A::AbstractArray, i::CartesianIndex) + Base._unsetindex!(A, to_indices(A, (i,))...) + return A +end + ## permutedims ## Permute array dims ## @@ -1607,17 +1649,17 @@ for (V, PT, BT) in Any[((:N,), BitArray, BitArray), ((:T,:N), Array, StridedArra #calculates all the strides native_strides = size_to_strides(1, size(B)...) - strides_1 = 0 - @nexprs $N d->(strides_{d+1} = native_strides[perm[d]]) + strides = @ntuple $N d->native_strides[perm[d]] + strides::NTuple{$N,Integer} #Creates offset, because indexing starts at 1 - offset = 1 - sum(@ntuple $N d->strides_{d+1}) + offset = 1 - reduce(+, strides, init = 0) sumc = 0 ind = 1 @nloops($N, i, P, - d->(sumc += i_d*strides_{d+1}), # PRE - d->(sumc -= i_d*strides_{d+1}), # POST + d->(sumc += i_d*strides[d]), # PRE + d->(sumc -= i_d*strides[d]), # POST begin # BODY @inbounds P[ind] = B[sumc+offset] ind += 1 @@ -1882,39 +1924,25 @@ julia> sortslices(reshape([5; 4; 3; 2; 1], (1,1,5)), dims=3, by=x->x[1,1]) ``` """ function sortslices(A::AbstractArray; dims::Union{Integer, Tuple{Vararg{Integer}}}, kws...) - _sortslices(A, Val{dims}(); kws...) -end + if A isa Matrix && dims isa Integer && dims == 1 + # TODO: remove once the generic version becomes as fast or faster + perm = sortperm(eachslice(A; dims); kws...) + return A[perm, :] + end -# Works around inference's lack of ability to recognize partial constness -struct DimSelector{dims, T} - A::T + B = similar(A) + _sortslices!(B, A, Val{dims}(); kws...) + B end -DimSelector{dims}(x::T) where {dims, T} = DimSelector{dims, T}(x) -(ds::DimSelector{dims, T})(i) where {dims, T} = i in dims ? axes(ds.A, i) : (:,) - -_negdims(n, dims) = filter(i->!(i in dims), 1:n) -function compute_itspace(A, ::Val{dims}) where {dims} - negdims = _negdims(ndims(A), dims) - axs = Iterators.product(ntuple(DimSelector{dims}(A), ndims(A))...) - vec(permutedims(collect(axs), (dims..., negdims...))) -end +function _sortslices!(B, A, ::Val{dims}; kws...) where dims + ves = vec(eachslice(A; dims)) + perm = sortperm(ves; kws...) + bes = eachslice(B; dims) -function _sortslices(A::AbstractArray, d::Val{dims}; kws...) where dims - itspace = compute_itspace(A, d) - vecs = map(its->view(A, its...), itspace) - p = sortperm(vecs; kws...) - if ndims(A) == 2 && isa(dims, Integer) && isa(A, Array) - # At the moment, the performance of the generic version is subpar - # (about 5x slower). Hardcode a fast-path until we're able to - # optimize this. - return dims == 1 ? A[p, :] : A[:, p] - else - B = similar(A) - for (x, its) in zip(p, itspace) - B[its...] = vecs[x] - end - B + # TODO for further optimization: traverse in memory order + for (slice, i) in zip(eachslice(B; dims), perm) + slice .= ves[i] end end diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 36ec56a4e0a2a..98192480db9dd 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -112,24 +112,24 @@ Core.NamedTuple if nameof(@__MODULE__) === :Base -@eval function NamedTuple{names,T}(args::Tuple) where {names, T <: Tuple} +@eval function (NT::Type{NamedTuple{names,T}})(args::Tuple) where {names, T <: Tuple} if length(args) != length(names::Tuple) throw(ArgumentError("Wrong number of arguments to named tuple constructor.")) end # Note T(args) might not return something of type T; e.g. # Tuple{Type{Float64}}((Float64,)) returns a Tuple{DataType} - $(Expr(:splatnew, :(NamedTuple{names,T}), :(T(args)))) + $(Expr(:splatnew, :NT, :(T(args)))) end -function NamedTuple{names, T}(nt::NamedTuple) where {names, T <: Tuple} +function (NT::Type{NamedTuple{names, T}})(nt::NamedTuple) where {names, T <: Tuple} if @generated - Expr(:new, :(NamedTuple{names, T}), - Any[ :(let Tn = fieldtype(T, $n), + Expr(:new, :NT, + Any[ :(let Tn = fieldtype(NT, $n), ntn = getfield(nt, $(QuoteNode(names[n]))) ntn isa Tn ? ntn : convert(Tn, ntn) end) for n in 1:length(names) ]...) else - NamedTuple{names, T}(map(Fix1(getfield, nt), names)) + NT(map(Fix1(getfield, nt), names)) end end @@ -145,14 +145,11 @@ function NamedTuple{names}(nt::NamedTuple) where {names} end end -NamedTuple{names, T}(itr) where {names, T <: Tuple} = NamedTuple{names, T}(T(itr)) -NamedTuple{names}(itr) where {names} = NamedTuple{names}(Tuple(itr)) +(NT::Type{NamedTuple{names, T}})(itr) where {names, T <: Tuple} = NT(T(itr)) +(NT::Type{NamedTuple{names}})(itr) where {names} = NT(Tuple(itr)) NamedTuple(itr) = (; itr...) -# avoids invalidating Union{}(...) -NamedTuple{names, Union{}}(itr::Tuple) where {names} = throw(MethodError(NamedTuple{names, Union{}}, (itr,))) - end # if Base # Like NamedTuple{names, T} as a constructor, but omits the additional @@ -182,6 +179,7 @@ nextind(@nospecialize(t::NamedTuple), i::Integer) = Int(i)+1 convert(::Type{NT}, nt::NT) where {names, NT<:NamedTuple{names}} = nt convert(::Type{NT}, nt::NT) where {names, T<:Tuple, NT<:NamedTuple{names,T}} = nt +convert(::Type{NT}, t::Tuple) where {NT<:NamedTuple} = NT(t) function convert(::Type{NamedTuple{names,T}}, nt::NamedTuple{names}) where {names,T<:Tuple} NamedTuple{names,T}(T(nt))::NamedTuple{names,T} @@ -196,9 +194,8 @@ function convert(::Type{NT}, nt::NamedTuple{names}) where {names, NT<:NamedTuple end if nameof(@__MODULE__) === :Base - Tuple(nt::NamedTuple) = (nt...,) - (::Type{T})(nt::NamedTuple) where {T <: Tuple} = (t = Tuple(nt); t isa T ? t : convert(T, t)::T) -end +Tuple(nt::NamedTuple) = (nt...,) +(::Type{T})(nt::NamedTuple) where {T <: Tuple} = (t = Tuple(nt); t isa T ? t : convert(T, t)::T) function show(io::IO, t::NamedTuple) n = nfields(t) @@ -232,6 +229,7 @@ function show(io::IO, t::NamedTuple) print(io, ")") end end +end eltype(::Type{T}) where T<:NamedTuple = nteltype(T) nteltype(::Type) = Any @@ -268,8 +266,11 @@ function map(f, nt::NamedTuple{names}, nts::NamedTuple...) where names NamedTuple{names}(map(f, map(Tuple, (nt, nts...))...)) end -@assume_effects :total function merge_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}}) - @nospecialize an bn +filter(f, xs::NamedTuple) = xs[filter(k -> f(xs[k]), keys(xs))] + +function merge_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}}) + @nospecialize + @_total_meta names = Symbol[an...] for n in bn if !sym_in(n, an) @@ -279,19 +280,21 @@ end (names...,) end -@assume_effects :total function merge_types(names::Tuple{Vararg{Symbol}}, a::Type{<:NamedTuple}, b::Type{<:NamedTuple}) - @nospecialize names a b +function merge_types(names::Tuple{Vararg{Symbol}}, a::Type{<:NamedTuple}, b::Type{<:NamedTuple}) + @nospecialize + @_total_meta bn = _nt_names(b) return Tuple{Any[ fieldtype(sym_in(names[n], bn) ? b : a, names[n]) for n in 1:length(names) ]...} end -@assume_effects :foldable function merge_fallback(a::NamedTuple, b::NamedTuple, - an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}}) +function merge_fallback(a::NamedTuple, b::NamedTuple, + an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}}) @nospecialize + @_foldable_meta names = merge_names(an, bn) types = merge_types(names, typeof(a), typeof(b)) n = length(names) - A = Vector{Any}(undef, n) + A = Memory{Any}(undef, n) for i=1:n n = names[i] A[i] = getfield(sym_in(n, bn) ? b : a, n) @@ -388,8 +391,9 @@ tail(t::NamedTuple{names}) where names = NamedTuple{tail(names::Tuple)}(t) front(t::NamedTuple{names}) where names = NamedTuple{front(names::Tuple)}(t) reverse(nt::NamedTuple) = NamedTuple{reverse(keys(nt))}(reverse(values(nt))) -@assume_effects :total function diff_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}}) - @nospecialize an bn +function diff_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}}) + @nospecialize + @_total_meta names = Symbol[] for n in an if !sym_in(n, bn) @@ -399,16 +403,20 @@ reverse(nt::NamedTuple) = NamedTuple{reverse(keys(nt))}(reverse(values(nt))) (names...,) end -@assume_effects :foldable function diff_types(@nospecialize(a::NamedTuple), @nospecialize(names::Tuple{Vararg{Symbol}})) +function diff_types(a::NamedTuple, names::Tuple{Vararg{Symbol}}) + @nospecialize + @_foldable_meta return Tuple{Any[ fieldtype(typeof(a), names[n]) for n in 1:length(names) ]...} end -@assume_effects :foldable function diff_fallback(@nospecialize(a::NamedTuple), @nospecialize(an::Tuple{Vararg{Symbol}}), @nospecialize(bn::Tuple{Vararg{Symbol}})) +function diff_fallback(a::NamedTuple, an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}}) + @nospecialize + @_foldable_meta names = diff_names(an, bn) isempty(names) && return (;) types = diff_types(a, names) n = length(names) - A = Vector{Any}(undef, n) + A = Memory{Any}(undef, n) for i=1:n n = names[i] A[i] = getfield(a, n) @@ -516,6 +524,7 @@ Base.Pairs{Symbol, Int64, Tuple{Symbol}, @NamedTuple{init::Int64}} julia> sum("julia"; init=1) ERROR: MethodError: no method matching +(::Char, ::Char) +The function `+` exists, but no method is defined for this combination of argument types. Closest candidates are: +(::Any, ::Any, ::Any, ::Any...) diff --git a/base/ntuple.jl b/base/ntuple.jl index 7391b86154ac4..74dc9f3796dbf 100644 --- a/base/ntuple.jl +++ b/base/ntuple.jl @@ -3,7 +3,7 @@ # `ntuple`, for constructing tuples of a given length """ - ntuple(f::Function, n::Integer) + ntuple(f, n::Integer) Create a tuple of length `n`, computing each element as `f(i)`, where `i` is the index of the element. @@ -88,3 +88,11 @@ end (t..., fill(val, N-M)...) end end + + +# Specialized extensions for NTuple +function reverse(t::NTuple{N}) where N + ntuple(Val{N}()) do i + t[end+1-i] + end +end diff --git a/base/number.jl b/base/number.jl index 923fc907d4038..39908222027c1 100644 --- a/base/number.jl +++ b/base/number.jl @@ -287,7 +287,12 @@ map(f, x::Number, ys::Number...) = f(x, ys...) zero(x) zero(::Type) -Get the additive identity element for the type of `x` (`x` can also specify the type itself). +Get the additive identity element for `x`. If the additive identity can be deduced +from the type alone, then a type may be given as an argument to `zero`. + +For example, `zero(Int)` will work because the additive identity is the same for all +instances of `Int`, but `zero(Vector{Int})` is not defined because vectors of different +lengths have different additive identities. See also [`iszero`](@ref), [`one`](@ref), [`oneunit`](@ref), [`oftype`](@ref). @@ -314,9 +319,12 @@ zero(::Type{Union{}}, slurp...) = Union{}(0) one(T::type) Return a multiplicative identity for `x`: a value such that -`one(x)*x == x*one(x) == x`. Alternatively `one(T)` can -take a type `T`, in which case `one` returns a multiplicative -identity for any `x` of type `T`. +`one(x)*x == x*one(x) == x`. If the multiplicative identity can +be deduced from the type alone, then a type may be given as +an argument to `one` (e.g. `one(Int)` will work because the +multiplicative identity is the same for all instances of `Int`, +but `one(Matrix{Int})` is not defined because matrices of +different shapes have different multiplicative identities.) If possible, `one(x)` returns a value of the same type as `x`, and `one(T)` returns a value of type `T`. However, this may @@ -354,9 +362,10 @@ one(::Type{Union{}}, slurp...) = Union{}(1) oneunit(x::T) oneunit(T::Type) -Return `T(one(x))`, where `T` is either the type of the argument or -(if a type is passed) the argument. This differs from [`one`](@ref) for -dimensionful quantities: `one` is dimensionless (a multiplicative identity) +Return `T(one(x))`, where `T` is either the type of the argument, or +the argument itself in cases where the `oneunit` can be deduced from +the type alone. This differs from [`one`](@ref) for dimensionful +quantities: `one` is dimensionless (a multiplicative identity) while `oneunit` is dimensionful (of the same type as `x`, or of type `T`). # Examples diff --git a/base/opaque_closure.jl b/base/opaque_closure.jl index cb3c00b128dcb..b00955e7a0ca0 100644 --- a/base/opaque_closure.jl +++ b/base/opaque_closure.jl @@ -58,29 +58,31 @@ end function Core.OpaqueClosure(ir::IRCode, @nospecialize env...; isva::Bool = false, - do_compile::Bool = true) + slotnames::Union{Nothing,Vector{Symbol}}=nothing, + kwargs...) # NOTE: we need ir.argtypes[1] == typeof(env) ir = Core.Compiler.copy(ir) - nargs = length(ir.argtypes)-1 + # if the user didn't specify a definition MethodInstance or filename Symbol to use for the debuginfo, set a filename now + ir.debuginfo.def === nothing && (ir.debuginfo.def = :var"generated IR for OpaqueClosure") + nargtypes = length(ir.argtypes) + nargs = nargtypes-1 sig = compute_oc_signature(ir, nargs, isva) rt = compute_ir_rettype(ir) src = ccall(:jl_new_code_info_uninit, Ref{CodeInfo}, ()) - src.slotnames = fill(:none, nargs+1) - src.slotflags = fill(zero(UInt8), length(ir.argtypes)) + if slotnames === nothing + src.slotnames = fill(:none, nargtypes) + else + length(slotnames) == nargtypes || error("mismatched `argtypes` and `slotnames`") + src.slotnames = slotnames + end + src.slotflags = fill(zero(UInt8), nargtypes) src.slottypes = copy(ir.argtypes) - src.rettype = rt src = Core.Compiler.ir_to_codeinf!(src, ir) - return generate_opaque_closure(sig, Union{}, rt, src, nargs, isva, env...; do_compile) + return generate_opaque_closure(sig, Union{}, rt, src, nargs, isva, env...; kwargs...) end -function Core.OpaqueClosure(src::CodeInfo, @nospecialize env...) - src.inferred || throw(ArgumentError("Expected inferred src::CodeInfo")) - mi = src.parent::Core.MethodInstance - sig = Base.tuple_type_tail(mi.specTypes) - method = mi.def::Method - nargs = method.nargs-1 - isva = method.isva - return generate_opaque_closure(sig, Union{}, src.rettype, src, nargs, isva, env...) +function Core.OpaqueClosure(src::CodeInfo, @nospecialize env...; rettype, sig, nargs, isva=false, kwargs...) + return generate_opaque_closure(sig, Union{}, rettype, src, nargs, isva, env...; kwargs...) end function generate_opaque_closure(@nospecialize(sig), @nospecialize(rt_lb), @nospecialize(rt_ub), @@ -88,7 +90,8 @@ function generate_opaque_closure(@nospecialize(sig), @nospecialize(rt_lb), @nosp mod::Module=@__MODULE__, lineno::Int=0, file::Union{Nothing,Symbol}=nothing, - do_compile::Bool=true) - return ccall(:jl_new_opaque_closure_from_code_info, Any, (Any, Any, Any, Any, Any, Cint, Any, Cint, Cint, Any, Cint), - sig, rt_lb, rt_ub, mod, src, lineno, file, nargs, isva, env, do_compile) + do_compile::Bool=true, + isinferred::Bool=true) + return ccall(:jl_new_opaque_closure_from_code_info, Any, (Any, Any, Any, Any, Any, Cint, Any, Cint, Cint, Any, Cint, Cint), + sig, rt_lb, rt_ub, mod, src, lineno, file, nargs, isva, env, do_compile, isinferred) end diff --git a/base/operators.jl b/base/operators.jl index 9f116c07ad14c..13d2b2fe684a2 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -3,10 +3,25 @@ ## types ## """ - <:(T1, T2) + <:(T1, T2)::Bool -Subtype operator: returns `true` if and only if all values of type `T1` are -also of type `T2`. +Subtyping relation, defined between two types. In Julia, a type `S` is said to be a +*subtype* of a type `T` if and only if we have `S <: T`. + +For any type `L` and any type `R`, `L <: R` implies that any value `v` of type `L` +is also of type `R`. I.e., `(L <: R) && (v isa L)` implies `v isa R`. + +The subtyping relation is a *partial order*. I.e., `<:` is: + +* *reflexive*: for any type `T`, `T <: T` holds + +* *antisymmetric*: for any type `A` and any type `B`, `(A <: B) && (B <: A)` + implies `A == B` + +* *transitive*: for any type `A`, any type `B` and any type `C`; + `(A <: B) && (B <: C)` implies `A <: C` + +See also info on [Types](@ref man-types), [`Union{}`](@ref), [`Any`](@ref), [`isa`](@ref). # Examples ```jldoctest @@ -16,9 +31,30 @@ true julia> Vector{Int} <: AbstractArray true -julia> Matrix{Float64} <: Matrix{AbstractFloat} +julia> Matrix{Float64} <: Matrix{AbstractFloat} # `Matrix` is invariant false + +julia> Tuple{Float64} <: Tuple{AbstractFloat} # `Tuple` is covariant +true + +julia> Union{} <: Int # The bottom type, `Union{}`, subtypes each type. +true + +julia> Union{} <: Float32 <: AbstractFloat <: Real <: Number <: Any # Operator chaining +true ``` + +The `<:` keyword also has several syntactic uses which represent the same subtyping relation, +but which do not execute the operator or return a Bool: + +* To specify the lower bound and the upper bound on a parameter of a + [`UnionAll`](@ref) type in a [`where`](@ref) statement. + +* To specify the lower bound and the upper bound on a (static) parameter of a + method, see [Parametric Methods](@ref). + +* To define a subtyping relation while declaring a new type, see [`struct`](@ref) + and [`abstract type`](@ref). """ (<:) @@ -52,8 +88,9 @@ Generic equality operator. Falls back to [`===`](@ref). Should be implemented for all types with a notion of equality, based on the abstract value that an instance represents. For example, all numeric types are compared by numeric value, ignoring type. Strings are compared as sequences of characters, ignoring encoding. -For collections, `==` is generally called recursively on all contents, -though other properties (like the shape for arrays) may also be taken into account. +Collections of the same type generally compare their key sets, and if those are `==`, then compare the values +for each of those keys, returning true if all such pairs are `==`. +Other properties are typically not taken into account (such as the exact type). This operator follows IEEE semantics for floating-point numbers: `0.0 == -0.0` and `NaN != NaN`. @@ -61,8 +98,8 @@ This operator follows IEEE semantics for floating-point numbers: `0.0 == -0.0` a The result is of type `Bool`, except when one of the operands is [`missing`](@ref), in which case `missing` is returned ([three-valued logic](https://en.wikipedia.org/wiki/Three-valued_logic)). -For collections, `missing` is returned if at least one of the operands contains -a `missing` value and all non-missing values are equal. +Collections generally implement three-valued logic akin to [`all`](@ref), returning +missing if any operands contain missing values and all other pairs are equal. Use [`isequal`](@ref) or [`===`](@ref) to always get a `Bool` result. # Implementation @@ -464,13 +501,17 @@ cmp(x::Integer, y::Integer) = ifelse(isless(x, y), -1, ifelse(isless(y, x), 1, 0 """ max(x, y, ...) -Return the maximum of the arguments (with respect to [`isless`](@ref)). See also the [`maximum`](@ref) function -to take the maximum element from a collection. +Return the maximum of the arguments, with respect to [`isless`](@ref). +If any of the arguments is [`missing`](@ref), return `missing`. +See also the [`maximum`](@ref) function to take the maximum element from a collection. # Examples ```jldoctest julia> max(2, 5, 1) 5 + +julia> max(5, missing, 6) +missing ``` """ max(x, y) = ifelse(isless(y, x), x, y) @@ -478,13 +519,17 @@ max(x, y) = ifelse(isless(y, x), x, y) """ min(x, y, ...) -Return the minimum of the arguments (with respect to [`isless`](@ref)). See also the [`minimum`](@ref) function -to take the minimum element from a collection. +Return the minimum of the arguments, with respect to [`isless`](@ref). +If any of the arguments is [`missing`](@ref), return `missing`. +See also the [`minimum`](@ref) function to take the minimum element from a collection. # Examples ```jldoctest julia> min(2, 5, 1) 1 + +julia> min(4, missing, 6) +missing ``` """ min(x,y) = ifelse(isless(y, x), y, x) @@ -1233,7 +1278,7 @@ it into the original function. This is useful as an adaptor to pass a multi-argument function in a context that expects a single argument, but passes a tuple as that single argument. -# Example usage: +# Examples ```jldoctest julia> map(splat(+), zip(1:3,4:6)) 3-element Vector{Int64}: @@ -1337,11 +1382,15 @@ a function equivalent to `y -> item in y`. Determine whether an item is in the given collection, in the sense that it is [`==`](@ref) to one of the values generated by iterating over the collection. +Can equivalently be used with infix syntax: + + item in collection + item ∈ collection + Return a `Bool` value, except if `item` is [`missing`](@ref) or `collection` contains `missing` but not `item`, in which case `missing` is returned ([three-valued logic](https://en.wikipedia.org/wiki/Three-valued_logic), matching the behavior of [`any`](@ref) and [`==`](@ref)). - Some collections follow a slightly different definition. For example, [`Set`](@ref)s check whether the item [`isequal`](@ref) to one of the elements; [`Dict`](@ref)s look for `key=>value` pairs, and the `key` is compared using @@ -1352,7 +1401,7 @@ or `k in keys(dict)`. For the collections mentioned above, the result is always a `Bool`. When broadcasting with `in.(items, collection)` or `items .∈ collection`, both -`item` and `collection` are broadcasted over, which is often not what is intended. +`items` and `collection` are broadcasted over, which is often not what is intended. For example, if both arguments are vectors (and the dimensions match), the result is a vector indicating whether each value in collection `items` is `in` the value at the corresponding position in `collection`. To get a vector indicating whether each value @@ -1406,7 +1455,7 @@ in Negation of `∈` and `∋`, i.e. checks that `item` is not in `collection`. -When broadcasting with `items .∉ collection`, both `item` and `collection` are +When broadcasting with `items .∉ collection`, both `items` and `collection` are broadcasted over, which is often not what is intended. For example, if both arguments are vectors (and the dimensions match), the result is a vector indicating whether each value in collection `items` is not in the value at the corresponding position diff --git a/base/optimized_generics.jl b/base/optimized_generics.jl new file mode 100644 index 0000000000000..86b54a294564d --- /dev/null +++ b/base/optimized_generics.jl @@ -0,0 +1,57 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +module OptimizedGenerics + +# This file defines interfaces that are recognized and optimized by the compiler +# They are intended to be used by data structure implementations that wish to +# opt into some level of compiler optimizations. These interfaces are +# EXPERIMENTAL and currently intended for use by Base only. They are subject +# to change or removal without notice. It is undefined behavior to add methods +# to these generics that do not conform to the specified interface. +# +# The intended way to use these generics is that data structures will provide +# appropriate implementations for a generic. In the absence of compiler +# optimizations, these behave like regular methods. However, the compiler is +# semantically allowed to perform certain structural optimizations on +# appropriate combinations of these intrinsics without proving correctness. + +# Compiler-recognized generics for immutable key-value stores (dicts, etc.) +""" + module KeyValue + +Implements a key-value like interface where the compiler has liberty to perform +the following transformations. The core optimization semantically allowed for +the compiler is: + + get(set(x, key, val), key) -> (val,) + +where the compiler will recursively look through `x`. Keys are compared by +egality. + +Implementations must observe the following constraints: + +1. It is undefined behavior for `get` not to return the exact (by egality) val + stored for a given `key`. +""" +module KeyValue + """ + set(collection, [key [, val]]) + set(T, collection, key, val) + + Set the `key` in `collection` to `val`. If `val` is omitted, deletes the + value from the collection. If `key` is omitted as well, deletes all elements + of the collection. + """ + function set end + + """ + get(collection, key) + + Retrieve the value corresponding to `key` in `collection` as a single + element tuple or `nothing` if no value corresponding to the key was found. + `key`s are compared by egal. + """ + function get end +end + +end diff --git a/base/ordering.jl b/base/ordering.jl index 13f5eceb2d4dc..585824bbeadfe 100644 --- a/base/ordering.jl +++ b/base/ordering.jl @@ -21,7 +21,8 @@ export # not exported by Base """ Base.Order.Ordering -Abstract type which represents a total order on some set of elements. +Abstract type which represents a strict weak order on some set of elements. See +[`sort!`](@ref) for more. Use [`Base.Order.lt`](@ref) to compare two elements according to the ordering. """ @@ -110,7 +111,7 @@ ReverseOrdering(by::By) = By(by.by, ReverseOrdering(by.order)) ReverseOrdering(perm::Perm) = Perm(ReverseOrdering(perm.order), perm.data) """ - lt(o::Ordering, a, b) + lt(o::Ordering, a, b) -> Bool Test whether `a` is less than `b` according to the ordering `o`. """ diff --git a/base/parse.jl b/base/parse.jl index f6a93e56369b7..3f714934206fc 100644 --- a/base/parse.jl +++ b/base/parse.jl @@ -251,8 +251,9 @@ function tryparse(::Type{T}, s::AbstractString; base::Union{Nothing,Integer} = n end function parse(::Type{T}, s::AbstractString; base::Union{Nothing,Integer} = nothing) where {T<:Integer} - convert(T, tryparse_internal(T, s, firstindex(s), lastindex(s), - base===nothing ? 0 : check_valid_base(base), true)) + v = tryparse_internal(T, s, firstindex(s), lastindex(s), base===nothing ? 0 : check_valid_base(base), true) + v === nothing && error("should not happoen") + convert(T, v) end tryparse(::Type{Union{}}, slurp...; kwargs...) = error("cannot parse a value as Union{}") @@ -321,14 +322,14 @@ function tryparse_internal(::Type{Complex{T}}, s::Union{String,SubString{String} if i₊ == i # leading ± sign i₊ = something(findnext(in(('+','-')), s, i₊+1), 0) end - if i₊ != 0 && s[i₊-1] in ('e','E') # exponent sign + if i₊ != 0 && s[prevind(s, i₊)] in ('e','E') # exponent sign i₊ = something(findnext(in(('+','-')), s, i₊+1), 0) end # find trailing im/i/j iᵢ = something(findprev(in(('m','i','j')), s, e), 0) if iᵢ > 0 && s[iᵢ] == 'm' # im - iᵢ -= 1 + iᵢ = prevind(s, iᵢ) if s[iᵢ] != 'i' raise && throw(ArgumentError("expected trailing \"im\", found only \"m\"")) return nothing @@ -337,7 +338,7 @@ function tryparse_internal(::Type{Complex{T}}, s::Union{String,SubString{String} if i₊ == 0 # purely real or imaginary value if iᵢ > i && !(iᵢ == i+1 && s[i] in ('+','-')) # purely imaginary (not "±inf") - x = tryparse_internal(T, s, i, iᵢ-1, raise) + x = tryparse_internal(T, s, i, prevind(s, iᵢ), raise) x === nothing && return nothing return Complex{T}(zero(x),x) else # purely real @@ -353,11 +354,11 @@ function tryparse_internal(::Type{Complex{T}}, s::Union{String,SubString{String} end # parse real part - re = tryparse_internal(T, s, i, i₊-1, raise) + re = tryparse_internal(T, s, i, prevind(s, i₊), raise) re === nothing && return nothing # parse imaginary part - im = tryparse_internal(T, s, i₊+1, iᵢ-1, raise) + im = tryparse_internal(T, s, i₊+1, prevind(s, iᵢ), raise) im === nothing && return nothing return Complex{T}(re, s[i₊]=='-' ? -im : im) diff --git a/base/path.jl b/base/path.jl index c439a2800acce..3b8124f34f174 100644 --- a/base/path.jl +++ b/base/path.jl @@ -34,8 +34,25 @@ elseif Sys.iswindows() const path_dir_splitter = r"^(.*?)([/\\]+)([^/\\]*)$"sa const path_ext_splitter = r"^((?:.*[/\\])?(?:\.|[^/\\\.])[^/\\]*?)(\.[^/\\\.]*|)$"sa + const splitdrive_re = let + # Slash in either direction. + S = raw"[\\/]" + # Not a slash in either direction. + N = raw"[^\\/]" + # Drive letter, e.g. `C:` + drive = "$(N)+:" + # UNC path, e.g. `\\server\share` + unc = "$(S)$(S)$(N)+$(S)$(N)+" + # Long drive letter, e.g. `\\?\C:` + long_drive = "$(S)$(S)\\?$(S)$(drive)" + # Long UNC path, e.g. `\\?\UNC\server\share` + long_unc = "$(S)$(S)\\?$(S)UNC$(S)$(N)+$(S)$(N)+" + # Need to match the long patterns first so they get priority. + Regex("^($long_unc|$long_drive|$unc|$drive|)(.*)\$", "sa") + end + function splitdrive(path::String) - m = match(r"^([^\\]+:|\\\\[^\\]+\\[^\\]+|\\\\\?\\UNC\\[^\\]+\\[^\\]+|\\\\\?\\[^\\]+:|)(.*)$"sa, path)::AbstractMatch + m = match(splitdrive_re, path)::AbstractMatch String(something(m.captures[1])), String(something(m.captures[2])) end else @@ -60,6 +77,8 @@ Return the current user's home directory. `homedir` determines the home directory via `libuv`'s `uv_os_homedir`. For details (for example on how to specify the home directory via environment variables), see the [`uv_os_homedir` documentation](http://docs.libuv.org/en/v1.x/misc.html#c.uv_os_homedir). + +See also [`Sys.username`](@ref). """ function homedir() buf = Base.StringVector(AVG_PATH - 1) # space for null-terminator implied by StringVector @@ -416,11 +435,11 @@ normpath(a::AbstractString, b::AbstractString...) = normpath(joinpath(a,b...)) Convert a path to an absolute path by adding the current directory if necessary. Also normalizes the path as in [`normpath`](@ref). -# Example +# Examples If you are in a directory called `JuliaExample` and the data you are using is two levels up relative to the `JuliaExample` directory, you could write: -abspath("../../data") + abspath("../../data") Which gives a path like `"/home/JuliaUser/data/"`. diff --git a/base/permuteddimsarray.jl b/base/permuteddimsarray.jl index 41c3636b40216..c7fe5a9dfe0db 100644 --- a/base/permuteddimsarray.jl +++ b/base/permuteddimsarray.jl @@ -12,7 +12,7 @@ struct PermutedDimsArray{T,N,perm,iperm,AA<:AbstractArray} <: AbstractArray{T,N} function PermutedDimsArray{T,N,perm,iperm,AA}(data::AA) where {T,N,perm,iperm,AA<:AbstractArray} (isa(perm, NTuple{N,Int}) && isa(iperm, NTuple{N,Int})) || error("perm and iperm must both be NTuple{$N,Int}") isperm(perm) || throw(ArgumentError(string(perm, " is not a valid permutation of dimensions 1:", N))) - all(map(d->iperm[perm[d]]==d, 1:N)) || throw(ArgumentError(string(perm, " and ", iperm, " must be inverses"))) + all(d->iperm[perm[d]]==d, 1:N) || throw(ArgumentError(string(perm, " and ", iperm, " must be inverses"))) new(data) end end @@ -49,10 +49,8 @@ Base.parent(A::PermutedDimsArray) = A.parent Base.size(A::PermutedDimsArray{T,N,perm}) where {T,N,perm} = genperm(size(parent(A)), perm) Base.axes(A::PermutedDimsArray{T,N,perm}) where {T,N,perm} = genperm(axes(parent(A)), perm) Base.has_offset_axes(A::PermutedDimsArray) = Base.has_offset_axes(A.parent) - Base.similar(A::PermutedDimsArray, T::Type, dims::Base.Dims) = similar(parent(A), T, dims) - -Base.unsafe_convert(::Type{Ptr{T}}, A::PermutedDimsArray{T}) where {T} = Base.unsafe_convert(Ptr{T}, parent(A)) +Base.cconvert(::Type{Ptr{T}}, A::PermutedDimsArray{T}) where {T} = Base.cconvert(Ptr{T}, parent(A)) # It's OK to return a pointer to the first element, and indeed quite # useful for wrapping C routines that require a different storage @@ -89,13 +87,68 @@ end """ permutedims(A::AbstractArray, perm) + permutedims(A::AbstractMatrix) -Permute the dimensions of array `A`. `perm` is a vector or a tuple of length `ndims(A)` +Permute the dimensions (axes) of array `A`. `perm` is a tuple or vector of `ndims(A)` integers specifying the permutation. +If `A` is a 2d array ([`AbstractMatrix`](@ref)), then +`perm` defaults to `(2,1)`, swapping the two axes of `A` (the rows and columns +of the matrix). This differs from [`transpose`](@ref) in that the +operation is not recursive, which is especially useful for arrays of non-numeric values +(where the recursive `transpose` would throw an error) and/or 2d arrays that do not represent +linear operators. + +For 1d arrays, see [`permutedims(v::AbstractVector)`](@ref), which returns a 1-row “matrix”. + See also [`permutedims!`](@ref), [`PermutedDimsArray`](@ref), [`transpose`](@ref), [`invperm`](@ref). # Examples + +## 2d arrays: +Unlike `transpose`, `permutedims` can be used to swap rows and columns of 2d arrays of +arbitrary non-numeric elements, such as strings: +```jldoctest +julia> A = ["a" "b" "c" + "d" "e" "f"] +2×3 Matrix{String}: + "a" "b" "c" + "d" "e" "f" + +julia> permutedims(A) +3×2 Matrix{String}: + "a" "d" + "b" "e" + "c" "f" +``` +And `permutedims` produces results that differ from `transpose` +for matrices whose elements are themselves numeric matrices: +```jldoctest; setup = :(using LinearAlgebra) +julia> a = [1 2; 3 4]; + +julia> b = [5 6; 7 8]; + +julia> c = [9 10; 11 12]; + +julia> d = [13 14; 15 16]; + +julia> X = [[a] [b]; [c] [d]] +2×2 Matrix{Matrix{Int64}}: + [1 2; 3 4] [5 6; 7 8] + [9 10; 11 12] [13 14; 15 16] + +julia> permutedims(X) +2×2 Matrix{Matrix{Int64}}: + [1 2; 3 4] [9 10; 11 12] + [5 6; 7 8] [13 14; 15 16] + +julia> transpose(X) +2×2 transpose(::Matrix{Matrix{Int64}}) with eltype Transpose{Int64, Matrix{Int64}}: + [1 3; 2 4] [9 11; 10 12] + [5 7; 6 8] [13 15; 14 16] +``` + +## Multi-dimensional arrays ```jldoctest julia> A = reshape(Vector(1:8), (2,2,2)) 2×2×2 Array{Int64, 3}: @@ -145,54 +198,62 @@ function permutedims(A::AbstractArray, perm) permutedims!(dest, A, perm) end -""" - permutedims(m::AbstractMatrix) - -Permute the dimensions of the matrix `m`, by flipping the elements across the diagonal of -the matrix. Differs from `LinearAlgebra`'s [`transpose`](@ref) in that the -operation is not recursive. - -# Examples -```jldoctest; setup = :(using LinearAlgebra) -julia> a = [1 2; 3 4]; - -julia> b = [5 6; 7 8]; - -julia> c = [9 10; 11 12]; - -julia> d = [13 14; 15 16]; - -julia> X = [[a] [b]; [c] [d]] -2×2 Matrix{Matrix{Int64}}: - [1 2; 3 4] [5 6; 7 8] - [9 10; 11 12] [13 14; 15 16] - -julia> permutedims(X) -2×2 Matrix{Matrix{Int64}}: - [1 2; 3 4] [9 10; 11 12] - [5 6; 7 8] [13 14; 15 16] - -julia> transpose(X) -2×2 transpose(::Matrix{Matrix{Int64}}) with eltype Transpose{Int64, Matrix{Int64}}: - [1 3; 2 4] [9 11; 10 12] - [5 7; 6 8] [13 15; 14 16] -``` -""" permutedims(A::AbstractMatrix) = permutedims(A, (2,1)) """ permutedims(v::AbstractVector) Reshape vector `v` into a `1 × length(v)` row matrix. -Differs from `LinearAlgebra`'s [`transpose`](@ref) in that -the operation is not recursive. +Differs from [`transpose`](@ref) in that +the operation is not recursive, which is especially useful for arrays of non-numeric values +(where the recursive `transpose` might throw an error). # Examples +Unlike `transpose`, `permutedims` can be used on vectors of +arbitrary non-numeric elements, such as strings: +```jldoctest +julia> permutedims(["a", "b", "c"]) +1×3 Matrix{String}: + "a" "b" "c" +``` +For vectors of numbers, `permutedims(v)` works much like `transpose(v)` +except that the return type differs (it uses [`reshape`](@ref) +rather than a `LinearAlgebra.Transpose` view, though both +share memory with the original array `v`): ```jldoctest; setup = :(using LinearAlgebra) -julia> permutedims([1, 2, 3, 4]) +julia> v = [1, 2, 3, 4] +4-element Vector{Int64}: + 1 + 2 + 3 + 4 + +julia> p = permutedims(v) 1×4 Matrix{Int64}: 1 2 3 4 +julia> r = transpose(v) +1×4 transpose(::Vector{Int64}) with eltype Int64: + 1 2 3 4 + +julia> p == r +true + +julia> typeof(r) +Transpose{Int64, Vector{Int64}} + +julia> p[1] = 5; r[2] = 6; # mutating p or r also changes v + +julia> v # shares memory with both p and r +4-element Vector{Int64}: + 5 + 6 + 3 + 4 +``` +However, `permutedims` produces results that differ from `transpose` +for vectors whose elements are themselves numeric matrices: +```jldoctest; setup = :(using LinearAlgebra) julia> V = [[[1 2; 3 4]]; [[5 6; 7 8]]] 2-element Vector{Matrix{Int64}}: [1 2; 3 4] diff --git a/base/pkgid.jl b/base/pkgid.jl index 20d9de559b334..8c776d79a69cb 100644 --- a/base/pkgid.jl +++ b/base/pkgid.jl @@ -23,7 +23,7 @@ function hash(pkg::PkgId, h::UInt) return h end -show(io::IO, pkg::PkgId) = +show(io::IO, ::MIME"text/plain", pkg::PkgId) = print(io, pkg.name, " [", pkg.uuid === nothing ? "top-level" : pkg.uuid, "]") function binpack(pkg::PkgId) @@ -37,7 +37,8 @@ end function binunpack(s::String) io = IOBuffer(s) - @assert read(io, UInt8) === 0x00 + z = read(io, UInt8) + @assert z === 0x00 uuid = read(io, UInt128) name = read(io, String) return PkgId(UUID(uuid), name) diff --git a/base/pointer.jl b/base/pointer.jl index a47f1e38edb9b..494e87996355c 100644 --- a/base/pointer.jl +++ b/base/pointer.jl @@ -54,17 +54,40 @@ See also [`cconvert`](@ref) """ function unsafe_convert end +# convert strings to String etc. to pass as pointers +cconvert(::Type{Ptr{UInt8}}, s::AbstractString) = String(s) +cconvert(::Type{Ptr{Int8}}, s::AbstractString) = String(s) unsafe_convert(::Type{Ptr{UInt8}}, x::Symbol) = ccall(:jl_symbol_name, Ptr{UInt8}, (Any,), x) unsafe_convert(::Type{Ptr{Int8}}, x::Symbol) = ccall(:jl_symbol_name, Ptr{Int8}, (Any,), x) unsafe_convert(::Type{Ptr{UInt8}}, s::String) = ccall(:jl_string_ptr, Ptr{UInt8}, (Any,), s) unsafe_convert(::Type{Ptr{Int8}}, s::String) = ccall(:jl_string_ptr, Ptr{Int8}, (Any,), s) -# convert strings to String etc. to pass as pointers -cconvert(::Type{Ptr{UInt8}}, s::AbstractString) = String(s) -cconvert(::Type{Ptr{Int8}}, s::AbstractString) = String(s) -unsafe_convert(::Type{Ptr{T}}, a::Array{T}) where {T} = ccall(:jl_array_ptr, Ptr{T}, (Any,), a) +cconvert(::Type{<:Ptr}, a::Array) = getfield(a, :ref) unsafe_convert(::Type{Ptr{S}}, a::AbstractArray{T}) where {S,T} = convert(Ptr{S}, unsafe_convert(Ptr{T}, a)) +unsafe_convert(::Type{Ptr{T}}, a::Array{T}) where {T} = unsafe_convert(Ptr{T}, a.ref) unsafe_convert(::Type{Ptr{T}}, a::AbstractArray{T}) where {T} = error("conversion to pointer not defined for $(typeof(a))") +# TODO: add this deprecation to give a better error: +# cconvert(::Type{<:Ptr}, a::AbstractArray) = error("conversion to pointer not defined for $(typeof(a))") +# unsafe_convert(::Type{Ptr{T}}, a::AbstractArray{T}) where {T} = error("missing call to cconvert for call to unsafe_convert for AbstractArray") + +cconvert(::Type{<:Ptr}, a::GenericMemory) = a +unsafe_convert(::Type{Ptr{Cvoid}}, a::GenericMemory{T}) where {T} = getfield(a, :ptr) +unsafe_convert(::Type{Ptr{T}}, a::GenericMemory) where {T} = convert(Ptr{T}, getfield(a, :ptr)) + +function unsafe_convert(::Type{Ptr{Cvoid}}, a::GenericMemoryRef{<:Any,T,Core.CPU}) where {T} + mem = getfield(a, :mem) + offset = getfield(a, :ptr_or_offset) + MemT = typeof(mem) + arrayelem = datatype_arrayelem(MemT) + elsz = datatype_layoutsize(MemT) + isboxed = 1; isunion = 2 + if arrayelem == isunion || elsz == 0 + offset = UInt(offset) * elsz + offset += unsafe_convert(Ptr{Cvoid}, mem) + end + return offset +end +unsafe_convert(::Type{Ptr{T}}, a::GenericMemoryRef) where {T} = convert(Ptr{T}, unsafe_convert(Ptr{Cvoid}, a)) # unsafe pointer to array conversions """ @@ -92,10 +115,21 @@ function unsafe_wrap(::Union{Type{Array},Type{Array{T}},Type{Array{T,1}}}, ccall(:jl_ptr_to_array_1d, Array{T,1}, (Any, Ptr{Cvoid}, Csize_t, Cint), Array{T,1}, p, d, own) end -unsafe_wrap(Atype::Union{Type{Array},Type{Array{T}},Type{Array{T,N}}}, +function unsafe_wrap(::Union{Type{GenericMemory{kind,<:Any,Core.CPU}},Type{GenericMemory{kind,T,Core.CPU}}}, + p::Ptr{T}, dims::Tuple{Int}; own::Bool = false) where {kind,T} + ccall(:jl_ptr_to_genericmemory, Ref{GenericMemory{kind,T,Core.CPU}}, + (Any, Ptr{Cvoid}, Csize_t, Cint), GenericMemory{kind,T,Core.CPU}, p, dim[1], own) +end +function unsafe_wrap(::Union{Type{GenericMemory{kind,<:Any,Core.CPU}},Type{GenericMemory{kind,T,Core.CPU}}}, + p::Ptr{T}, d::Integer; own::Bool = false) where {kind,T} + ccall(:jl_ptr_to_genericmemory, Ref{GenericMemory{kind,T,Core.CPU}}, + (Any, Ptr{Cvoid}, Csize_t, Cint), GenericMemory{kind,T,Core.CPU}, p, d, own) +end +unsafe_wrap(Atype::Union{Type{Array},Type{Array{T}},Type{Array{T,N}},Type{GenericMemory{kind,<:Any,Core.CPU}},Type{GenericMemory{kind,T,Core.CPU}}} where {kind}, p::Ptr{T}, dims::NTuple{N,<:Integer}; own::Bool = false) where {T,N} = unsafe_wrap(Atype, p, convert(Tuple{Vararg{Int}}, dims), own = own) + """ unsafe_load(p::Ptr{T}, i::Integer=1) unsafe_load(p::Ptr{T}, order::Symbol) @@ -279,8 +313,8 @@ isless(x::Ptr{T}, y::Ptr{T}) where {T} = x < y <(x::Ptr, y::Ptr) = UInt(x) < UInt(y) -(x::Ptr, y::Ptr) = UInt(x) - UInt(y) -+(x::Ptr, y::Integer) = oftype(x, add_ptr(UInt(x), (y % UInt) % UInt)) --(x::Ptr, y::Integer) = oftype(x, sub_ptr(UInt(x), (y % UInt) % UInt)) ++(x::Ptr, y::Integer) = add_ptr(x, (y % UInt) % UInt) +-(x::Ptr, y::Integer) = sub_ptr(x, (y % UInt) % UInt) +(x::Integer, y::Ptr) = y + x unsigned(x::Ptr) = UInt(x) diff --git a/base/precompilation.jl b/base/precompilation.jl new file mode 100644 index 0000000000000..9acbd2a11f202 --- /dev/null +++ b/base/precompilation.jl @@ -0,0 +1,1004 @@ +module Precompilation + +using Base: PkgId, UUID, SHA1, parsed_toml, project_file_name_uuid, project_names, + project_file_manifest_path, get_deps, preferences_names, isaccessibledir, isfile_casesensitive, + base_project + +# This is currently only used for pkgprecompile but the plan is to use this in code loading in the future +# see the `kc/codeloading2.0` branch +struct ExplicitEnv + path::String + project_deps::Dict{String, UUID} # [deps] in Project.toml + project_weakdeps::Dict{String, UUID} # [weakdeps] in Project.toml + project_extras::Dict{String, UUID} # [extras] in Project.toml + project_extensions::Dict{String, Vector{UUID}} # [exts] in Project.toml + deps::Dict{UUID, Vector{UUID}} # all dependencies in Manifest.toml + weakdeps::Dict{UUID, Vector{UUID}} # all weak dependencies in Manifest.toml + extensions::Dict{UUID, Dict{String, Vector{UUID}}} + # Lookup name for a UUID + names::Dict{UUID, String} + lookup_strategy::Dict{UUID, Union{ + SHA1, # `git-tree-sha1` entry + String, # `path` entry + Nothing, # stdlib (no `path` nor `git-tree-sha1`) + Missing}} # not present in the manifest + #prefs::Union{Nothing, Dict{String, Any}} + #local_prefs::Union{Nothing, Dict{String, Any}} +end + +function ExplicitEnv(envpath::String=Base.active_project()) + if !isfile(envpath) + error("expected a project file at $(repr(envpath))") + end + envpath = abspath(envpath) + project_d = parsed_toml(envpath) + + # TODO: Perhaps verify that two packages with the same UUID do not have different names? + names = Dict{UUID, String}() + project_uuid_to_name = Dict{String, UUID}() + + project_deps = Dict{String, UUID}() + project_weakdeps = Dict{String, UUID}() + project_extras = Dict{String, UUID}() + + # Collect all direct dependencies of the project + for key in ["deps", "weakdeps", "extras"] + for (name, _uuid) in get(Dict{String, Any}, project_d, key)::Dict{String, Any} + v = key == "deps" ? project_deps : + key == "weakdeps" ? project_weakdeps : + key == "extras" ? project_extras : + error() + uuid = UUID(_uuid) + v[name] = uuid + names[UUID(uuid)] = name + project_uuid_to_name[name] = UUID(uuid) + end + end + + # A package in both deps and weakdeps is in fact only a weakdep + for (name, _) in project_weakdeps + delete!(project_deps, name) + end + + # This project might be a package, in that case, that is also a "dependency" + # of the project. + proj_name = get(project_d, "name", nothing)::Union{String, Nothing} + _proj_uuid = get(project_d, "uuid", nothing)::Union{String, Nothing} + proj_uuid = _proj_uuid === nothing ? nothing : UUID(_proj_uuid) + + project_is_package = proj_name !== nothing && proj_uuid !== nothing + if project_is_package + # TODO: Error on missing uuid? + project_deps[proj_name] = UUID(proj_uuid) + names[UUID(proj_uuid)] = proj_name + end + + project_extensions = Dict{String, Vector{UUID}}() + # Collect all extensions of the project + for (name, triggers::Union{String, Vector{String}}) in get(Dict{String, Any}, project_d, "extensions")::Dict{String, Any} + if triggers isa String + triggers = [triggers] + end + uuids = UUID[] + for trigger in triggers + uuid = get(project_uuid_to_name, trigger, nothing) + if uuid === nothing + error("Trigger $trigger for extension $name not found in project") + end + push!(uuids, uuid) + end + project_extensions[name] = uuids + end + + manifest = project_file_manifest_path(envpath) + manifest_d = manifest === nothing ? Dict{String, Any}() : parsed_toml(manifest) + + # Dependencies in a manifest can either be stored compressed (when name is unique among all packages) + # in which case it is a `Vector{String}` or expanded where it is a `name => uuid` mapping. + deps = Dict{UUID, Union{Vector{String}, Vector{UUID}}}() + weakdeps = Dict{UUID, Union{Vector{String}, Vector{UUID}}}() + extensions = Dict{UUID, Dict{String, Vector{String}}}() + name_to_uuid = Dict{String, UUID}() + lookup_strategy = Dict{UUID, Union{SHA1, String, Nothing, Missing}}() + + sizehint!(deps, length(manifest_d)) + sizehint!(weakdeps, length(manifest_d)) + sizehint!(extensions, length(manifest_d)) + sizehint!(name_to_uuid, length(manifest_d)) + sizehint!(lookup_strategy, length(manifest_d)) + + for (name, pkg_infos) in get_deps(manifest_d) + pkg_infos = pkg_infos::Vector{Any} + for pkg_info in pkg_infos + m_uuid = UUID(pkg_info["uuid"]::String) + + # If we have multiple packages with the same name we will overwrite things here + # but that is fine since we will only use the information in here for packages + # with unique names + names[m_uuid] = name + name_to_uuid[name] = m_uuid + + for key in ["deps", "weakdeps"] + deps_pkg = get(Vector{String}, pkg_info, key)::Union{Vector{String}, Dict{String, Any}} + d = key == "deps" ? deps : + key == "weakdeps" ? weakdeps : + error() + + # Compressed format with unique names: + if deps_pkg isa Vector{String} + d[m_uuid] = deps_pkg + # Expanded format: + else + uuids = UUID[] + for (name_dep, _dep_uuid::String) in deps_pkg + dep_uuid = UUID(_dep_uuid) + push!(uuids, dep_uuid) + names[dep_uuid] = name_dep + end + d[m_uuid] = uuids + end + end + + # Extensions + deps_pkg = get(Dict{String, Any}, pkg_info, "extensions")::Dict{String, Any} + for (ext, triggers) in deps_pkg + triggers = triggers::Union{String, Vector{String}} + if triggers isa String + triggers = [triggers] + end + deps_pkg[ext] = triggers + end + extensions[m_uuid] = deps_pkg + + # Determine strategy to find package + lookup_strat = begin + if (path = get(pkg_info, "path", nothing)::Union{String, Nothing}) !== nothing + path + elseif (git_tree_sha_str = get(pkg_info, "git-tree-sha1", nothing)::Union{String, Nothing}) !== nothing + SHA1(git_tree_sha_str) + else + nothing + end + end + lookup_strategy[m_uuid] = lookup_strat + end + end + + # No matter if the deps were stored compressed or not in the manifest, + # we internally store them expanded + deps_expanded = Dict{UUID, Vector{UUID}}() + weakdeps_expanded = Dict{UUID, Vector{UUID}}() + extensions_expanded = Dict{UUID, Dict{String, Vector{UUID}}}() + sizehint!(deps_expanded, length(deps)) + sizehint!(weakdeps_expanded, length(deps)) + sizehint!(extensions_expanded, length(deps)) + + if proj_name !== nothing && proj_uuid !== nothing + deps_expanded[proj_uuid] = filter!(!=(proj_uuid), collect(values(project_deps))) + extensions_expanded[proj_uuid] = project_extensions + path = get(project_d, "path", nothing) + entry_point = path !== nothing ? path : dirname(envpath) + lookup_strategy[proj_uuid] = entry_point + end + + for key in ["deps", "weakdeps"] + d = key == "deps" ? deps : + key == "weakdeps" ? weakdeps : + error() + d_expanded = key == "deps" ? deps_expanded : + key == "weakdeps" ? weakdeps_expanded : + error() + for (pkg, deps) in d + # dependencies was already expanded so use it directly: + if deps isa Vector{UUID} + d_expanded[pkg] = deps + for dep in deps + name_to_uuid[names[dep]] = dep + end + # find the (unique) UUID associated with the name + else + deps_pkg = UUID[] + sizehint!(deps_pkg, length(deps)) + for dep in deps + push!(deps_pkg, name_to_uuid[dep]) + end + d_expanded[pkg] = deps_pkg + end + end + end + + for (pkg, exts) in extensions + exts_expanded = Dict{String, Vector{UUID}}() + for (ext, triggers) in exts + triggers_expanded = UUID[] + sizehint!(triggers_expanded, length(triggers)) + for trigger in triggers + push!(triggers_expanded, name_to_uuid[trigger]) + end + exts_expanded[ext] = triggers_expanded + end + extensions_expanded[pkg] = exts_expanded + end + + # Everything that does not yet have a lookup_strategy is missing from the manifest + for (_, uuid) in project_deps + get!(lookup_strategy, uuid, missing) + end + + #= + # Preferences: + prefs = get(project_d, "preferences", nothing) + + # `(Julia)LocalPreferences.toml` + project_dir = dirname(envpath) + local_prefs = nothing + for name in preferences_names + toml_path = joinpath(project_dir, name) + if isfile(toml_path) + local_prefs = parsed_toml(toml_path) + break + end + end + =# + + return ExplicitEnv(envpath, project_deps, project_weakdeps, project_extras, + project_extensions, deps_expanded, weakdeps_expanded, extensions_expanded, + names, lookup_strategy, #=prefs, local_prefs=#) +end + +## PROGRESS BAR + +# using Printf +Base.@kwdef mutable struct MiniProgressBar + max::Int = 1.0 + header::String = "" + color::Symbol = :nothing + width::Int = 40 + current::Int = 0.0 + prev::Int = 0.0 + has_shown::Bool = false + time_shown::Float64 = 0.0 + percentage::Bool = true + always_reprint::Bool = false + indent::Int = 4 +end + +const PROGRESS_BAR_TIME_GRANULARITY = Ref(1 / 30.0) # 30 fps +const PROGRESS_BAR_PERCENTAGE_GRANULARITY = Ref(0.1) + +function start_progress(io::IO, _::MiniProgressBar) + ansi_disablecursor = "\e[?25l" + print(io, ansi_disablecursor) +end + +function show_progress(io::IO, p::MiniProgressBar; termwidth=nothing, carriagereturn=true) + if p.max == 0 + perc = 0.0 + prev_perc = 0.0 + else + perc = p.current / p.max * 100 + prev_perc = p.prev / p.max * 100 + end + # Bail early if we are not updating the progress bar, + # Saves printing to the terminal + if !p.always_reprint && p.has_shown && !((perc - prev_perc) > PROGRESS_BAR_PERCENTAGE_GRANULARITY[]) + return + end + t = time() + if p.has_shown && (t - p.time_shown) < PROGRESS_BAR_TIME_GRANULARITY[] + return + end + p.time_shown = t + p.prev = p.current + p.has_shown = true + + progress_text = if false # p.percentage + # @sprintf "%2.1f %%" perc + else + string(p.current, "/", p.max) + end + termwidth = @something termwidth displaysize(io)[2] + max_progress_width = max(0, min(termwidth - textwidth(p.header) - textwidth(progress_text) - 10 , p.width)) + n_filled = ceil(Int, max_progress_width * perc / 100) + n_left = max_progress_width - n_filled + to_print = sprint(; context=io) do io + print(io, " "^p.indent) + printstyled(io, p.header, color=p.color, bold=true) + print(io, " [") + print(io, "="^n_filled, ">") + print(io, " "^n_left, "] ", ) + print(io, progress_text) + carriagereturn && print(io, "\r") + end + # Print everything in one call + print(io, to_print) +end + +function end_progress(io, p::MiniProgressBar) + ansi_enablecursor = "\e[?25h" + ansi_clearline = "\e[2K" + print(io, ansi_enablecursor * ansi_clearline) +end + +function print_progress_bottom(io::IO) + ansi_clearline = "\e[2K" + ansi_movecol1 = "\e[1G" + ansi_moveup(n::Int) = string("\e[", n, "A") + print(io, "\e[S" * ansi_moveup(1) * ansi_clearline * ansi_movecol1) +end + + +############ +struct PkgPrecompileError <: Exception + msg::String +end +Base.showerror(io::IO, err::PkgPrecompileError) = print(io, err.msg) +Base.showerror(io::IO, err::PkgPrecompileError, bt; kw...) = Base.showerror(io, err) # hide stacktrace + +# This needs a show method to make `julia> err` show nicely +Base.show(io::IO, err::PkgPrecompileError) = print(io, "PkgPrecompileError: ", err.msg) + +import Base: StaleCacheKey + +can_fancyprint(io::IO) = io isa Base.TTY && (get(ENV, "CI", nothing) != "true") + +function printpkgstyle(io, header, msg; color=:light_green) + printstyled(io, header; color, bold=true) + println(io, " ", msg) +end + +const Config = Pair{Cmd, Base.CacheFlags} +const PkgConfig = Tuple{Base.PkgId,Config} + +function precompilepkgs(pkgs::Vector{String}=String[]; + internal_call::Bool=false, + strict::Bool = false, + warn_loaded::Bool = true, + timing::Bool = false, + _from_loading::Bool=false, + configs::Union{Config,Vector{Config}}=(``=>Base.CacheFlags()), + io::IO=stderr, + # asking for timing disables fancy mode, as timing is shown in non-fancy mode + fancyprint::Bool = can_fancyprint(io) && !timing, + manifest::Bool=false,) + + configs = configs isa Config ? [configs] : configs + requested_pkgs = copy(pkgs) # for understanding user intent + + time_start = time_ns() + + env = ExplicitEnv() + + # Windows sometimes hits a ReadOnlyMemoryError, so we halve the default number of tasks. Issue #2323 + # TODO: Investigate why this happens in windows and restore the full task limit + default_num_tasks = Sys.iswindows() ? div(Sys.CPU_THREADS::Int, 2) + 1 : Sys.CPU_THREADS::Int + 1 + default_num_tasks = min(default_num_tasks, 16) # limit for better stability on shared resource systems + + num_tasks = parse(Int, get(ENV, "JULIA_NUM_PRECOMPILE_TASKS", string(default_num_tasks))) + parallel_limiter = Base.Semaphore(num_tasks) + + if _from_loading && !Sys.isinteractive() && Base.get_bool_env("JULIA_TESTS", false) + # suppress passive loading printing in julia test suite. `JULIA_TESTS` is set in Base.runtests + io = devnull + end + + hascolor = get(io, :color, false)::Bool + color_string(cstr::String, col::Union{Int64, Symbol}) = _color_string(cstr, col, hascolor) + + direct_deps = [ + Base.PkgId(uuid, name) + for (name, uuid) in env.project_deps if !Base.in_sysimage(Base.PkgId(uuid, name)) + ] + stale_cache = Dict{StaleCacheKey, Bool}() + exts = Dict{Base.PkgId, String}() # ext -> parent + # make a flat map of each dep and its direct deps + depsmap = Dict{Base.PkgId, Vector{Base.PkgId}}() + pkg_exts_map = Dict{Base.PkgId, Vector{Base.PkgId}}() + + for (dep, deps) in env.deps + pkg = Base.PkgId(dep, env.names[dep]) + Base.in_sysimage(pkg) && continue + deps = [Base.PkgId(x, env.names[x]) for x in deps] + depsmap[pkg] = filter!(!Base.in_sysimage, deps) + # add any extensions + pkg_exts = Dict{Base.PkgId, Vector{Base.PkgId}}() + prev_ext = nothing + for (ext_name, extdep_uuids) in env.extensions[dep] + ext_deps = Base.PkgId[] + push!(ext_deps, pkg) # depends on parent package + all_extdeps_available = true + for extdep_uuid in extdep_uuids + extdep_name = env.names[extdep_uuid] + if extdep_uuid in keys(env.deps) + push!(ext_deps, Base.PkgId(extdep_uuid, extdep_name)) + else + all_extdeps_available = false + break + end + end + all_extdeps_available || continue + if prev_ext isa Base.PkgId + # also make the exts depend on eachother sequentially to avoid race + push!(ext_deps, prev_ext) + end + ext_uuid = Base.uuid5(pkg.uuid, ext_name) + ext = Base.PkgId(ext_uuid, ext_name) + prev_ext = ext + filter!(!Base.in_sysimage, ext_deps) + depsmap[ext] = ext_deps + exts[ext] = pkg.name + pkg_exts[ext] = ext_deps + end + if !isempty(pkg_exts) + pkg_exts_map[pkg] = collect(keys(pkg_exts)) + end + end + + @debug "precompile: deps collected" + # this loop must be run after the full depsmap has been populated + for (pkg, pkg_exts) in pkg_exts_map + # find any packages that depend on the extension(s)'s deps and replace those deps in their deps list with the extension(s), + # basically injecting the extension into the precompile order in the graph, to avoid race to precompile extensions + for (_pkg, deps) in depsmap # for each manifest dep + if !in(_pkg, keys(exts)) && pkg in deps # if not an extension and depends on pkg + append!(deps, pkg_exts) # add the package extensions to deps + filter!(!isequal(pkg), deps) # remove the pkg from deps + end + end + end + @debug "precompile: extensions collected" + + # return early if no deps + if isempty(depsmap) + if isempty(pkgs) + return + elseif _from_loading + # if called from loading precompilation it may be a package from another environment stack so + # don't error and allow serial precompilation to try + # TODO: actually handle packages from other envs in the stack + return + else + error("No direct dependencies outside of the sysimage found matching $(pkgs)") + end + end + + # initialize signalling + started = Dict{PkgConfig,Bool}() + was_processed = Dict{PkgConfig,Base.Event}() + was_recompiled = Dict{PkgConfig,Bool}() + for config in configs + for pkgid in keys(depsmap) + pkg_config = (pkgid, config) + started[pkg_config] = false + was_processed[pkg_config] = Base.Event() + was_recompiled[pkg_config] = false + end + end + @debug "precompile: signalling initialized" + + + # find and guard against circular deps + circular_deps = Base.PkgId[] + # Three states + # !haskey -> never visited + # true -> cannot be compiled due to a cycle (or not yet determined) + # false -> not depending on a cycle + could_be_cycle = Dict{Base.PkgId, Bool}() + function scan_pkg!(pkg, dmap) + did_visit_dep = true + inpath = get!(could_be_cycle, pkg) do + did_visit_dep = false + return true + end + if did_visit_dep ? inpath : scan_deps!(pkg, dmap) + # Found a cycle. Delete this and all parents + return true + end + return false + end + function scan_deps!(pkg, dmap) + for dep in dmap[pkg] + scan_pkg!(dep, dmap) && return true + end + could_be_cycle[pkg] = false + return false + end + for pkg in keys(depsmap) + if scan_pkg!(pkg, depsmap) + push!(circular_deps, pkg) + for pkg_config in keys(was_processed) + # notify all to allow skipping + pkg_config[1] == pkg && notify(was_processed[pkg_config]) + end + end + end + if !isempty(circular_deps) + @warn """Circular dependency detected. Precompilation will be skipped for:\n $(join(string.(circular_deps), "\n "))""" + end + @debug "precompile: circular dep check done" + + if !manifest + if isempty(pkgs) + pkgs = [pkg.name for pkg in direct_deps] + target = "all packages" + else + target = join(pkgs, ", ") + end + # restrict to dependencies of given packages + function collect_all_deps(depsmap, dep, alldeps=Set{Base.PkgId}()) + for _dep in depsmap[dep] + if !(_dep in alldeps) + push!(alldeps, _dep) + collect_all_deps(depsmap, _dep, alldeps) + end + end + return alldeps + end + keep = Set{Base.PkgId}() + for dep in depsmap + dep_pkgid = first(dep) + if dep_pkgid.name in pkgs + push!(keep, dep_pkgid) + collect_all_deps(depsmap, dep_pkgid, keep) + end + end + for ext in keys(exts) + if issubset(collect_all_deps(depsmap, ext), keep) # if all extension deps are kept + push!(keep, ext) + end + end + filter!(d->in(first(d), keep), depsmap) + if isempty(depsmap) + if _from_loading + # if called from loading precompilation it may be a package from another environment stack so + # don't error and allow serial precompilation to try + # TODO: actually handle packages from other envs in the stack + return + else + return + end + end + else + target = "manifest" + end + + nconfigs = length(configs) + if nconfigs == 1 + if !isempty(only(configs)[1]) + target *= " for configuration $(join(only(configs)[1], " "))" + end + target *= "..." + else + target *= " for $nconfigs compilation configurations..." + end + @debug "precompile: packages filtered" + + pkg_queue = PkgConfig[] + failed_deps = Dict{PkgConfig, String}() + precomperr_deps = PkgConfig[] # packages that may succeed after a restart (i.e. loaded packages with no cache file) + + print_lock = io isa Base.LibuvStream ? io.lock::ReentrantLock : ReentrantLock() + first_started = Base.Event() + printloop_should_exit::Bool = !fancyprint # exit print loop immediately if not fancy printing + interrupted_or_done = Base.Event() + + ansi_moveup(n::Int) = string("\e[", n, "A") + ansi_movecol1 = "\e[1G" + ansi_cleartoend = "\e[0J" + ansi_cleartoendofline = "\e[0K" + ansi_enablecursor = "\e[?25h" + ansi_disablecursor = "\e[?25l" + n_done::Int = 0 + n_already_precomp::Int = 0 + n_loaded::Int = 0 + interrupted = false + + function handle_interrupt(err, in_printloop = false) + notify(interrupted_or_done) + in_printloop || wait(t_print) # wait to let the print loop cease first + if err isa InterruptException + lock(print_lock) do + println(io, " Interrupted: Exiting precompilation...", ansi_cleartoendofline) + end + interrupted = true + return true + else + return false + end + end + std_outputs = Dict{PkgConfig,String}() + taskwaiting = Set{PkgConfig}() + pkgspidlocked = Dict{PkgConfig,String}() + pkg_liveprinted = nothing + + function monitor_std(pkg_config, pipe; single_requested_pkg=false) + pkg, config = pkg_config + try + liveprinting = false + while !eof(pipe) + str = readline(pipe, keep=true) + if single_requested_pkg && (liveprinting || !isempty(str)) + lock(print_lock) do + if !liveprinting + printpkgstyle(io, :Info, "Given $(pkg.name) was explicitly requested, output will be shown live $ansi_cleartoendofline", + color = Base.info_color()) + liveprinting = true + pkg_liveprinted = pkg + end + print(io, ansi_cleartoendofline, str) + end + end + std_outputs[pkg_config] = string(get(std_outputs, pkg_config, ""), str) + if !in(pkg_config, taskwaiting) && occursin("waiting for IO to finish", str) + !fancyprint && lock(print_lock) do + println(io, pkg.name, color_string(" Waiting for background task / IO / timer.", Base.warn_color())) + end + push!(taskwaiting, pkg_config) + end + if !fancyprint && in(pkg_config, taskwaiting) + lock(print_lock) do + print(io, str) + end + end + end + catch err + err isa InterruptException || rethrow() + end + end + + ## fancy print loop + t_print = @async begin + try + wait(first_started) + (isempty(pkg_queue) || interrupted_or_done.set) && return + fancyprint && lock(print_lock) do + printpkgstyle(io, :Precompiling, target) + print(io, ansi_disablecursor) + end + t = Timer(0; interval=1/10) + anim_chars = ["◐","◓","◑","◒"] + i = 1 + last_length = 0 + bar = MiniProgressBar(; indent=2, header = "Progress", color = Base.info_color(), percentage=false, always_reprint=true) + n_total = length(depsmap) * length(configs) + bar.max = n_total - n_already_precomp + final_loop = false + n_print_rows = 0 + while !printloop_should_exit + lock(print_lock) do + term_size = Base.displaysize(io)::Tuple{Int,Int} + num_deps_show = term_size[1] - 3 + pkg_queue_show = if !interrupted_or_done.set && length(pkg_queue) > num_deps_show + last(pkg_queue, num_deps_show) + else + pkg_queue + end + str_ = sprint() do iostr + if i > 1 + print(iostr, ansi_cleartoend) + end + bar.current = n_done - n_already_precomp + bar.max = n_total - n_already_precomp + # when sizing to the terminal width subtract a little to give some tolerance to resizing the + # window between print cycles + termwidth = displaysize(io)[2] - 4 + if !final_loop + str = sprint(io -> show_progress(io, bar; termwidth, carriagereturn=false); context=io) + print(iostr, Base._truncate_at_width_or_chars(true, str, termwidth), "\n") + end + for pkg_config in pkg_queue_show + dep, config = pkg_config + loaded = warn_loaded && haskey(Base.loaded_modules, dep) + _name = haskey(exts, dep) ? string(exts[dep], " → ", dep.name) : dep.name + name = dep in direct_deps ? _name : string(color_string(_name, :light_black)) + if nconfigs > 1 && !isempty(config[1]) + config_str = "$(join(config[1], " "))" + name *= color_string(" $(config_str)", :light_black) + end + line = if pkg_config in precomperr_deps + string(color_string(" ? ", Base.warn_color()), name) + elseif haskey(failed_deps, pkg_config) + string(color_string(" ✗ ", Base.error_color()), name) + elseif was_recompiled[pkg_config] + !loaded && interrupted_or_done.set && continue + loaded || @async begin # keep successful deps visible for short period + sleep(1); + filter!(!isequal(pkg_config), pkg_queue) + end + string(color_string(" ✓ ", loaded ? Base.warn_color() : :green), name) + elseif started[pkg_config] + # Offset each spinner animation using the first character in the package name as the seed. + # If not offset, on larger terminal fonts it looks odd that they all sync-up + anim_char = anim_chars[(i + Int(dep.name[1])) % length(anim_chars) + 1] + anim_char_colored = dep in direct_deps ? anim_char : color_string(anim_char, :light_black) + waiting = if haskey(pkgspidlocked, pkg_config) + who_has_lock = pkgspidlocked[pkg_config] + color_string(" Being precompiled by $(who_has_lock)", Base.info_color()) + elseif pkg_config in taskwaiting + color_string(" Waiting for background task / IO / timer. Interrupt to inspect", Base.warn_color()) + else + "" + end + string(" ", anim_char_colored, " ", name, waiting) + else + string(" ", name) + end + println(iostr, Base._truncate_at_width_or_chars(true, line, termwidth)) + end + end + last_length = length(pkg_queue_show) + n_print_rows = count("\n", str_) + print(io, str_) + printloop_should_exit = interrupted_or_done.set && final_loop + final_loop = interrupted_or_done.set # ensures one more loop to tidy last task after finish + i += 1 + printloop_should_exit || print(io, ansi_moveup(n_print_rows), ansi_movecol1) + end + wait(t) + end + catch err + handle_interrupt(err, true) || rethrow() + finally + fancyprint && print(io, ansi_enablecursor) + end + end + tasks = Task[] + if !_from_loading + Base.LOADING_CACHE[] = Base.LoadingCache() + end + @debug "precompile: starting precompilation loop" depsmap direct_deps + ## precompilation loop + for (pkg, deps) in depsmap + cachepaths = Base.find_all_in_cache_path(pkg) + sourcepath = Base.locate_package(pkg) + single_requested_pkg = length(requested_pkgs) == 1 && only(requested_pkgs) == pkg.name + for config in configs + pkg_config = (pkg, config) + if sourcepath === nothing + failed_deps[pkg_config] = "Error: Missing source file for $(pkg)" + notify(was_processed[pkg_config]) + continue + end + # Heuristic for when precompilation is disabled + if occursin(r"\b__precompile__\(\s*false\s*\)", read(sourcepath, String)) + notify(was_processed[pkg_config]) + continue + end + flags, cacheflags = config + task = @async begin + try + loaded = haskey(Base.loaded_modules, pkg) + for dep in deps # wait for deps to finish + wait(was_processed[(dep,config)]) + end + circular = pkg in circular_deps + is_stale = !Base.isprecompiled(pkg; ignore_loaded=true, stale_cache, cachepaths, sourcepath, flags=cacheflags) + if !circular && is_stale + Base.acquire(parallel_limiter) + is_direct_dep = pkg in direct_deps + + # std monitoring + std_pipe = Base.link_pipe!(Pipe(); reader_supports_async=true, writer_supports_async=true) + t_monitor = @async monitor_std(pkg_config, std_pipe; single_requested_pkg) + + _name = haskey(exts, pkg) ? string(exts[pkg], " → ", pkg.name) : pkg.name + name = is_direct_dep ? _name : string(color_string(_name, :light_black)) + if nconfigs > 1 && !isempty(flags) + config_str = "$(join(flags, " "))" + name *= color_string(" $(config_str)", :light_black) + end + !fancyprint && lock(print_lock) do + isempty(pkg_queue) && printpkgstyle(io, :Precompiling, target) + end + push!(pkg_queue, pkg_config) + started[pkg_config] = true + fancyprint && notify(first_started) + if interrupted_or_done.set + notify(was_processed[pkg_config]) + Base.release(parallel_limiter) + return + end + try + # allows processes to wait if another process is precompiling a given package to + # a functionally identical package cache (except for preferences, which may differ) + t = @elapsed ret = precompile_pkgs_maybe_cachefile_lock(io, print_lock, fancyprint, pkg_config, pkgspidlocked, hascolor) do + Base.with_logger(Base.NullLogger()) do + # The false here means we ignore loaded modules, so precompile for a fresh session + Base.compilecache(pkg, sourcepath, std_pipe, std_pipe, false; flags, cacheflags) + end + end + if ret isa Base.PrecompilableError + push!(precomperr_deps, pkg_config) + !fancyprint && lock(print_lock) do + println(io, _timing_string(t), color_string(" ? ", Base.warn_color()), name) + end + else + !fancyprint && lock(print_lock) do + println(io, _timing_string(t), color_string(" ✓ ", loaded ? Base.warn_color() : :green), name) + end + was_recompiled[pkg_config] = true + end + loaded && (n_loaded += 1) + catch err + close(std_pipe.in) # close pipe to end the std output monitor + wait(t_monitor) + if err isa ErrorException || (err isa ArgumentError && startswith(err.msg, "Invalid header in cache file")) + failed_deps[pkg_config] = (strict || is_direct_dep) ? string(sprint(showerror, err), "\n", strip(get(std_outputs, pkg_config, ""))) : "" + delete!(std_outputs, pkg_config) # so it's not shown as warnings, given error report + !fancyprint && lock(print_lock) do + println(io, " "^9, color_string(" ✗ ", Base.error_color()), name) + end + else + rethrow() + end + finally + isopen(std_pipe.in) && close(std_pipe.in) # close pipe to end the std output monitor + wait(t_monitor) + Base.release(parallel_limiter) + end + else + is_stale || (n_already_precomp += 1) + end + n_done += 1 + notify(was_processed[pkg_config]) + catch err_outer + # For debugging: + # println("Task failed $err_outer") # logging doesn't show here + handle_interrupt(err_outer) || rethrow() + notify(was_processed[pkg_config]) + finally + filter!(!istaskdone, tasks) + length(tasks) == 1 && notify(interrupted_or_done) + end + end + push!(tasks, task) + end + end + isempty(tasks) && notify(interrupted_or_done) + try + wait(interrupted_or_done) + catch err + handle_interrupt(err) || rethrow() + finally + Base.LOADING_CACHE[] = nothing + end + notify(first_started) # in cases of no-op or !fancyprint + fancyprint && wait(t_print) + quick_exit = !all(istaskdone, tasks) || interrupted # if some not finished internal error is likely + seconds_elapsed = round(Int, (time_ns() - time_start) / 1e9) + ndeps = count(values(was_recompiled)) + if ndeps > 0 || !isempty(failed_deps) || (quick_exit && !isempty(std_outputs)) + str = sprint() do iostr + if !quick_exit + plural = length(configs) > 1 ? "dependency configurations" : ndeps == 1 ? "dependency" : "dependencies" + print(iostr, " $(ndeps) $(plural) successfully precompiled in $(seconds_elapsed) seconds") + if n_already_precomp > 0 || !isempty(circular_deps) + n_already_precomp > 0 && (print(iostr, ". $n_already_precomp already precompiled")) + !isempty(circular_deps) && (print(iostr, ". $(length(circular_deps)) skipped due to circular dependency")) + print(iostr, ".") + end + if n_loaded > 0 + plural1 = length(configs) > 1 ? "dependency configurations" : n_loaded == 1 ? "dependency" : "dependencies" + plural2 = n_loaded == 1 ? "a different version is" : "different versions are" + plural3 = n_loaded == 1 ? "" : "s" + print(iostr, "\n ", + color_string(string(n_loaded), Base.warn_color()), + " $(plural1) precompiled but $(plural2) currently loaded. Restart julia to access the new version$(plural3)" + ) + end + if !isempty(precomperr_deps) + pluralpc = length(configs) > 1 ? "dependency configurations" : precomperr_deps == 1 ? "dependency" : "dependencies" + print(iostr, "\n ", + color_string(string(length(precomperr_deps)), Base.warn_color()), + " $(pluralpc) failed but may be precompilable after restarting julia" + ) + end + end + # show any stderr output, even if Pkg.precompile has been interrupted (quick_exit=true), given user may be + # interrupting a hanging precompile job with stderr output. julia#48371 + filter!(kv -> !isempty(strip(last(kv))), std_outputs) # remove empty output + if !isempty(std_outputs) + plural1 = length(std_outputs) == 1 ? "y" : "ies" + plural2 = length(std_outputs) == 1 ? "" : "s" + print(iostr, "\n ", color_string("$(length(std_outputs))", Base.warn_color()), " dependenc$(plural1) had output during precompilation:") + for (pkg_config, err) in std_outputs + pkg, config = pkg_config + err = if pkg == pkg_liveprinted + "[Output was shown above]" + else + join(split(strip(err), "\n"), color_string("\n│ ", Base.warn_color())) + end + name = haskey(exts, pkg) ? string(exts[pkg], " → ", pkg.name) : pkg.name + print(iostr, color_string("\n┌ ", Base.warn_color()), name, color_string("\n│ ", Base.warn_color()), err, color_string("\n└ ", Base.warn_color())) + end + end + end + let str=str + lock(print_lock) do + println(io, str) + end + end + quick_exit && return + err_str = "" + n_direct_errs = 0 + for (pkg_config, err) in failed_deps + dep, config = pkg_config + if strict || (dep in direct_deps) + config_str = isempty(config[1]) ? "" : "$(join(config[1], " ")) " + err_str = string(err_str, "\n$(dep.name) $config_str\n\n$err", (n_direct_errs > 0 ? "\n" : "")) + n_direct_errs += 1 + end + end + if err_str != "" + pluralde = n_direct_errs == 1 ? "y" : "ies" + direct = strict ? "" : "direct " + err_msg = "The following $n_direct_errs $(direct)dependenc$(pluralde) failed to precompile:\n$(err_str[1:end-1])" + if internal_call # aka. auto-precompilation + if isinteractive() && !get(ENV, "CI", false) + plural1 = length(failed_deps) == 1 ? "y" : "ies" + println(io, " ", color_string("$(length(failed_deps))", Base.error_color()), " dependenc$(plural1) errored.") + println(io, " For a report of the errors see `julia> err`. To retry use `pkg> precompile`") + setglobal!(Base.MainInclude, :err, PkgPrecompileError(err_msg)) + else + # auto-precompilation shouldn't throw but if the user can't easily access the + # error messages, just show them + print(io, "\n", err_msg) + end + else + println(io) + throw(PkgPrecompileError(err_msg)) + end + end + end + nothing +end + +_timing_string(t) = string(lpad(round(t * 1e3, digits = 1), 9), " ms") + +function _color_string(cstr::String, col::Union{Int64, Symbol}, hascolor) + if hascolor + enable_ansi = get(Base.text_colors, col, Base.text_colors[:default]) + disable_ansi = get(Base.disable_text_style, col, Base.text_colors[:default]) + return string(enable_ansi, cstr, disable_ansi) + else + return cstr + end +end + +# Can be merged with `maybe_cachefile_lock` in loading? +function precompile_pkgs_maybe_cachefile_lock(f, io::IO, print_lock::ReentrantLock, fancyprint::Bool, pkg_config, pkgspidlocked, hascolor) + pkg, config = pkg_config + flags, cacheflags = config + FileWatching = Base.loaded_modules[Base.PkgId(Base.UUID("7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"), "FileWatching")] + stale_age = Base.compilecache_pidlock_stale_age + pidfile = Base.compilecache_pidfile_path(pkg, flags=cacheflags) + cachefile = FileWatching.trymkpidlock(f, pidfile; stale_age) + if cachefile === false + pid, hostname, age = FileWatching.Pidfile.parse_pidfile(pidfile) + pkgspidlocked[pkg_config] = if isempty(hostname) || hostname == gethostname() + if pid == getpid() + "an async task in this process (pidfile: $pidfile)" + else + "another process (pid: $pid, pidfile: $pidfile)" + end + else + "another machine (hostname: $hostname, pid: $pid, pidfile: $pidfile)" + end + !fancyprint && lock(print_lock) do + println(io, " ", pkg.name, _color_string(" Being precompiled by $(pkgspidlocked[pkg_config])", Base.info_color(), hascolor)) + end + # wait until the lock is available + FileWatching.mkpidlock(pidfile; stale_age) do + # double-check in case the other process crashed or the lock expired + if Base.isprecompiled(pkg; ignore_loaded=true, flags=cacheflags) # don't use caches for this as the env state will have changed + return nothing # returning nothing indicates a process waited for another + else + delete!(pkgspidlocked, pkg_config) + return f() # precompile + end + end + end + return cachefile +end + +end diff --git a/base/process.jl b/base/process.jl index ed51a30ae3ced..fbc4acfd83e80 100644 --- a/base/process.jl +++ b/base/process.jl @@ -6,11 +6,12 @@ mutable struct Process <: AbstractPipe in::IO out::IO err::IO + syncd::Vector{Task} exitcode::Int64 termsignal::Int32 exitnotify::ThreadSynchronizer - function Process(cmd::Cmd, handle::Ptr{Cvoid}) - this = new(cmd, handle, devnull, devnull, devnull, + function Process(cmd::Cmd, handle::Ptr{Cvoid}, syncd::Vector{Task}) + this = new(cmd, handle, devnull, devnull, devnull, syncd, typemin(fieldtype(Process, :exitcode)), typemin(fieldtype(Process, :termsignal)), ThreadSynchronizer()) @@ -35,6 +36,15 @@ end pipe_reader(p::ProcessChain) = p.out pipe_writer(p::ProcessChain) = p.in +# a lightweight pair of a child OS_HANDLE and associated Task that will +# complete only after all content has been read from it for synchronizing +# state without the kernel to aide +struct SyncCloseFD + fd + t::Task +end +rawhandle(io::SyncCloseFD) = rawhandle(io.fd) + # release ownership of the libuv handle function uvfinalize(proc::Process) if proc.handle != C_NULL @@ -74,8 +84,8 @@ function _uv_hook_close(proc::Process) nothing end -const SpawnIO = Union{IO, RawFD, OS_HANDLE} -const SpawnIOs = Vector{SpawnIO} # convenience name for readability +const SpawnIO = Union{IO, RawFD, OS_HANDLE, SyncCloseFD} # internal copy of Redirectable, removing FileRedirect and adding SyncCloseFD +const SpawnIOs = Memory{SpawnIO} # convenience name for readability (used for dispatch also to clearly distinguish from Vector{Redirectable}) function as_cpumask(cpus::Vector{UInt16}) n = max(Int(maximum(cpus)), Int(ccall(:uv_cpumask_size, Cint, ()))) @@ -100,9 +110,11 @@ end error("invalid spawn handle $h from $io") end for io in stdio] + syncd = Task[io.t for io in stdio if io isa SyncCloseFD] handle = Libc.malloc(_sizeof_uv_process) disassociate_julia_struct(handle) (; exec, flags, env, dir) = cmd + flags ⊻= UV_PROCESS_WINDOWS_DISABLE_EXACT_NAME # libuv inverts the default for this, so flip this bit now iolock_begin() err = ccall(:jl_spawn, Int32, (Cstring, Ptr{Cstring}, Ptr{Cvoid}, Ptr{Cvoid}, @@ -117,7 +129,7 @@ end cpumask === nothing ? 0 : length(cpumask), @cfunction(uv_return_spawn, Cvoid, (Ptr{Cvoid}, Int64, Int32))) if err == 0 - pp = Process(cmd, handle) + pp = Process(cmd, handle, syncd) associate_julia_struct(handle, pp) else ccall(:jl_forceclose_uv, Cvoid, (Ptr{Cvoid},), handle) # will call free on handle eventually @@ -130,23 +142,24 @@ end return pp end -_spawn(cmds::AbstractCmd) = _spawn(cmds, SpawnIO[]) +_spawn(cmds::AbstractCmd) = _spawn(cmds, SpawnIOs()) -# optimization: we can spawn `Cmd` directly without allocating the ProcessChain -function _spawn(cmd::Cmd, stdios::SpawnIOs) - isempty(cmd.exec) && throw(ArgumentError("cannot spawn empty command")) +function _spawn(cmd::AbstractCmd, stdios::Vector{Redirectable}) pp = setup_stdios(stdios) do stdios - return _spawn_primitive(cmd.exec[1], cmd, stdios) + return _spawn(cmd, stdios) end return pp end +# optimization: we can spawn `Cmd` directly without allocating the ProcessChain +function _spawn(cmd::Cmd, stdios::SpawnIOs) + isempty(cmd.exec) && throw(ArgumentError("cannot spawn empty command")) + return _spawn_primitive(cmd.exec[1], cmd, stdios) +end + # assume that having a ProcessChain means that the stdio are setup function _spawn(cmds::AbstractCmd, stdios::SpawnIOs) - pp = setup_stdios(stdios) do stdios - return _spawn(cmds, stdios, ProcessChain()) - end - return pp + return _spawn(cmds, stdios, ProcessChain()) end # helper function for making a copy of a SpawnIOs, with replacement @@ -212,7 +225,7 @@ end # open the child end of each element of `stdios`, and initialize the parent end -function setup_stdios(f, stdios::SpawnIOs) +function setup_stdios(f, stdios::Vector{Redirectable}) nstdio = length(stdios) open_io = SpawnIOs(undef, nstdio) close_io = falses(nstdio) @@ -295,25 +308,26 @@ function setup_stdio(stdio::IO, child_readable::Bool) child = child_readable ? rd : wr try let in = (child_readable ? parent : stdio), - out = (child_readable ? stdio : parent) - @async try + out = (child_readable ? stdio : parent), + t = @async try write(in, out) catch ex @warn "Process I/O error" exception=(ex, catch_backtrace()) + rethrow() finally close(parent) - child_readable || closewrite(stdio) end + return (SyncCloseFD(child, t), true) end catch close_pipe_sync(child) rethrow() end - return (child, true) end -close_stdio(stdio::OS_HANDLE) = close_pipe_sync(stdio) close_stdio(stdio) = close(stdio) +close_stdio(stdio::OS_HANDLE) = close_pipe_sync(stdio) +close_stdio(stdio::SyncCloseFD) = close_stdio(stdio.fd) # INTERNAL # pad out stdio to have at least three elements, @@ -325,19 +339,19 @@ close_stdio(stdio) = close(stdio) # - An Filesystem.File or IOStream object to redirect the output to # - A FileRedirect, containing a string specifying a filename to be opened for the child -spawn_opts_swallow(stdios::StdIOSet) = SpawnIO[stdios...] -spawn_opts_inherit(stdios::StdIOSet) = SpawnIO[stdios...] +spawn_opts_swallow(stdios::StdIOSet) = Redirectable[stdios...] +spawn_opts_inherit(stdios::StdIOSet) = Redirectable[stdios...] spawn_opts_swallow(in::Redirectable=devnull, out::Redirectable=devnull, err::Redirectable=devnull) = - SpawnIO[in, out, err] + Redirectable[in, out, err] # pass original descriptors to child processes by default, because we might # have already exhausted and closed the libuv object for our standard streams. # ref issue #8529 spawn_opts_inherit(in::Redirectable=RawFD(0), out::Redirectable=RawFD(1), err::Redirectable=RawFD(2)) = - SpawnIO[in, out, err] + Redirectable[in, out, err] function eachline(cmd::AbstractCmd; keep::Bool=false) out = PipeEndpoint() - processes = _spawn(cmd, SpawnIO[devnull, out, stderr]) + processes = _spawn(cmd, Redirectable[devnull, out, stderr]) # if the user consumes all the data, also check process exit status for success ondone = () -> (success(processes) || pipeline_error(processes); nothing) return EachLine(out, keep=keep, ondone=ondone)::EachLine @@ -385,20 +399,20 @@ function open(cmds::AbstractCmd, stdio::Redirectable=devnull; write::Bool=false, stdio === devnull || throw(ArgumentError("no stream can be specified for `stdio` in read-write mode")) in = PipeEndpoint() out = PipeEndpoint() - processes = _spawn(cmds, SpawnIO[in, out, stderr]) + processes = _spawn(cmds, Redirectable[in, out, stderr]) processes.in = in processes.out = out elseif read out = PipeEndpoint() - processes = _spawn(cmds, SpawnIO[stdio, out, stderr]) + processes = _spawn(cmds, Redirectable[stdio, out, stderr]) processes.out = out elseif write in = PipeEndpoint() - processes = _spawn(cmds, SpawnIO[in, stdio, stderr]) + processes = _spawn(cmds, Redirectable[in, stdio, stderr]) processes.in = in else stdio === devnull || throw(ArgumentError("no stream can be specified for `stdio` in no-access mode")) - processes = _spawn(cmds, SpawnIO[devnull, devnull, stderr]) + processes = _spawn(cmds, Redirectable[devnull, devnull, stderr]) end return processes end @@ -415,12 +429,18 @@ function open(f::Function, cmds::AbstractCmd, args...; kwargs...) P = open(cmds, args...; kwargs...) function waitkill(P::Union{Process,ProcessChain}) close(P) - # 0.1 seconds after we hope it dies (from closing stdio), - # we kill the process with SIGTERM (15) - local t = Timer(0.1) do t + # shortly after we hope it starts cleanup and dies (from closing + # stdio), we kill the process with SIGTERM (15) so that we can proceed + # with throwing the error and hope it will exit soon from that + local t = Timer(2) do t process_running(P) && kill(P) end - wait(P) + # pass false to indicate that we do not care about data-races on the + # Julia stdio objects after this point, since we already know this is + # an error path and the state of them is fairly unpredictable anyways + # in that case. Since we closed P some of those should come crumbling + # down already, and we don't want to throw that error here either. + wait(P, false) close(t) end ret = try @@ -430,10 +450,23 @@ function open(f::Function, cmds::AbstractCmd, args...; kwargs...) rethrow() end close(P.in) + closestdio = @async begin + # wait for P to complete (including sync'd), then mark the output streams for EOF (if applicable to that stream type) + wait(P) + err = P.err + applicable(closewrite, err) && closewrite(err) + out = P.out + applicable(closewrite, out) && closewrite(out) + nothing + end + # now verify that the output stream is at EOF, and the user didn't fail to consume it successfully + # (we do not currently verify the user dealt with the stderr stream) if !(eof(P.out)::Bool) waitkill(P) throw(_UVError("open(do)", UV_EPIPE)) end + # make sure to closestdio is completely done to avoid data-races later + wait(closestdio) success(P) || pipeline_error(P) return ret end @@ -650,26 +683,31 @@ function process_status(s::Process) error("process status error") end -function wait(x::Process) - process_exited(x) && return - iolock_begin() +function wait(x::Process, syncd::Bool=true) if !process_exited(x) - preserve_handle(x) - lock(x.exitnotify) - iolock_end() - try - wait(x.exitnotify) - finally - unlock(x.exitnotify) - unpreserve_handle(x) + iolock_begin() + if !process_exited(x) + preserve_handle(x) + lock(x.exitnotify) + iolock_end() + try + wait(x.exitnotify) + finally + unlock(x.exitnotify) + unpreserve_handle(x) + end + else + iolock_end() end - else - iolock_end() + end + # and make sure all sync'd Tasks are complete too + syncd && for t in x.syncd + wait(t) end nothing end -wait(x::ProcessChain) = foreach(wait, x.processes) +wait(x::ProcessChain, syncd::Bool=true) = foreach(p -> wait(p, syncd), x.processes) show(io::IO, p::Process) = print(io, "Process(", p.cmd, ", ", process_status(p), ")") diff --git a/base/promotion.jl b/base/promotion.jl index bc081b15c7a31..689a4e4be8f39 100644 --- a/base/promotion.jl +++ b/base/promotion.jl @@ -19,7 +19,8 @@ Number """ typejoin() = Bottom typejoin(@nospecialize(t)) = (@_nospecializeinfer_meta; t) -typejoin(@nospecialize(t), ts...) = (@_foldable_meta; @_nospecializeinfer_meta; typejoin(t, typejoin(ts...))) +typejoin(@nospecialize(t), @nospecialize(s), @nospecialize(u)) = (@_foldable_meta; @_nospecializeinfer_meta; typejoin(typejoin(t, s), u)) +typejoin(@nospecialize(t), @nospecialize(s), @nospecialize(u), ts...) = (@_foldable_meta; @_nospecializeinfer_meta; afoldl(typejoin, typejoin(t, s, u), ts...)) function typejoin(@nospecialize(a), @nospecialize(b)) @_foldable_meta @_nospecializeinfer_meta @@ -120,7 +121,7 @@ function typejoin(@nospecialize(a), @nospecialize(b)) aprimary = aprimary::UnionAll # pushfirst!(vars, aprimary.var) _growbeg!(vars, 1) - arrayset(false, vars, aprimary.var, 1) + vars[1] = aprimary.var aprimary = aprimary.body end end @@ -299,7 +300,8 @@ function promote_type end promote_type() = Bottom promote_type(T) = T -promote_type(T, S, U, V...) = (@inline; promote_type(T, promote_type(S, U, V...))) +promote_type(T, S, U) = (@inline; promote_type(promote_type(T, S), U)) +promote_type(T, S, U, V...) = (@inline; afoldl(promote_type, promote_type(T, S, U), V...)) promote_type(::Type{Bottom}, ::Type{Bottom}) = Bottom promote_type(::Type{T}, ::Type{T}) where {T} = T @@ -373,7 +375,9 @@ function _promote(x::T, y::S) where {T,S} return (convert(R, x), convert(R, y)) end promote_typeof(x) = typeof(x) -promote_typeof(x, xs...) = (@inline; promote_type(typeof(x), promote_typeof(xs...))) +promote_typeof(x, y) = (@inline; promote_type(typeof(x), typeof(y))) +promote_typeof(x, y, z) = (@inline; promote_type(typeof(x), typeof(y), typeof(z))) +promote_typeof(x, y, z, a...) = (@inline; afoldl(((::Type{T}, y) where {T}) -> promote_type(T, typeof(y)), promote_typeof(x, y, z), a...)) function _promote(x, y, z) @inline R = promote_typeof(x, y, z) @@ -430,7 +434,11 @@ end """ ^(x, y) -Exponentiation operator. If `x` is a matrix, computes matrix exponentiation. +Exponentiation operator. + +If `x` and `y` are integers, the result may overflow. +To enter numbers in scientific notation, use [`Float64`](@ref) literals +such as `1.2e3` rather than `1.2 * 10^3`. If `y` is an `Int` literal (e.g. `2` in `x^2` or `-3` in `x^-3`), the Julia code `x^y` is transformed by the compiler to `Base.literal_pow(^, x, Val(y))`, to @@ -440,20 +448,31 @@ where usually `^ == Base.^` unless `^` has been defined in the calling namespace.) If `y` is a negative integer literal, then `Base.literal_pow` transforms the operation to `inv(x)^-y` by default, where `-y` is positive. +See also [`exp2`](@ref), [`<<`](@ref). + # Examples ```jldoctest julia> 3^5 243 -julia> A = [1 2; 3 4] -2×2 Matrix{Int64}: - 1 2 - 3 4 +julia> 3^-1 # uses Base.literal_pow +0.3333333333333333 + +julia> p = -1; + +julia> 3^p +ERROR: DomainError with -1: +Cannot raise an integer x to a negative power -1. +[...] -julia> A^3 -2×2 Matrix{Int64}: - 37 54 - 81 118 +julia> 3.0^p +0.3333333333333333 + +julia> 10^19 > 0 # integer overflow +false + +julia> big(10)^19 == 1e19 +true ``` """ ^(x::Number, y::Number) = ^(promote(x,y)...) @@ -492,10 +511,106 @@ end Guess what an appropriate container eltype would be for storing results of `f(::argtypes...)`. The guess is in part based on type inference, so can change any time. +Accordingly, return a type `R` such that `f(args...) isa R` where `args isa T`. + !!! warning Due to its fragility, use of `promote_op` should be avoided. It is preferable to base the container eltype on the type of the actual elements. Only in the absence of any elements (for an empty result container), it may be unavoidable to call `promote_op`. + +The type `R` obtained from `promote_op` is merely an upper bound. There may exist a stricter +type `S` such that `f(args...) isa S` for every `args isa T` with `S <: R` and `S != R`. +Furthermore, the exact type `R` obtained from `promote_op` depends on various factors +including but not limited to the exact Julia version used, packages loaded, and command line +options. As such, when used in publicly registered packages, **it is the package authors' +responsibility to ensure that the API guarantees provided by the package do not depend on +the exact type `R` obtained from `promote_op`.** + +Additionally, the result may return overly exact types, such as `DataType`, `Type`, or +`Union{...}`, while the desired inputs or outputs may be different from those. The internal +`promote_typejoin_union` function may be helpful to improve the result in some of these +cases. + +# Extended help + +## Examples + +The following function is an invalid use-case of `promote_op`. + +```julia +\""" + invalid_usecase1(f, xs::AbstractArray) -> ys::Array + +Return an array `ys` such that `vec(ys)` is `isequal`-equivalent to + + [f(xs[1]), f(xs[2]), ..., f(xs[end])] +\""" +function invalid_usecase1(f, xs) + R = promote_op(f, eltype(xs)) + ys = similar(xs, R) + for i in eachindex(xs, ys) + ys[i] = f(xs[i]) + end + return ys +end +``` + +This is because the value obtained through `eltype(invalid_usecase1(f, xs))` depends on +exactly what `promote_op` returns. It may be improved by re-computing the element type +before returning the result. + +```julia +function valid_usecase1(f, xs) + R = promote_typejoin_union(promote_op(f, eltype(xs))) + ys = similar(xs, R) + S = Union{} + for i in eachindex(xs, ys) + ys[i] = f(xs[i]) + S = promote_type(S, typeof(ys[i])) + end + if S != R + zs = similar(xs, S) + copyto!(zs, ys) + return zs + end + return ys +end +``` + +Note that using [`isconcretetype`](@ref) on the result is not enough to safely use +`promote_op`. The following function is another invalid use-case of `promote_op`. + +```julia +function invalid_usecase2(f, xs) + R = promote_op(f, eltype(xs)) + if isconcretetype(R) + ys = similar(xs, R) + else + ys = similar(xs, Any) + end + for i in eachindex(xs, ys) + ys[i] = f(xs[i]) + end + return ys +end +``` + +This is because whether or not the caller gets `Any` element type depends on if `promote_op` +can infer a concrete return type of the given function. A fix similar to `valid_usecase1` +can be used. + +*Technically*, another possible fix for `invalid_usecase1` and `invalid_usecase2` is to +loosen the API guarantee: + +> another_valid_usecase1(f, xs::AbstractArray) -> ys::Array +> +> Return an array `ys` such that every element in `xs` with the same index +> is mapped with `f`. +> +> The element type of `ys` is _undefined_. It must not be used with generic +> functions whose behavior depend on the element type of `ys`. + +However, it is discouraged to define such unconventional API guarantees. """ function promote_op(f, S::Type...) argT = TupleOrBottom(S...) diff --git a/base/public.jl b/base/public.jl new file mode 100644 index 0000000000000..912953795c801 --- /dev/null +++ b/base/public.jl @@ -0,0 +1,101 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +public +# Modules + Checked, + Filesystem, + Order, + Sort, + +# Types + AbstractLock, + AsyncCondition, + CodeUnits, + Event, + Fix1, + Fix2, + Generator, + ImmutableDict, + OneTo, + LogRange, + AnnotatedString, + AnnotatedChar, + UUID, + +# Annotated strings + annotatedstring, + annotate!, + annotations, + +# Semaphores + Semaphore, + acquire, + release, + +# collections + IteratorEltype, + IteratorSize, + to_index, + vect, + isdone, + front, + rest, + split_rest, + tail, + checked_length, + +# Loading + DL_LOAD_PATH, + load_path, + active_project, + +# Reflection and introspection + isambiguous, + isexpr, + isidentifier, + issingletontype, + identify_package, + locate_package, + moduleroot, + jit_total_bytes, + summarysize, + isexported, + ispublic, + remove_linenums!, + +# Opperators + operator_associativity, + operator_precedence, + isbinaryoperator, + isoperator, + isunaryoperator, + +# C interface + cconvert, + unsafe_convert, + +# Error handling + exit_on_sigint, + windowserror, + +# Macros + @assume_effects, + @constprop, + @locals, + @propagate_inbounds, + +# IO + # types + BufferStream, + IOServer, + OS_HANDLE, + PipeEndpoint, + TTY, + # functions + reseteof, + link_pipe!, + +# misc + notnothing, + runtests, + text_colors diff --git a/base/range.jl b/base/range.jl index 13a8bd2701ae5..b987e5f50c7f7 100644 --- a/base/range.jl +++ b/base/range.jl @@ -32,7 +32,7 @@ _colon(::Any, ::Any, start::T, step, stop::T) where {T} = (:)(start, [step], stop) Range operator. `a:b` constructs a range from `a` to `b` with a step size -equal to 1, which produces: +equal to +1, which produces: * a [`UnitRange`](@ref) when `a` and `b` are integers, or * a [`StepRange`](@ref) when `a` and `b` are characters, or @@ -41,6 +41,9 @@ equal to 1, which produces: `a:s:b` is similar but uses a step size of `s` (a [`StepRange`](@ref) or [`StepRangeLen`](@ref)). See also [`range`](@ref) for more control. +To create a descending range, use `reverse(a:b)` or a negative step size, e.g. `b:-1:a`. +Otherwise, when `b < a`, an empty range will be constructed and normalized to `a:a-1`. + The operator `:` is also used in indexing to select whole dimensions, e.g. in `A[:, 1]`. `:` is also used to [`quote`](@ref) code, e.g. `:(x + y) isa Expr` and `:x isa Symbol`. @@ -66,10 +69,15 @@ Mathematically a range is uniquely determined by any three of `start`, `step`, ` Valid invocations of range are: * Call `range` with any three of `start`, `step`, `stop`, `length`. * Call `range` with two of `start`, `stop`, `length`. In this case `step` will be assumed - to be one. If both arguments are Integers, a [`UnitRange`](@ref) will be returned. -* Call `range` with one of `stop` or `length`. `start` and `step` will be assumed to be one. + to be positive one. If both arguments are Integers, a [`UnitRange`](@ref) will be returned. +* Call `range` with one of `stop` or `length`. `start` and `step` will be assumed to be positive one. + +To construct a descending range, specify a negative step size, e.g. `range(5, 1; step = -1)` => [5,4,3,2,1]. Otherwise, +a `stop` value less than the `start` value, with the default `step` of `+1`, constructs an empty range. Empty ranges +are normalized such that the `stop` is one less than the `start`, e.g. `range(5, 1) == 5:4`. See Extended Help for additional details on the returned type. +See also [`logrange`](@ref) for logarithmically spaced points. # Examples ```jldoctest @@ -252,10 +260,13 @@ end ## 1-dimensional ranges ## """ - AbstractRange{T} + AbstractRange{T} <: AbstractVector{T} -Supertype for ranges with elements of type `T`. -[`UnitRange`](@ref) and other types are subtypes of this. +Supertype for linear ranges with elements of type `T`. +[`UnitRange`](@ref), [`LinRange`](@ref) and other types are subtypes of this. + +All subtypes must define [`step`](@ref). +Thus [`LogRange`](@ref Base.LogRange) is not a subtype of `AbstractRange`. """ abstract type AbstractRange{T} <: AbstractArray{T,1} end @@ -372,6 +383,7 @@ function steprange_last_empty(start::Integer, step, stop)::typeof(stop) end return last end +steprange_last_empty(start::Bool, step, stop) = start ⊻ (step > zero(step)) # isnegative(step) ? start : !start # For types where x+oneunit(x) may not be well-defined use the user-given value for stop steprange_last_empty(start, step, stop) = stop @@ -549,6 +561,8 @@ julia> collect(LinRange(-0.1, 0.3, 5)) 0.19999999999999998 0.3 ``` + +See also [`Logrange`](@ref Base.LogRange) for logarithmically spaced points. """ struct LinRange{T,L<:Integer} <: AbstractRange{T} start::T @@ -597,7 +611,7 @@ function show(io::IO, r::LinRange{T}) where {T} print(io, "LinRange{") show(io, T) print(io, "}(") - ioc = IOContext(io, :typeinto=>T) + ioc = IOContext(io, :typeinfo=>T) show(ioc, first(r)) print(io, ", ") show(ioc, last(r)) @@ -619,7 +633,7 @@ parameters `pre` and `post` characters for each printed row, `sep` separator string between printed elements, `hdots` string for the horizontal ellipsis. """ -function print_range(io::IO, r::AbstractRange, +function print_range(io::IO, r::AbstractArray, pre::AbstractString = " ", sep::AbstractString = ", ", post::AbstractString = "", @@ -987,13 +1001,14 @@ function getindex(r::AbstractUnitRange, s::StepRange{T}) where {T<:Integer} @boundscheck checkbounds(r, s) if T === Bool - return range(first(s) ? first(r) : last(r), step=oneunit(eltype(r)), length=last(s)) + len = Int(last(s)) + return range(first(s) ? first(r) : last(r), step=oneunit(eltype(r)), length=len) else f = first(r) start = oftype(f, f + s.start - firstindex(r)) st = step(s) len = length(s) - stop = oftype(f, start + (len - oneunit(len)) * st) + stop = oftype(f, start + (len - oneunit(len)) * (iszero(len) ? copysign(oneunit(st), st) : st)) return range(start, stop; step=st) end end @@ -1003,26 +1018,22 @@ function getindex(r::StepRange, s::AbstractRange{T}) where {T<:Integer} @boundscheck checkbounds(r, s) if T === Bool - if length(s) == 0 - start, len = first(r), 0 - elseif length(s) == 1 - if first(s) - start, len = first(r), 1 - else - start, len = first(r), 0 - end - else # length(s) == 2 - start, len = last(r), 1 - end - return range(start, step=step(r); length=len) + # treat as a zero, one, or two-element vector, where at most one element is true + # staying inbounds on the original range (preserving either start or + # stop as either stop or start, depending on the length) + st = step(s) + nonempty = st > zero(st) ? last(s) : first(s) + # n.b. isempty(r) implies isempty(r) which means !nonempty and !first(s) + range((first(s) ⊻ nonempty) ⊻ isempty(r) ? last(r) : first(r), step=step(r), length=Int(nonempty)) else f = r.start fs = first(s) st = r.step - start = oftype(f, f + (fs - oneunit(fs)) * st) - st = st * step(s) + start = oftype(f, f + (fs - firstindex(r)) * st) + st *= step(s) len = length(s) - stop = oftype(f, start + (len - oneunit(len)) * st) + # mimic steprange_last_empty here, to try to avoid overflow + stop = oftype(f, start + (len - oneunit(len)) * (iszero(len) ? copysign(oneunit(st), st) : st)) return range(start, stop; step=st) end end @@ -1395,8 +1406,8 @@ sort!(r::AbstractUnitRange) = r sort(r::AbstractRange) = issorted(r) ? r : reverse(r) -sortperm(r::AbstractUnitRange) = 1:length(r) -sortperm(r::AbstractRange) = issorted(r) ? (1:1:length(r)) : (length(r):-1:1) +sortperm(r::AbstractUnitRange) = eachindex(r) +sortperm(r::AbstractRange) = issorted(r) ? (firstindex(r):1:lastindex(r)) : (lastindex(r):-1:firstindex(r)) function sum(r::AbstractRange{<:Real}) l = length(r) @@ -1490,3 +1501,179 @@ julia> mod(3, 0:2) # mod(3, 3) """ mod(i::Integer, r::OneTo) = mod1(i, last(r)) mod(i::Integer, r::AbstractUnitRange{<:Integer}) = mod(i-first(r), length(r)) + first(r) + + +""" + logrange(start, stop, length) + logrange(start, stop; length) + +Construct a specialized array whose elements are spaced logarithmically +between the given endpoints. That is, the ratio of successive elements is +a constant, calculated from the length. + +This is similar to `geomspace` in Python. Unlike `PowerRange` in Mathematica, +you specify the number of elements not the ratio. +Unlike `logspace` in Python and Matlab, the `start` and `stop` arguments are +always the first and last elements of the result, not powers applied to some base. + +# Examples +```jldoctest +julia> logrange(10, 4000, length=3) +3-element Base.LogRange{Float64, Base.TwicePrecision{Float64}}: + 10.0, 200.0, 4000.0 + +julia> ans[2] ≈ sqrt(10 * 4000) # middle element is the geometric mean +true + +julia> range(10, 40, length=3)[2] ≈ (10 + 40)/2 # arithmetic mean +true + +julia> logrange(1f0, 32f0, 11) +11-element Base.LogRange{Float32, Float64}: + 1.0, 1.41421, 2.0, 2.82843, 4.0, 5.65685, 8.0, 11.3137, 16.0, 22.6274, 32.0 + +julia> logrange(1, 1000, length=4) ≈ 10 .^ (0:3) +true +``` + +See the [`LogRange`](@ref Base.LogRange) type for further details. + +See also [`range`](@ref) for linearly spaced points. + +!!! compat "Julia 1.11" + This function requires at least Julia 1.11. +""" +logrange(start::Real, stop::Real, length::Integer) = LogRange(start, stop, Int(length)) +logrange(start::Real, stop::Real; length::Integer) = logrange(start, stop, length) + + +""" + LogRange{T}(start, stop, len) <: AbstractVector{T} + +A range whose elements are spaced logarithmically between `start` and `stop`, +with spacing controlled by `len`. Returned by [`logrange`](@ref). + +Like [`LinRange`](@ref), the first and last elements will be exactly those +provided, but intermediate values may have small floating-point errors. +These are calculated using the logs of the endpoints, which are +stored on construction, often in higher precision than `T`. + +# Examples +```jldoctest +julia> logrange(1, 4, length=5) +5-element Base.LogRange{Float64, Base.TwicePrecision{Float64}}: + 1.0, 1.41421, 2.0, 2.82843, 4.0 + +julia> Base.LogRange{Float16}(1, 4, 5) +5-element Base.LogRange{Float16, Float64}: + 1.0, 1.414, 2.0, 2.828, 4.0 + +julia> logrange(1e-310, 1e-300, 11)[1:2:end] +6-element Vector{Float64}: + 1.0e-310 + 9.999999999999974e-309 + 9.999999999999981e-307 + 9.999999999999988e-305 + 9.999999999999994e-303 + 1.0e-300 + +julia> prevfloat(1e-308, 5) == ans[2] +true +``` + +Note that integer eltype `T` is not allowed. +Use for instance `round.(Int, xs)`, or explicit powers of some integer base: + +```jldoctest +julia> xs = logrange(1, 512, 4) +4-element Base.LogRange{Float64, Base.TwicePrecision{Float64}}: + 1.0, 8.0, 64.0, 512.0 + +julia> 2 .^ (0:3:9) |> println +[1, 8, 64, 512] +``` + +!!! compat "Julia 1.11" + This type requires at least Julia 1.11. +""" +struct LogRange{T<:Real,X} <: AbstractArray{T,1} + start::T + stop::T + len::Int + extra::Tuple{X,X} + function LogRange{T}(start::T, stop::T, len::Int) where {T<:Real} + if T <: Integer + # LogRange{Int}(1, 512, 4) produces InexactError: Int64(7.999999999999998) + throw(ArgumentError("LogRange{T} does not support integer types")) + end + if iszero(start) || iszero(stop) + throw(DomainError((start, stop), + "LogRange cannot start or stop at zero")) + elseif start < 0 || stop < 0 + # log would throw, but _log_twice64_unchecked does not + throw(DomainError((start, stop), + "LogRange does not accept negative numbers")) + elseif !isfinite(start) || !isfinite(stop) + throw(DomainError((start, stop), + "LogRange is only defined for finite start & stop")) + elseif len < 0 + throw(ArgumentError(LazyString( + "LogRange(", start, ", ", stop, ", ", len, "): can't have negative length"))) + elseif len == 1 && start != stop + throw(ArgumentError(LazyString( + "LogRange(", start, ", ", stop, ", ", len, "): endpoints differ, while length is 1"))) + end + ex = _logrange_extra(start, stop, len) + new{T,typeof(ex[1])}(start, stop, len, ex) + end +end + +function LogRange{T}(start::Real, stop::Real, len::Integer) where {T} + LogRange{T}(convert(T, start), convert(T, stop), convert(Int, len)) +end +function LogRange(start::Real, stop::Real, len::Integer) + T = float(promote_type(typeof(start), typeof(stop))) + LogRange{T}(convert(T, start), convert(T, stop), convert(Int, len)) +end + +size(r::LogRange) = (r.len,) +length(r::LogRange) = r.len + +first(r::LogRange) = r.start +last(r::LogRange) = r.stop + +function _logrange_extra(a::Real, b::Real, len::Int) + loga = log(1.0 * a) # widen to at least Float64 + logb = log(1.0 * b) + (loga/(len-1), logb/(len-1)) +end +function _logrange_extra(a::Float64, b::Float64, len::Int) + loga = _log_twice64_unchecked(a) + logb = _log_twice64_unchecked(b) + # The reason not to do linear interpolation on log(a)..log(b) in `getindex` is + # that division of TwicePrecision is quite slow, so do it once on construction: + (loga/(len-1), logb/(len-1)) +end + +function getindex(r::LogRange{T}, i::Int) where {T} + @inline + @boundscheck checkbounds(r, i) + i == 1 && return r.start + i == r.len && return r.stop + # Main path uses Math.exp_impl for TwicePrecision, but is not perfectly + # accurate, hence the special cases for endpoints above. + logx = (r.len-i) * r.extra[1] + (i-1) * r.extra[2] + x = _exp_allowing_twice64(logx) + return T(x) +end + +function show(io::IO, r::LogRange{T}) where {T} + print(io, "LogRange{", T, "}(") + ioc = IOContext(io, :typeinfo => T) + show(ioc, first(r)) + print(io, ", ") + show(ioc, last(r)) + print(io, ", ") + show(io, length(r)) + print(io, ')') +end diff --git a/base/rational.jl b/base/rational.jl index cfa1e257bc539..bd1633bd3dd28 100644 --- a/base/rational.jl +++ b/base/rational.jl @@ -17,12 +17,10 @@ end unsafe_rational(num::T, den::T) where {T<:Integer} = unsafe_rational(T, num, den) unsafe_rational(num::Integer, den::Integer) = unsafe_rational(promote(num, den)...) -@noinline __throw_rational_argerror_typemin(T) = throw(ArgumentError("invalid rational: denominator can't be typemin($T)")) function checked_den(::Type{T}, num::T, den::T) where T<:Integer if signbit(den) - den = -den - signbit(den) && __throw_rational_argerror_typemin(typeof(den)) - num = -num + den = checked_neg(den) + num = checked_neg(num) end return unsafe_rational(T, num, den) end @@ -32,16 +30,27 @@ checked_den(num::Integer, den::Integer) = checked_den(promote(num, den)...) @noinline __throw_rational_argerror_zero(T) = throw(ArgumentError("invalid rational: zero($T)//zero($T)")) function Rational{T}(num::Integer, den::Integer) where T<:Integer iszero(den) && iszero(num) && __throw_rational_argerror_zero(T) - num, den = divgcd(num, den) - return checked_den(T, T(num), T(den)) + if T <: Union{Unsigned, Bool} + # Throw InexactError if the result is negative. + if !iszero(num) && (signbit(den) ⊻ signbit(num)) + throw(InexactError(:Rational, Rational{T}, num, den)) + end + unum = uabs(num) + uden = uabs(den) + r_unum, r_uden = divgcd(unum, uden) + return unsafe_rational(T, promote(T(r_unum), T(r_uden))...) + else + r_num, r_den = divgcd(num, den) + return checked_den(T, promote(T(r_num), T(r_den))...) + end end Rational(n::T, d::T) where {T<:Integer} = Rational{T}(n, d) Rational(n::Integer, d::Integer) = Rational(promote(n, d)...) Rational(n::Integer) = unsafe_rational(n, one(n)) -function divgcd(x::Integer,y::Integer) - g = gcd(x,y) +function divgcd(x::TX, y::TY)::Tuple{TX, TY} where {TX<:Integer, TY<:Integer} + g = gcd(uabs(x), uabs(y)) div(x,g), div(y,g) end @@ -49,6 +58,12 @@ end //(num, den) Divide two integers or rational numbers, giving a [`Rational`](@ref) result. +More generally, `//` can be used for exact rational division of other numeric types +with integer or rational components, such as complex numbers with integer components. + +Note that floating-point ([`AbstractFloat`](@ref)) arguments are not permitted by `//` +(even if the values are rational). +The arguments must be subtypes of [`Integer`](@ref), `Rational`, or composites thereof. # Examples ```jldoctest @@ -57,6 +72,13 @@ julia> 3 // 5 julia> (3 // 5) // (2 // 1) 3//10 + +julia> (1+2im) // (3+4im) +11//25 + 2//25*im + +julia> 1.0 // 2 +ERROR: MethodError: no method matching //(::Float64, ::Int64) +[...] ``` """ //(n::Integer, d::Integer) = Rational(n,d) @@ -84,7 +106,7 @@ end function show(io::IO, x::Rational) show(io, numerator(x)) - if isone(denominator(x)) && get(io, :typeinfo, Any) <: Rational + if isone(denominator(x)) && nonnothing_nonmissing_typeinfo(io) <: Rational return end @@ -119,7 +141,7 @@ function Rational{T}(x::Rational) where T<:Integer unsafe_rational(T, convert(T, x.num), convert(T, x.den)) end function Rational{T}(x::Integer) where T<:Integer - unsafe_rational(T, convert(T, x), one(T)) + unsafe_rational(T, T(x), T(one(x))) end Rational(x::Rational) = x @@ -134,6 +156,14 @@ function (::Type{T})(x::Rational{S}) where T<:AbstractFloat where S P = promote_type(T,S) convert(T, convert(P,x.num)/convert(P,x.den))::T end + # avoid spurious overflow (#52394). (Needed for UInt16 or larger; + # we also include Int16 for consistency of accuracy.) +Float16(x::Rational{<:Union{Int16,Int32,Int64,UInt16,UInt32,UInt64}}) = + Float16(Float32(x)) +Float16(x::Rational{<:Union{Int128,UInt128}}) = + Float16(Float64(x)) # UInt128 overflows Float32, include Int128 for consistency +Float32(x::Rational{<:Union{Int128,UInt128}}) = + Float32(Float64(x)) # UInt128 overflows Float32, include Int128 for consistency function Rational{T}(x::AbstractFloat) where T<:Integer r = rationalize(T, x, tol=0) @@ -234,7 +264,7 @@ function rationalize(::Type{T}, x::Union{AbstractFloat, Rational}, tol::Real) wh end end rationalize(::Type{T}, x::AbstractFloat; tol::Real = eps(x)) where {T<:Integer} = rationalize(T, x, tol) -rationalize(x::AbstractFloat; kvs...) = rationalize(Int, x; kvs...) +rationalize(x::Real; kvs...) = rationalize(Int, x; kvs...) rationalize(::Type{T}, x::Complex; kvs...) where {T<:Integer} = Complex(rationalize(T, x.re; kvs...), rationalize(T, x.im; kvs...)) rationalize(x::Complex; kvs...) = Complex(rationalize(Int, x.re; kvs...), rationalize(Int, x.im; kvs...)) rationalize(::Type{T}, x::Rational; tol::Real = 0) where {T<:Integer} = rationalize(T, x, tol) @@ -285,8 +315,6 @@ denominator(x::Rational) = x.den sign(x::Rational) = oftype(x, sign(x.num)) signbit(x::Rational) = signbit(x.num) -copysign(x::Rational, y::Real) = unsafe_rational(copysign(x.num, y), x.den) -copysign(x::Rational, y::Rational) = unsafe_rational(copysign(x.num, y.num), x.den) abs(x::Rational) = unsafe_rational(checked_abs(x.num), x.den) diff --git a/base/reduce.jl b/base/reduce.jl index d98b237e4997c..c4020faf4c85f 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -4,14 +4,6 @@ ###### Generic (map)reduce functions ###### -if Int === Int32 - const SmallSigned = Union{Int8,Int16} - const SmallUnsigned = Union{UInt8,UInt16} -else - const SmallSigned = Union{Int8,Int16,Int32} - const SmallUnsigned = Union{UInt8,UInt16,UInt32} -end - abstract type AbstractBroadcasted end const AbstractArrayOrBroadcasted = Union{AbstractArray, AbstractBroadcasted} @@ -22,8 +14,8 @@ The reduction operator used in `sum`. The main difference from [`+`](@ref) is th integers are promoted to `Int`/`UInt`. """ add_sum(x, y) = x + y -add_sum(x::SmallSigned, y::SmallSigned) = Int(x) + Int(y) -add_sum(x::SmallUnsigned, y::SmallUnsigned) = UInt(x) + UInt(y) +add_sum(x::BitSignedSmall, y::BitSignedSmall) = Int(x) + Int(y) +add_sum(x::BitUnsignedSmall, y::BitUnsignedSmall) = UInt(x) + UInt(y) add_sum(x::Real, y::Real)::Real = x + y """ @@ -33,8 +25,8 @@ The reduction operator used in `prod`. The main difference from [`*`](@ref) is t integers are promoted to `Int`/`UInt`. """ mul_prod(x, y) = x * y -mul_prod(x::SmallSigned, y::SmallSigned) = Int(x) * Int(y) -mul_prod(x::SmallUnsigned, y::SmallUnsigned) = UInt(x) * UInt(y) +mul_prod(x::BitSignedSmall, y::BitSignedSmall) = Int(x) * Int(y) +mul_prod(x::BitUnsignedSmall, y::BitUnsignedSmall) = UInt(x) * UInt(y) mul_prod(x::Real, y::Real)::Real = x * y ## foldl && mapfoldl @@ -316,10 +308,11 @@ pairwise_blocksize(::typeof(abs2), ::typeof(+)) = 4096 # handling empty arrays -_empty_reduce_error() = throw(ArgumentError("reducing over an empty collection is not allowed")) -_empty_reduce_error(@nospecialize(f), @nospecialize(T::Type)) = throw(ArgumentError(""" - reducing with $f over an empty collection of element type $T is not allowed. - You may be able to prevent this error by supplying an `init` value to the reducer.""")) +_empty_reduce_error() = throw(ArgumentError("reducing over an empty collection is not allowed; consider supplying `init` to the reducer")) +reduce_empty(f, T) = _empty_reduce_error() +mapreduce_empty(f, op, T) = _empty_reduce_error() +reduce_empty(f, ::Type{Union{}}, splat...) = _empty_reduce_error() +mapreduce_empty(f, op, ::Type{Union{}}, splat...) = _empty_reduce_error() """ Base.reduce_empty(op, T) @@ -339,23 +332,19 @@ is generally ambiguous, and especially so when the element type is unknown). As an alternative, consider supplying an `init` value to the reducer. """ -reduce_empty(::typeof(+), ::Type{Union{}}) = _empty_reduce_error(+, Union{}) reduce_empty(::typeof(+), ::Type{T}) where {T} = zero(T) reduce_empty(::typeof(+), ::Type{Bool}) = zero(Int) -reduce_empty(::typeof(*), ::Type{Union{}}) = _empty_reduce_error(*, Union{}) reduce_empty(::typeof(*), ::Type{T}) where {T} = one(T) reduce_empty(::typeof(*), ::Type{<:AbstractChar}) = "" reduce_empty(::typeof(&), ::Type{Bool}) = true reduce_empty(::typeof(|), ::Type{Bool}) = false -reduce_empty(::typeof(add_sum), ::Type{Union{}}) = _empty_reduce_error(add_sum, Union{}) reduce_empty(::typeof(add_sum), ::Type{T}) where {T} = reduce_empty(+, T) -reduce_empty(::typeof(add_sum), ::Type{T}) where {T<:SmallSigned} = zero(Int) -reduce_empty(::typeof(add_sum), ::Type{T}) where {T<:SmallUnsigned} = zero(UInt) -reduce_empty(::typeof(mul_prod), ::Type{Union{}}) = _empty_reduce_error(mul_prod, Union{}) +reduce_empty(::typeof(add_sum), ::Type{T}) where {T<:BitSignedSmall} = zero(Int) +reduce_empty(::typeof(add_sum), ::Type{T}) where {T<:BitUnsignedSmall} = zero(UInt) reduce_empty(::typeof(mul_prod), ::Type{T}) where {T} = reduce_empty(*, T) -reduce_empty(::typeof(mul_prod), ::Type{T}) where {T<:SmallSigned} = one(Int) -reduce_empty(::typeof(mul_prod), ::Type{T}) where {T<:SmallUnsigned} = one(UInt) +reduce_empty(::typeof(mul_prod), ::Type{T}) where {T<:BitSignedSmall} = one(Int) +reduce_empty(::typeof(mul_prod), ::Type{T}) where {T<:BitUnsignedSmall} = one(UInt) reduce_empty(op::BottomRF, ::Type{T}) where {T} = reduce_empty(op.rf, T) reduce_empty(op::MappingRF, ::Type{T}) where {T} = mapreduce_empty(op.f, op.rf, T) @@ -405,11 +394,11 @@ reduce_first(::typeof(+), x::Bool) = Int(x) reduce_first(::typeof(*), x::AbstractChar) = string(x) reduce_first(::typeof(add_sum), x) = reduce_first(+, x) -reduce_first(::typeof(add_sum), x::SmallSigned) = Int(x) -reduce_first(::typeof(add_sum), x::SmallUnsigned) = UInt(x) +reduce_first(::typeof(add_sum), x::BitSignedSmall) = Int(x) +reduce_first(::typeof(add_sum), x::BitUnsignedSmall) = UInt(x) reduce_first(::typeof(mul_prod), x) = reduce_first(*, x) -reduce_first(::typeof(mul_prod), x::SmallSigned) = Int(x) -reduce_first(::typeof(mul_prod), x::SmallUnsigned) = UInt(x) +reduce_first(::typeof(mul_prod), x::BitSignedSmall) = Int(x) +reduce_first(::typeof(mul_prod), x::BitUnsignedSmall) = UInt(x) """ Base.mapreduce_first(f, op, x) @@ -753,7 +742,7 @@ julia> maximum([1,2,3]) 3 julia> maximum(()) -ERROR: MethodError: reducing over an empty collection is not allowed; consider supplying `init` to the reducer +ERROR: ArgumentError: reducing over an empty collection is not allowed; consider supplying `init` to the reducer Stacktrace: [...] @@ -785,7 +774,7 @@ julia> minimum([1,2,3]) 1 julia> minimum([]) -ERROR: MethodError: reducing over an empty collection is not allowed; consider supplying `init` to the reducer +ERROR: ArgumentError: reducing over an empty collection is not allowed; consider supplying `init` to the reducer Stacktrace: [...] @@ -877,11 +866,12 @@ end """ findmax(f, domain) -> (f(x), index) -Return a pair of a value in the codomain (outputs of `f`) and the index of +Return a pair of a value in the codomain (outputs of `f`) and the index or key of the corresponding value in the `domain` (inputs to `f`) such that `f(x)` is maximised. If there are multiple maximal points, then the first one will be returned. -`domain` must be a non-empty iterable. +`domain` must be a non-empty iterable supporting [`keys`](@ref). Indices +are of the same type as those returned by [`keys(domain)`](@ref). Values are compared with `isless`. @@ -915,6 +905,9 @@ Return the maximal element of the collection `itr` and its index or key. If there are multiple maximal elements, then the first one will be returned. Values are compared with `isless`. +Indices are of the same type as those returned by [`keys(itr)`](@ref) +and [`pairs(itr)`](@ref). + See also: [`findmin`](@ref), [`argmax`](@ref), [`maximum`](@ref). # Examples @@ -936,12 +929,15 @@ _findmax(a, ::Colon) = findmax(identity, a) """ findmin(f, domain) -> (f(x), index) -Return a pair of a value in the codomain (outputs of `f`) and the index of +Return a pair of a value in the codomain (outputs of `f`) and the index or key of the corresponding value in the `domain` (inputs to `f`) such that `f(x)` is minimised. If there are multiple minimal points, then the first one will be returned. `domain` must be a non-empty iterable. +Indices are of the same type as those returned by [`keys(domain)`](@ref) +and [`pairs(domain)`](@ref). + `NaN` is treated as less than all other values except `missing`. !!! compat "Julia 1.7" @@ -975,6 +971,9 @@ Return the minimal element of the collection `itr` and its index or key. If there are multiple minimal elements, then the first one will be returned. `NaN` is treated as less than all other values except `missing`. +Indices are of the same type as those returned by [`keys(itr)`](@ref) +and [`pairs(itr)`](@ref). + See also: [`findmax`](@ref), [`argmin`](@ref), [`minimum`](@ref). # Examples @@ -1027,6 +1026,9 @@ If there are multiple maximal elements, then the first one will be returned. The collection must not be empty. +Indices are of the same type as those returned by [`keys(itr)`](@ref) +and [`pairs(itr)`](@ref). + Values are compared with `isless`. See also: [`argmin`](@ref), [`findmax`](@ref). @@ -1082,6 +1084,9 @@ If there are multiple minimal elements, then the first one will be returned. The collection must not be empty. +Indices are of the same type as those returned by [`keys(itr)`](@ref) +and [`pairs(itr)`](@ref). + `NaN` is treated as less than all other values except `missing`. See also: [`argmax`](@ref), [`findmin`](@ref). @@ -1113,7 +1118,7 @@ If the input contains [`missing`](@ref) values, return `missing` if all non-miss values are `false` (or equivalently, if the input contains no `true` value), following [three-valued logic](https://en.wikipedia.org/wiki/Three-valued_logic). -See also: [`all`](@ref), [`count`](@ref), [`sum`](@ref), [`|`](@ref), , [`||`](@ref). +See also: [`all`](@ref), [`count`](@ref), [`sum`](@ref), [`|`](@ref), [`||`](@ref). # Examples ```jldoctest @@ -1151,7 +1156,7 @@ If the input contains [`missing`](@ref) values, return `missing` if all non-miss values are `true` (or equivalently, if the input contains no `false` value), following [three-valued logic](https://en.wikipedia.org/wiki/Three-valued_logic). -See also: [`all!`](@ref), [`any`](@ref), [`count`](@ref), [`&`](@ref), , [`&&`](@ref), [`allunique`](@ref). +See also: [`all!`](@ref), [`any`](@ref), [`count`](@ref), [`&`](@ref), [`&&`](@ref), [`allunique`](@ref). # Examples ```jldoctest diff --git a/base/reducedim.jl b/base/reducedim.jl index c1c58ccdfefed..1a7a6feb33d5e 100644 --- a/base/reducedim.jl +++ b/base/reducedim.jl @@ -17,59 +17,21 @@ reduced_indices(a::AbstractArrayOrBroadcasted, region) = reduced_indices(axes(a) # for reductions that keep 0 dims as 0 reduced_indices0(a::AbstractArray, region) = reduced_indices0(axes(a), region) -function reduced_indices(inds::Indices{N}, d::Int) where N - d < 1 && throw(ArgumentError("dimension must be ≥ 1, got $d")) - if d == 1 - return (reduced_index(inds[1]), tail(inds)...)::typeof(inds) - elseif 1 < d <= N - return tuple(inds[1:d-1]..., oftype(inds[d], reduced_index(inds[d])), inds[d+1:N]...)::typeof(inds) - else - return inds - end -end - -function reduced_indices0(inds::Indices{N}, d::Int) where N - d < 1 && throw(ArgumentError("dimension must be ≥ 1, got $d")) - if d <= N - ind = inds[d] - rd = isempty(ind) ? ind : reduced_index(inds[d]) - if d == 1 - return (rd, tail(inds)...)::typeof(inds) - else - return tuple(inds[1:d-1]..., oftype(inds[d], rd), inds[d+1:N]...)::typeof(inds) - end - else - return inds - end +function reduced_indices(axs::Indices{N}, region) where N + _check_valid_region(region) + ntuple(d -> d in region ? reduced_index(axs[d]) : axs[d], Val(N)) end -function reduced_indices(inds::Indices{N}, region) where N - rinds = collect(inds) - for i in region - isa(i, Integer) || throw(ArgumentError("reduced dimension(s) must be integers")) - d = Int(i) - if d < 1 - throw(ArgumentError("region dimension(s) must be ≥ 1, got $d")) - elseif d <= N - rinds[d] = reduced_index(rinds[d]) - end - end - tuple(rinds...)::typeof(inds) +function reduced_indices0(axs::Indices{N}, region) where N + _check_valid_region(region) + ntuple(d -> d in region && !isempty(axs[d]) ? reduced_index(axs[d]) : axs[d], Val(N)) end -function reduced_indices0(inds::Indices{N}, region) where N - rinds = collect(inds) - for i in region - isa(i, Integer) || throw(ArgumentError("reduced dimension(s) must be integers")) - d = Int(i) - if d < 1 - throw(ArgumentError("region dimension(s) must be ≥ 1, got $d")) - elseif d <= N - rind = rinds[d] - rinds[d] = isempty(rind) ? rind : reduced_index(rind) - end +function _check_valid_region(region) + for d in region + isa(d, Integer) || throw(ArgumentError("reduced dimension(s) must be integers")) + Int(d) < 1 && throw(ArgumentError("region dimension(s) must be ≥ 1, got $d")) end - tuple(rinds...)::typeof(inds) end ###### Generic reduction functions ##### @@ -146,16 +108,18 @@ for (f1, f2, initval, typeextreme) in ((:min, :max, :Inf, :typemax), (:max, :min T = _realtype(f, promote_union(eltype(A))) Tr = v0 isa T ? T : typeof(v0) - # but NaNs and missing need to be avoided as initial values + # but NaNs, missing and unordered values need to be avoided as initial values if v0 isa Number && isnan(v0) # v0 is NaN v0 = oftype(v0, $initval) elseif isunordered(v0) # v0 is missing or a third-party unordered value Tnm = nonmissingtype(Tr) - # TODO: Some types, like BigInt, don't support typemin/typemax. - # So a Matrix{Union{BigInt, Missing}} can still error here. - v0 = $typeextreme(Tnm) + if Tnm <: Union{BitInteger, IEEEFloat, BigFloat} + v0 = $typeextreme(Tnm) + elseif !all(isunordered, A1) + v0 = mapreduce(f, $f2, Iterators.filter(!isunordered, A1)) + end end # v0 may have changed type. Tr = v0 isa T ? T : typeof(v0) @@ -186,12 +150,18 @@ function reducedim_init(f::ExtremaMap, op::typeof(_extrema_rf), A::AbstractArray # but NaNs and missing need to be avoided as initial values if v0[1] isa Number && isnan(v0[1]) + # v0 is NaN v0 = oftype(v0[1], Inf), oftype(v0[2], -Inf) elseif isunordered(v0[1]) # v0 is missing or a third-party unordered value - # TODO: Some types, like BigInt, don't support typemin/typemax. - # So a Matrix{Union{BigInt, Missing}} can still error here. - v0 = typemax(nonmissingtype(Tmin)), typemin(nonmissingtype(Tmax)) + Tminnm = nonmissingtype(Tmin) + Tmaxnm = nonmissingtype(Tmax) + if Tminnm <: Union{BitInteger, IEEEFloat, BigFloat} && + Tmaxnm <: Union{BitInteger, IEEEFloat, BigFloat} + v0 = (typemax(Tminnm), typemin(Tmaxnm)) + elseif !all(isunordered, A1) + v0 = reverse(mapreduce(f, op, Iterators.filter(!isunordered, A1))) + end end # v0 may have changed type. Tmin = v0[1] isa T ? T : typeof(v0[1]) @@ -448,6 +418,8 @@ _count(f, A::AbstractArrayOrBroadcasted, dims, init) = mapreduce(_bool(f), add_s Count the number of elements in `A` for which `f` returns `true` over the singleton dimensions of `r`, writing the result into `r` in-place. +$(_DOCS_ALIASING_WARNING) + !!! compat "Julia 1.5" inplace `count!` was added in Julia 1.5. @@ -525,8 +497,8 @@ sum(f, A::AbstractArray; dims) sum!(r, A) Sum elements of `A` over the singleton dimensions of `r`, and write results to `r`. -Note that since the sum! function is intended to operate without making any allocations, -the target should not alias with the source. + +$(_DOCS_ALIASING_WARNING) # Examples ```jldoctest @@ -601,6 +573,8 @@ prod(f, A::AbstractArray; dims) Multiply elements of `A` over the singleton dimensions of `r`, and write results to `r`. +$(_DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> A = [1 2; 3 4] @@ -678,6 +652,8 @@ maximum(f, A::AbstractArray; dims) Compute the maximum value of `A` over the singleton dimensions of `r`, and write results to `r`. +$(_DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> A = [1 2; 3 4] @@ -755,6 +731,8 @@ minimum(f, A::AbstractArray; dims) Compute the minimum value of `A` over the singleton dimensions of `r`, and write results to `r`. +$(_DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> A = [1 2; 3 4] @@ -820,6 +798,8 @@ extrema(f, A::AbstractArray; dims) Compute the minimum and maximum value of `A` over the singleton dimensions of `r`, and write results to `r`. +$(_DOCS_ALIASING_WARNING) + !!! compat "Julia 1.8" This method requires Julia 1.8 or later. @@ -895,6 +875,8 @@ all(::Function, ::AbstractArray; dims) Test whether all values in `A` along the singleton dimensions of `r` are `true`, and write results to `r`. +$(_DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> A = [true false; true false] @@ -968,6 +950,8 @@ any(::Function, ::AbstractArray; dims) Test whether any values in `A` along the singleton dimensions of `r` are `true`, and write results to `r`. +$(_DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> A = [true false; true false] @@ -1085,6 +1069,8 @@ end Find the minimum of `A` and the corresponding linear index along singleton dimensions of `rval` and `rind`, and store the results in `rval` and `rind`. `NaN` is treated as less than all other values except `missing`. + +$(_DOCS_ALIASING_WARNING) """ function findmin!(rval::AbstractArray, rind::AbstractArray, A::AbstractArray; init::Bool=true) @@ -1156,6 +1142,8 @@ end Find the maximum of `A` and the corresponding linear index along singleton dimensions of `rval` and `rind`, and store the results in `rval` and `rind`. `NaN` is treated as greater than all other values except `missing`. + +$(_DOCS_ALIASING_WARNING) """ function findmax!(rval::AbstractArray, rind::AbstractArray, A::AbstractArray; init::Bool=true) diff --git a/base/reflection.jl b/base/reflection.jl index 6204a4cb44482..2df713a531fae 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -20,6 +20,8 @@ Base """ parentmodule(m::Module) = ccall(:jl_module_parent, Ref{Module}, (Any,), m) +is_root_module(m::Module) = parentmodule(m) === m || (isdefined(Main, :Base) && m === Main.Base) + """ moduleroot(m::Module) -> Module @@ -75,16 +77,21 @@ end """ names(x::Module; all::Bool = false, imported::Bool = false) -Get an array of the public names of a `Module`, excluding deprecated names. +Get a vector of the public names of a `Module`, excluding deprecated names. If `all` is true, then the list also includes non-public names defined in the module, deprecated names, and compiler-generated names. If `imported` is true, then names explicitly imported from other modules -are also included. +are also included. Names are returned in sorted order. As a special case, all names defined in `Main` are considered \"public\", since it is not idiomatic to explicitly mark names from `Main` as public. -See also: [`isexported`](@ref), [`ispublic`](@ref), [`@locals`](@ref Base.@locals), [`@__MODULE__`](@ref). +!!! note + `sym ∈ names(SomeModule)` does *not* imply `isdefined(SomeModule, sym)`. + `names` will return symbols marked with `public` or `export`, even if + they are not defined in the module. + +See also: [`Base.isexported`](@ref), [`Base.ispublic`](@ref), [`Base.@locals`](@ref), [`@__MODULE__`](@ref). """ names(m::Module; all::Bool = false, imported::Bool = false) = sort!(unsorted_names(m; all, imported)) @@ -430,13 +437,16 @@ struct DataTypeLayout flags::UInt16 # haspadding : 1; # fielddesc_type : 2; + # arrayelem_isboxed : 1; + # arrayelem_isunion : 1; end """ Base.datatype_alignment(dt::DataType) -> Int Memory allocation minimum alignment for instances of this type. -Can be called on any `isconcretetype`. +Can be called on any `isconcretetype`, although for Memory it will give the +alignment of the elements, not the whole object. """ function datatype_alignment(dt::DataType) @_foldable_meta @@ -478,7 +488,8 @@ gc_alignment(T::Type) = gc_alignment(Core.sizeof(T)) Base.datatype_haspadding(dt::DataType) -> Bool Return whether the fields of instances of this type are packed in memory, -with no intervening padding bytes. +with no intervening padding bits (defined as bits whose value does not uniquely +impact the egal test when applied to the struct fields). Can be called on any `isconcretetype`. """ function datatype_haspadding(dt::DataType) @@ -489,9 +500,10 @@ function datatype_haspadding(dt::DataType) end """ - Base.datatype_nfields(dt::DataType) -> Bool + Base.datatype_nfields(dt::DataType) -> UInt32 -Return the number of fields known to this datatype's layout. +Return the number of fields known to this datatype's layout. This may be +different from the number of actual fields of the type for opaque types. Can be called on any `isconcretetype`. """ function datatype_nfields(dt::DataType) @@ -529,6 +541,31 @@ function datatype_fielddesc_type(dt::DataType) return (flags >> 1) & 3 end +""" + Base.datatype_arrayelem(dt::DataType) -> Int + +Return the behavior of the trailing array types allocations. +Can be called on any `isconcretetype`, but only meaningful on `Memory`. + +0 = inlinealloc +1 = isboxed +2 = isbitsunion +""" +function datatype_arrayelem(dt::DataType) + @_foldable_meta + dt.layout == C_NULL && throw(UndefRefError()) + flags = unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).flags + return (flags >> 3) & 3 +end + +function datatype_layoutsize(dt::DataType) + @_foldable_meta + dt.layout == C_NULL && throw(UndefRefError()) + size = unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).size + return size % Int +end + + # For type stability, we only expose a single struct that describes everything struct FieldDesc isforeign::Bool @@ -555,7 +592,7 @@ end function getindex(dtfd::DataTypeFieldDesc, i::Int) layout_ptr = convert(Ptr{DataTypeLayout}, dtfd.dt.layout) - fd_ptr = layout_ptr + sizeof(DataTypeLayout) + fd_ptr = layout_ptr + Core.sizeof(DataTypeLayout) layout = unsafe_load(layout_ptr) fielddesc_type = (layout.flags >> 1) & 3 nfields = layout.nfields @@ -597,7 +634,9 @@ true !!! compat "Julia 1.5" This function requires at least Julia 1.5. """ -ismutable(@nospecialize(x)) = (@_total_meta; typeof(x).name.flags & 0x2 == 0x2) +ismutable(@nospecialize(x)) = (@_total_meta; (typeof(x).name::Core.TypeName).flags & 0x2 == 0x2) +# The type assertion above is required to fix some invalidations. +# See also https://github.com/JuliaLang/julia/issues/52134 """ ismutabletype(T) -> Bool @@ -690,18 +729,10 @@ If `x === y` then `objectid(x) == objectid(y)`, and usually when `x !== y`, `obj See also [`hash`](@ref), [`IdDict`](@ref). """ -function objectid(x) - # objectid is foldable iff it isn't a pointer. - if isidentityfree(typeof(x)) - return _foldable_objectid(x) - end - return _objectid(x) -end -function _foldable_objectid(@nospecialize(x)) - @_foldable_meta - _objectid(x) +function objectid(@nospecialize(x)) + @_total_meta + return ccall(:jl_object_id, UInt, (Any,), x) end -_objectid(@nospecialize(x)) = ccall(:jl_object_id, UInt, (Any,), x) """ isdispatchtuple(T) @@ -826,24 +857,39 @@ function isabstracttype(@nospecialize(t)) return isa(t, DataType) && (t.name.flags & 0x1) == 0x1 end +function is_datatype_layoutopaque(dt::DataType) + datatype_nfields(dt) == 0 && !datatype_pointerfree(dt) +end + +function is_valid_intrinsic_elptr(@nospecialize(ety)) + ety === Any && return true + isconcretetype(ety) || return false + ety <: Array && return false + return !is_datatype_layoutopaque(ety) +end + """ Base.issingletontype(T) Determine whether type `T` has exactly one possible instance; for example, a -struct type with no fields. -If `T` is not a type, then return `false`. +struct type with no fields except other singleton values. +If `T` is not a concrete type, then return `false`. """ -issingletontype(@nospecialize(t)) = (@_total_meta; isa(t, DataType) && isdefined(t, :instance)) +issingletontype(@nospecialize(t)) = (@_total_meta; isa(t, DataType) && isdefined(t, :instance) && datatype_layoutsize(t) == 0 && datatype_pointerfree(t)) """ typeintersect(T::Type, S::Type) Compute a type that contains the intersection of `T` and `S`. Usually this will be the smallest such type or one close to it. + +A special case where exact behavior is guaranteed: when `T <: S`, +`typeintersect(S, T) == T == typeintersect(T, S)`. """ typeintersect(@nospecialize(a), @nospecialize(b)) = (@_total_meta; ccall(:jl_type_intersection, Any, (Any, Any), a::Type, b::Type)) morespecific(@nospecialize(a), @nospecialize(b)) = (@_total_meta; ccall(:jl_type_morespecific, Cint, (Any, Any), a::Type, b::Type) != 0) +morespecific(a::Method, b::Method) = ccall(:jl_method_morespecific, Cint, (Any, Any), a, b) != 0 """ fieldoffset(type, i) @@ -1021,7 +1067,7 @@ fieldtypes(T::Type) = (@_foldable_meta; ntupleany(i -> fieldtype(T, i), fieldcou Return a collection of all instances of the given type, if applicable. Mostly used for enumerated types (see `@enum`). -# Example +# Examples ```jldoctest julia> @enum Color red blue green @@ -1083,20 +1129,24 @@ function code_lowered(@nospecialize(f), @nospecialize(t=Tuple); generated::Bool= throw(ArgumentError("'debuginfo' must be either :source or :none")) end world = get_world_counter() - return map(method_instances(f, t, world)) do m + world == typemax(UInt) && error("code reflection cannot be used from generated functions") + ret = CodeInfo[] + for m in method_instances(f, t, world) if generated && hasgenerator(m) if may_invoke_generator(m) - return ccall(:jl_code_for_staged, Any, (Any, UInt), m, world)::CodeInfo + code = ccall(:jl_code_for_staged, Any, (Any, UInt), m, world)::CodeInfo else error("Could not expand generator for `@generated` method ", m, ". ", "This can happen if the provided argument types (", t, ") are ", "not leaf types, but the `generated` argument is `true`.") end + else + code = uncompressed_ir(m.def::Method) + debuginfo === :none && remove_linenums!(code) end - code = uncompressed_ir(m.def::Method) - debuginfo === :none && remove_linenums!(code) - return code + push!(ret, code) end + return ret end hasgenerator(m::Method) = isdefined(m, :generator) @@ -1157,7 +1207,7 @@ A list of modules can also be specified as an array. !!! compat "Julia 1.4" At least Julia 1.4 is required for specifying a module. -See also: [`which`](@ref) and `@which`. +See also: [`which`](@ref), [`@which`](@ref Main.InteractiveUtils.@which) and [`methodswith`](@ref Main.InteractiveUtils.methodswith). """ function methods(@nospecialize(f), @nospecialize(t), mod::Union{Tuple{Module},AbstractArray{Module},Nothing}=nothing) @@ -1194,11 +1244,11 @@ function visit(f, mt::Core.MethodTable) nothing end function visit(f, mc::Core.TypeMapLevel) - function avisit(f, e::Array{Any,1}) + function avisit(f, e::Memory{Any}) for i in 2:2:length(e) isassigned(e, i) || continue ei = e[i] - if ei isa Vector{Any} + if ei isa Memory{Any} for j in 2:2:length(ei) isassigned(ei, j) || continue visit(f, ei[j]) @@ -1209,16 +1259,16 @@ function visit(f, mc::Core.TypeMapLevel) end end if mc.targ !== nothing - avisit(f, mc.targ::Vector{Any}) + avisit(f, mc.targ::Memory{Any}) end if mc.arg1 !== nothing - avisit(f, mc.arg1::Vector{Any}) + avisit(f, mc.arg1::Memory{Any}) end if mc.tname !== nothing - avisit(f, mc.tname::Vector{Any}) + avisit(f, mc.tname::Memory{Any}) end if mc.name1 !== nothing - avisit(f, mc.name1::Vector{Any}) + avisit(f, mc.name1::Memory{Any}) end mc.list !== nothing && visit(f, mc.list) mc.any !== nothing && visit(f, mc.any) @@ -1269,12 +1319,17 @@ function length(mt::Core.MethodTable) end isempty(mt::Core.MethodTable) = (mt.defs === nothing) -uncompressed_ir(m::Method) = isdefined(m, :source) ? _uncompressed_ir(m, m.source) : +uncompressed_ir(m::Method) = isdefined(m, :source) ? _uncompressed_ir(m) : isdefined(m, :generator) ? error("Method is @generated; try `code_lowered` instead.") : error("Code for this Method is not available.") -_uncompressed_ir(m::Method, s::CodeInfo) = copy(s) -_uncompressed_ir(m::Method, s::String) = ccall(:jl_uncompress_ir, Any, (Any, Ptr{Cvoid}, Any), m, C_NULL, s)::CodeInfo -_uncompressed_ir(ci::Core.CodeInstance, s::String) = ccall(:jl_uncompress_ir, Any, (Any, Any, Any), ci.def.def::Method, ci, s)::CodeInfo +function _uncompressed_ir(m::Method) + s = m.source + if s isa String + s = ccall(:jl_uncompress_ir, Ref{CodeInfo}, (Any, Ptr{Cvoid}, Any), m, C_NULL, s) + end + return s::CodeInfo +end + # for backwards compat const uncompressed_ast = uncompressed_ir const _uncompressed_ast = _uncompressed_ir @@ -1285,12 +1340,21 @@ function method_instances(@nospecialize(f), @nospecialize(t), world::UInt) # this make a better error message than the typeassert that follows world == typemax(UInt) && error("code reflection cannot be used from generated functions") for match in _methods_by_ftype(tt, -1, world)::Vector - instance = Core.Compiler.specialize_method(match) + instance = Core.Compiler.specialize_method(match::Core.MethodMatch) push!(results, instance) end return results end +function method_instance(@nospecialize(f), @nospecialize(t); + world=Base.get_world_counter(), method_table=nothing) + tt = signature_type(f, t) + mi = ccall(:jl_method_lookup_by_tt, Any, + (Any, Csize_t, Any), + tt, world, method_table) + return mi::Union{Nothing, MethodInstance} +end + default_debug_info_kind() = unsafe_load(cglobal(:jl_default_debug_info_kind, Cint)) # this type mirrors jl_cgparams_t (documented in julia.h) @@ -1354,7 +1418,7 @@ struct CodegenParams using the `swiftself` convention, which in the ordinary case means that the pointer is kept in a register and accesses are thus very fast. If this option is disabled, the task local state pointer must be loaded from thread local - stroage, which incurs a small amount of additional overhead. The option is enabled by + storage, which incurs a small amount of additional overhead. The option is enabled by default. """ gcstack_arg::Cint @@ -1442,7 +1506,7 @@ function may_invoke_generator(method::Method, @nospecialize(atype), sparams::Sim gen_mthds isa Vector || return false length(gen_mthds) == 1 || return false - generator_method = first(gen_mthds).method + generator_method = (first(gen_mthds)::Core.MethodMatch).method nsparams = length(sparams) isdefined(generator_method, :source) || return false code = generator_method.source @@ -1505,7 +1569,7 @@ internals. - `interp::Core.Compiler.AbstractInterpreter = Core.Compiler.NativeInterpreter(world)`: optional, controls the abstract interpreter to use, use the native interpreter if not specified. -# Example +# Examples One can put the argument types in a tuple to get the corresponding `code_typed`. @@ -1520,7 +1584,7 @@ julia> code_typed(+, (Float64, Float64)) """ function code_typed(@nospecialize(f), @nospecialize(types=default_tt(f)); kwargs...) if isa(f, Core.OpaqueClosure) - return code_typed_opaque_closure(f; kwargs...) + return code_typed_opaque_closure(f, types; kwargs...) end tt = signature_type(f, types) return code_typed_by_type(tt; kwargs...) @@ -1575,21 +1639,42 @@ function code_typed_by_type(@nospecialize(tt::Type); return asts end -function code_typed_opaque_closure(@nospecialize(oc::Core.OpaqueClosure); - debuginfo::Symbol=:default, _...) - ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions") +function get_oc_code_rt(oc::Core.OpaqueClosure, types, optimize::Bool) + @nospecialize oc types + ccall(:jl_is_in_pure_context, Bool, ()) && + error("code reflection cannot be used from generated functions") m = oc.source if isa(m, Method) - code = _uncompressed_ir(m, m.source) - debuginfo === :none && remove_linenums!(code) - # intersect the declared return type and the inferred return type (if available) - rt = typeintersect(code.rettype, typeof(oc).parameters[2]) - return Any[code => rt] + if isdefined(m, :source) + if optimize + tt = Tuple{typeof(oc.captures), to_tuple_type(types).parameters...} + mi = Core.Compiler.specialize_method(m, tt, Core.svec()) + interp = Core.Compiler.NativeInterpreter(m.primary_world) + return Core.Compiler.typeinf_code(interp, mi, optimize) + else + code = _uncompressed_ir(m) + return Pair{CodeInfo,Any}(code, typeof(oc).parameters[2]) + end + else + # OC constructed from optimized IR + codeinst = m.specializations.cache + return Pair{CodeInfo, Any}(codeinst.inferred, codeinst.rettype) + end else error("encountered invalid Core.OpaqueClosure object") end end +function code_typed_opaque_closure(oc::Core.OpaqueClosure, types; + debuginfo::Symbol=:default, + optimize::Bool=true, + _...) + @nospecialize oc types + (code, rt) = get_oc_code_rt(oc, types, optimize) + debuginfo === :none && remove_linenums!(code) + return Any[Pair{CodeInfo,Any}(code, rt)] +end + """ code_ircode(f, [types]) @@ -1613,7 +1698,7 @@ internals. If it is an integer, it specifies the number of passes to run. If it is `nothing` (default), all passes are run. -# Example +# Examples One can put the argument types in a tuple to get the corresponding `code_ircode`. @@ -1670,16 +1755,48 @@ function code_ircode_by_type( return asts end +function _builtin_return_type(interp::Core.Compiler.AbstractInterpreter, + @nospecialize(f::Core.Builtin), @nospecialize(types)) + argtypes = Any[to_tuple_type(types).parameters...] + rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing) + return Core.Compiler.widenconst(rt) +end + +function _builtin_effects(interp::Core.Compiler.AbstractInterpreter, + @nospecialize(f::Core.Builtin), @nospecialize(types)) + argtypes = Any[to_tuple_type(types).parameters...] + rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing) + return Core.Compiler.builtin_effects(Core.Compiler.typeinf_lattice(interp), f, argtypes, rt) +end + +check_generated_context(world::UInt) = + (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && + error("code reflection cannot be used from generated functions") + +# TODO rename `Base.return_types` to `Base.infer_return_types` """ - Base.return_types(f::Function, types::DataType=default_tt(f); - world::UInt=get_world_counter(), interp::NativeInterpreter=Core.Compiler.NativeInterpreter(world)) + Base.return_types( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::NativeInterpreter=Core.Compiler.NativeInterpreter(world)) -> rts::Vector{Any} Return a list of possible return types for a given function `f` and argument types `types`. The list corresponds to the results of type inference on all the possible method match candidates for `f` and `types` (see also [`methods(f, types)`](@ref methods). -# Example +# Arguments +- `f`: The function to analyze. +- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. +- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. + +# Returns +- `rts::Vector{Any}`: The list of return types that are figured out by inference on + methods matching with the given `f` and `types`. The list's order matches the order + returned by `methods(f, types)`. + +# Examples ```julia julia> Base.return_types(sum, Tuple{Vector{Int}}) @@ -1689,9 +1806,9 @@ julia> Base.return_types(sum, Tuple{Vector{Int}}) julia> methods(sum, (Union{Vector{Int},UnitRange{Int}},)) # 2 methods for generic function "sum" from Base: [1] sum(r::AbstractRange{<:Real}) - @ range.jl:1396 + @ range.jl:1399 [2] sum(a::AbstractArray; dims, kw...) - @ reducedim.jl:996 + @ reducedim.jl:1010 julia> Base.return_types(sum, (Union{Vector{Int},UnitRange{Int}},)) 2-element Vector{Any}: @@ -1700,65 +1817,318 @@ julia> Base.return_types(sum, (Union{Vector{Int},UnitRange{Int}},)) ``` !!! warning - The `return_types` function should not be used from generated functions; + The `Base.return_types` function should not be used from generated functions; doing so will result in an error. """ function return_types(@nospecialize(f), @nospecialize(types=default_tt(f)); world::UInt=get_world_counter(), interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) - (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && - error("code reflection cannot be used from generated functions") + check_generated_context(world) if isa(f, Core.OpaqueClosure) - _, rt = only(code_typed_opaque_closure(f)) + _, rt = only(code_typed_opaque_closure(f, types)) return Any[rt] end - if isa(f, Core.Builtin) - argtypes = Any[to_tuple_type(types).parameters...] - rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing) - return Any[Core.Compiler.widenconst(rt)] + rt = _builtin_return_type(interp, f, types) + return Any[rt] end - rts = [] + rts = Any[] tt = signature_type(f, types) matches = _methods_by_ftype(tt, #=lim=#-1, world)::Vector for match in matches - match = match::Core.MethodMatch - ty = Core.Compiler.typeinf_type(interp, match.method, match.spec_types, match.sparams) + ty = Core.Compiler.typeinf_type(interp, match::Core.MethodMatch) push!(rts, something(ty, Any)) end return rts end """ - infer_effects(f, types=default_tt(f); world=get_world_counter(), interp=Core.Compiler.NativeInterpreter(world)) + Base.infer_return_type( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) -> rt::Type -Compute the `Effects` of a function `f` with argument types `types`. The `Effects` represents the computational effects of the function call, such as whether it is free of side effects, guaranteed not to throw an exception, guaranteed to terminate, etc. The `world` and `interp` arguments specify the world counter and the native interpreter to use for the analysis. +Returns an inferred return type of the function call specified by `f` and `types`. # Arguments - `f`: The function to analyze. - `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. - `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. -- `interp` (optional): The native interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. # Returns -- `effects::Effects`: The computed effects of the function call. +- `rt::Type`: An inferred return type of the function call specified by the given call signature. -# Example +!!! note + Note that, different from [`Base.return_types`](@ref), this doesn't give you the list + return types of every possible method matching with the given `f` and `types`. + It returns a single return type, taking into account all potential outcomes of + any function call entailed by the given signature type. + +# Examples ```julia -julia> function foo(x) - y = x * 2 - return y - end; +julia> checksym(::Symbol) = :symbol; + +julia> checksym(x::Any) = x; + +julia> Base.infer_return_type(checksym, (Union{Symbol,String},)) +Union{String, Symbol} + +julia> Base.return_types(checksym, (Union{Symbol,String},)) +2-element Vector{Any}: + Symbol + Union{String, Symbol} +``` + +It's important to note the difference here: `Base.return_types` gives back inferred results +for each method that matches the given signature `checksum(::Union{Symbol,String})`. +On the other hand `Base.infer_return_type` returns one collective result that sums up all those possibilities. + +!!! warning + The `Base.infer_return_type` function should not be used from generated functions; + doing so will result in an error. +""" +function infer_return_type(@nospecialize(f), @nospecialize(types=default_tt(f)); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) + check_generated_context(world) + if isa(f, Core.OpaqueClosure) + return last(only(code_typed_opaque_closure(f, types))) + end + if isa(f, Core.Builtin) + return _builtin_return_type(interp, f, types) + end + tt = signature_type(f, types) + matches = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) + if matches === nothing + # unanalyzable call, i.e. the interpreter world might be newer than the world where + # the `f` is defined, return the unknown return type + return Any + end + rt = Union{} + for match in matches.matches + ty = Core.Compiler.typeinf_type(interp, match::Core.MethodMatch) + rt = Core.Compiler.tmerge(rt, something(ty, Any)) + end + return rt +end + +""" + Base.infer_exception_types( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::NativeInterpreter=Core.Compiler.NativeInterpreter(world)) -> excts::Vector{Any} -julia> effects = Base.infer_effects(foo, (Int,)) +Return a list of possible exception types for a given function `f` and argument types `types`. +The list corresponds to the results of type inference on all the possible method match +candidates for `f` and `types` (see also [`methods(f, types)`](@ref methods). +It works like [`Base.return_types`](@ref), but it infers the exception types instead of the return types. + +# Arguments +- `f`: The function to analyze. +- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. +- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. + +# Returns +- `excts::Vector{Any}`: The list of exception types that are figured out by inference on + methods matching with the given `f` and `types`. The list's order matches the order + returned by `methods(f, types)`. + +# Examples + +```julia +julia> throw_if_number(::Number) = error("number is given"); + +julia> throw_if_number(::Any) = nothing; + +julia> Base.infer_exception_types(throw_if_number, (Int,)) +1-element Vector{Any}: + ErrorException + +julia> methods(throw_if_number, (Any,)) +# 2 methods for generic function "throw_if_number" from Main: + [1] throw_if_number(x::Number) + @ REPL[1]:1 + [2] throw_if_number(::Any) + @ REPL[2]:1 + +julia> Base.infer_exception_types(throw_if_number, (Any,)) +2-element Vector{Any}: + ErrorException # the result of inference on `throw_if_number(::Number)` + Union{} # the result of inference on `throw_if_number(::Any)` +``` + +!!! warning + The `Base.infer_exception_types` function should not be used from generated functions; + doing so will result in an error. +""" +function infer_exception_types(@nospecialize(f), @nospecialize(types=default_tt(f)); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) + check_generated_context(world) + if isa(f, Core.OpaqueClosure) + return Any[Any] # TODO + end + if isa(f, Core.Builtin) + effects = _builtin_effects(interp, f, types) + exct = Core.Compiler.is_nothrow(effects) ? Union{} : Any + return Any[exct] + end + excts = Any[] + tt = signature_type(f, types) + matches = _methods_by_ftype(tt, #=lim=#-1, world)::Vector + for match in matches + match = match::Core.MethodMatch + frame = Core.Compiler.typeinf_frame(interp, match, #=run_optimizer=#false) + if frame === nothing + exct = Any + else + exct = Core.Compiler.widenconst(frame.result.exc_result) + end + push!(excts, exct) + end + return excts +end + +_may_throw_methoderror(matches#=::Core.Compiler.MethodLookupResult=#) = + matches.ambig || !any(match::Core.MethodMatch->match.fully_covers, matches.matches) + +""" + Base.infer_exception_type( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) -> exct::Type + +Returns the type of exception potentially thrown by the function call specified by `f` and `types`. + +# Arguments +- `f`: The function to analyze. +- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. +- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. + +# Returns +- `exct::Type`: The inferred type of exception that can be thrown by the function call + specified by the given call signature. + +!!! note + Note that, different from [`Base.infer_exception_types`](@ref), this doesn't give you the list + exception types for every possible matching method with the given `f` and `types`. + It returns a single exception type, taking into account all potential outcomes of + any function call entailed by the given signature type. + +# Examples + +```julia +julia> f1(x) = x * 2; + +julia> Base.infer_exception_type(f1, (Int,)) +Union{} +``` + +The exception inferred as `Union{}` indicates that `f1(::Int)` will not throw any exception. + +```julia +julia> f2(x::Int) = x * 2; + +julia> Base.infer_exception_type(f2, (Integer,)) +MethodError +``` + +This case is pretty much the same as with `f1`, but there's a key difference to note. For +`f2`, the argument type is limited to `Int`, while the argument type is given as `Tuple{Integer}`. +Because of this, taking into account the chance of the method error entailed by the call +signature, the exception type is widened to `MethodError`. + +!!! warning + The `Base.infer_exception_type` function should not be used from generated functions; + doing so will result in an error. +""" +function infer_exception_type(@nospecialize(f), @nospecialize(types=default_tt(f)); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) + check_generated_context(world) + if isa(f, Core.OpaqueClosure) + return Any # TODO + end + if isa(f, Core.Builtin) + effects = _builtin_effects(interp, f, types) + return Core.Compiler.is_nothrow(effects) ? Union{} : Any + end + tt = signature_type(f, types) + matches = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) + if matches === nothing + # unanalyzable call, i.e. the interpreter world might be newer than the world where + # the `f` is defined, return the unknown exception type + return Any + end + exct = Union{} + if _may_throw_methoderror(matches) + # account for the fact that we may encounter a MethodError with a non-covered or ambiguous signature. + exct = Core.Compiler.tmerge(exct, MethodError) + end + for match in matches.matches + match = match::Core.MethodMatch + frame = Core.Compiler.typeinf_frame(interp, match, #=run_optimizer=#false) + frame === nothing && return Any + exct = Core.Compiler.tmerge(exct, Core.Compiler.widenconst(frame.result.exc_result)) + end + return exct +end + +""" + Base.infer_effects( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) -> effects::Effects + +Returns the possible computation effects of the function call specified by `f` and `types`. + +# Arguments +- `f`: The function to analyze. +- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. +- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. + +# Returns +- `effects::Effects`: The computed effects of the function call specified by the given call signature. + See the documentation of [`Effects`](@ref Core.Compiler.Effects) or [`Base.@assume_effects`](@ref) + for more information on the various effect properties. + +!!! note + Note that, different from [`Base.return_types`](@ref), this doesn't give you the list + effect analysis results for every possible matching method with the given `f` and `types`. + It returns a single effect, taking into account all potential outcomes of any function + call entailed by the given signature type. + +# Examples + +```julia +julia> f1(x) = x * 2; + +julia> Base.infer_effects(f1, (Int,)) (+c,+e,+n,+t,+s,+m,+i) ``` -This function will return an `Effects` object with information about the computational effects of the function `foo` when called with an `Int` argument. See the documentation for `Effects` for more information on the various effect properties. +This function will return an `Effects` object with information about the computational +effects of the function `f1` when called with an `Int` argument. + +```julia +julia> f2(x::Int) = x * 2; + +julia> Base.infer_effects(f2, (Integer,)) +(+c,+e,!n,+t,+s,+m,+i) +``` + +This case is pretty much the same as with `f1`, but there's a key difference to note. For +`f2`, the argument type is limited to `Int`, while the argument type is given as `Tuple{Integer}`. +Because of this, taking into account the chance of the method error entailed by the call +signature, the `:nothrow` bit gets tainted. !!! warning - The `infer_effects` function should not be used from generated functions; + The `Base.infer_effects` function should not be used from generated functions; doing so will result in an error. # See Also @@ -1768,14 +2138,9 @@ This function will return an `Effects` object with information about the computa function infer_effects(@nospecialize(f), @nospecialize(types=default_tt(f)); world::UInt=get_world_counter(), interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) - (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && - error("code reflection cannot be used from generated functions") + check_generated_context(world) if isa(f, Core.Builtin) - types = to_tuple_type(types) - argtypes = Any[Core.Const(f), types.parameters...] - rt = Core.Compiler.builtin_tfunction(interp, f, argtypes[2:end], nothing) - return Core.Compiler.builtin_effects(Core.Compiler.typeinf_lattice(interp), f, - Core.Compiler.ArgInfo(nothing, argtypes), rt) + return _builtin_effects(interp, f, types) end tt = signature_type(f, types) matches = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) @@ -1785,7 +2150,7 @@ function infer_effects(@nospecialize(f), @nospecialize(types=default_tt(f)); return Core.Compiler.Effects() end effects = Core.Compiler.EFFECTS_TOTAL - if matches.ambig || !any(match::Core.MethodMatch->match.fully_covers, matches.matches) + if _may_throw_methoderror(matches) # account for the fact that we may encounter a MethodError with a non-covered or ambiguous signature. effects = Core.Compiler.Effects(effects; nothrow=false) end @@ -1868,11 +2233,21 @@ Returns the method of `f` (a `Method` object) that would be called for arguments If `types` is an abstract type, then the method that would be called by `invoke` is returned. -See also: [`parentmodule`](@ref), and `@which` and `@edit` in [`InteractiveUtils`](@ref man-interactive-utils). +See also: [`parentmodule`](@ref), [`@which`](@ref Main.InteractiveUtils.@which), and [`@edit`](@ref Main.InteractiveUtils.@edit). """ function which(@nospecialize(f), @nospecialize(t)) tt = signature_type(f, t) - return which(tt) + world = get_world_counter() + match, _ = Core.Compiler._findsup(tt, nothing, world) + if match === nothing + me = MethodError(f, t, world) + ee = ErrorException(sprint(io -> begin + println(io, "Calling invoke(f, t, args...) would throw:"); + Base.showerror(io, me); + end)) + throw(ee) + end + return match.method end """ @@ -1999,6 +2374,7 @@ end function hasmethod(f, t, kwnames::Tuple{Vararg{Symbol}}; world::UInt=get_world_counter()) @nospecialize + world == typemax(UInt) && error("code reflection cannot be used from generated functions") isempty(kwnames) && return hasmethod(f, t; world) t = to_tuple_type(t) ft = Core.Typeof(f) @@ -2051,6 +2427,8 @@ function bodyfunction(basemethod::Method) else return nothing end + elseif isa(fsym, Core.SSAValue) + fsym = ast.code[fsym.id] else return nothing end @@ -2108,7 +2486,7 @@ function isambiguous(m1::Method, m2::Method; ambiguous_bottom::Bool=false) min = Ref{UInt}(typemin(UInt)) max = Ref{UInt}(typemax(UInt)) has_ambig = Ref{Int32}(0) - ms = _methods_by_ftype(ti, nothing, -1, world, true, min, max, has_ambig)::Vector + ms = collect(Core.MethodMatch, _methods_by_ftype(ti, nothing, -1, world, true, min, max, has_ambig)::Vector) has_ambig[] == 0 && return false if !ambiguous_bottom filter!(ms) do m::Core.MethodMatch @@ -2121,7 +2499,6 @@ function isambiguous(m1::Method, m2::Method; ambiguous_bottom::Bool=false) # report the other ambiguous pair) have_m1 = have_m2 = false for match in ms - match = match::Core.MethodMatch m = match.method m === m1 && (have_m1 = true) m === m2 && (have_m2 = true) @@ -2139,7 +2516,7 @@ function isambiguous(m1::Method, m2::Method; ambiguous_bottom::Bool=false) for match in ms m = match.method match.fully_covers || continue - if minmax === nothing || morespecific(m.sig, minmax.sig) + if minmax === nothing || morespecific(m, minmax) minmax = m end end @@ -2149,8 +2526,8 @@ function isambiguous(m1::Method, m2::Method; ambiguous_bottom::Bool=false) for match in ms m = match.method m === minmax && continue - if !morespecific(minmax.sig, m.sig) - if match.fully_covers || !morespecific(m.sig, minmax.sig) + if !morespecific(minmax, m) + if match.fully_covers || !morespecific(m, minmax) return true end end @@ -2190,7 +2567,11 @@ function delete_method(m::Method) end function get_methodtable(m::Method) - return ccall(:jl_method_get_table, Any, (Any,), m)::Core.MethodTable + mt = ccall(:jl_method_get_table, Any, (Any,), m) + if mt === nothing + return nothing + end + return mt::Core.MethodTable end """ @@ -2214,8 +2595,22 @@ min_world(m::Core.CodeInstance) = m.min_world max_world(m::Core.CodeInstance) = m.max_world min_world(m::Core.CodeInfo) = m.min_world max_world(m::Core.CodeInfo) = m.max_world + +""" + get_world_counter() + +Returns the current maximum world-age counter. This counter is global and monotonically +increasing. +""" get_world_counter() = ccall(:jl_get_world_counter, UInt, ()) +""" + tls_world_age() + +Returns the world the [current_task()](@ref) is executing within. +""" +tls_world_age() = ccall(:jl_get_tls_world_age, UInt, ()) + """ propertynames(x, private=false) @@ -2234,6 +2629,7 @@ See also: [`hasproperty`](@ref), [`hasfield`](@ref). propertynames(x) = fieldnames(typeof(x)) propertynames(m::Module) = names(m) propertynames(x, private::Bool) = propertynames(x) # ignore private flag by default +propertynames(x::Array) = () # hide the fields from tab completion to discourage calling `x.size` instead of `size(x)`, even though they are equivalent """ hasproperty(x, s::Symbol) diff --git a/base/refpointer.jl b/base/refpointer.jl index b89b474e8714c..5027462eeb6b6 100644 --- a/base/refpointer.jl +++ b/base/refpointer.jl @@ -148,13 +148,14 @@ if is_primary_base_module Ref(x::Ptr{T}, i::Integer) where {T} = x + (i - 1) * Core.sizeof(T) # convert Arrays to pointer arrays for ccall - function Ref{P}(a::Array{<:Union{Ptr,Cwstring,Cstring}}) where P<:Union{Ptr,Cwstring,Cstring} - return RefArray(a) # effectively a no-op - end + # For example `["a", "b"]` to Ptr{Cstring} for `char **argv` function Ref{P}(a::Array{T}) where P<:Union{Ptr,Cwstring,Cstring} where T - if (!isbitstype(T) && T <: eltype(P)) + if P == T + return getfield(a, :ref) + elseif (isbitstype(T) ? T <: Ptr || T <: Union{Cwstring,Cstring} : T <: eltype(P)) # this Array already has the right memory layout for the requested Ref - return RefArray(a,1,false) # root something, so that this function is type-stable + # but the wrong eltype for the constructor + return RefArray{P,typeof(a),Nothing}(a, 1, nothing) # effectively a no-op else ptrs = Vector{P}(undef, length(a)+1) roots = Vector{Any}(undef, length(a)) @@ -164,14 +165,14 @@ if is_primary_base_module roots[i] = root end ptrs[length(a)+1] = C_NULL - return RefArray(ptrs,1,roots) + return RefArray{P,typeof(ptrs),typeof(roots)}(ptrs, 1, roots) end end Ref(x::AbstractArray, i::Integer) = RefArray(x, i) end -cconvert(::Type{Ptr{P}}, a::Array{<:Ptr}) where {P<:Ptr} = a -cconvert(::Type{Ref{P}}, a::Array{<:Ptr}) where {P<:Ptr} = a +cconvert(::Type{Ptr{P}}, a::Array{<:Union{Ptr,Cwstring,Cstring}}) where {P<:Union{Ptr,Cwstring,Cstring}} = getfield(a, :ref) +cconvert(::Type{Ref{P}}, a::Array{<:Union{Ptr,Cwstring,Cstring}}) where {P<:Union{Ptr,Cwstring,Cstring}} = getfield(a, :ref) cconvert(::Type{Ptr{P}}, a::Array) where {P<:Union{Ptr,Cwstring,Cstring}} = Ref{P}(a) cconvert(::Type{Ref{P}}, a::Array) where {P<:Union{Ptr,Cwstring,Cstring}} = Ref{P}(a) diff --git a/base/regex.jl b/base/regex.jl index 3e161806c50ea..38eb4cc512552 100644 --- a/base/regex.jl +++ b/base/regex.jl @@ -185,9 +185,14 @@ If a group was not captured, `nothing` will be yielded instead of a substring. Methods that accept a `RegexMatch` object are defined for [`iterate`](@ref), [`length`](@ref), [`eltype`](@ref), [`keys`](@ref keys(::RegexMatch)), [`haskey`](@ref), and -[`getindex`](@ref), where keys are the the names or numbers of a capture group. +[`getindex`](@ref), where keys are the names or numbers of a capture group. See [`keys`](@ref keys(::RegexMatch)) for more information. +`Tuple(m)`, `NamedTuple(m)`, and `Dict(m)` can be used to construct more flexible collection types from `RegexMatch` objects. + +!!! compat "Julia 1.11" + Constructing NamedTuples and Dicts from RegexMatches requires Julia 1.11 + # Examples ```jldoctest julia> m = match(r"(?\\d+):(?\\d+)(am|pm)?", "11:30 in the morning") @@ -210,16 +215,26 @@ julia> hr, min, ampm = m; # destructure capture groups by iteration julia> hr "11" + +julia> Dict(m) +Dict{Any, Union{Nothing, SubString{String}}} with 3 entries: + "hour" => "11" + 3 => nothing + "minute" => "30" ``` """ -struct RegexMatch <: AbstractMatch - match::SubString{String} - captures::Vector{Union{Nothing,SubString{String}}} +struct RegexMatch{S<:AbstractString} <: AbstractMatch + match::SubString{S} + captures::Vector{Union{Nothing,SubString{S}}} offset::Int offsets::Vector{Int} regex::Regex end +RegexMatch(match::SubString{S}, captures::Vector{Union{Nothing,SubString{S}}}, + offset::Union{Int, UInt}, offsets::Vector{Int}, regex::Regex) where {S<:AbstractString} = + RegexMatch{S}(match, captures, offset, offsets, regex) + """ keys(m::RegexMatch) -> Vector @@ -285,6 +300,9 @@ iterate(m::RegexMatch, args...) = iterate(m.captures, args...) length(m::RegexMatch) = length(m.captures) eltype(m::RegexMatch) = eltype(m.captures) +NamedTuple(m::RegexMatch) = NamedTuple{Symbol.(Tuple(keys(m)))}(values(m)) +Dict(m::RegexMatch) = Dict(pairs(m)) + function occursin(r::Regex, s::AbstractString; offset::Integer=0) compile(r) return PCRE.exec_r(r.regex, String(s), offset, r.match_options) @@ -377,9 +395,13 @@ end match(r::Regex, s::AbstractString[, idx::Integer[, addopts]]) Search for the first match of the regular expression `r` in `s` and return a [`RegexMatch`](@ref) -object containing the match, or nothing if the match failed. The matching substring can be -retrieved by accessing `m.match` and the captured sequences can be retrieved by accessing -`m.captures` The optional `idx` argument specifies an index at which to start the search. +object containing the match, or nothing if the match failed. +The optional `idx` argument specifies an index at which to start the search. +The matching substring can be retrieved by accessing `m.match`, the captured sequences can be retrieved by accessing `m.captures`. +The resulting [`RegexMatch`](@ref) object can be used to construct other collections: e.g. `Tuple(m)`, `NamedTuple(m)`. + +!!! compat "Julia 1.11" + Constructing NamedTuples and Dicts requires Julia 1.11 # Examples ```jldoctest @@ -423,9 +445,35 @@ function match(re::Regex, str::Union{SubString{String}, String}, idx::Integer, return result end +function _annotatedmatch(m::RegexMatch{S}, str::AnnotatedString{S}) where {S<:AbstractString} + RegexMatch{AnnotatedString{S}}( + (@inbounds SubString{AnnotatedString{S}}( + str, m.match.offset, m.match.ncodeunits, Val(:noshift))), + Union{Nothing,SubString{AnnotatedString{S}}}[ + if !isnothing(cap) + (@inbounds SubString{AnnotatedString{S}}( + str, cap.offset, cap.ncodeunits, Val(:noshift))) + end for cap in m.captures], + m.offset, m.offsets, m.regex) +end + +function match(re::Regex, str::AnnotatedString) + m = match(re, str.string) + if !isnothing(m) + _annotatedmatch(m, str) + end +end + +function match(re::Regex, str::AnnotatedString, idx::Integer, add_opts::UInt32=UInt32(0)) + m = match(re, str.string, idx, add_opts) + if !isnothing(m) + _annotatedmatch(m, str) + end +end + match(r::Regex, s::AbstractString) = match(r, s, firstindex(s)) match(r::Regex, s::AbstractString, i::Integer) = throw(ArgumentError( - "regex matching is only available for the String type; use String(s) to convert" + "regex matching is only available for the String and AnnotatedString types; use String(s) to convert" )) findnext(re::Regex, str::Union{String,SubString}, idx::Integer) = _findnext_re(re, str, idx, C_NULL) @@ -671,18 +719,19 @@ function _replace(io, repl_s::SubstitutionString, str, r, re) end end -struct RegexMatchIterator +struct RegexMatchIterator{S <: AbstractString} regex::Regex - string::String + string::S overlap::Bool - function RegexMatchIterator(regex::Regex, string::AbstractString, ovr::Bool=false) - new(regex, string, ovr) - end + RegexMatchIterator(regex::Regex, string::AbstractString, ovr::Bool=false) = + new{String}(regex, String(string), ovr) + RegexMatchIterator(regex::Regex, string::AnnotatedString, ovr::Bool=false) = + new{AnnotatedString{String}}(regex, AnnotatedString(String(string.string), string.annotations), ovr) end compile(itr::RegexMatchIterator) = (compile(itr.regex); itr) -eltype(::Type{RegexMatchIterator}) = RegexMatch -IteratorSize(::Type{RegexMatchIterator}) = SizeUnknown() +eltype(::Type{<:RegexMatchIterator}) = RegexMatch +IteratorSize(::Type{<:RegexMatchIterator}) = SizeUnknown() function iterate(itr::RegexMatchIterator, (offset,prevempty)=(1,false)) opts_nonempty = UInt32(PCRE.ANCHORED | PCRE.NOTEMPTY_ATSTART) @@ -727,7 +776,7 @@ julia> rx = r"a.a" r"a.a" julia> m = eachmatch(rx, "a1a2a3a") -Base.RegexMatchIterator(r"a.a", "a1a2a3a", false) +Base.RegexMatchIterator{String}(r"a.a", "a1a2a3a", false) julia> collect(m) 2-element Vector{RegexMatch}: diff --git a/base/reinterpretarray.jl b/base/reinterpretarray.jl index 3844edc331c7c..8b4025e6903cd 100644 --- a/base/reinterpretarray.jl +++ b/base/reinterpretarray.jl @@ -46,6 +46,23 @@ struct ReinterpretArray{T,N,S,A<:AbstractArray{S},IsReshaped} <: AbstractArray{T 3 + 4im 5 + 6im ``` + + If the location of padding bits does not line up between `T` and `eltype(A)`, the resulting array will be + read-only or write-only, to prevent invalid bits from being written to or read from, respectively. + + ```jldoctest + julia> a = reinterpret(Tuple{UInt8, UInt32}, UInt32[1, 2]) + 1-element reinterpret(Tuple{UInt8, UInt32}, ::Vector{UInt32}): + (0x01, 0x00000002) + + julia> a[1] = 3 + ERROR: Padding of type Tuple{UInt8, UInt32} is not compatible with type UInt32. + + julia> b = reinterpret(UInt32, Tuple{UInt8, UInt32}[(0x01, 0x00000002)]); # showing will error + + julia> b[1] + ERROR: Padding of type UInt32 is not compatible with type Tuple{UInt8, UInt32}. + ``` """ function reinterpret(::Type{T}, a::A) where {T,N,S,A<:AbstractArray{S, N}} function thrownonint(S::Type, T::Type, dim) @@ -350,9 +367,9 @@ axes(a::NonReshapedReinterpretArray{T,0}) where {T} = () has_offset_axes(a::ReinterpretArray) = has_offset_axes(a.parent) elsize(::Type{<:ReinterpretArray{T}}) where {T} = sizeof(T) -unsafe_convert(::Type{Ptr{T}}, a::ReinterpretArray{T,N,S} where N) where {T,S} = Ptr{T}(unsafe_convert(Ptr{S},a.parent)) +cconvert(::Type{Ptr{T}}, a::ReinterpretArray{T,N,S} where N) where {T,S} = cconvert(Ptr{S}, a.parent) -@inline @propagate_inbounds function getindex(a::NonReshapedReinterpretArray{T,0,S}) where {T,S} +@propagate_inbounds function getindex(a::NonReshapedReinterpretArray{T,0,S}) where {T,S} if isprimitivetype(T) && isprimitivetype(S) reinterpret(T, a.parent[]) else @@ -360,15 +377,28 @@ unsafe_convert(::Type{Ptr{T}}, a::ReinterpretArray{T,N,S} where N) where {T,S} = end end -@inline @propagate_inbounds getindex(a::ReinterpretArray) = a[firstindex(a)] +check_ptr_indexable(a::ReinterpretArray, sz = elsize(a)) = check_ptr_indexable(parent(a), sz) +check_ptr_indexable(a::ReshapedArray, sz) = check_ptr_indexable(parent(a), sz) +check_ptr_indexable(a::FastContiguousSubArray, sz) = check_ptr_indexable(parent(a), sz) +check_ptr_indexable(a::Array, sz) = sizeof(eltype(a)) !== sz +check_ptr_indexable(a::Memory, sz) = true +check_ptr_indexable(a::AbstractArray, sz) = false + +@propagate_inbounds getindex(a::ReinterpretArray) = a[firstindex(a)] + +@propagate_inbounds isassigned(a::ReinterpretArray, inds::Integer...) = checkbounds(Bool, a, inds...) && (check_ptr_indexable(a) || _isassigned_ra(a, inds...)) +@propagate_inbounds isassigned(a::ReinterpretArray, inds::SCartesianIndex2) = isassigned(a.parent, inds.j) +@propagate_inbounds _isassigned_ra(a::ReinterpretArray, inds...) = true # that is not entirely true, but computing exactly which indexes will be accessed in the parent requires a lot of duplication from the _getindex_ra code -@inline @propagate_inbounds function getindex(a::ReinterpretArray{T,N,S}, inds::Vararg{Int, N}) where {T,N,S} +@propagate_inbounds function getindex(a::ReinterpretArray{T,N,S}, inds::Vararg{Int, N}) where {T,N,S} check_readable(a) + check_ptr_indexable(a) && return _getindex_ptr(a, inds...) _getindex_ra(a, inds[1], tail(inds)) end -@inline @propagate_inbounds function getindex(a::ReinterpretArray{T,N,S}, i::Int) where {T,N,S} +@propagate_inbounds function getindex(a::ReinterpretArray{T,N,S}, i::Int) where {T,N,S} check_readable(a) + check_ptr_indexable(a) && return _getindex_ptr(a, i) if isa(IndexStyle(a), IndexLinear) return _getindex_ra(a, i, ()) end @@ -378,16 +408,22 @@ end isempty(inds) ? _getindex_ra(a, 1, ()) : _getindex_ra(a, inds[1], tail(inds)) end -@inline @propagate_inbounds function getindex(a::ReshapedReinterpretArray{T,N,S}, ind::SCartesianIndex2) where {T,N,S} +@propagate_inbounds function getindex(a::ReshapedReinterpretArray{T,N,S}, ind::SCartesianIndex2) where {T,N,S} check_readable(a) s = Ref{S}(a.parent[ind.j]) - GC.@preserve s begin - tptr = Ptr{T}(unsafe_convert(Ref{S}, s)) - return unsafe_load(tptr, ind.i) - end + tptr = Ptr{T}(unsafe_convert(Ref{S}, s)) + GC.@preserve s return unsafe_load(tptr, ind.i) +end + +@inline function _getindex_ptr(a::ReinterpretArray{T}, inds...) where {T} + @boundscheck checkbounds(a, inds...) + li = _to_linear_index(a, inds...) + ap = cconvert(Ptr{T}, a) + p = unsafe_convert(Ptr{T}, ap) + sizeof(T) * (li - 1) + GC.@preserve ap return unsafe_load(p) end -@inline @propagate_inbounds function _getindex_ra(a::NonReshapedReinterpretArray{T,N,S}, i1::Int, tailinds::TT) where {T,N,S,TT} +@propagate_inbounds function _getindex_ra(a::NonReshapedReinterpretArray{T,N,S}, i1::Int, tailinds::TT) where {T,N,S,TT} # Make sure to match the scalar reinterpret if that is applicable if sizeof(T) == sizeof(S) && (fieldcount(T) + fieldcount(S)) == 0 if issingletontype(T) # singleton types @@ -443,7 +479,7 @@ end end end -@inline @propagate_inbounds function _getindex_ra(a::ReshapedReinterpretArray{T,N,S}, i1::Int, tailinds::TT) where {T,N,S,TT} +@propagate_inbounds function _getindex_ra(a::ReshapedReinterpretArray{T,N,S}, i1::Int, tailinds::TT) where {T,N,S,TT} # Make sure to match the scalar reinterpret if that is applicable if sizeof(T) == sizeof(S) && (fieldcount(T) + fieldcount(S)) == 0 if issingletontype(T) # singleton types @@ -490,7 +526,7 @@ end end end -@inline @propagate_inbounds function setindex!(a::NonReshapedReinterpretArray{T,0,S}, v) where {T,S} +@propagate_inbounds function setindex!(a::NonReshapedReinterpretArray{T,0,S}, v) where {T,S} if isprimitivetype(S) && isprimitivetype(T) a.parent[] = reinterpret(S, v) return a @@ -498,15 +534,17 @@ end setindex!(a, v, firstindex(a)) end -@inline @propagate_inbounds setindex!(a::ReinterpretArray, v) = setindex!(a, v, firstindex(a)) +@propagate_inbounds setindex!(a::ReinterpretArray, v) = setindex!(a, v, firstindex(a)) -@inline @propagate_inbounds function setindex!(a::ReinterpretArray{T,N,S}, v, inds::Vararg{Int, N}) where {T,N,S} +@propagate_inbounds function setindex!(a::ReinterpretArray{T,N,S}, v, inds::Vararg{Int, N}) where {T,N,S} check_writable(a) + check_ptr_indexable(a) && return _setindex_ptr!(a, v, inds...) _setindex_ra!(a, v, inds[1], tail(inds)) end -@inline @propagate_inbounds function setindex!(a::ReinterpretArray{T,N,S}, v, i::Int) where {T,N,S} +@propagate_inbounds function setindex!(a::ReinterpretArray{T,N,S}, v, i::Int) where {T,N,S} check_writable(a) + check_ptr_indexable(a) && return _setindex_ptr!(a, v, i) if isa(IndexStyle(a), IndexLinear) return _setindex_ra!(a, v, i, ()) end @@ -514,7 +552,7 @@ end _setindex_ra!(a, v, inds[1], tail(inds)) end -@inline @propagate_inbounds function setindex!(a::ReshapedReinterpretArray{T,N,S}, v, ind::SCartesianIndex2) where {T,N,S} +@propagate_inbounds function setindex!(a::ReshapedReinterpretArray{T,N,S}, v, ind::SCartesianIndex2) where {T,N,S} check_writable(a) v = convert(T, v)::T s = Ref{S}(a.parent[ind.j]) @@ -526,7 +564,16 @@ end return a end -@inline @propagate_inbounds function _setindex_ra!(a::NonReshapedReinterpretArray{T,N,S}, v, i1::Int, tailinds::TT) where {T,N,S,TT} +@inline function _setindex_ptr!(a::ReinterpretArray{T}, v, inds...) where {T} + @boundscheck checkbounds(a, inds...) + li = _to_linear_index(a, inds...) + ap = cconvert(Ptr{T}, a) + p = unsafe_convert(Ptr{T}, ap) + sizeof(T) * (li - 1) + GC.@preserve ap unsafe_store!(p, v) + return a +end + +@propagate_inbounds function _setindex_ra!(a::NonReshapedReinterpretArray{T,N,S}, v, i1::Int, tailinds::TT) where {T,N,S,TT} v = convert(T, v)::T # Make sure to match the scalar reinterpret if that is applicable if sizeof(T) == sizeof(S) && (fieldcount(T) + fieldcount(S)) == 0 @@ -599,7 +646,7 @@ end return a end -@inline @propagate_inbounds function _setindex_ra!(a::ReshapedReinterpretArray{T,N,S}, v, i1::Int, tailinds::TT) where {T,N,S,TT} +@propagate_inbounds function _setindex_ra!(a::ReshapedReinterpretArray{T,N,S}, v, i1::Int, tailinds::TT) where {T,N,S,TT} v = convert(T, v)::T # Make sure to match the scalar reinterpret if that is applicable if sizeof(T) == sizeof(S) && (fieldcount(T) + fieldcount(S)) == 0 @@ -672,7 +719,7 @@ end """ CyclePadding(padding, total_size) -Cylces an iterator of `Padding` structs, restarting the padding at `total_size`. +Cycles an iterator of `Padding` structs, restarting the padding at `total_size`. E.g. if `padding` is all the padding in a struct and `total_size` is the total aligned size of that array, `CyclePadding` will correspond to the padding in an infinite vector of such structs. diff --git a/base/reshapedarray.jl b/base/reshapedarray.jl index bcb47a9359392..6cf2b9b482016 100644 --- a/base/reshapedarray.jl +++ b/base/reshapedarray.jl @@ -37,20 +37,30 @@ eltype(::Type{<:ReshapedArrayIterator{I}}) where {I} = @isdefined(I) ? ReshapedI ## reshape(::Array, ::Dims) returns an Array, except for isbitsunion eltypes (issue #28611) # reshaping to same # of dimensions -function reshape(a::Array{T,M}, dims::NTuple{N,Int}) where {T,N,M} +@eval function reshape(a::Array{T,M}, dims::NTuple{N,Int}) where {T,N,M} throw_dmrsa(dims, len) = - throw(DimensionMismatch("new dimensions $(dims) must be consistent with array size $len")) - - if prod(dims) != length(a) + throw(DimensionMismatch("new dimensions $(dims) must be consistent with array length $len")) + len = Core.checked_dims(dims...) # make sure prod(dims) doesn't overflow (and because of the comparison to length(a)) + if len != length(a) throw_dmrsa(dims, length(a)) end isbitsunion(T) && return ReshapedArray(a, dims, ()) if N == M && dims == size(a) return a end - ccall(:jl_reshape_array, Array{T,N}, (Any, Any, Any), Array{T,N}, a, dims) + ref = a.ref + if M == 1 && N !== 1 + mem = ref.mem::Memory{T} + if !(ref === GenericMemoryRef(mem) && len === mem.length) + mem = ccall(:jl_genericmemory_slice, Memory{T}, (Any, Ptr{Cvoid}, Int), mem, ref.ptr_or_offset, len) + ref = GenericMemoryRef(mem)::typeof(ref) + end + end + # or we could use `a = Array{T,N}(undef, ntuple(0, Val(N))); a.ref = ref; a.size = dims; return a` here + return $(Expr(:new, :(Array{T,N}), :ref, :dims)) end + """ reshape(A, dims...) -> AbstractArray reshape(A, dims) -> AbstractArray @@ -215,6 +225,11 @@ elsize(::Type{<:ReshapedArray{<:Any,<:Any,P}}) where {P} = elsize(P) unaliascopy(A::ReshapedArray) = typeof(A)(unaliascopy(A.parent), A.dims, A.mi) dataids(A::ReshapedArray) = dataids(A.parent) +# forward the aliasing check the parent in case there are specializations +mightalias(A::ReshapedArray, B::ReshapedArray) = mightalias(parent(A), parent(B)) +# special handling for reshaped SubArrays that dispatches to the subarray aliasing check +mightalias(A::ReshapedArray, B::SubArray) = mightalias(parent(A), B) +mightalias(A::SubArray, B::ReshapedArray) = mightalias(A, parent(B)) @inline ind2sub_rs(ax, ::Tuple{}, i::Int) = (i,) @inline ind2sub_rs(ax, strds, i) = _ind2sub_rs(ax, strds, i - 1) @@ -228,7 +243,8 @@ offset_if_vec(i::Integer, axs::Tuple) = i @inline function isassigned(A::ReshapedArrayLF, index::Int) @boundscheck checkbounds(Bool, A, index) || return false - @inbounds ret = isassigned(parent(A), index) + indexparent = index - firstindex(A) + firstindex(parent(A)) + @inbounds ret = isassigned(parent(A), indexparent) ret end @inline function isassigned(A::ReshapedArray{T,N}, indices::Vararg{Int, N}) where {T,N} @@ -241,7 +257,8 @@ end @inline function getindex(A::ReshapedArrayLF, index::Int) @boundscheck checkbounds(A, index) - @inbounds ret = parent(A)[index] + indexparent = index - firstindex(A) + firstindex(parent(A)) + @inbounds ret = parent(A)[indexparent] ret end @inline function getindex(A::ReshapedArray{T,N}, indices::Vararg{Int,N}) where {T,N} @@ -265,7 +282,8 @@ end @inline function setindex!(A::ReshapedArrayLF, val, index::Int) @boundscheck checkbounds(A, index) - @inbounds parent(A)[index] = val + indexparent = index - firstindex(A) + firstindex(parent(A)) + @inbounds parent(A)[indexparent] = val val end @inline function setindex!(A::ReshapedArray{T,N}, val, indices::Vararg{Int,N}) where {T,N} @@ -293,7 +311,7 @@ setindex!(A::ReshapedRange, val, index::ReshapedIndex) = _rs_setindex!_err() @noinline _rs_setindex!_err() = error("indexed assignment fails for a reshaped range; consider calling collect") -unsafe_convert(::Type{Ptr{T}}, a::ReshapedArray{T}) where {T} = unsafe_convert(Ptr{T}, parent(a)) +cconvert(::Type{Ptr{T}}, a::ReshapedArray{T}) where {T} = cconvert(Ptr{T}, parent(a)) # Add a few handy specializations to further speed up views of reshaped ranges const ReshapedUnitRange{T,N,A<:AbstractUnitRange} = ReshapedArray{T,N,A,Tuple{}} @@ -304,9 +322,18 @@ compute_offset1(parent::AbstractVector, stride1::Integer, I::Tuple{ReshapedRange (@inline; first(I[1]) - first(axes1(I[1]))*stride1) substrides(strds::NTuple{N,Int}, I::Tuple{ReshapedUnitRange, Vararg{Any}}) where N = (size_to_strides(strds[1], size(I[1])...)..., substrides(tail(strds), tail(I))...) -unsafe_convert(::Type{Ptr{T}}, V::SubArray{T,N,P,<:Tuple{Vararg{Union{RangeIndex,ReshapedUnitRange}}}}) where {T,N,P} = - unsafe_convert(Ptr{T}, V.parent) + (first_index(V)-1)*sizeof(T) +# cconvert(::Type{<:Ptr}, V::SubArray{T,N,P,<:Tuple{Vararg{Union{RangeIndex,ReshapedUnitRange}}}}) where {T,N,P} = V +function unsafe_convert(::Type{Ptr{S}}, V::SubArray{T,N,P,<:Tuple{Vararg{Union{RangeIndex,ReshapedUnitRange}}}}) where {S,T,N,P} + parent = V.parent + p = cconvert(Ptr{T}, parent) # XXX: this should occur in cconvert, the result is not GC-rooted + Δmem = if _checkcontiguous(Bool, parent) + (first_index(V) - firstindex(parent)) * elsize(parent) + else + _memory_offset(parent, map(first, V.indices)...) + end + return Ptr{S}(unsafe_convert(Ptr{T}, p) + Δmem) +end _checkcontiguous(::Type{Bool}, A::AbstractArray) = false # `strides(A::DenseArray)` calls `size_to_strides` by default. diff --git a/base/rounding.jl b/base/rounding.jl index 2da605bc36f8f..d80edda1e418f 100644 --- a/base/rounding.jl +++ b/base/rounding.jl @@ -131,15 +131,14 @@ rounds_away_from_zero(t::Tuple{Any,Bool}) = rounds_away_from_zero(t...) tie_breaker_is_to_even(t::Tuple{Any,Bool}) = tie_breaker_is_to_even(first(t)) tie_breaker_rounds_away_from_zero(t::Tuple{Any,Bool}) = tie_breaker_rounds_away_from_zero(t...) -abstract type RoundingIncrementHelper end -struct FinalBit <: RoundingIncrementHelper end -struct RoundBit <: RoundingIncrementHelper end -struct StickyBit <: RoundingIncrementHelper end +struct FinalBit end +struct RoundBit end +struct StickyBit end function correct_rounding_requires_increment(x, rounding_mode, sign_bit::Bool) r = (rounding_mode, sign_bit) f = let y = x - (z::RoundingIncrementHelper) -> y(z)::Bool + (z::Union{FinalBit,RoundBit,StickyBit}) -> y(z)::Bool end if rounds_to_nearest(r) if f(RoundBit()) diff --git a/base/ryu/Ryu.jl b/base/ryu/Ryu.jl index 9b236caeb6ff1..89589aa4ab668 100644 --- a/base/ryu/Ryu.jl +++ b/base/ryu/Ryu.jl @@ -112,7 +112,7 @@ end function Base.show(io::IO, x::T, forceuntyped::Bool=false, fromprint::Bool=false) where {T <: Base.IEEEFloat} compact = get(io, :compact, false)::Bool buf = Base.StringVector(neededdigits(T)) - typed = !forceuntyped && !compact && get(io, :typeinfo, Any) != typeof(x) + typed = !forceuntyped && !compact && Base.nonnothing_nonmissing_typeinfo(io) != typeof(x) pos = writeshortest(buf, 1, x, false, false, true, -1, (x isa Float32 && !fromprint) ? UInt8('f') : UInt8('e'), false, UInt8('.'), typed, compact) write(io, resize!(buf, pos - 1)) diff --git a/base/ryu/exp.jl b/base/ryu/exp.jl index b38b2c7ae9a29..4f749668867e2 100644 --- a/base/ryu/exp.jl +++ b/base/ryu/exp.jl @@ -7,7 +7,7 @@ function writeexp(buf, pos, v::T, pos = append_sign(x, plus, space, buf, pos) # special cases - if x == 0 + if iszero(x) @inbounds buf[pos] = UInt8('0') pos += 1 if precision > 0 && !trimtrailingzeros @@ -42,7 +42,7 @@ function writeexp(buf, pos, v::T, mant = bits & MANTISSA_MASK exp = Int((bits >> 52) & EXP_MASK) - if exp == 0 + if iszero(exp) e2 = 1 - 1023 - 52 m2 = mant else @@ -51,7 +51,7 @@ function writeexp(buf, pos, v::T, end nonzero = false precision += 1 - digits = 0 + digits = zero(UInt32) printedDigits = 0 availableDigits = 0 e = 0 @@ -64,14 +64,14 @@ function writeexp(buf, pos, v::T, j = p10bits - e2 #=@inbounds=# mula, mulb, mulc = POW10_SPLIT[POW10_OFFSET[idx + 1] + i + 1] digits = mulshiftmod1e9(m2 << 8, mula, mulb, mulc, j + 8) - if printedDigits != 0 + if !iszero(printedDigits) if printedDigits + 9 > precision availableDigits = 9 break end pos = append_nine_digits(digits, buf, pos) printedDigits += 9 - elseif digits != 0 + elseif !iszero(digits) availableDigits = decimallength(digits) e = i * 9 + availableDigits - 1 if availableDigits > precision @@ -93,26 +93,26 @@ function writeexp(buf, pos, v::T, i -= 1 end end - if e2 < 0 && availableDigits == 0 + if e2 < 0 && iszero(availableDigits) idx = div(-e2, 16) - i = MIN_BLOCK_2[idx + 1] + i = Int(MIN_BLOCK_2[idx + 1]) while i < 200 j = 120 + (-e2 - 16 * idx) p = POW10_OFFSET_2[idx + 1] + i - MIN_BLOCK_2[idx + 1] if p >= POW10_OFFSET_2[idx + 2] - digits = 0 + digits = zero(UInt32) else #=@inbounds=# mula, mulb, mulc = POW10_SPLIT_2[p + 1] digits = mulshiftmod1e9(m2 << 8, mula, mulb, mulc, j + 8) end - if printedDigits != 0 + if !iszero(printedDigits) if printedDigits + 9 > precision availableDigits = 9 break end pos = append_nine_digits(digits, buf, pos) printedDigits += 9 - elseif digits != 0 + elseif !iszero(digits) availableDigits = decimallength(digits) e = -(i + 1) * 9 + availableDigits - 1 if availableDigits > precision @@ -135,14 +135,14 @@ function writeexp(buf, pos, v::T, end end maximum = precision - printedDigits - if availableDigits == 0 - digits = 0 + if iszero(availableDigits) + digits = zero(UInt32) end - lastDigit = 0 + lastDigit = zero(UInt32) if availableDigits > maximum for k = 0:(availableDigits - maximum - 1) - lastDigit = digits % 10 - digits = div(digits, 10) + lastDigit = digits % UInt32(10) + digits = div(digits, UInt32(10)) end end roundUp = 0 @@ -159,8 +159,8 @@ function writeexp(buf, pos, v::T, end roundUp = trailingZeros ? 2 : 1 end - if printedDigits != 0 - if digits == 0 + if !iszero(printedDigits) + if iszero(digits) for _ = 1:maximum @inbounds buf[pos] = UInt8('0') pos += 1 @@ -180,7 +180,7 @@ function writeexp(buf, pos, v::T, end end end - if roundUp != 0 + if !iszero(roundUp) roundPos = pos while true roundPos -= 1 @@ -197,7 +197,7 @@ function writeexp(buf, pos, v::T, roundUp = 1 continue else - if roundUp == 2 && UInt8(c) % 2 == 0 + if roundUp == 2 && iseven(c) break end @inbounds buf[roundPos] = c + 1 @@ -224,7 +224,7 @@ function writeexp(buf, pos, v::T, pos += 1 end if e >= 100 - c = e % 10 + c = (e % 10) % UInt8 @inbounds d100 = DIGIT_TABLE16[div(e, 10) + 1] @inbounds buf[pos] = d100 % UInt8 @inbounds buf[pos + 1] = (d100 >> 0x8) % UInt8 diff --git a/base/scopedvalues.jl b/base/scopedvalues.jl index 4c46809e75ffe..6ccd4687c5c65 100644 --- a/base/scopedvalues.jl +++ b/base/scopedvalues.jl @@ -3,6 +3,7 @@ module ScopedValues export ScopedValue, with, @with +public get """ ScopedValue(x) @@ -19,6 +20,8 @@ Dynamic scopes are propagated across tasks. # Examples ```jldoctest +julia> using Base.ScopedValues; + julia> const sval = ScopedValue(1); julia> sval[] @@ -38,7 +41,9 @@ julia> sval[] implementation is available from the package ScopedValues.jl. """ mutable struct ScopedValue{T} - const has_default::Bool + # NOTE this struct must be defined as mutable one since it's used as a key of + # `ScopeStorage` dictionary and thus needs object identity + const has_default::Bool # this field is necessary since isbitstype `default` field may be initialized with undefined value const default::T ScopedValue{T}() where T = new(false) ScopedValue{T}(val) where T = new{T}(true, val) @@ -50,13 +55,33 @@ Base.eltype(::ScopedValue{T}) where {T} = T """ isassigned(val::ScopedValue) -Test if the ScopedValue has a default value. +Test whether a `ScopedValue` has an assigned value. + +See also: [`ScopedValues.with`](@ref), [`ScopedValues.@with`](@ref), [`ScopedValues.get`](@ref). + +# Examples +```jldoctest +julia> using Base.ScopedValues + +julia> a = ScopedValue(1); b = ScopedValue{Int}(); + +julia> isassigned(a) +true + +julia> isassigned(b) +false +``` """ -Base.isassigned(val::ScopedValue) = val.has_default +function Base.isassigned(val::ScopedValue) + val.has_default && return true + scope = Core.current_scope()::Union{Scope, Nothing} + scope === nothing && return false + return haskey((scope::Scope).values, val) +end const ScopeStorage = Base.PersistentDict{ScopedValue, Any} -mutable struct Scope +struct Scope values::ScopeStorage end @@ -68,20 +93,16 @@ function Scope(parent::Union{Nothing, Scope}, key::ScopedValue{T}, value) where return Scope(ScopeStorage(parent.values, key=>val)) end -function Scope(scope, pairs::Pair{<:ScopedValue}...) - for pair in pairs - scope = Scope(scope, pair...) - end - return scope::Scope +function Scope(scope, pair::Pair{<:ScopedValue}) + return Scope(scope, pair...) end -Scope(::Nothing) = nothing -""" - current_scope()::Union{Nothing, Scope} - -Return the current dynamic scope. -""" -current_scope() = current_task().scope::Union{Nothing, Scope} +function Scope(scope, pair1::Pair{<:ScopedValue}, pair2::Pair{<:ScopedValue}, pairs::Pair{<:ScopedValue}...) + # Unroll this loop through recursion to make sure that + # our compiler optimization support works + return Scope(Scope(scope, pair1...), pair2, pairs...) +end +Scope(::Nothing) = nothing function Base.show(io::IO, scope::Scope) print(io, Scope, "(") @@ -109,20 +130,34 @@ const novalue = NoValue() If the scoped value isn't set and doesn't have a default value, return `nothing`. Otherwise returns `Some{T}` with the current value. + +See also: [`ScopedValues.with`](@ref), [`ScopedValues.@with`](@ref), [`ScopedValues.ScopedValue`](@ref). + +# Examples +```jldoctest +julia> using Base.ScopedValues + +julia> a = ScopedValue(42); b = ScopedValue{Int}(); + +julia> ScopedValues.get(a) +Some(42) + +julia> isnothing(ScopedValues.get(b)) +true +``` """ function get(val::ScopedValue{T}) where {T} - # Inline current_scope to avoid doing the type assertion twice. - scope = current_task().scope + scope = Core.current_scope()::Union{Scope, Nothing} if scope === nothing - isassigned(val) && return Some(val.default) + val.has_default && return Some{T}(val.default) return nothing end scope = scope::Scope - if isassigned(val) - return Some(Base.get(scope.values, val, val.default)::T) + if val.has_default + return Some{T}(Base.get(scope.values, val, val.default)::T) else v = Base.get(scope.values, val, novalue) - v === novalue || return Some(v::T) + v === novalue || return Some{T}(v::T) end return nothing end @@ -147,30 +182,32 @@ function Base.show(io::IO, val::ScopedValue) end """ - with(f, (var::ScopedValue{T} => val::T)...) + @with (var::ScopedValue{T} => val)... expr -Execute `f` in a new scope with `var` set to `val`. -""" -function with(f, pair::Pair{<:ScopedValue}, rest::Pair{<:ScopedValue}...) - @nospecialize - ct = Base.current_task() - current_scope = ct.scope::Union{Nothing, Scope} - ct.scope = Scope(current_scope, pair, rest...) - try - return f() - finally - ct.scope = current_scope - end -end +Macro version of `with`. The expression `@with var=>val expr` evaluates `expr` in a +new dynamic scope with `var` set to `val`. `val` will be converted to type `T`. +`@with var=>val expr` is equivalent to `with(var=>val) do expr end`, but `@with` +avoids creating a closure. -with(@nospecialize(f)) = f() +See also: [`ScopedValues.with`](@ref), [`ScopedValues.ScopedValue`](@ref), [`ScopedValues.get`](@ref). -""" - @with vars... expr +# Examples +```jldoctest +julia> using Base.ScopedValues + +julia> const a = ScopedValue(1); + +julia> f(x) = a[] + x; + +julia> @with a=>2 f(10) +12 -Macro version of `with(f, vars...)` but with `expr` instead of `f` function. -This is similar to using [`with`](@ref) with a `do` block, but avoids creating -a closure. +julia> @with a=>3 begin + x = 100 + f(x) + end +103 +``` """ macro with(exprs...) if length(exprs) > 1 @@ -182,18 +219,53 @@ macro with(exprs...) else error("@with expects at least one argument") end - for expr in exprs - if expr.head !== :call || first(expr.args) !== :(=>) - error("@with expects arguments of the form `A => 2` got $expr") - end - end exprs = map(esc, exprs) - quote - ct = $(Base.current_task)() - current_scope = ct.scope::$(Union{Nothing, Scope}) - ct.scope = $(Scope)(current_scope, $(exprs...)) - $(Expr(:tryfinally, esc(ex), :(ct.scope = current_scope))) - end + Expr(:tryfinally, esc(ex), nothing, :(Scope(Core.current_scope()::Union{Nothing, Scope}, $(exprs...)))) +end + +""" + with(f, (var::ScopedValue{T} => val)...) + +Execute `f` in a new dynamic scope with `var` set to `val`. `val` will be converted +to type `T`. + +See also: [`ScopedValues.@with`](@ref), [`ScopedValues.ScopedValue`](@ref), [`ScopedValues.get`](@ref). + +# Examples +```jldoctest +julia> using Base.ScopedValues + +julia> a = ScopedValue(1); + +julia> f(x) = a[] + x; + +julia> f(10) +11 + +julia> with(a=>2) do + f(10) + end +12 + +julia> f(10) +11 + +julia> b = ScopedValue(2); + +julia> g(x) = a[] + b[] + x; + +julia> with(a=>10, b=>20) do + g(30) + end +60 + +julia> with(() -> a[] * b[], a=>3, b=>4) +12 +``` +""" +function with(f, pair::Pair{<:ScopedValue}, rest::Pair{<:ScopedValue}...) + @with(pair, rest..., f()) end +with(@nospecialize(f)) = f() end # module ScopedValues diff --git a/base/secretbuffer.jl b/base/secretbuffer.jl index eedfd8cbe84c1..bf37c3caa6c23 100644 --- a/base/secretbuffer.jl +++ b/base/secretbuffer.jl @@ -29,12 +29,12 @@ true ``` """ mutable struct SecretBuffer <: IO - data::Vector{UInt8} + data::Memory{UInt8} size::Int ptr::Int function SecretBuffer(; sizehint=128) - s = new(Vector{UInt8}(undef, sizehint), 0, 1) + s = new(Memory{UInt8}(undef, sizehint), 0, 1) finalizer(final_shred!, s) return s end @@ -49,7 +49,7 @@ Strings are bad at keeping secrets because they are unable to be securely zeroed or destroyed. Therefore, avoid using this constructor with secret data. Instead of starting with a string, either construct the `SecretBuffer` incrementally with `SecretBuffer()` and [`write`](@ref), or use a `Vector{UInt8}` with -the `Base.SecretBuffer!(::Vector{UInt8})` constructor. +the `Base.SecretBuffer!(::AbstractVector{UInt8})` constructor. """ SecretBuffer(str::AbstractString) = SecretBuffer(String(str)) function SecretBuffer(str::String) @@ -68,7 +68,7 @@ convert(::Type{SecretBuffer}, s::AbstractString) = SecretBuffer(String(s)) Initialize a new `SecretBuffer` from `data`, securely zeroing `data` afterwards. """ -function SecretBuffer!(d::Vector{UInt8}) +function SecretBuffer!(d::AbstractVector{UInt8}) len = length(d) s = SecretBuffer(sizehint=len) for i in 1:len @@ -106,7 +106,7 @@ show(io::IO, s::SecretBuffer) = print(io, "SecretBuffer(\"*******\")") ==(s1::SecretBuffer, s2::SecretBuffer) = (s1.ptr == s2.ptr) && (s1.size == s2.size) && (UInt8(0) == _bufcmp(s1.data, s2.data, min(s1.size, s2.size))) # Also attempt a constant time buffer comparison algorithm — the length of the secret might be # inferred by a timing attack, but not its values. -@noinline function _bufcmp(data1::Vector{UInt8}, data2::Vector{UInt8}, sz::Int) +@noinline function _bufcmp(data1::Memory{UInt8}, data2::Memory{UInt8}, sz::Int) res = UInt8(0) for i = 1:sz res |= xor(data1[i], data2[i]) @@ -117,11 +117,23 @@ end const _sb_hash = UInt === UInt32 ? 0x111c0925 : 0xb06061e370557428 hash(s::SecretBuffer, h::UInt) = hash(_sb_hash, h) +copy(s::SecretBuffer) = copy!(SecretBuffer(sizehint=length(s.data)), s) +function copy!(dest::SecretBuffer, src::SecretBuffer) + if length(dest.data) != length(src.data) + securezero!(dest.data) + dest.data = copy(src.data) + else + copyto!(dest.data, src.data) + end + dest.size = src.size + dest.ptr = src.ptr + return dest +end function write(io::SecretBuffer, b::UInt8) if io.ptr > length(io.data) # We need to resize! the array: do this manually to ensure no copies are left behind - newdata = Vector{UInt8}(undef, (io.size+16)*2) + newdata = Memory{UInt8}(undef, (io.size+16)*2) copyto!(newdata, io.data) securezero!(io.data) io.data = newdata @@ -140,8 +152,7 @@ function write(io::IO, s::SecretBuffer) return nb end -cconvert(::Type{Cstring}, s::SecretBuffer) = unsafe_convert(Cstring, s) -function unsafe_convert(::Type{Cstring}, s::SecretBuffer) +function cconvert(::Type{Cstring}, s::SecretBuffer) # Ensure that no nuls appear in the valid region if any(==(0x00), s.data[i] for i in 1:s.size) throw(ArgumentError("`SecretBuffers` containing nul bytes cannot be converted to C strings")) @@ -152,8 +163,10 @@ function unsafe_convert(::Type{Cstring}, s::SecretBuffer) write(s, '\0') s.ptr = p s.size -= 1 - return Cstring(unsafe_convert(Ptr{Cchar}, s.data)) + return s.data end +# optional shim for manual calls to unsafe_convert: +# unsafe_convert(::Type{Cstring}, s::SecretBuffer) = unsafe_convert(Cstring, cconvert(Cstring, s)) seek(io::SecretBuffer, n::Integer) = (io.ptr = max(min(n+1, io.size+1), 1); io) seekend(io::SecretBuffer) = seek(io, io.size+1) @@ -187,7 +200,7 @@ resetting its pointer and size. This function is used to securely erase the sensitive data held in the buffer, reducing the potential for information leaks. -# Example +# Examples ```julia s = SecretBuffer() write(s, 's', 'e', 'c', 'r', 'e', 't') diff --git a/base/set.jl b/base/set.jl index a91bf328bd911..2f96cef626b6f 100644 --- a/base/set.jl +++ b/base/set.jl @@ -91,18 +91,70 @@ isempty(s::Set) = isempty(s.dict) length(s::Set) = length(s.dict) in(x, s::Set) = haskey(s.dict, x) -# This avoids hashing and probing twice and it works the same as -# in!(x, s::Set) = in(x, s) ? true : (push!(s, x); false) +""" + in!(x, s::AbstractSet) -> Bool + +If `x` is in `s`, return `true`. If not, push `x` into `s` and return `false`. +This is equivalent to `in(x, s) ? true : (push!(s, x); false)`, but may have a +more efficient implementation. + +See also: [`in`](@ref), [`push!`](@ref), [`Set`](@ref) + +!!! compat "Julia 1.11" + This function requires at least 1.11. + +# Examples +```jldoctest; filter = r"^ [1234]\$" +julia> s = Set{Any}([1, 2, 3]); in!(4, s) +false + +julia> length(s) +4 + +julia> in!(0x04, s) +true + +julia> s +Set{Any} with 4 elements: + 4 + 2 + 3 + 1 +``` +""" +function in!(x, s::AbstractSet) + x ∈ s ? true : (push!(s, x); false) +end + function in!(x, s::Set) - idx, sh = ht_keyindex2_shorthash!(s.dict, x) + xT = convert(eltype(s), x) + idx, sh = ht_keyindex2_shorthash!(s.dict, xT) idx > 0 && return true - _setindex!(s.dict, nothing, x, -idx, sh) + _setindex!(s.dict, nothing, xT, -idx, sh) return false end push!(s::Set, x) = (s.dict[x] = nothing; s) -pop!(s::Set, x) = (pop!(s.dict, x); x) -pop!(s::Set, x, default) = (x in s ? pop!(s, x) : default) + +function pop!(s::Set, x, default) + dict = s.dict + index = ht_keyindex(dict, x) + if index > 0 + @inbounds key = dict.keys[index] + _delete!(dict, index) + return key + else + return default + end +end + +function pop!(s::Set, x) + index = ht_keyindex(s.dict, x) + index < 1 && throw(KeyError(x)) + result = @inbounds s.dict.keys[index] + _delete!(s.dict, index) + result +end function pop!(s::Set) isempty(s) && throw(ArgumentError("set must be non-empty")) @@ -117,12 +169,14 @@ copymutable(s::Set{T}) where {T} = Set{T}(s) # Set is the default mutable fall-back copymutable(s::AbstractSet{T}) where {T} = Set{T}(s) -sizehint!(s::Set, newsz) = (sizehint!(s.dict, newsz); s) +sizehint!(s::Set, newsz; shrink::Bool=true) = (sizehint!(s.dict, newsz; shrink); s) empty!(s::Set) = (empty!(s.dict); s) rehash!(s::Set) = (rehash!(s.dict); s) iterate(s::Set, i...) = iterate(KeySet(s.dict), i...) +@propagate_inbounds Iterators.only(s::Set) = Iterators._only(s, first) + # In case the size(s) is smaller than size(t) its more efficient to iterate through # elements of s instead and only delete the ones also contained in t. # The threshold for this decision boils down to a tradeoff between @@ -147,7 +201,7 @@ end unique(itr) Return an array containing only the unique elements of collection `itr`, -as determined by [`isequal`](@ref), in the order that the first of each +as determined by [`isequal`](@ref) and [`hash`](@ref), in the order that the first of each set of equivalent elements originally appears. The element type of the input is preserved. @@ -382,7 +436,7 @@ end """ unique!(A::AbstractVector) -Remove duplicate items as determined by [`isequal`](@ref), then return the modified `A`. +Remove duplicate items as determined by [`isequal`](@ref) and [`hash`](@ref), then return the modified `A`. `unique!` will return the elements of `A` in the order that they occur. If you do not care about the order of the returned data, then calling `(sort!(A); unique!(A))` will be much more efficient as long as the elements of `A` can be sorted. @@ -425,11 +479,21 @@ end """ allunique(itr) -> Bool + allunique(f, itr) -> Bool Return `true` if all values from `itr` are distinct when compared with [`isequal`](@ref). +Or if all of `[f(x) for x in itr]` are distinct, for the second method. + +Note that `allunique(f, itr)` may call `f` fewer than `length(itr)` times. +The precise number of calls is regarded as an implementation detail. + +`allunique` may use a specialized implementation when the input is sorted. See also: [`unique`](@ref), [`issorted`](@ref), [`allequal`](@ref). +!!! compat "Julia 1.11" + The method `allunique(f, itr)` requires at least Julia 1.11. + # Examples ```jldoctest julia> allunique([1, 2, 3]) @@ -443,6 +507,9 @@ false julia> allunique([NaN, 2.0, NaN, 4.0]) false + +julia> allunique(abs, [1, -1, 2]) +false ``` """ function allunique(C) @@ -453,8 +520,10 @@ function allunique(C) return _hashed_allunique(C) end +allunique(f, xs) = allunique(Generator(f, xs)) + function _hashed_allunique(C) - seen = Set{eltype(C)}() + seen = Set{@default_eltype(C)}() x = iterate(C) if haslength(C) && length(C) > 1000 for i in OneTo(1000) @@ -476,7 +545,31 @@ allunique(::Union{AbstractSet,AbstractDict}) = true allunique(r::AbstractRange) = !iszero(step(r)) || length(r) <= 1 -allunique(A::StridedArray) = length(A) < 32 ? _indexed_allunique(A) : _hashed_allunique(A) +function allunique(A::StridedArray) + if length(A) < 32 + _indexed_allunique(A) + elseif OrderStyle(eltype(A)) === Ordered() + a1, rest1 = Iterators.peel(A) + a2, rest = Iterators.peel(rest1) + if !isequal(a1, a2) + compare = isless(a1, a2) ? isless : (a,b) -> isless(b,a) + for a in rest + if compare(a2, a) + a2 = a + elseif isequal(a2, a) + return false + else + return _hashed_allunique(A) + end + end + else # isequal(a1, a2) + return false + end + return true + else + _hashed_allunique(A) + end +end function _indexed_allunique(A) length(A) < 2 && return true @@ -502,16 +595,30 @@ function allunique(t::Tuple) end allunique(t::Tuple{}) = true +function allunique(f::F, t::Tuple) where {F} + length(t) < 2 && return true + length(t) < 32 || return _hashed_allunique(Generator(f, t)) + return allunique(map(f, t)) +end + """ allequal(itr) -> Bool + allequal(f, itr) -> Bool Return `true` if all values from `itr` are equal when compared with [`isequal`](@ref). +Or if all of `[f(x) for x in itr]` are equal, for the second method. + +Note that `allequal(f, itr)` may call `f` fewer than `length(itr)` times. +The precise number of calls is regarded as an implementation detail. See also: [`unique`](@ref), [`allunique`](@ref). !!! compat "Julia 1.8" The `allequal` function requires at least Julia 1.8. +!!! compat "Julia 1.11" + The method `allequal(f, itr)` requires at least Julia 1.11. + # Examples ```jldoctest julia> allequal([]) @@ -528,14 +635,36 @@ false julia> allequal(Dict(:a => 1, :b => 1)) false + +julia> allequal(abs2, [1, -1]) +true ``` """ -allequal(itr) = isempty(itr) ? true : all(isequal(first(itr)), itr) +function allequal(itr) + if haslength(itr) + length(itr) <= 1 && return true + end + pl = Iterators.peel(itr) + isnothing(pl) && return true + a, rest = pl + return all(isequal(a), rest) +end allequal(c::Union{AbstractSet,AbstractDict}) = length(c) <= 1 allequal(r::AbstractRange) = iszero(step(r)) || length(r) <= 1 +allequal(f, xs) = allequal(Generator(f, xs)) + +function allequal(f, xs::Tuple) + length(xs) <= 1 && return true + f1 = f(xs[1]) + for x in tail(xs) + isequal(f1, f(x)) || return false + end + return true +end + filter!(f, s::Set) = unsafe_filter!(f, s) const hashs_seed = UInt === UInt64 ? 0x852ada37cfe8e0ce : 0xcfe8e0ce diff --git a/base/shell.jl b/base/shell.jl index 5bfd11fb46d29..137150b585d86 100644 --- a/base/shell.jl +++ b/base/shell.jl @@ -4,7 +4,19 @@ const shell_special = "#{}()[]<>|&*?~;" -# strips the end but respects the space when the string ends with "\\ " +(@doc raw""" + rstrip_shell(s::AbstractString) + +Strip trailing whitespace from a shell command string, while respecting a trailing backslash followed by a space ("\\ "). + +```jldoctest +julia> Base.rstrip_shell("echo 'Hello World' \\ ") +"echo 'Hello World' \\ " + +julia> Base.rstrip_shell("echo 'Hello World' ") +"echo 'Hello World'" +``` +""" function rstrip_shell(s::AbstractString) c_old = nothing for (i, c) in Iterators.reverse(pairs(s)) @@ -14,16 +26,15 @@ function rstrip_shell(s::AbstractString) c_old = c end SubString(s, 1, 0) -end +end) function shell_parse(str::AbstractString, interpolate::Bool=true; special::AbstractString="", filename="none") - s = SubString(str, firstindex(str)) + last_arg = firstindex(str) # N.B.: This is used by REPLCompletions + s = SubString(str, last_arg) s = rstrip_shell(lstrip(s)) - # N.B.: This is used by REPLCompletions - last_parse = 0:-1 - isempty(s) && return interpolate ? (Expr(:tuple,:()),last_parse) : ([],last_parse) + isempty(s) && return interpolate ? (Expr(:tuple,:()), last_arg) : ([], last_arg) in_single_quotes = false in_double_quotes = false @@ -32,6 +43,7 @@ function shell_parse(str::AbstractString, interpolate::Bool=true; arg = [] i = firstindex(s) st = Iterators.Stateful(pairs(s)) + update_last_arg = false # true after spaces or interpolate function push_nonempty!(list, x) if !isa(x,AbstractString) || !isempty(x) @@ -54,6 +66,7 @@ function shell_parse(str::AbstractString, interpolate::Bool=true; for (j, c) in st j, c = j::Int, c::C if !in_single_quotes && !in_double_quotes && isspace(c) + update_last_arg = true i = consume_upto!(arg, s, i, j) append_2to1!(args, arg) while !isempty(st) @@ -77,12 +90,17 @@ function shell_parse(str::AbstractString, interpolate::Bool=true; # use parseatom instead of parse to respect filename (#28188) ex, j = Meta.parseatom(s, stpos, filename=filename) end - last_parse = (stpos:prevind(s, j)) .+ s.offset - push_nonempty!(arg, ex) + last_arg = stpos + s.offset + update_last_arg = true + push!(arg, ex) s = SubString(s, j) Iterators.reset!(st, pairs(s)) i = firstindex(s) else + if update_last_arg + last_arg = i + s.offset + update_last_arg = false + end if !in_double_quotes && c == '\'' in_single_quotes = !in_single_quotes i = consume_upto!(arg, s, i, j) @@ -124,16 +142,31 @@ function shell_parse(str::AbstractString, interpolate::Bool=true; push_nonempty!(arg, s[i:end]) append_2to1!(args, arg) - interpolate || return args, last_parse + interpolate || return args, last_arg # construct an expression ex = Expr(:tuple) for arg in args push!(ex.args, Expr(:tuple, arg...)) end - return ex, last_parse + return ex, last_arg end +""" + shell_split(command::AbstractString) + +Split a shell command string into its individual components. + +# Examples +```jldoctest +julia> Base.shell_split("git commit -m 'Initial commit'") +4-element Vector{String}: + "git" + "commit" + "-m" + "Initial commit" +``` +""" function shell_split(s::AbstractString) parsed = shell_parse(s, false)[1] args = String[] @@ -216,7 +249,7 @@ function print_shell_escaped_posixly(io::IO, args::AbstractString...) function isword(c::AbstractChar) if '0' <= c <= '9' || 'a' <= c <= 'z' || 'A' <= c <= 'Z' # word characters - elseif c == '_' || c == '/' || c == '+' || c == '-' + elseif c == '_' || c == '/' || c == '+' || c == '-' || c == '.' # other common characters elseif c == '\'' have_single = true @@ -383,7 +416,7 @@ rather than returned as a string. See also [`escape_microsoft_c_args`](@ref), [`shell_escape_posixly`](@ref). -# Example +# Examples ```jldoctest julia> Base.shell_escape_wincmd("a^\\"^o\\"^u\\"") "a^^\\"^o\\"^^u^\\"" diff --git a/base/show.jl b/base/show.jl index 079bf5a423cc0..8f52fefdd7152 100644 --- a/base/show.jl +++ b/base/show.jl @@ -23,6 +23,13 @@ function show(io::IO, ::MIME"text/plain", r::LinRange) print_range(io, r) end +function show(io::IO, ::MIME"text/plain", r::LogRange) # display LogRange like LinRange + isempty(r) && return show(io, r) + summary(io, r) + println(io, ":") + print_range(io, r, " ", ", ", "", " \u2026 ") +end + function _isself(ft::DataType) ftname = ft.name isdefined(ftname, :mt) || return false @@ -80,7 +87,7 @@ function iterate(I::ANSIIterator, (i, m_st)=(1, iterate(I.captures))) end textwidth(I::ANSIIterator) = mapreduce(textwidth∘last, +, I; init=0) -function _truncate_at_width_or_chars(ignore_ANSI::Bool, str, width, rpad=false, chars="\r\n", truncmark="…") +function _truncate_at_width_or_chars(ignore_ANSI::Bool, str::AbstractString, width::Int, rpad::Bool=false, chars="\r\n", truncmark="…") truncwidth = textwidth(truncmark) (width <= 0 || width < truncwidth) && return "" wid = truncidx = lastidx = 0 @@ -294,27 +301,35 @@ struct IOContext{IO_t <: IO} <: AbstractPipe dict::ImmutableDict{Symbol, Any} function IOContext{IO_t}(io::IO_t, dict::ImmutableDict{Symbol, Any}) where IO_t<:IO - @assert !(IO_t <: IOContext) "Cannot create `IOContext` from another `IOContext`." + io isa IOContext && (io = io.io) # implicitly unwrap, since the io.dict field is not useful anymore, and could confuse pipe_reader consumers return new(io, dict) end end -# (Note that TTY and TTYTerminal io types have a :color property.) -unwrapcontext(io::IO) = io, get(io,:color,false) ? ImmutableDict{Symbol,Any}(:color, true) : ImmutableDict{Symbol,Any}() -unwrapcontext(io::IOContext) = io.io, io.dict +# (Note that TTY and TTYTerminal io types have an implied :color property.) +ioproperties(io::IO) = get(io, :color, false) ? ImmutableDict{Symbol,Any}(:color, true) : ImmutableDict{Symbol,Any}() +ioproperties(io::IOContext) = io.dict +# these can probably be deprecated, but there is a use in the ecosystem for them +unwrapcontext(io::IO) = (io,) +unwrapcontext(io::IOContext) = (io.io,) -function IOContext(io::IO, dict::ImmutableDict) - io0 = unwrapcontext(io)[1] - IOContext{typeof(io0)}(io0, dict) +function IOContext(io::IO, dict::ImmutableDict{Symbol, Any}) + return IOContext{typeof(io)}(io, dict) +end + +function IOContext(io::IOContext, dict::ImmutableDict{Symbol, Any}) + return typeof(io)(io.io, dict) end -convert(::Type{IOContext}, io::IO) = IOContext(unwrapcontext(io)...)::IOContext + +convert(::Type{IOContext}, io::IOContext) = io +convert(::Type{IOContext}, io::IO) = IOContext(io, ioproperties(io))::IOContext IOContext(io::IO) = convert(IOContext, io) function IOContext(io::IO, KV::Pair) - io0, d = unwrapcontext(io) - IOContext(io0, ImmutableDict{Symbol,Any}(d, KV[1], KV[2])) + d = ioproperties(io) + return IOContext(io, ImmutableDict{Symbol,Any}(d, KV[1], KV[2])) end """ @@ -322,7 +337,7 @@ end Create an `IOContext` that wraps an alternate `IO` but inherits the properties of `context`. """ -IOContext(io::IO, context::IO) = IOContext(unwrapcontext(io)[1], unwrapcontext(context)[2]) +IOContext(io::IO, context::IO) = IOContext(io, ioproperties(context)) """ IOContext(io::IO, KV::Pair...) @@ -1041,7 +1056,7 @@ function show_type_name(io::IO, tn::Core.TypeName) # IOContext If :module is not set, default to Main (or current active module). # nothing can be used to force printing prefix from = get(io, :module, active_module()) - if isdefined(tn, :module) && (from === nothing || !isvisible(sym, tn.module, from)) + if isdefined(tn, :module) && (from === nothing || !isvisible(sym, tn.module, from::Module)) show(io, tn.module) print(io, ".") if globfunc && !is_id_start_char(first(string(sym))) @@ -1177,7 +1192,7 @@ function show_at_namedtuple(io::IO, syms::Tuple, types::DataType) if !first print(io, ", ") end - print(io, syms[i]) + show_sym(io, syms[i]) typ = types.parameters[i] if typ !== Any print(io, "::") @@ -1231,8 +1246,9 @@ function show(io::IO, tn::Core.TypeName) print(io, ")") end +nonnothing_nonmissing_typeinfo(io::IO) = nonmissingtype(nonnothingtype(get(io, :typeinfo, Any))) +show(io::IO, b::Bool) = print(io, nonnothing_nonmissing_typeinfo(io) === Bool ? (b ? "1" : "0") : (b ? "true" : "false")) show(io::IO, ::Nothing) = print(io, "nothing") -show(io::IO, b::Bool) = print(io, get(io, :typeinfo, Any) === Bool ? (b ? "1" : "0") : (b ? "true" : "false")) show(io::IO, n::Signed) = (write(io, string(n)); nothing) show(io::IO, n::Unsigned) = print(io, "0x", string(n, pad = sizeof(n)<<1, base = 16)) print(io::IO, n::Unsigned) = print(io, string(n)) @@ -1337,8 +1353,10 @@ function show_mi(io::IO, l::Core.MethodInstance, from_stackframe::Bool=false) # added by other means. But if it isn't, then we should try # to print a little more identifying information. if !from_stackframe - linetable = l.uninferred.linetable - line = isempty(linetable) ? "unknown" : (lt = linetable[1]::Union{LineNumberNode,Core.LineInfoNode}; string(lt.file, ':', lt.line)) + di = mi.uninferred.debuginfo + file, line = IRShow.debuginfo_firstline(di) + file = string(file) + line = isempty(file) || line < 0 ? "" : "$file:$line" print(io, " from ", def, " starting at ", line) end end @@ -1361,8 +1379,10 @@ function show(io::IO, mi_info::Core.Compiler.Timings.InferenceFrameInfo) show_tuple_as_call(io, def.name, mi.specTypes; argnames, qualified=true) end else - linetable = mi.uninferred.linetable - line = isempty(linetable) ? "" : (lt = linetable[1]; string(lt.file, ':', lt.line)) + di = mi.uninferred.debuginfo + file, line = IRShow.debuginfo_firstline(di) + file = string(file) + line = isempty(file) || line < 0 ? "" : "$file:$line" print(io, "Toplevel InferenceFrameInfo thunk from ", def, " starting at ", line) end end @@ -1783,7 +1803,7 @@ function show_sym(io::IO, sym::Symbol; allow_macroname=false) print(io, '@') show_sym(io, Symbol(sym_str[2:end])) else - print(io, "var", repr(string(sym))) + print(io, "var", repr(string(sym))) # TODO: this is not quite right, since repr uses String escaping rules, and Symbol uses raw string rules end end @@ -2484,6 +2504,11 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In elseif head === :meta && nargs == 2 && args[1] === :pop_loc print(io, "# meta: pop locations ($(args[2]::Int))") # print anything else as "Expr(head, args...)" + elseif head === :toplevel + # Reset SOURCE_SLOTNAMES. Raw SlotNumbers are not valid in Expr(:toplevel), but + # we want to show bad ASTs reasonably to make errors understandable. + lambda_io = IOContext(io, :SOURCE_SLOTNAMES => false) + show_unquoted_expr_fallback(lambda_io, ex, indent, quote_level) else unhandled = true end @@ -2547,7 +2572,8 @@ function show_tuple_as_call(out::IO, name::Symbol, sig::Type; return end tv = Any[] - io = IOContext(IOBuffer(), out) + buf = IOBuffer() + io = IOContext(buf, out) env_io = io while isa(sig, UnionAll) push!(tv, sig.var) @@ -2590,7 +2616,7 @@ function show_tuple_as_call(out::IO, name::Symbol, sig::Type; end print_within_stacktrace(io, ")", bold=true) show_method_params(io, tv) - str = String(take!(unwrapcontext(io)[1])) + str = String(take!(buf)) str = type_limited_string_from_context(out, str) print(out, str) nothing @@ -2803,7 +2829,7 @@ module IRShow import ..Base import .Compiler: IRCode, CFG, scan_ssa_use!, isexpr, compute_basic_blocks, block_for_inst, IncrementalCompact, - Effects, ALWAYS_TRUE, ALWAYS_FALSE + Effects, ALWAYS_TRUE, ALWAYS_FALSE, DebugInfoStream, getdebugidx Base.getindex(r::Compiler.StmtRange, ind::Integer) = Compiler.getindex(r, ind) Base.size(r::Compiler.StmtRange) = Compiler.size(r) Base.first(r::Compiler.StmtRange) = Compiler.first(r) @@ -2816,9 +2842,9 @@ module IRShow include("compiler/ssair/show.jl") const __debuginfo = Dict{Symbol, Any}( - # :full => src -> Base.IRShow.statementidx_lineinfo_printer(src), # and add variable slot information - :source => src -> Base.IRShow.statementidx_lineinfo_printer(src), - # :oneliner => src -> Base.IRShow.statementidx_lineinfo_printer(Base.IRShow.PartialLineInfoPrinter, src), + # :full => src -> statementidx_lineinfo_printer(src), # and add variable slot information + :source => src -> statementidx_lineinfo_printer(src), + # :oneliner => src -> statementidx_lineinfo_printer(PartialLineInfoPrinter, src), :none => src -> Base.IRShow.lineinfo_disabled, ) const default_debuginfo = Ref{Symbol}(:none) @@ -2832,18 +2858,11 @@ function show(io::IO, src::CodeInfo; debuginfo::Symbol=:source) if src.slotnames !== nothing lambda_io = IOContext(lambda_io, :SOURCE_SLOTNAMES => sourceinfo_slotnames(src)) end - if isempty(src.linetable) || src.linetable[1] isa LineInfoNode - println(io) - # TODO: static parameter values? - # only accepts :source or :none, we can't have a fallback for default since - # that would break code_typed(, debuginfo=:source) iff IRShow.default_debuginfo[] = :none - IRShow.show_ir(lambda_io, src, IRShow.IRShowConfig(IRShow.__debuginfo[debuginfo](src))) - else - # this is a CodeInfo that has not been used as a method yet, so its locations are still LineNumberNodes - body = Expr(:block) - body.args = src.code - show(lambda_io, body) - end + println(io) + # TODO: static parameter values? + # only accepts :source or :none, we can't have a fallback for default since + # that would break code_typed(, debuginfo=:source) iff IRShow.default_debuginfo[] = :none + IRShow.show_ir(lambda_io, src, IRShow.IRShowConfig(IRShow.__debuginfo[debuginfo](src))) print(io, ")") end @@ -2871,6 +2890,12 @@ show(io::IO, ::Core.Compiler.NativeInterpreter) = show(io::IO, cache::Core.Compiler.CachedMethodTable) = print(io, typeof(cache), "(", Core.Compiler.length(cache.cache), " entries)") +function show(io::IO, limited::Core.Compiler.LimitedAccuracy) + print(io, "Core.Compiler.LimitedAccuracy(") + show(io, limited.typ) + print(io, ", #= ", Core.Compiler.length(limited.causes), " cause(s) =#)") +end + function dump(io::IOContext, x::SimpleVector, n::Int, indent) if isempty(x) print(io, "empty SimpleVector") @@ -2969,6 +2994,13 @@ end # Types function dump(io::IOContext, x::DataType, n::Int, indent) + # For some reason, tuples are structs + is_struct = isstructtype(x) && !(x <: Tuple) + is_mut = is_struct && ismutabletype(x) + is_mut && print(io, "mutable ") + is_struct && print(io, "struct ") + isprimitivetype(x) && print(io, "primitive type ") + isabstracttype(x) && print(io, "abstract type ") print(io, x) if x !== Any print(io, " <: ", supertype(x)) @@ -2990,8 +3022,13 @@ function dump(io::IOContext, x::DataType, n::Int, indent) fieldtypes = datatype_fieldtypes(x) for idx in 1:length(fields) println(io) - print(io, indent, " ", fields[idx], "::") - print(tvar_io, fieldtypes[idx]) + print(io, indent, " ") + is_mut && isconst(x, idx) && print(io, "const ") + print(io, fields[idx]) + if isassigned(fieldtypes, idx) + print(io, "::") + print(tvar_io, fieldtypes[idx]) + end end end nothing @@ -3161,7 +3198,7 @@ representing argument `x` in terms of its type. (The double-colon is omitted if `toplevel=true`.) However, you can specialize this function for specific types to customize printing. -# Example +# Examples A SubArray created as `view(a, :, 3, 2:5)`, where `a` is a 3-dimensional Float64 array, has type diff --git a/base/slicearray.jl b/base/slicearray.jl index e5a433cdb8d2a..215cf13f9651e 100644 --- a/base/slicearray.jl +++ b/base/slicearray.jl @@ -78,7 +78,7 @@ end eachslice(A::AbstractArray; dims, drop=true) Create a [`Slices`](@ref) object that is an array of slices over dimensions `dims` of `A`, returning -views that select all the data from the other dimensions in `A`. `dims` can either by an +views that select all the data from the other dimensions in `A`. `dims` can either be an integer or a tuple of integers. If `drop = true` (the default), the outer `Slices` will drop the inner dimensions, and @@ -96,7 +96,7 @@ See also [`eachrow`](@ref), [`eachcol`](@ref), [`mapslices`](@ref) and [`selectd !!! compat "Julia 1.9" Prior to Julia 1.9, this returned an iterator, and only a single dimension `dims` was supported. -# Example +# Examples ```jldoctest julia> m = [1 2 3; 4 5 6; 7 8 9] @@ -144,7 +144,7 @@ See also [`eachcol`](@ref), [`eachslice`](@ref) and [`mapslices`](@ref). !!! compat "Julia 1.9" Prior to Julia 1.9, this returned an iterator. -# Example +# Examples ```jldoctest julia> a = [1 2; 3 4] @@ -182,7 +182,7 @@ See also [`eachrow`](@ref), [`eachslice`](@ref) and [`mapslices`](@ref). !!! compat "Julia 1.9" Prior to Julia 1.9, this returned an iterator. -# Example +# Examples ```jldoctest julia> a = [1 2; 3 4] diff --git a/base/some.jl b/base/some.jl index 0d538cbed6c23..46b912243f859 100644 --- a/base/some.jl +++ b/base/some.jl @@ -138,10 +138,31 @@ true This macro is available as of Julia 1.7. """ macro something(args...) - expr = :(nothing) + noth = GlobalRef(Base, :nothing) + something = GlobalRef(Base, :something) + + # This preserves existing semantics of throwing on `nothing` + expr = :($something($noth)) + + #= + We go through the arguments in reverse + because we're building a nested if/else + expression from the inside out. + The innermost thing to check is the last argument, + which is why we need the last argument first + when building the final expression. + =# for arg in reverse(args) - expr = :(val = $(esc(arg)); val !== nothing ? val : ($expr)) + val = gensym() + expr = quote + $val = $(esc(arg)) + if !isnothing($val) + # unwrap eagerly to help type inference + $something($val) + else + $expr + end + end end - something = GlobalRef(Base, :something) - return :($something($expr)) + return expr end diff --git a/base/sort.jl b/base/sort.jl index ceea7365a08b2..b0b650adcfcaf 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -5,8 +5,7 @@ module Sort using Base.Order using Base: copymutable, midpoint, require_one_based_indexing, uinttype, - sub_with_overflow, add_with_overflow, OneTo, BitSigned, BitIntegerType, top_set_bit, - IteratorSize, HasShape, IsInfinite, tail + sub_with_overflow, add_with_overflow, OneTo, BitSigned, BitIntegerType, top_set_bit import Base: sort, @@ -63,7 +62,7 @@ function issorted(itr, order::Ordering) end """ - issorted(v, lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward) + issorted(v, lt=isless, by=identity, rev::Bool=false, order::Base.Order.Ordering=Base.Order.Forward) Test whether a collection is in sorted order. The keywords modify what order is considered sorted, as described in the [`sort!`](@ref) documentation. @@ -91,7 +90,15 @@ issorted(itr; issorted(itr, ord(lt,by,rev,order)) function partialsort!(v::AbstractVector, k::Union{Integer,OrdinalRange}, o::Ordering) - _sort!(v, InitialOptimizations(ScratchQuickSort(k)), o, (;)) + # TODO move k from `alg` to `kw` + # Don't perform InitialOptimizations before Bracketing. The optimizations take O(n) + # time and so does the whole sort. But do perform them before recursive calls because + # that can cause significant speedups when the target range is large so the runtime is + # dominated by k log k and the optimizations runs in O(k) time. + _sort!(v, BoolOptimization( + Small{12}( # Very small inputs should go straight to insertion sort + BracketedSort(k))), + o, (;)) maybeview(v, k) end @@ -165,7 +172,7 @@ partialsort(v::AbstractVector, k::Union{Integer,OrdinalRange}; kws...) = partialsort!(copymutable(v), k; kws...) # reference on sorted binary search: -# http://www.tbray.org/ongoing/When/200x/2003/03/22/Binary +# https://www.tbray.org/ongoing/When/200x/2003/03/22/Binary # index of the first value of vector a that is greater than or equivalent to x; # returns lastindex(v)+1 if x is greater than all values in v. @@ -507,7 +514,7 @@ end ## sorting algorithm components ## """ - _sort!(v::AbstractVector, a::Algorithm, o::Ordering, kw; t, offset) + _sort!(v::AbstractVector, a::Base.Sort.Algorithm, o::Base.Order.Ordering, kw; t, offset) An internal function that sorts `v` using the algorithm `a` under the ordering `o`, subject to specifications provided in `kw` (such as `lo` and `hi` in which case it only @@ -521,7 +528,7 @@ no scratch space is present. A returned scratch space will be a `Vector{T}` where `T` is usually the eltype of `v`. There are some exceptions, for example if `eltype(v) == Union{Missing, T}` then the scratch space -may be be a `Vector{T}` due to `MissingOptimization` changing the eltype of `v` to `T`. +may be a `Vector{T}` due to `MissingOptimization` changing the eltype of `v` to `T`. `t` is an appropriate scratch space for the algorithm at hand, to be accessed as `t[i + offset]`. `t` is used for an algorithm to pass a scratch space back to itself in @@ -529,9 +536,37 @@ internal or recursive calls. """ function _sort! end +# TODO: delete this optimization when views have no overhead. +const UnwrappableSubArray = SubArray{T, 1, <:AbstractArray{T}, <:Tuple{AbstractUnitRange, Vararg{Number}}, true} where T +""" + SubArrayOptimization(next) isa Base.Sort.Algorithm + +Unwrap certain known SubArrays because views have a performance overhead 😢 + +Specifically, unwraps some instances of the type + + $UnwrappableSubArray +""" +struct SubArrayOptimization{T <: Algorithm} <: Algorithm + next::T +end + +_sort!(v::AbstractVector, a::SubArrayOptimization, o::Ordering, kw) = _sort!(v, a.next, o, kw) +function _sort!(v::UnwrappableSubArray, a::SubArrayOptimization, o::Ordering, kw) + @getkw lo hi + # @assert v.stride1 == 1 + parent = v.parent + if parent isa Array && !(parent isa Vector) && hi - lo < 100 + # vec(::Array{T, ≠1}) allocates and is therefore somewhat expensive. + # We don't want that for small inputs. + _sort!(v, a.next, o, kw) + else + _sort!(vec(parent), a.next, o, (;kw..., lo = lo + v.offset1, hi = hi + v.offset1)) + end +end """ - MissingOptimization(next) <: Algorithm + MissingOptimization(next) isa Base.Sort.Algorithm Filter out missing values. @@ -590,7 +625,7 @@ function send_to_end!(f::F, v::AbstractVector; lo=firstindex(v), hi=lastindex(v) i - 1 end """ - send_to_end!(f::Function, v::AbstractVector, o::DirectOrdering[, end_stable]; lo, hi) + send_to_end!(f::Function, v::AbstractVector, o::Base.Order.DirectOrdering[, end_stable]; lo, hi) Return `(a, b)` where `v[a:b]` are the elements that are not sent to the end. @@ -644,7 +679,7 @@ end """ - IEEEFloatOptimization(next) <: Algorithm + IEEEFloatOptimization(next) isa Base.Sort.Algorithm Move NaN values to the end, partition by sign, and reinterpret the rest as unsigned integers. @@ -689,7 +724,7 @@ end """ - BoolOptimization(next) <: Algorithm + BoolOptimization(next) isa Base.Sort.Algorithm Sort `AbstractVector{Bool}`s using a specialized version of counting sort. @@ -716,7 +751,7 @@ end """ - IsUIntMappable(yes, no) <: Algorithm + IsUIntMappable(yes, no) isa Base.Sort.Algorithm Determines if the elements of a vector can be mapped to unsigned integers while preserving their order under the specified ordering. @@ -738,7 +773,7 @@ end """ - Small{N}(small=SMALL_ALGORITHM, big) <: Algorithm + Small{N}(small=SMALL_ALGORITHM, big) isa Base.Sort.Algorithm Sort inputs with `length(lo:hi) <= N` using the `small` algorithm. Otherwise use the `big` algorithm. @@ -777,6 +812,16 @@ Characteristics: it is well-suited to small collections but should not be used for large ones. """ const InsertionSort = InsertionSortAlg() + +""" + SMALL_ALGORITHM + +Default sorting algorithm for small arrays. + +This is an alias for a simple low-overhead algorithm that does not scale well +to large arrays, unlike high-overhead recursive algorithms used for larger arrays. +`SMALL_ALGORITHM` is a good choice for the base case of a recursive algorithm. +""" const SMALL_ALGORITHM = InsertionSortAlg() function _sort!(v::AbstractVector, ::InsertionSortAlg, o::Ordering, kw) @@ -800,7 +845,7 @@ end """ - CheckSorted(next) <: Algorithm + CheckSorted(next) isa Base.Sort.Algorithm Check if the input is already sorted and for large inputs, also check if it is reverse-sorted. The reverse-sorted check is unstable. @@ -827,7 +872,7 @@ end """ - ComputeExtrema(next) <: Algorithm + ComputeExtrema(next) isa Base.Sort.Algorithm Compute the extrema of the input under the provided order. @@ -853,7 +898,7 @@ end """ - ConsiderCountingSort(counting=CountingSort(), next) <: Algorithm + ConsiderCountingSort(counting=CountingSort(), next) isa Base.Sort.Algorithm If the input's range is small enough, use the `counting` algorithm. Otherwise, dispatch to the `next` algorithm. @@ -881,7 +926,7 @@ _sort!(v::AbstractVector, a::ConsiderCountingSort, o::Ordering, kw) = _sort!(v, """ - CountingSort <: Algorithm + CountingSort() isa Base.Sort.Algorithm Use the counting sort algorithm. @@ -917,7 +962,7 @@ end """ - ConsiderRadixSort(radix=RadixSort(), next) <: Algorithm + ConsiderRadixSort(radix=RadixSort(), next) isa Base.Sort.Algorithm If the number of bits in the input's range is small enough and the input supports efficient bitshifts, use the `radix` algorithm. Otherwise, dispatch to the `next` algorithm. @@ -940,7 +985,7 @@ end """ - RadixSort <: Algorithm + RadixSort() isa Base.Sort.Algorithm Use the radix sort algorithm. @@ -995,8 +1040,8 @@ end """ - ScratchQuickSort(next::Algorithm=SMALL_ALGORITHM) <: Algorithm - ScratchQuickSort(lo::Union{Integer, Missing}, hi::Union{Integer, Missing}=lo, next::Algorithm=SMALL_ALGORITHM) <: Algorithm + ScratchQuickSort(next::Base.Sort.Algorithm=Base.Sort.SMALL_ALGORITHM) isa Base.Sort.Algorithm + ScratchQuickSort(lo::Union{Integer, Missing}, hi::Union{Integer, Missing}=lo, next::Base.Sort.Algorithm=Base.Sort.SMALL_ALGORITHM) isa Base.Sort.Algorithm Use the `ScratchQuickSort` algorithm with the `next` algorithm as a base case. @@ -1036,7 +1081,7 @@ function partition!(t::AbstractVector, lo::Integer, hi::Integer, offset::Integer v::AbstractVector, rev::Bool, pivot_dest::AbstractVector, pivot_index_offset::Integer) # Ideally we would use `pivot_index = rand(lo:hi)`, but that requires Random.jl # and would mutate the global RNG in sorting. - pivot_index = typeof(hi-lo)(hash(lo) % (hi-lo+1)) + lo + pivot_index = mod(hash(lo), lo:hi) @inbounds begin pivot = v[pivot_index] while lo < pivot_index @@ -1112,7 +1157,196 @@ end """ - StableCheckSorted(next) <: Algorithm + BracketedSort(target[, next::Algorithm]) isa Base.Sort.Algorithm + +Perform a partialsort for the elements that fall into the indices specified by the `target` +using BracketedSort with the `next` algorithm for subproblems. + +BracketedSort takes a random* sample of the input, estimates the quantiles of the input +using the quantiles of the sample to find signposts that almost certainly bracket the target +values, filters the value in the input that fall between the signpost values to the front of +the input, and then, if that "almost certainly" turned out to be true, finds the target +within the small chunk that are, by value, between the signposts and now by position, at the +front of the vector. On small inputs or when target is close to the size of the input, +BracketedSort falls back to the `next` algorithm directly. Otherwise, BracketedSort uses the +`next` algorithm only to compute quantiles of the sample and to find the target within the +small chunk. + +## Performance + +If the `next` algorithm has `O(n * log(n))` runtime and the input is not pathological then +the runtime of this algorithm is `O(n + k * log(k))` where `n` is the length of the input +and `k` is `length(target)`. On pathological inputs the asymptotic runtime is the same as +the runtime of the `next` algorithm. + +BracketedSort itself does not allocate. If `next` is in-place then BracketedSort is also +in-place. If `next` is not in place, and it's space usage increases monotonically with input +length then BracketedSort's maximum space usage will never be more than the space usage +of `next` on the input BracketedSort receives. For large nonpathological inputs and targets +substantially smaller than the size of the input, BracketedSort's maximum memory usage will +be much less than `next`'s. If the maximum additional space usage of `next` scales linearly +then for small k the average* maximum additional space usage of BracketedSort will be +`O(n^(2.3/3))`. + +By default, BracketedSort uses the `O(n)` space and `O(n + k log k)` runtime +`ScratchQuickSort` algorithm recursively. + +*Sorting is unable to depend on Random.jl because Random.jl depends on sorting. + Consequently, we use `hash` as a source of randomness. The average runtime guarantees + assume that `hash(x::Int)` produces a random result. However, as this randomization is + deterministic, if you try hard enough you can find inputs that consistently reach the + worst case bounds. Actually constructing such inputs is an exercise left to the reader. + Have fun :). + +Characteristics: + * *unstable*: does not preserve the ordering of elements that compare equal + (e.g. "a" and "A" in a sort of letters that ignores case). + * *in-place* in memory if the `next` algorithm is in-place. + * *estimate-and-filter*: strategy + * *linear runtime* if `length(target)` is constant and `next` is reasonable + * *n + k log k* worst case runtime if `next` has that runtime. + * *pathological inputs* can significantly increase constant factors. +""" +struct BracketedSort{T, F} <: Algorithm + target::T + get_next::F +end + +# TODO: this composition between BracketedSort and ScratchQuickSort does not bring me joy +BracketedSort(k) = BracketedSort(k, k -> InitialOptimizations(ScratchQuickSort(k))) + +function bracket_kernel!(v::AbstractVector, lo, hi, lo_signpost, hi_signpost, o) + i = 0 + count_below = 0 + checkbounds(v, lo:hi) + for j in lo:hi + x = @inbounds v[j] + a = lo_signpost !== nothing && lt(o, x, lo_signpost) + b = hi_signpost === nothing || !lt(o, hi_signpost, x) + count_below += a + # if a != b # This branch is almost never taken, so making it branchless is bad. + # @inbounds v[i], v[j] = v[j], v[i] + # i += 1 + # end + c = a != b # JK, this is faster. + k = i * c + j + # Invariant: @assert firstindex(v) ≤ lo ≤ i + j ≤ k ≤ j ≤ hi ≤ lastindex(v) + @inbounds v[j], v[k] = v[k], v[j] + i += c - 1 + end + count_below, i+hi +end + +function move!(v, target, source) + # This function never dominates runtime—only add `@inbounds` if you can demonstrate a + # performance improvement. And if you do, also double check behavior when `target` + # is out of bounds. + @assert length(target) == length(source) + if length(target) == 1 || isdisjoint(target, source) + for (i, j) in zip(target, source) + v[i], v[j] = v[j], v[i] + end + else + @assert minimum(source) <= minimum(target) + reverse!(v, minimum(source), maximum(target)) + reverse!(v, minimum(target), maximum(target)) + end +end + +function _sort!(v::AbstractVector, a::BracketedSort, o::Ordering, kw) + @getkw lo hi scratch + # TODO for further optimization: reuse scratch between trials better, from signpost + # selection to recursive calls, and from the fallback (but be aware of type stability, + # especially when sorting IEEE floats. + + # We don't need to bounds check target because that is done higher up in the stack + # However, we cannot assume the target is inbounds. + lo < hi || return scratch + ln = hi - lo + 1 + + # This is simply a precomputed short-circuit to avoid doing scalar math for small inputs. + # It does not change dispatch at all. + ln < 260 && return _sort!(v, a.get_next(a.target), o, kw) + + target = a.target + k = cbrt(ln) + k2 = round(Int, k^2) + k2ln = k2/ln + offset = .15k*top_set_bit(k2) # TODO for further optimization: tune this + lo_signpost_i, hi_signpost_i = + (floor(Int, (tar - lo) * k2ln + lo + off) for (tar, off) in + ((minimum(target), -offset), (maximum(target), offset))) + lastindex_sample = lo+k2-1 + expected_middle_ln = (min(lastindex_sample, hi_signpost_i) - max(lo, lo_signpost_i) + 1) / k2ln + # This heuristic is complicated because it fairly accurately reflects the runtime of + # this algorithm which is necessary to get good dispatch when both the target is large + # and the input are large. + # expected_middle_ln is a float and k2 is significantly below typemax(Int), so this will + # not overflow: + # TODO move target from alg to kw to avoid this ickyness: + ln <= 130 + 2k2 + 2expected_middle_ln && return _sort!(v, a.get_next(a.target), o, kw) + + # We store the random sample in + # sample = view(v, lo:lo+k2) + # but views are not quite as fast as using the input array directly, + # so we don't actually construct this view at runtime. + + # TODO for further optimization: handle lots of duplicates better. + # Right now lots of duplicates rounds up when it could use some super fast optimizations + # in some cases. + # e.g. + # + # Target: |----| + # Sorted input: 000000000000000000011111112222223333333333 + # + # Will filter all zeros and ones to the front when it could just take the first few + # it encounters. This optimization would be especially potent when `allequal(ans)` and + # equal elements are egal. + + # 3 random trials should typically give us 0.99999 reliability; we can assume + # the input is pathological and abort to fallback if we fail three trials. + seed = hash(ln, Int === Int64 ? 0x85eb830e0216012d : 0xae6c4e15) + for attempt in 1:3 + seed = hash(attempt, seed) + for i in lo:lo+k2-1 + j = mod(hash(i, seed), i:hi) # TODO for further optimization: be sneaky and remove this division + v[i], v[j] = v[j], v[i] + end + count_below, lastindex_middle = if lo_signpost_i <= lo && lastindex_sample <= hi_signpost_i + # The heuristics higher up in this function that dispatch to the `next` + # algorithm should prevent this from happening. + # Specifically, this means that expected_middle_ln == ln, so + # ln <= ... + 2.0expected_middle_ln && return ... + # will trigger. + @assert false + # But if it does happen, the kernel reduces to + 0, hi + elseif lo_signpost_i <= lo + _sort!(v, a.get_next(hi_signpost_i), o, (;kw..., hi=lastindex_sample)) + bracket_kernel!(v, lo, hi, nothing, v[hi_signpost_i], o) + elseif lastindex_sample <= hi_signpost_i + _sort!(v, a.get_next(lo_signpost_i), o, (;kw..., hi=lastindex_sample)) + bracket_kernel!(v, lo, hi, v[lo_signpost_i], nothing, o) + else + # TODO for further optimization: don't sort the middle elements + _sort!(v, a.get_next(lo_signpost_i:hi_signpost_i), o, (;kw..., hi=lastindex_sample)) + bracket_kernel!(v, lo, hi, v[lo_signpost_i], v[hi_signpost_i], o) + end + target_in_middle = target .- count_below + if lo <= minimum(target_in_middle) && maximum(target_in_middle) <= lastindex_middle + scratch = _sort!(v, a.get_next(target_in_middle), o, (;kw..., hi=lastindex_middle)) + move!(v, target, target_in_middle) + return scratch + end + # This line almost never runs. + end + # This line only runs on pathological inputs. Make sure it's covered by tests :) + _sort!(v, a.get_next(target), o, kw) +end + + +""" + StableCheckSorted(next) isa Base.Sort.Algorithm Check if an input is sorted and/or reverse-sorted. @@ -1212,7 +1446,7 @@ end ## default sorting policy ## """ - InitialOptimizations(next) <: Algorithm + InitialOptimizations(next) isa Base.Sort.Algorithm Attempt to apply a suite of low-cost optimizations to the input vector before sorting. These optimizations may be automatically applied by the `sort!` family of functions when @@ -1224,14 +1458,16 @@ future versions of Julia. If `next` is stable, then `InitialOptimizations(next)` is also stable. The specific optimizations attempted by `InitialOptimizations` are -[`MissingOptimization`](@ref), [`BoolOptimization`](@ref), dispatch to -[`InsertionSort`](@ref) for inputs with `length <= 10`, and [`IEEEFloatOptimization`](@ref). +[`SubArrayOptimization`](@ref), [`MissingOptimization`](@ref), [`BoolOptimization`](@ref), +dispatch to [`InsertionSort`](@ref) for inputs with `length <= 10`, and +[`IEEEFloatOptimization`](@ref). """ -InitialOptimizations(next) = MissingOptimization( - BoolOptimization( - Small{10}( - IEEEFloatOptimization( - next)))) +InitialOptimizations(next) = SubArrayOptimization( + MissingOptimization( + BoolOptimization( + Small{10}( + IEEEFloatOptimization( + next))))) """ DEFAULT_STABLE @@ -1299,7 +1535,7 @@ Next, we [`ConsiderCountingSort`](@ref). If the range the input is small compare length, we apply [`CountingSort`](@ref). Next, we [`ConsiderRadixSort`](@ref). This is similar to the dispatch to counting sort, -but we conside rthe number of _bits_ in the range, rather than the range itself. +but we consider the number of _bits_ in the range, rather than the range itself. Consequently, we apply [`RadixSort`](@ref) for any reasonably long inputs that reach this stage. @@ -1357,7 +1593,7 @@ defalg(v::AbstractArray{Missing}) = DEFAULT_UNSTABLE # for method disambiguation defalg(v::AbstractArray{Union{}}) = DEFAULT_UNSTABLE # for method disambiguation """ - sort!(v; alg::Algorithm=defalg(v), lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward) + sort!(v; alg::Base.Sort.Algorithm=Base.Sort.defalg(v), lt=isless, by=identity, rev::Bool=false, order::Base.Order.Ordering=Base.Order.Forward) Sort the vector `v` in place. A stable algorithm is used by default: the ordering of elements that compare equal is preserved. A specific algorithm can @@ -1367,7 +1603,7 @@ algorithms). Elements are first transformed with the function `by` and then compared according to either the function `lt` or the ordering `order`. Finally, the resulting order is reversed if `rev=true` (this preserves forward stability: -elements that compare equal are not reversed). The current implemention applies +elements that compare equal are not reversed). The current implementation applies the `by` transformation before each comparison rather than once per element. Passing an `lt` other than `isless` along with an `order` other than @@ -1470,15 +1706,10 @@ function sort!(v::AbstractVector{T}; end """ - sort(v; alg::Algorithm=defalg(v), lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward) + sort(v; alg::Base.Sort.Algorithm=Base.Sort.defalg(v), lt=isless, by=identity, rev::Bool=false, order::Base.Order.Ordering=Base.Order.Forward) Variant of [`sort!`](@ref) that returns a sorted copy of `v` leaving `v` itself unmodified. -Uses `Base.copymutable` to support immutable collections and iterables. - -!!! compat "Julia 1.10" - `sort` of arbitrary iterables requires at least Julia 1.10. - # Examples ```jldoctest julia> v = [3, 1, 2]; @@ -1496,44 +1727,12 @@ julia> v 2 ``` """ -function sort(v; kws...) - size = IteratorSize(v) - size == HasShape{0}() && throw(ArgumentError("$v cannot be sorted")) - size == IsInfinite() && throw(ArgumentError("infinite iterator $v cannot be sorted")) - sort!(copymutable(v); kws...) -end -sort(v::AbstractVector; kws...) = sort!(copymutable(v); kws...) # for method disambiguation -sort(::AbstractString; kws...) = - throw(ArgumentError("sort(::AbstractString) is not supported")) -sort(::Tuple; kws...) = - throw(ArgumentError("sort(::Tuple) is only supported for NTuples")) - -function sort(x::NTuple{N}; lt::Function=isless, by::Function=identity, - rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward) where N - o = ord(lt,by,rev,order) - if N > 9 - v = sort!(copymutable(x), DEFAULT_STABLE, o) - tuple((v[i] for i in 1:N)...) - else - _sort(x, o) - end -end -_sort(x::Union{NTuple{0}, NTuple{1}}, o::Ordering) = x -function _sort(x::NTuple, o::Ordering) - a, b = Base.IteratorsMD.split(x, Val(length(x)>>1)) - merge(_sort(a, o), _sort(b, o), o) -end -merge(x::NTuple, y::NTuple{0}, o::Ordering) = x -merge(x::NTuple{0}, y::NTuple, o::Ordering) = y -merge(x::NTuple{0}, y::NTuple{0}, o::Ordering) = x # Method ambiguity -merge(x::NTuple, y::NTuple, o::Ordering) = - (lt(o, y[1], x[1]) ? (y[1], merge(x, tail(y), o)...) : (x[1], merge(tail(x), y, o)...)) - +sort(v::AbstractVector; kws...) = sort!(copymutable(v); kws...) ## partialsortperm: the permutation to sort the first k elements of an array ## """ - partialsortperm(v, k; by=ientity, lt=isless, rev=false) + partialsortperm(v, k; by=identity, lt=isless, rev=false) Return a partial permutation `I` of the vector `v`, so that `v[I]` returns values of a fully sorted version of `v` at index `k`. If `k` is a range, a vector of indices is returned; if @@ -1587,6 +1786,8 @@ v[ix[k]] == partialsort(v, k) The return value is the `k`th element of `ix` if `k` is an integer, or view into `ix` if `k` is a range. +$(Base._DOCS_ALIASING_WARNING) + # Examples ```jldoctest julia> v = [3, 1, 2, 1]; @@ -1628,7 +1829,7 @@ end ## sortperm: the permutation to sort an array ## """ - sortperm(A; alg::Algorithm=DEFAULT_UNSTABLE, lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward, [dims::Integer]) + sortperm(A; alg::Base.Sort.Algorithm=Base.Sort.DEFAULT_UNSTABLE, lt=isless, by=identity, rev::Bool=false, order::Base.Order.Ordering=Base.Order.Forward, [dims::Integer]) Return a permutation vector or array `I` that puts `A[I]` in sorted order along the given dimension. If `A` has more than one dimension, then the `dims` keyword argument must be specified. The order is specified @@ -1706,11 +1907,13 @@ end """ - sortperm!(ix, A; alg::Algorithm=DEFAULT_UNSTABLE, lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward, [dims::Integer]) + sortperm!(ix, A; alg::Base.Sort.Algorithm=Base.Sort.DEFAULT_UNSTABLE, lt=isless, by=identity, rev::Bool=false, order::Base.Order.Ordering=Base.Order.Forward, [dims::Integer]) Like [`sortperm`](@ref), but accepts a preallocated index vector or array `ix` with the same `axes` as `A`. `ix` is initialized to contain the values `LinearIndices(A)`. +$(Base._DOCS_ALIASING_WARNING) + !!! compat "Julia 1.9" The method accepting `dims` requires at least Julia 1.9. @@ -1792,7 +1995,7 @@ end ## sorting multi-dimensional arrays ## """ - sort(A; dims::Integer, alg::Algorithm=defalg(A), lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward) + sort(A; dims::Integer, alg::Base.Sort.Algorithm=Base.Sort.defalg(A), lt=isless, by=identity, rev::Bool=false, order::Base.Order.Ordering=Base.Order.Forward) Sort a multidimensional array `A` along the given dimension. See [`sort!`](@ref) for a description of possible @@ -1864,7 +2067,7 @@ end """ - sort!(A; dims::Integer, alg::Algorithm=defalg(A), lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward) + sort!(A; dims::Integer, alg::Base.Sort.Algorithm=Base.Sort.defalg(A), lt=isless, by=identity, rev::Bool=false, order::Base.Order.Ordering=Base.Order.Forward) Sort the multidimensional array `A` along dimension `dims`. See the one-dimensional version of [`sort!`](@ref) for a description of @@ -1900,30 +2103,43 @@ function sort!(A::AbstractArray{T}; by=identity, rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward, # TODO stop eagerly over-allocating. - scratch::Union{Vector{T}, Nothing}=Vector{T}(undef, size(A, dims))) where T - __sort!(A, Val(dims), maybe_apply_initial_optimizations(alg), ord(lt, by, rev, order), scratch) -end -function __sort!(A::AbstractArray{T}, ::Val{K}, - alg::Algorithm, - order::Ordering, - scratch::Union{Vector{T}, Nothing}) where {K,T} + scratch::Union{Vector{T}, Nothing}=size(A, dims) < 10 ? nothing : Vector{T}(undef, size(A, dims))) where T nd = ndims(A) - - 1 <= K <= nd || throw(ArgumentError("dimension out of range")) - - remdims = ntuple(i -> i == K ? 1 : axes(A, i), nd) - for idx in CartesianIndices(remdims) - Av = view(A, ntuple(i -> i == K ? Colon() : idx[i], nd)...) - sort!(Av; alg, order, scratch) + 1 <= dims <= nd || throw(ArgumentError("dimension out of range")) + alg2 = maybe_apply_initial_optimizations(alg) + order2 = ord(lt, by, rev, order) + foreach(ntuple(Val, nd)) do d + get_value(d) == dims || return + # We assume that an Integer between 1 and nd must be equal to one of the + # values 1:nd. If this assumption is false, then what's an integer? and + # also sort! will silently do nothing. + + idxs = CartesianIndices(ntuple(i -> i == get_value(d) ? 1 : axes(A, i), ndims(A))) + get_view(idx) = view(A, ntuple(i -> i == get_value(d) ? Colon() : idx[i], ndims(A))...) + if d == Val(1) || size(A, get_value(d)) < 30 + for idx in idxs + sort!(get_view(idx); alg=alg2, order=order2, scratch) + end + else + v = similar(get_view(first(idxs))) + for idx in idxs + vw = get_view(idx) + v .= vw + sort!(v; alg=alg2, order=order2, scratch) + vw .= v + end + end + A end A end +get_value(::Val{x}) where x = x ## uint mapping to allow radix sorting primitives other than UInts ## """ - UIntMappable(T::Type, order::Ordering) + UIntMappable(T::Type, order::Base.Order.Ordering) Return `typeof(uint_map(x::T, order))` if [`uint_map`](@ref) and [`uint_unmap`](@ref) are implemented. @@ -1933,7 +2149,7 @@ If either is not implemented, return `nothing`. UIntMappable(T::Type, order::Ordering) = nothing """ - uint_map(x, order::Ordering)::Unsigned + uint_map(x, order::Base.Order.Ordering)::Unsigned Map `x` to an un unsigned integer, maintaining sort order. @@ -1947,7 +2163,7 @@ See also: [`UIntMappable`](@ref) [`uint_unmap`](@ref) function uint_map end """ - uint_unmap(T::Type, u::Unsigned, order::Ordering) + uint_unmap(T::Type, u::Unsigned, order::Base.Order.Ordering) Reconstruct the unique value `x::T` that uint_maps to `u`. Satisfies `x === uint_unmap(T, uint_map(x::T, order), order)` for all `x <: T`. diff --git a/base/special/cbrt.jl b/base/special/cbrt.jl index 9fda5c41fb09e..ce3a3d67e3ba4 100644 --- a/base/special/cbrt.jl +++ b/base/special/cbrt.jl @@ -1,6 +1,6 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -# Float32/Float64 based on C implementations from FDLIBM (http://www.netlib.org/fdlibm/) +# Float32/Float64 based on C implementations from FDLIBM (https://www.netlib.org/fdlibm/) # and FreeBSD: # ## ==================================================== diff --git a/base/special/exp.jl b/base/special/exp.jl index 8e940a4d85ad9..312197339a086 100644 --- a/base/special/exp.jl +++ b/base/special/exp.jl @@ -250,7 +250,7 @@ end twopk = (k + UInt64(53)) << 52 return reinterpret(T, twopk + reinterpret(UInt64, small_part))*0x1p-53 end - #k == 1024 && return (small_part * 2.0) * 2.0^1023 + k == 1024 && return (small_part * 2.0) * 2.0^1023 end twopk = Int64(k) << 52 return reinterpret(T, twopk + reinterpret(Int64, small_part)) diff --git a/base/special/log.jl b/base/special/log.jl index 488f0c89d48e3..029394b7a63f1 100644 --- a/base/special/log.jl +++ b/base/special/log.jl @@ -155,14 +155,11 @@ logbU(::Type{Float64},::Val{10}) = 0.4342944819032518 logbL(::Type{Float64},::Val{10}) = 1.098319650216765e-17 # Procedure 1 -# XXX we want to mark :noub here so that this function can be concrete-folded, -# because the effect analysis currently can't prove it in the presence of `@inbounds` or -# `:boundscheck`, but still the access to `t_log_Float64` is really safe here -Base.@assume_effects :consistent :noub @inline function log_proc1(y::Float64,mf::Float64,F::Float64,f::Float64,base=Val(:ℯ)) +@inline function log_proc1(y::Float64,mf::Float64,F::Float64,f::Float64,base=Val(:ℯ)) jp = unsafe_trunc(Int,128.0*F)-127 ## Steps 1 and 2 - @inbounds hi,lo = t_log_Float64[jp] + Base.@assume_effects :nothrow :noub @inbounds hi,lo = t_log_Float64[jp] l_hi = mf* 0.6931471805601177 + hi l_lo = mf*-1.7239444525614835e-13 + lo @@ -216,14 +213,11 @@ end end # Procedure 1 -# XXX we want to mark :noub here so that this function can be concrete-folded, -# because the effect analysis currently can't prove it in the presence of `@inbounds` or -# `:boundscheck`, but still the access to `t_log_Float32` is really safe here -Base.@assume_effects :consistent :noub @inline function log_proc1(y::Float32,mf::Float32,F::Float32,f::Float32,base=Val(:ℯ)) +@inline function log_proc1(y::Float32,mf::Float32,F::Float32,f::Float32,base=Val(:ℯ)) jp = unsafe_trunc(Int,128.0f0*F)-127 ## Steps 1 and 2 - @inbounds hi = t_log_Float32[jp] + Base.@assume_effects :nothrow :noub @inbounds hi = t_log_Float32[jp] l = mf*0.6931471805599453 + hi ## Step 3 @@ -267,7 +261,7 @@ end @noinline log(x::Float64) = _log(x, Val(:ℯ), :log) @noinline log10(x::Float64) = _log(x, Val(10), :log10) -@inline function _log(x::Float64, base, func) +@inline function _log(x::Float64, base, func::Symbol) if x > 0.0 x == Inf && return x @@ -294,15 +288,15 @@ end return log_proc1(y,mf,F,f,base) elseif x == 0.0 - -Inf + return -Inf elseif isnan(x) - NaN + return NaN else throw_complex_domainerror(func, x) end end -@inline function _log(x::Float32, base, func) +@inline function _log(x::Float32, base, func::Symbol) if x > 0f0 x == Inf32 && return x @@ -327,11 +321,11 @@ end F = (y + 65536.0f0) - 65536.0f0 # 0x1p-7*round(0x1p7*y) f = y-F - log_proc1(y,mf,F,f,base) + return log_proc1(y,mf,F,f,base) elseif x == 0f0 - -Inf32 + return -Inf32 elseif isnan(x) - NaN32 + return NaN32 else throw_complex_domainerror(func, x) end @@ -562,17 +556,17 @@ end # Adapted and modified from https://github.com/ARM-software/optimized-routines/blob/master/math/pow.c # Copyright (c) 2018-2020, Arm Limited. (which is also MIT licensed) # note that this isn't an exact translation as this version compacts the table to reduce cache pressure. -function _log_ext(xu) +function _log_ext(xu::UInt64) # x = 2^k z; where z is in range [0x1.69555p-1,0x1.69555p-0) and exact. # The range is split into N subintervals. # The ith subinterval contains z and c is near the center of the interval. tmp = reinterpret(Int64, xu - 0x3fe6955500000000) #0x1.69555p-1 - i = (tmp >> 45) & 127 z = reinterpret(Float64, xu - (tmp & 0xfff0000000000000)) k = Float64(tmp >> 52) # log(x) = k*Ln2 + log(c) + log1p(z/c-1). - # getfield instead of getindex to satisfy effect analysis not knowing whether this is inbounds - t, logctail = getfield(t_log_table_compact, Int(i+1)) + # N.B. :nothrow and :noub since `idx` is known to be `1 ≤ idx ≤ length(t_log_table_compact)` + idx = (tmp >> 45) & (length(t_log_table_compact)-1) + 1 + t, logctail = Base.@assume_effects :nothrow :noub @inbounds t_log_table_compact[idx] invc, logc = log_tab_unpack(t) # Note: invc is j/N or j/N/2 where j is an integer in [N,2N) and # |z/c - 1| < 1/N, so r = z/c - 1 is exactly representable. diff --git a/base/special/rem_pio2.jl b/base/special/rem_pio2.jl index 3086f3ebc02c9..b0a17fdc25087 100644 --- a/base/special/rem_pio2.jl +++ b/base/special/rem_pio2.jl @@ -126,10 +126,7 @@ function fromfraction(f::Int128) return (z1,z2) end -# XXX we want to mark :noub here so that this function can be concrete-folded, -# because the effect analysis currently can't prove it in the presence of `@inbounds` or -# `:boundscheck`, but still the accesses to `INV_2PI` are really safe here -Base.@assume_effects :consistent :noub function paynehanek(x::Float64) +function paynehanek(x::Float64) # 1. Convert to form # # x = X * 2^k, @@ -168,15 +165,15 @@ Base.@assume_effects :consistent :noub function paynehanek(x::Float64) idx = k >> 6 shift = k - (idx << 6) - if shift == 0 - @inbounds a1 = INV_2PI[idx+1] - @inbounds a2 = INV_2PI[idx+2] - @inbounds a3 = INV_2PI[idx+3] + Base.@assume_effects :nothrow :noub @inbounds if shift == 0 + a1 = INV_2PI[idx+1] + a2 = INV_2PI[idx+2] + a3 = INV_2PI[idx+3] else # use shifts to extract the relevant 64 bit window - @inbounds a1 = (idx < 0 ? zero(UInt64) : INV_2PI[idx+1] << shift) | (INV_2PI[idx+2] >> (64 - shift)) - @inbounds a2 = (INV_2PI[idx+2] << shift) | (INV_2PI[idx+3] >> (64 - shift)) - @inbounds a3 = (INV_2PI[idx+3] << shift) | (INV_2PI[idx+4] >> (64 - shift)) + a1 = (idx < 0 ? zero(UInt64) : INV_2PI[idx+1] << shift) | (INV_2PI[idx+2] >> (64 - shift)) + a2 = (INV_2PI[idx+2] << shift) | (INV_2PI[idx+3] >> (64 - shift)) + a3 = (INV_2PI[idx+3] << shift) | (INV_2PI[idx+4] >> (64 - shift)) end # 3. Perform the multiplication: diff --git a/base/special/trig.jl b/base/special/trig.jl index 50d3d69585217..66e4b46d7d489 100644 --- a/base/special/trig.jl +++ b/base/special/trig.jl @@ -165,11 +165,13 @@ end @noinline sincos_domain_error(x) = throw(DomainError(x, "sincos(x) is only defined for finite x.")) """ - sincos(x) + sincos(x::T) where T -> Tuple{float(T),float(T)} Simultaneously compute the sine and cosine of `x`, where `x` is in radians, returning a tuple `(sine, cosine)`. +Throw a [`DomainError`](@ref) if `isinf(x)`, return a `(T(NaN), T(NaN))` if `isnan(x)`. + See also [`cis`](@ref), [`sincospi`](@ref), [`sincosd`](@ref). """ function sincos(x::T) where T<:Union{Float32, Float64} @@ -783,17 +785,19 @@ end end """ - sinpi(x) + sinpi(x::T) where T -> float(T) Compute ``\\sin(\\pi x)`` more accurately than `sin(pi*x)`, especially for large `x`. +Throw a [`DomainError`](@ref) if `isinf(x)`, return a `T(NaN)` if `isnan(x)`. + See also [`sind`](@ref), [`cospi`](@ref), [`sincospi`](@ref). """ function sinpi(_x::T) where T<:IEEEFloat x = abs(_x) if !isfinite(x) isnan(x) && return x - throw(DomainError(x, "`x` cannot be infinite.")) + throw(DomainError(x, "`sinpi(x)` is only defined for finite `x`.")) end # For large x, answers are all 1 or zero. x >= maxintfloat(T) && return copysign(zero(T), _x) @@ -814,15 +818,19 @@ function sinpi(_x::T) where T<:IEEEFloat return ifelse(signbit(_x), -res, res) end """ - cospi(x) + cospi(x::T) where T -> float(T) Compute ``\\cos(\\pi x)`` more accurately than `cos(pi*x)`, especially for large `x`. + +Throw a [`DomainError`](@ref) if `isinf(x)`, return a `T(NaN)` if `isnan(x)`. + +See also: [`cispi`](@ref), [`sincosd`](@ref), [`cospi`](@ref). """ function cospi(x::T) where T<:IEEEFloat x = abs(x) if !isfinite(x) isnan(x) && return x - throw(DomainError(x, "`x` cannot be infinite.")) + throw(DomainError(x, "`cospi(x)` is only defined for finite `x`.")) end # For large x, answers are all 1 or zero. x >= maxintfloat(T) && return one(T) @@ -842,11 +850,13 @@ function cospi(x::T) where T<:IEEEFloat end end """ - sincospi(x) + sincospi(x::T) where T -> Tuple{float(T),float(T)} Simultaneously compute [`sinpi(x)`](@ref) and [`cospi(x)`](@ref) (the sine and cosine of `π*x`, where `x` is in radians), returning a tuple `(sine, cosine)`. +Throw a [`DomainError`](@ref) if `isinf(x)`, return a `(T(NaN), T(NaN))` tuple if `isnan(x)`. + !!! compat "Julia 1.6" This function requires Julia 1.6 or later. @@ -856,7 +866,7 @@ function sincospi(_x::T) where T<:IEEEFloat x = abs(_x) if !isfinite(x) isnan(x) && return x, x - throw(DomainError(x, "`x` cannot be infinite.")) + throw(DomainError(x, "`sincospi(x)` is only defined for finite `x`.")) end # For large x, answers are all 1 or zero. x >= maxintfloat(T) && return (copysign(zero(T), _x), one(T)) @@ -880,10 +890,12 @@ function sincospi(_x::T) where T<:IEEEFloat end """ - tanpi(x) + tanpi(x::T) where T -> float(T) Compute ``\\tan(\\pi x)`` more accurately than `tan(pi*x)`, especially for large `x`. +Throw a [`DomainError`](@ref) if `isinf(x)`, return a `T(NaN)` if `isnan(x)`. + !!! compat "Julia 1.10" This function requires at least Julia 1.10. @@ -895,7 +907,7 @@ function tanpi(_x::T) where T<:IEEEFloat x = abs(_x) if !isfinite(x) isnan(x) && return x - throw(DomainError(x, "`x` cannot be infinite.")) + throw(DomainError(x, "`tanpi(x)` is only defined for finite `x`.")) end # For large x, answers are all zero. # All integer values for floats larger than maxintfloat are even. @@ -1063,9 +1075,11 @@ isinf_real(x::Complex) = isinf(real(x)) && isfinite(imag(x)) isinf_real(x::Number) = false """ - sinc(x) + sinc(x::T) where {T <: Number} -> float(T) + +Compute normalized sinc function ``\\operatorname{sinc}(x) = \\sin(\\pi x) / (\\pi x)`` if ``x \\neq 0``, and ``1`` if ``x = 0``. -Compute ``\\sin(\\pi x) / (\\pi x)`` if ``x \\neq 0``, and ``1`` if ``x = 0``. +Return a `T(NaN)` if `isnan(x)`. See also [`cosc`](@ref), its derivative. """ @@ -1080,10 +1094,14 @@ _sinc(x::Float16) = Float16(_sinc(Float32(x))) _sinc(x::ComplexF16) = ComplexF16(_sinc(ComplexF32(x))) """ - cosc(x) + cosc(x::T) where {T <: Number} -> float(T) Compute ``\\cos(\\pi x) / x - \\sin(\\pi x) / (\\pi x^2)`` if ``x \\neq 0``, and ``0`` if ``x = 0``. This is the derivative of `sinc(x)`. + +Return a `T(NaN)` if `isnan(x)`. + +See also [`sinc`](@ref). """ cosc(x::Number) = _cosc(float(x)) function _cosc(x::Number) @@ -1127,19 +1145,25 @@ for (finv, f, finvh, fh, finvd, fd, fn) in ((:sec, :cos, :sech, :cosh, :secd, :c dname = string(finvd) @eval begin @doc """ - $($name)(x) + $($name)(x::T) where {T <: Number} -> float(T) Compute the $($fn) of `x`, where `x` is in radians. + + Throw a [`DomainError`](@ref) if `isinf(x)`, return a `T(NaN)` if `isnan(x)`. """ ($finv)(z::Number) = inv(($f)(z)) @doc """ - $($hname)(x) + $($hname)(x::T) where {T <: Number} -> float(T) Compute the hyperbolic $($fn) of `x`. + + Return a `T(NaN)` if `isnan(x)`. """ ($finvh)(z::Number) = inv(($fh)(z)) @doc """ - $($dname)(x) + $($dname)(x::T) where {T <: Number} -> float(T) Compute the $($fn) of `x`, where `x` is in degrees. + + Throw a [`DomainError`](@ref) if `isinf(x)`, return a `T(NaN)` if `isnan(x)`. """ ($finvd)(z::Number) = inv(($fd)(z)) end end @@ -1151,11 +1175,15 @@ for (tfa, tfainv, hfa, hfainv, fn) in ((:asec, :acos, :asech, :acosh, "secant"), hname = string(hfa) @eval begin @doc """ - $($tname)(x) - Compute the inverse $($fn) of `x`, where the output is in radians. """ ($tfa)(y::Number) = ($tfainv)(inv(y)) + $($tname)(x::T) where {T <: Number} -> float(T) + + Compute the inverse $($fn) of `x`, where the output is in radians. + """ ($tfa)(y::Number) = ($tfainv)(inv(y)) @doc """ - $($hname)(x) - Compute the inverse hyperbolic $($fn) of `x`. """ ($hfa)(y::Number) = ($hfainv)(inv(y)) + $($hname)(x::T) where {T <: Number} -> float(T) + + Compute the inverse hyperbolic $($fn) of `x`. + """ ($hfa)(y::Number) = ($hfainv)(inv(y)) end end @@ -1180,7 +1208,7 @@ deg2rad_ext(x::Real) = deg2rad(x) # Fallback function sind(x::Real) if isinf(x) - return throw(DomainError(x, "`x` cannot be infinite.")) + return throw(DomainError(x, "`sind(x)` is only defined for finite `x`.")) elseif isnan(x) return x end @@ -1211,7 +1239,7 @@ end function cosd(x::Real) if isinf(x) - return throw(DomainError(x, "`x` cannot be infinite.")) + return throw(DomainError(x, "`cosd(x)` is only defined for finite `x`.")) elseif isnan(x) return x end @@ -1238,9 +1266,12 @@ end tand(x::Real) = sind(x) / cosd(x) """ - sincosd(x) + sincosd(x::T) where T -> Tuple{float(T),float(T)} -Simultaneously compute the sine and cosine of `x`, where `x` is in degrees. +Simultaneously compute the sine and cosine of `x`, where `x` is in degrees, returning +a tuple `(sine, cosine)`. + +Throw a [`DomainError`](@ref) if `isinf(x)`, return a `(T(NaN), T(NaN))` tuple if `isnan(x)`. !!! compat "Julia 1.3" This function requires at least Julia 1.3. @@ -1256,11 +1287,13 @@ for (fd, f, fn) in ((:sind, :sin, "sine"), (:cosd, :cos, "cosine"), (:tand, :tan name = string(fd) @eval begin @doc """ - $($name)(x) + $($name)(x::T) where T -> float(T) Compute $($fn) of `x`, where `x` is in $($un). If `x` is a matrix, `x` needs to be a square matrix. + Throw a [`DomainError`](@ref) if `isinf(x)`, return a `T(NaN)` if `isnan(x)`. + !!! compat "Julia 1.7" Matrix arguments require Julia 1.7 or later. """ ($fd)(x) = ($f)(($fu).(x)) @@ -1288,11 +1321,15 @@ for (fd, f, fn) in ((:asind, :asin, "sine"), (:acosd, :acos, "cosine"), end """ - atand(y) - atand(y,x) + atand(y::T) where T -> float(T) + atand(y::T, x::S) where {T,S} -> promote_type(T,S) + atand(y::AbstractMatrix{T}) where T -> AbstractMatrix{Complex{float(T)}} Compute the inverse tangent of `y` or `y/x`, respectively, where the output is in degrees. +Return a `NaN` if `isnan(y)` or `isnan(x)`. The returned `NaN` is either a `T` in the single +argument version, or a `promote_type(T,S)` in the two argument version. + !!! compat "Julia 1.7" The one-argument method supports square matrix arguments as of Julia 1.7. """ diff --git a/base/stacktraces.jl b/base/stacktraces.jl index bb70b7ea1c099..84d280973c28a 100644 --- a/base/stacktraces.jl +++ b/base/stacktraces.jl @@ -8,6 +8,7 @@ module StackTraces import Base: hash, ==, show import Core: CodeInfo, MethodInstance +using Base.IRShow: normalize_method_name, append_scopes!, LineInfoNode export StackTrace, StackFrame, stacktrace @@ -20,10 +21,10 @@ Stack information representing execution context, with the following fields: The name of the function containing the execution context. -- `linfo::Union{Core.MethodInstance, Method, Module, Core.CodeInfo, Nothing}` +- `linfo::Union{Method, Core.MethodInstance, Core.CodeInfo, Nothing}` - The MethodInstance or CodeInfo containing the execution context (if it could be found), \ - or Module (for macro expansions)" + The Method, MethodInstance, or CodeInfo containing the execution context (if it could be found), \ + or nothing (for example, if the inlining was a result of macro expansion). - `file::Symbol` @@ -54,8 +55,8 @@ struct StackFrame # this type should be kept platform-agnostic so that profiles "the line number in the file containing the execution context" line::Int "the MethodInstance or CodeInfo containing the execution context (if it could be found), \ - or Module (for macro expansions)" - linfo::Union{MethodInstance, Method, Module, CodeInfo, Nothing} + or nothing (for example, if the inlining was a result of macro expansion)." + linfo::Union{MethodInstance, Method, CodeInfo, Nothing} "true if the code is from C" from_c::Bool "true if the code is from an inlined frame" @@ -97,87 +98,6 @@ function hash(frame::StackFrame, h::UInt) return h end -get_inlinetable(::Any) = nothing -function get_inlinetable(mi::MethodInstance) - isdefined(mi, :def) && mi.def isa Method && isdefined(mi, :cache) && isdefined(mi.cache, :inferred) && - mi.cache.inferred !== nothing || return nothing - linetable = ccall(:jl_uncompress_ir, Any, (Any, Any, Any), mi.def, mi.cache, mi.cache.inferred).linetable - return filter!(x -> x.inlined_at > 0, linetable) -end - -get_method_instance_roots(::Any) = nothing -function get_method_instance_roots(mi::Union{Method, MethodInstance}) - m = mi isa MethodInstance ? mi.def : mi - m isa Method && isdefined(m, :roots) || return nothing - return filter(x -> x isa MethodInstance, m.roots) -end - -function lookup_inline_frame_info(func::Symbol, file::Symbol, linenum::Int, inlinetable::Vector{Core.LineInfoNode}) - #REPL frames and some base files lack this prefix while others have it; should fix? - filestripped = Symbol(lstrip(string(file), ('.', '\\', '/'))) - linfo = nothing - #= - Some matching entries contain the MethodInstance directly. - Other matching entries contain only a Method or Symbol (function name); such entries - are located after the entry with the MethodInstance, so backtracking is required. - If backtracking fails, the Method or Module is stored for return, but we continue - the search in case a MethodInstance is found later. - TODO: If a backtrack has failed, do we need to backtrack again later if another Method - or Symbol match is found? Or can a limit on the subsequent backtracks be placed? - =# - for (i, line) in enumerate(inlinetable) - Base.IRShow.method_name(line) === func && line.file ∈ (file, filestripped) && line.line == linenum || continue - if line.method isa MethodInstance - linfo = line.method - break - elseif line.method isa Method || line.method isa Symbol - linfo = line.method isa Method ? line.method : line.module - # backtrack to find the matching MethodInstance, if possible - for j in (i - 1):-1:1 - nextline = inlinetable[j] - nextline.inlined_at == line.inlined_at && Base.IRShow.method_name(line) === Base.IRShow.method_name(nextline) && line.file === nextline.file || break - if nextline.method isa MethodInstance - linfo = nextline.method - break - end - end - end - end - return linfo -end - -function lookup_inline_frame_info(func::Symbol, file::Symbol, miroots::Vector{Any}) - # REPL frames and some base files lack this prefix while others have it; should fix? - filestripped = Symbol(lstrip(string(file), ('.', '\\', '/'))) - matches = filter(miroots) do x - x.def isa Method || return false - m = x.def::Method - return m.name == func && m.file ∈ (file, filestripped) - end - if length(matches) > 1 - # ambiguous, check if method is same and return that instead - all_matched = true - for m in matches - all_matched = m.def.line == matches[1].def.line && - m.def.module == matches[1].def.module - all_matched || break - end - if all_matched - return matches[1].def - end - # all else fails, return module if they match, or give up - all_matched = true - for m in matches - all_matched = m.def.module == matches[1].def.module - all_matched || break - end - return all_matched ? matches[1].def.module : nothing - elseif length(matches) == 1 - return matches[1] - end - return nothing -end - """ lookup(pointer::Ptr{Cvoid}) -> Vector{StackFrame} @@ -189,24 +109,14 @@ Base.@constprop :none function lookup(pointer::Ptr{Cvoid}) infos = ccall(:jl_lookup_code_address, Any, (Ptr{Cvoid}, Cint), pointer, false)::Core.SimpleVector pointer = convert(UInt64, pointer) isempty(infos) && return [StackFrame(empty_sym, empty_sym, -1, nothing, true, false, pointer)] # this is equal to UNKNOWN - parent_linfo = infos[end][4] - inlinetable = get_inlinetable(parent_linfo) - miroots = inlinetable === nothing ? get_method_instance_roots(parent_linfo) : nothing # fallback if linetable missing res = Vector{StackFrame}(undef, length(infos)) - for i in reverse(1:length(infos)) + for i in 1:length(infos) info = infos[i]::Core.SimpleVector @assert(length(info) == 6) func = info[1]::Symbol file = info[2]::Symbol linenum = info[3]::Int linfo = info[4] - if i < length(infos) - if inlinetable !== nothing - linfo = lookup_inline_frame_info(func, file, linenum, inlinetable) - elseif miroots !== nothing - linfo = lookup_inline_frame_info(func, file, miroots) - end - end res[i] = StackFrame(func, file, linenum, linfo, info[5]::Bool, info[6]::Bool, pointer) end return res @@ -231,17 +141,24 @@ function lookup(ip::Union{Base.InterpreterIP,Core.Compiler.InterpreterIP}) file = empty_sym line = Int32(0) end - i = max(ip.stmt+1, 1) # ip.stmt is 0-indexed - if i > length(codeinfo.codelocs) || codeinfo.codelocs[i] == 0 + def = (code isa MethodInstance ? code : StackTraces) # Module just used as a token for top-level code + pc::Int = max(ip.stmt + 1, 0) # n.b. ip.stmt is 0-indexed + scopes = LineInfoNode[] + append_scopes!(scopes, pc, codeinfo.debuginfo, def) + if isempty(scopes) return [StackFrame(func, file, line, code, false, false, 0)] end - lineinfo = codeinfo.linetable[codeinfo.codelocs[i]]::Core.LineInfoNode - scopes = StackFrame[] - while true - inlined = lineinfo.inlined_at != 0 - push!(scopes, StackFrame(Base.IRShow.method_name(lineinfo)::Symbol, lineinfo.file, lineinfo.line, inlined ? nothing : code, false, inlined, 0)) - inlined || break - lineinfo = codeinfo.linetable[lineinfo.inlined_at]::Core.LineInfoNode + inlined = false + scopes = map(scopes) do lno + if inlined + def = lno.method + def isa Union{Method,MethodInstance} || (def = nothing) + else + def = codeinfo + end + sf = StackFrame(normalize_method_name(lno.method), lno.file, lno.line, def, false, inlined, 0) + inlined = true + return sf end return scopes end diff --git a/base/stat.jl b/base/stat.jl index 3aa04e45b4936..6db687f94dbcc 100644 --- a/base/stat.jl +++ b/base/stat.jl @@ -25,6 +25,30 @@ export stat, uperm +""" + StatStruct + +A struct which stores the information from `stat`. +The following fields of this struct is considered public API: + +| Name | Type | Description | +|:--------|:--------------------------------|:-------------------------------------------------------------------| +| desc | `Union{String, Base.OS_HANDLE}` | The path or OS file descriptor | +| size | `Int64` | The size (in bytes) of the file | +| device | `UInt` | ID of the device that contains the file | +| inode | `UInt` | The inode number of the file | +| mode | `UInt` | The protection mode of the file | +| nlink | `Int` | The number of hard links to the file | +| uid | `UInt` | The user id of the owner of the file | +| gid | `UInt` | The group id of the file owner | +| rdev | `UInt` | If this file refers to a device, the ID of the device it refers to | +| blksize | `Int64` | The file-system preferred block size for the file | +| blocks | `Int64` | The number of 512-byte blocks allocated | +| mtime | `Float64` | Unix timestamp of when the file was last modified | +| ctime | `Float64` | Unix timestamp of when the file's metadata was changed | + +See also: [`stat`](@ref) +""" struct StatStruct desc :: Union{String, OS_HANDLE} # for show method, not included in equality or hash device :: UInt @@ -173,22 +197,21 @@ stat(fd::Integer) = stat(RawFD(fd)) Return a structure whose fields contain information about the file. The fields of the structure are: -| Name | Description | -|:--------|:-------------------------------------------------------------------| -| desc | The path or OS file descriptor | -| size | The size (in bytes) of the file | -| device | ID of the device that contains the file | -| inode | The inode number of the file | -| mode | The protection mode of the file | -| nlink | The number of hard links to the file | -| uid | The user id of the owner of the file | -| gid | The group id of the file owner | -| rdev | If this file refers to a device, the ID of the device it refers to | -| blksize | The file-system preferred block size for the file | -| blocks | The number of 512-byte blocks allocated | -| mtime | Unix timestamp of when the file was last modified | -| ctime | Unix timestamp of when the file's metadata was changed | - +| Name | Type | Description | +|:--------|:--------------------------------|:-------------------------------------------------------------------| +| desc | `Union{String, Base.OS_HANDLE}` | The path or OS file descriptor | +| size | `Int64` | The size (in bytes) of the file | +| device | `UInt` | ID of the device that contains the file | +| inode | `UInt` | The inode number of the file | +| mode | `UInt` | The protection mode of the file | +| nlink | `Int` | The number of hard links to the file | +| uid | `UInt` | The user id of the owner of the file | +| gid | `UInt` | The group id of the file owner | +| rdev | `UInt` | If this file refers to a device, the ID of the device it refers to | +| blksize | `Int64` | The file-system preferred block size for the file | +| blocks | `Int64` | The number of 512-byte blocks allocated | +| mtime | `Float64` | Unix timestamp of when the file was last modified | +| ctime | `Float64` | Unix timestamp of when the file's metadata was changed | """ stat(path...) = stat(joinpath(path...)) @@ -303,6 +326,16 @@ otherwise returns `false`. This is the generalization of [`isfile`](@ref), [`isdir`](@ref) etc. """ ispath(st::StatStruct) = filemode(st) & 0xf000 != 0x0000 +function ispath(path::String) + # We use `access()` and `F_OK` to determine if a given path exists. `F_OK` comes from `unistd.h`. + F_OK = 0x00 + r = ccall(:jl_fs_access, Cint, (Cstring, Cint), path, F_OK) + if !(r in (0, Base.UV_ENOENT, Base.UV_ENOTDIR, Base.UV_EINVAL)) + uv_error(string("ispath(", repr(path), ")"), r) + end + return r == 0 +end +ispath(path::AbstractString) = ispath(String(path)) """ isfifo(path) -> Bool diff --git a/base/stream.jl b/base/stream.jl index 22af8d59359f3..e80d27c837802 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -304,7 +304,7 @@ function init_stdio(handle::Ptr{Cvoid}) elseif t == UV_TTY io = TTY(handle, StatusOpen) elseif t == UV_TCP - Sockets = require(PkgId(UUID((0x6462fe0b_24de_5631, 0x8697_dd941f90decc)), "Sockets")) + Sockets = require_stdlib(PkgId(UUID((0x6462fe0b_24de_5631, 0x8697_dd941f90decc)), "Sockets")) io = Sockets.TCPSocket(handle, StatusOpen) elseif t == UV_NAMED_PIPE io = PipeEndpoint(handle, StatusOpen) @@ -341,7 +341,7 @@ function open(h::OS_HANDLE) elseif t == UV_TTY io = TTY(h) elseif t == UV_TCP - Sockets = require(PkgId(UUID((0x6462fe0b_24de_5631, 0x8697_dd941f90decc)), "Sockets")) + Sockets = require_stdlib(PkgId(UUID((0x6462fe0b_24de_5631, 0x8697_dd941f90decc)), "Sockets")) io = Sockets.TCPSocket(h) elseif t == UV_NAMED_PIPE io = PipeEndpoint(h) @@ -436,7 +436,10 @@ end function closewrite(s::LibuvStream) iolock_begin() - check_open(s) + if !iswritable(s) + iolock_end() + return + end req = Libc.malloc(_sizeof_uv_shutdown) uv_req_set_data(req, C_NULL) # in case we get interrupted before arriving at the wait call err = ccall(:uv_shutdown, Int32, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}), @@ -450,9 +453,11 @@ function closewrite(s::LibuvStream) sigatomic_begin() uv_req_set_data(req, ct) iolock_end() - status = try + local status + try sigatomic_end() - wait()::Cint + status = wait()::Cint + sigatomic_begin() finally # try-finally unwinds the sigatomic level, so need to repeat sigatomic_end sigatomic_end() @@ -605,7 +610,7 @@ end function alloc_request(buffer::IOBuffer, recommended_size::UInt) ensureroom(buffer, Int(recommended_size)) ptr = buffer.append ? buffer.size + 1 : buffer.ptr - nb = min(length(buffer.data), buffer.maxsize) - ptr + 1 + nb = min(length(buffer.data)-buffer.offset, buffer.maxsize) + buffer.offset - ptr + 1 return (Ptr{Cvoid}(pointer(buffer.data, ptr)), nb) end @@ -616,6 +621,7 @@ function notify_filled(buffer::IOBuffer, nread::Int) buffer.size += nread else buffer.ptr += nread + buffer.size = max(buffer.size, buffer.ptr - 1) end nothing end @@ -740,24 +746,42 @@ mutable struct Pipe <: AbstractPipe end """ -Construct an uninitialized Pipe object. + Pipe() + +Construct an uninitialized Pipe object, especially for IO communication between multiple processes. -The appropriate end of the pipe will be automatically initialized if -the object is used in process spawning. This can be useful to easily -obtain references in process pipelines, e.g.: +The appropriate end of the pipe will be automatically initialized if the object is used in +process spawning. This can be useful to easily obtain references in process pipelines, e.g.: ``` julia> err = Pipe() # After this `err` will be initialized and you may read `foo`'s -# stderr from the `err` pipe. +# stderr from the `err` pipe, or pass `err` to other pipelines. julia> run(pipeline(pipeline(`foo`, stderr=err), `cat`), wait=false) + +# Now destroy the write half of the pipe, so that the read half will get EOF +julia> closewrite(err) + +julia> read(err, String) +"stderr messages" ``` + +See also [`Base.link_pipe!`](@ref). """ Pipe() = Pipe(PipeEndpoint(), PipeEndpoint()) pipe_reader(p::Pipe) = p.out pipe_writer(p::Pipe) = p.in +""" + link_pipe!(pipe; reader_supports_async=false, writer_supports_async=false) + +Initialize `pipe` and link the `in` endpoint to the `out` endpoint. The keyword +arguments `reader_supports_async`/`writer_supports_async` correspond to +`OVERLAPPED` on Windows and `O_NONBLOCK` on POSIX systems. They should be `true` +unless they'll be used by an external program (e.g. the output of a command +executed with [`run`](@ref)). +""" function link_pipe!(pipe::Pipe; reader_supports_async = false, writer_supports_async = false) @@ -911,7 +935,7 @@ function readbytes!(s::LibuvStream, a::Vector{UInt8}, nb::Int) nread = readbytes!(sbuf, a, nb) else newbuf = PipeBuffer(a, maxsize=nb) - newbuf.size = 0 # reset the write pointer to the beginning + newbuf.size = newbuf.offset # reset the write pointer to the beginning nread = try s.buffer = newbuf write(newbuf, sbuf) @@ -958,7 +982,7 @@ function unsafe_read(s::LibuvStream, p::Ptr{UInt8}, nb::UInt) unsafe_read(sbuf, p, nb) else newbuf = PipeBuffer(unsafe_wrap(Array, p, nb), maxsize=Int(nb)) - newbuf.size = 0 # reset the write pointer to the beginning + newbuf.size = newbuf.offset # reset the write pointer to the beginning try s.buffer = newbuf write(newbuf, sbuf) @@ -1040,12 +1064,14 @@ function uv_write(s::LibuvStream, p::Ptr{UInt8}, n::UInt) sigatomic_begin() uv_req_set_data(uvw, ct) iolock_end() - status = try + local status + try sigatomic_end() # wait for the last chunk to complete (or error) # assume that any errors would be sticky, # (so we don't need to monitor the error status of the intermediate writes) - wait()::Cint + status = wait()::Cint + sigatomic_begin() finally # try-finally unwinds the sigatomic level, so need to repeat sigatomic_end sigatomic_end() @@ -1273,7 +1299,7 @@ the pipe. !!! note `stream` must be a compatible objects, such as an `IOStream`, `TTY`, - `Pipe`, socket, or `devnull`. + [`Pipe`](@ref), socket, or `devnull`. See also [`redirect_stdio`](@ref). """ @@ -1286,7 +1312,7 @@ Like [`redirect_stdout`](@ref), but for [`stderr`](@ref). !!! note `stream` must be a compatible objects, such as an `IOStream`, `TTY`, - `Pipe`, socket, or `devnull`. + [`Pipe`](@ref), socket, or `devnull`. See also [`redirect_stdio`](@ref). """ @@ -1300,7 +1326,7 @@ Note that the direction of the stream is reversed. !!! note `stream` must be a compatible objects, such as an `IOStream`, `TTY`, - `Pipe`, socket, or `devnull`. + [`Pipe`](@ref), socket, or `devnull`. See also [`redirect_stdio`](@ref). """ @@ -1310,7 +1336,8 @@ redirect_stdin redirect_stdio(;stdin=stdin, stderr=stderr, stdout=stdout) Redirect a subset of the streams `stdin`, `stderr`, `stdout`. -Each argument must be an `IOStream`, `TTY`, `Pipe`, socket, or `devnull`. +Each argument must be an `IOStream`, `TTY`, [`Pipe`](@ref), socket, or +`devnull`. !!! compat "Julia 1.7" `redirect_stdio` requires Julia 1.7 or later. @@ -1330,7 +1357,7 @@ call `f()` and restore each stream. Possible values for each stream are: * `nothing` indicating the stream should not be redirected. * `path::AbstractString` redirecting the stream to the file at `path`. -* `io` an `IOStream`, `TTY`, `Pipe`, socket, or `devnull`. +* `io` an `IOStream`, `TTY`, [`Pipe`](@ref), socket, or `devnull`. # Examples ```julia-repl @@ -1489,7 +1516,7 @@ closewrite(s::BufferStream) = close(s) function close(s::BufferStream) lock(s.cond) do s.status = StatusClosed - notify(s.cond) + notify(s.cond) # aka flush nothing end end @@ -1549,6 +1576,7 @@ stop_reading(s::BufferStream) = nothing write(s::BufferStream, b::UInt8) = write(s, Ref{UInt8}(b)) function unsafe_write(s::BufferStream, p::Ptr{UInt8}, nb::UInt) nwrite = lock(s.cond) do + check_open(s) rv = unsafe_write(s.buffer, p, nb) s.buffer_writes || notify(s.cond) rv @@ -1569,9 +1597,18 @@ end buffer_writes(s::BufferStream, bufsize=0) = (s.buffer_writes = true; s) function flush(s::BufferStream) lock(s.cond) do + check_open(s) notify(s.cond) nothing end end skip(s::BufferStream, n) = skip(s.buffer, n) + +function reseteof(x::BufferStream) + lock(s.cond) do + s.status = StatusOpen + nothing + end + nothing +end diff --git a/base/strings/annotated.jl b/base/strings/annotated.jl new file mode 100644 index 0000000000000..2a331c5133f83 --- /dev/null +++ b/base/strings/annotated.jl @@ -0,0 +1,573 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +""" + AnnotatedString{S <: AbstractString} <: AbstractString + +A string with metadata, in the form of annotated regions. + +More specifically, this is a simple wrapper around any other +[`AbstractString`](@ref) that allows for regions of the wrapped string to be +annotated with labeled values. + +```text + C + ┌──────┸─────────┐ + "this is an example annotated string" + └──┰────────┼─────┘ │ + A └─────┰─────────┘ + B +``` + +The above diagram represents a `AnnotatedString` where three ranges have been +annotated (labeled `A`, `B`, and `C`). Each annotation holds a label (`Symbol`) +and a value (`Any`), paired together as a `Pair{Symbol, <:Any}`. + +Labels do not need to be unique, the same region can hold multiple annotations +with the same label. + +See also [`AnnotatedChar`](@ref), [`annotatedstring`](@ref), +[`annotations`](@ref), and [`annotate!`](@ref). + +!!! warning + While the constructors are part of the Base public API, the fields + of `AnnotatedString` are not. This is to allow for potential future + changes in the implementation of this type. Instead use the + [`annotations`](@ref), and [`annotate!`](@ref) getter/setter + functions. + +# Constructors + +```julia +AnnotatedString(s::S<:AbstractString) -> AnnotatedString{S} +AnnotatedString(s::S<:AbstractString, annotations::Vector{Tuple{UnitRange{Int}, Pair{Symbol, <:Any}}}) +``` + +A AnnotatedString can also be created with [`annotatedstring`](@ref), which acts much +like [`string`](@ref) but preserves any annotations present in the arguments. + +# Examples + +```julia-repl +julia> AnnotatedString("this is an example annotated string", + [(1:18, :A => 1), (12:28, :B => 2), (18:35, :C => 3)]) +"this is an example annotated string" +``` +""" +struct AnnotatedString{S <: AbstractString} <: AbstractString + string::S + annotations::Vector{Tuple{UnitRange{Int}, Pair{Symbol, Any}}} +end + +""" + AnnotatedChar{S <: AbstractChar} <: AbstractChar + +A Char with annotations. + +More specifically, this is a simple wrapper around any other +[`AbstractChar`](@ref), which holds a list of arbitrary labeled annotations +(`Pair{Symbol, <:Any}`) with the wrapped character. + +See also: [`AnnotatedString`](@ref), [`annotatedstring`](@ref), `annotations`, +and `annotate!`. + +!!! warning + While the constructors are part of the Base public API, the fields + of `AnnotatedChar` are not. This it to allow for potential future + changes in the implementation of this type. Instead use the + [`annotations`](@ref), and [`annotate!`](@ref) getter/setter + functions. + +# Constructors + +```julia +AnnotatedChar(s::S) -> AnnotatedChar{S} +AnnotatedChar(s::S, annotations::Vector{Pair{Symbol, <:Any}}) +``` + +# Examples + +```julia-repl +julia> AnnotatedChar('j', :label => 1) +'j': ASCII/Unicode U+006A (category Ll: Letter, lowercase) +``` +""" +struct AnnotatedChar{C <: AbstractChar} <: AbstractChar + char::C + annotations::Vector{Pair{Symbol, Any}} +end + +## Constructors ## + +# When called with overly-specialised arguments + +AnnotatedString(s::AbstractString, annots::Vector{<:Tuple{UnitRange{Int}, <:Pair{Symbol, <:Any}}}) = + AnnotatedString(s, Vector{Tuple{UnitRange{Int}, Pair{Symbol, Any}}}(annots)) + +AnnotatedChar(c::AbstractChar, annots::Vector{<:Pair{Symbol, <:Any}}) = + AnnotatedChar(c, Vector{Pair{Symbol, Any}}(annots)) + +# Constructors to avoid recursive wrapping + +AnnotatedString(s::AnnotatedString, annots::Vector{Tuple{UnitRange{Int}, Pair{Symbol, Any}}}) = + AnnotatedString(s.string, vcat(s.annotations, annots)) + +AnnotatedChar(c::AnnotatedChar, annots::Vector{Pair{Symbol, Any}}) = + AnnotatedChar(c.char, vcat(c.annotations, annots)) + +String(s::AnnotatedString{String}) = s.string # To avoid pointless overhead + +## Conversion/promotion ## + +convert(::Type{AnnotatedString}, s::AnnotatedString) = s +convert(::Type{AnnotatedString{S}}, s::S) where {S <: AbstractString} = + AnnotatedString(s, Vector{Tuple{UnitRange{Int}, Pair{Symbol, Any}}}()) +convert(::Type{AnnotatedString}, s::S) where {S <: AbstractString} = + convert(AnnotatedString{S}, s) +AnnotatedString(s::S) where {S <: AbstractString} = convert(AnnotatedString{S}, s) + +convert(::Type{AnnotatedChar}, c::AnnotatedChar) = c +convert(::Type{AnnotatedChar{C}}, c::C) where { C <: AbstractChar } = + AnnotatedChar{C}(c, Vector{Pair{Symbol, Any}}()) +convert(::Type{AnnotatedChar}, c::C) where { C <: AbstractChar } = + convert(AnnotatedChar{C}, c) + +AnnotatedChar(c::AbstractChar) = convert(AnnotatedChar, c) +AnnotatedChar(c::UInt32) = convert(AnnotatedChar, Char(c)) +AnnotatedChar{C}(c::UInt32) where {C <: AbstractChar} = convert(AnnotatedChar, C(c)) + +promote_rule(::Type{<:AnnotatedString}, ::Type{<:AbstractString}) = AnnotatedString + +## AbstractString interface ## + +ncodeunits(s::AnnotatedString) = ncodeunits(s.string) +codeunits(s::AnnotatedString) = codeunits(s.string) +codeunit(s::AnnotatedString) = codeunit(s.string) +codeunit(s::AnnotatedString, i::Integer) = codeunit(s.string, i) +isvalid(s::AnnotatedString, i::Integer) = isvalid(s.string, i) +@propagate_inbounds iterate(s::AnnotatedString, i::Integer=firstindex(s)) = + if i <= lastindex(s.string); (s[i], nextind(s, i)) end +eltype(::Type{<:AnnotatedString{S}}) where {S} = AnnotatedChar{eltype(S)} +firstindex(s::AnnotatedString) = firstindex(s.string) +lastindex(s::AnnotatedString) = lastindex(s.string) + +function getindex(s::AnnotatedString, i::Integer) + @boundscheck checkbounds(s, i) + @inbounds if isvalid(s, i) + AnnotatedChar(s.string[i], map(last, annotations(s, i))) + else + string_index_err(s, i) + end +end + +## AbstractChar interface ## + +ncodeunits(c::AnnotatedChar) = ncodeunits(c.char) +codepoint(c::AnnotatedChar) = codepoint(c.char) + +# Avoid the iteration fallback with comparison +cmp(a::AnnotatedString, b::AbstractString) = cmp(a.string, b) +cmp(a::AbstractString, b::AnnotatedString) = cmp(a, b.string) +# To avoid method ambiguity +cmp(a::AnnotatedString, b::AnnotatedString) = cmp(a.string, b.string) + +==(a::AnnotatedString, b::AnnotatedString) = + a.string == b.string && a.annotations == b.annotations + +==(a::AnnotatedString, b::AbstractString) = isempty(a.annotations) && a.string == b +==(a::AbstractString, b::AnnotatedString) = isempty(b.annotations) && a == b.string + +""" + annotatedstring(values...) + +Create a `AnnotatedString` from any number of `values` using their +[`print`](@ref)ed representation. + +This acts like [`string`](@ref), but takes care to preserve any annotations +present (in the form of [`AnnotatedString`](@ref) or [`AnnotatedChar`](@ref) values). + +See also [`AnnotatedString`](@ref) and [`AnnotatedChar`](@ref). + +## Examples + +```julia-repl +julia> annotatedstring("now a AnnotatedString") +"now a AnnotatedString" + +julia> annotatedstring(AnnotatedString("annotated", [(1:9, :label => 1)]), ", and unannotated") +"annotated, and unannotated" +``` +""" +function annotatedstring(xs...) + isempty(xs) && return AnnotatedString("") + size = mapreduce(_str_sizehint, +, xs) + buf = IOBuffer(sizehint=size) + s = IOContext(buf, :color => true) + annotations = Vector{Tuple{UnitRange{Int}, Pair{Symbol, Any}}}() + for x in xs + size = filesize(s.io) + if x isa AnnotatedString + for (region, annot) in x.annotations + push!(annotations, (size .+ (region), annot)) + end + print(s, x.string) + elseif x isa SubString{<:AnnotatedString} + for (region, annot) in x.string.annotations + start, stop = first(region), last(region) + if start <= x.offset + x.ncodeunits && stop > x.offset + rstart = size + max(0, start - x.offset - 1) + 1 + rstop = size + min(stop, x.offset + x.ncodeunits) - x.offset + push!(annotations, (rstart:rstop, annot)) + end + end + print(s, SubString(x.string.string, x.offset, x.ncodeunits, Val(:noshift))) + elseif x isa AnnotatedChar + for annot in x.annotations + push!(annotations, (1+size:1+size, annot)) + end + print(s, x.char) + else + print(s, x) + end + end + str = String(take!(buf)) + AnnotatedString(str, annotations) +end + +annotatedstring(s::AnnotatedString) = s +annotatedstring(c::AnnotatedChar) = + AnnotatedString(string(c.char), [(1:ncodeunits(c), annot) for annot in c.annotations]) + +AnnotatedString(s::SubString{<:AnnotatedString}) = annotatedstring(s) + +""" + annotatedstring_optimize!(str::AnnotatedString) + +Merge contiguous identical annotations in `str`. +""" +function annotatedstring_optimize!(s::AnnotatedString) + last_seen = Dict{Pair{Symbol, Any}, Int}() + i = 1 + while i <= length(s.annotations) + region, keyval = s.annotations[i] + prev = get(last_seen, keyval, 0) + if prev > 0 + lregion, _ = s.annotations[prev] + if last(lregion) + 1 == first(region) + s.annotations[prev] = + setindex(s.annotations[prev], + first(lregion):last(region), + 1) + deleteat!(s.annotations, i) + else + delete!(last_seen, keyval) + end + else + last_seen[keyval] = i + i += 1 + end + end + s +end + +function repeat(str::AnnotatedString, r::Integer) + r == 0 && return one(AnnotatedString) + r == 1 && return str + unannot = repeat(str.string, r) + annotations = Vector{Tuple{UnitRange{Int}, Pair{Symbol, Any}}}() + len = ncodeunits(str) + fullregion = firstindex(str):lastindex(str) + for (region, annot) in str.annotations + if region == fullregion + push!(annotations, (firstindex(unannot):lastindex(unannot), annot)) + end + end + for offset in 0:len:(r-1)*len + for (region, annot) in str.annotations + if region != fullregion + push!(annotations, (region .+ offset, annot)) + end + end + end + AnnotatedString(unannot, annotations) |> annotatedstring_optimize! +end + +repeat(str::SubString{<:AnnotatedString}, r::Integer) = + repeat(AnnotatedString(str), r) + +function repeat(c::AnnotatedChar, r::Integer) + str = repeat(c.char, r) + fullregion = firstindex(str):lastindex(str) + AnnotatedString(str, [(fullregion, annot) for annot in c.annotations]) +end + +function reverse(s::AnnotatedString) + lastind = lastindex(s) + AnnotatedString(reverse(s.string), + [(UnitRange(1 + lastind - last(region), + 1 + lastind - first(region)), + annot) + for (region, annot) in s.annotations]) +end + +# TODO optimise? +reverse(s::SubString{<:AnnotatedString}) = reverse(AnnotatedString(s)) + +# TODO implement `replace(::AnnotatedString, ...)` + +## End AbstractString interface ## + +function _annotate!(annlist::Vector{Tuple{UnitRange{Int}, Pair{Symbol, Any}}}, range::UnitRange{Int}, @nospecialize(labelval::Pair{Symbol, <:Any})) + label, val = labelval + if val === nothing + indices = searchsorted(annlist, (range,), by=first) + labelindex = filter(i -> first(annlist[i][2]) === label, indices) + for index in Iterators.reverse(labelindex) + deleteat!(annlist, index) + end + else + sortedindex = searchsortedlast(annlist, (range,), by=first) + 1 + insert!(annlist, sortedindex, (range, Pair{Symbol, Any}(label, val))) + end +end + +""" + annotate!(str::AnnotatedString, [range::UnitRange{Int}], label::Symbol => value) + annotate!(str::SubString{AnnotatedString}, [range::UnitRange{Int}], label::Symbol => value) + +Annotate a `range` of `str` (or the entire string) with a labeled value (`label` => `value`). +To remove existing `label` annotations, use a value of `nothing`. +""" +annotate!(s::AnnotatedString, range::UnitRange{Int}, @nospecialize(labelval::Pair{Symbol, <:Any})) = + (_annotate!(s.annotations, range, labelval); s) + +annotate!(ss::AnnotatedString, @nospecialize(labelval::Pair{Symbol, <:Any})) = + annotate!(ss, firstindex(ss):lastindex(ss), labelval) + +annotate!(s::SubString{<:AnnotatedString}, range::UnitRange{Int}, @nospecialize(labelval::Pair{Symbol, <:Any})) = + (annotate!(s.string, s.offset .+ (range), labelval); s) + +annotate!(s::SubString{<:AnnotatedString}, @nospecialize(labelval::Pair{Symbol, <:Any})) = + (annotate!(s.string, s.offset .+ (1:s.ncodeunits), labelval); s) + +""" + annotate!(char::AnnotatedChar, label::Symbol => value) + +Annotate `char` with the pair `label => value`. +""" +annotate!(c::AnnotatedChar, @nospecialize(labelval::Pair{Symbol, <:Any})) = + (push!(c.annotations, labelval); c) + +""" + annotations(str::Union{AnnotatedString, SubString{AnnotatedString}}, + [position::Union{Integer, UnitRange}]) -> + Vector{Tuple{UnitRange{Int}, Pair{Symbol, Any}}} + +Get all annotations that apply to `str`. Should `position` be provided, only +annotations that overlap with `position` will be returned. + +Annotations are provided together with the regions they apply to, in the form of +a vector of region–annotation tuples. + +See also: `annotate!`. +""" +annotations(s::AnnotatedString) = s.annotations + +function annotations(s::SubString{<:AnnotatedString}) + map(((region, annot),) -> (first(region)-s.offset:last(region)-s.offset, annot), + annotations(s.string, s.offset+1:s.offset+s.ncodeunits)) +end + +function annotations(s::AnnotatedString, pos::UnitRange{<:Integer}) + # TODO optimise + Tuple{UnitRange{Int64}, Pair{Symbol, Any}}[ + (max(first(pos), first(region)):min(last(pos), last(region)), annot) + for (region, annot) in s.annotations if !isempty(intersect(pos, region))] +end + +annotations(s::AnnotatedString, pos::Integer) = annotations(s, pos:pos) + +annotations(s::SubString{<:AnnotatedString}, pos::Integer) = + annotations(s.string, s.offset + pos) + +annotations(s::SubString{<:AnnotatedString}, pos::UnitRange{<:Integer}) = + annotations(s.string, first(pos)+s.offset:last(pos)+s.offset) + +""" + annotations(chr::AnnotatedChar) -> Vector{Pair{Symbol, Any}} + +Get all annotations of `chr`, in the form of a vector of annotation pairs. +""" +annotations(c::AnnotatedChar) = c.annotations + +## Character transformation helper function, c.f. `unicode.jl`. + +""" + annotated_chartransform(f::Function, str::AnnotatedString, state=nothing) + +Transform every character in `str` with `f`, adjusting annotation regions as +appropriate. `f` must take one of two forms, either: +- `f(c::Char) -> Char`, or +- `f(c::Char, state) -> (Char, state)`. + +This works by comparing the number of code units of each character before and +after transforming with `f`, recording and aggregating any differences, then +applying them to the annotation regions. + +Returns an `AnnotatedString{String}` (regardless of the original underling +string type of `str`). +""" +function annotated_chartransform(f::Function, str::AnnotatedString, state=nothing) + outstr = IOBuffer() + annots = Tuple{UnitRange{Int}, Pair{Symbol, Any}}[] + bytepos = firstindex(str) - 1 + offsets = [bytepos => 0] + for c in str.string + oldnb = ncodeunits(c) + bytepos += oldnb + if isnothing(state) + c = f(c) + else + c, state = f(c, state) + end + nb = write(outstr, c) + if nb != oldnb + push!(offsets, bytepos => last(last(offsets)) + nb - oldnb) + end + end + for annot in str.annotations + region, value = annot + start, stop = first(region), last(region) + start_offset = last(offsets[findlast(<=(start) ∘ first, offsets)::Int]) + stop_offset = last(offsets[findlast(<=(stop) ∘ first, offsets)::Int]) + push!(annots, ((start + start_offset):(stop + stop_offset), value)) + end + AnnotatedString(String(take!(outstr)), annots) +end + +## AnnotatedIOBuffer + +struct AnnotatedIOBuffer <: AbstractPipe + io::IOBuffer + annotations::Vector{Tuple{UnitRange{Int}, Pair{Symbol, Any}}} +end + +AnnotatedIOBuffer(io::IOBuffer) = AnnotatedIOBuffer(io, Vector{Tuple{UnitRange{Int}, Pair{Symbol, Any}}}()) +AnnotatedIOBuffer() = AnnotatedIOBuffer(IOBuffer()) + +function show(io::IO, aio::AnnotatedIOBuffer) + show(io, AnnotatedIOBuffer) + size = filesize(aio.io) + print(io, '(', size, " byte", ifelse(size == 1, "", "s"), ", ", + length(aio.annotations), " annotation", ifelse(length(aio.annotations) == 1, "", "s"), ")") +end + +pipe_reader(io::AnnotatedIOBuffer) = io.io +pipe_writer(io::AnnotatedIOBuffer) = io.io + +# Useful `IOBuffer` methods that we don't get from `AbstractPipe` +position(io::AnnotatedIOBuffer) = position(io.io) +seek(io::AnnotatedIOBuffer, n::Integer) = (seek(io.io, n); io) +seekend(io::AnnotatedIOBuffer) = (seekend(io.io); io) +skip(io::AnnotatedIOBuffer, n::Integer) = (skip(io.io, n); io) +copy(io::AnnotatedIOBuffer) = AnnotatedIOBuffer(copy(io.io), copy(io.annotations)) + +annotations(io::AnnotatedIOBuffer) = io.annotations + +annotate!(io::AnnotatedIOBuffer, range::UnitRange{Int}, @nospecialize(labelval::Pair{Symbol, <:Any})) = + (_annotate!(io.annotations, range, labelval); io) + +function write(io::AnnotatedIOBuffer, astr::Union{AnnotatedString, SubString{<:AnnotatedString}}) + astr = AnnotatedString(astr) + offset = position(io.io) + eof(io) || _clear_annotations_in_region!(io.annotations, offset+1:offset+ncodeunits(astr)) + _insert_annotations!(io, astr.annotations) + write(io.io, String(astr)) +end + +write(io::AnnotatedIOBuffer, c::AnnotatedChar) = + write(io, AnnotatedString(string(c), map(a -> (1:ncodeunits(c), a), annotations(c)))) +write(io::AnnotatedIOBuffer, x::AbstractString) = write(io.io, x) +write(io::AnnotatedIOBuffer, s::Union{SubString{String}, String}) = write(io.io, s) +write(io::AnnotatedIOBuffer, b::UInt8) = write(io.io, b) + +function write(dest::AnnotatedIOBuffer, src::AnnotatedIOBuffer) + destpos = position(dest) + isappending = eof(dest) + srcpos = position(src) + nb = write(dest.io, src.io) + isappending || _clear_annotations_in_region!(dest.annotations, destpos:destpos+nb) + srcannots = [(max(1 + srcpos, first(region)):last(region), annot) + for (region, annot) in src.annotations if first(region) >= srcpos] + _insert_annotations!(dest, srcannots, destpos - srcpos) + nb +end + +function _clear_annotations_in_region!(annotations::Vector{Tuple{UnitRange{Int}, Pair{Symbol, Any}}}, span::UnitRange{Int}) + # Clear out any overlapping pre-existing annotations. + filter!(((region, _),) -> first(region) < first(span) || last(region) > last(span), annotations) + extras = Tuple{UnitRange{Int}, Pair{Symbol, Any}}[] + for i in eachindex(annotations) + region, annot = annotations[i] + # Test for partial overlap + if first(region) <= first(span) <= last(region) || first(region) <= last(span) <= last(region) + annotations[i] = (if first(region) < first(span) + first(region):first(span)-1 + else last(span)+1:last(region) end, annot) + # If `span` fits exactly within `region`, then we've only copied over + # the beginning overhang, but also need to conserve the end overhang. + if first(region) < first(span) && last(span) < last(region) + push!(extras, (last(span)+1:last(region), annot)) + end + end + # Insert any extra entries in the appropriate position + for entry in extras + sortedindex = searchsortedlast(annotations, (first(entry),), by=first) + 1 + insert!(annotations, sortedindex, entry) + end + end + annotations +end + +function _insert_annotations!(io::AnnotatedIOBuffer, annotations::Vector{Tuple{UnitRange{Int}, Pair{Symbol, Any}}}, offset::Int = position(io)) + if !eof(io) + for (region, annot) in annotations + region = first(region)+offset:last(region)+offset + sortedindex = searchsortedlast(io.annotations, (region,), by=first) + 1 + insert!(io.annotations, sortedindex, (region, annot)) + end + else + for (region, annot) in annotations + region = first(region)+offset:last(region)+offset + push!(io.annotations, (region, annot)) + end + end +end + +function read(io::AnnotatedIOBuffer, ::Type{AnnotatedString{T}}) where {T <: AbstractString} + if (start = position(io)) == 0 + AnnotatedString(read(io.io, T), copy(io.annotations)) + else + annots = [(UnitRange{Int}(max(1, first(region) - start), last(region)-start), val) + for (region, val) in io.annotations if last(region) > start] + AnnotatedString(read(io.io, T), annots) + end +end +read(io::AnnotatedIOBuffer, ::Type{AnnotatedString{AbstractString}}) = read(io, AnnotatedString{String}) +read(io::AnnotatedIOBuffer, ::Type{AnnotatedString}) = read(io, AnnotatedString{String}) + +function read(io::AnnotatedIOBuffer, ::Type{AnnotatedChar{T}}) where {T <: AbstractChar} + pos = position(io) + char = read(io.io, T) + annots = [annot for (range, annot) in io.annotations if pos+1 in range] + AnnotatedChar(char, annots) +end +read(io::AnnotatedIOBuffer, ::Type{AnnotatedChar{AbstractChar}}) = read(io, AnnotatedChar{Char}) +read(io::AnnotatedIOBuffer, ::Type{AnnotatedChar}) = read(io, AnnotatedChar{Char}) + +function truncate(io::AnnotatedIOBuffer, size::Integer) + truncate(io.io, size) + filter!(((range, _),) -> first(range) <= size, io.annotations) + map!(((range, val),) -> (first(range):min(size, last(range)), val), + io.annotations, io.annotations) + io +end diff --git a/base/strings/basic.jl b/base/strings/basic.jl index d2bc157aefd94..2d5f0cea26e36 100644 --- a/base/strings/basic.jl +++ b/base/strings/basic.jl @@ -179,6 +179,8 @@ firstindex(s::AbstractString) = 1 lastindex(s::AbstractString) = thisind(s, ncodeunits(s)::Int) isempty(s::AbstractString) = iszero(ncodeunits(s)::Int) +@propagate_inbounds first(s::AbstractString) = s[firstindex(s)] + function getindex(s::AbstractString, i::Integer) @boundscheck checkbounds(s, i) @inbounds return isvalid(s, i) ? (iterate(s, i)::NTuple{2,Any})[1] : string_index_err(s, i) @@ -241,9 +243,10 @@ end """ *(s::Union{AbstractString, AbstractChar}, t::Union{AbstractString, AbstractChar}...) -> AbstractString -Concatenate strings and/or characters, producing a [`String`](@ref). This is equivalent -to calling the [`string`](@ref) function on the arguments. Concatenation of built-in -string types always produces a value of type `String` but other string types may choose +Concatenate strings and/or characters, producing a [`String`](@ref) or +[`AnnotatedString`](@ref) (as appropriate). This is equivalent to calling the +[`string`](@ref) or [`annotatedstring`](@ref) function on the arguments. Concatenation of built-in string +types always produces a value of type `String` but other string types may choose to return a string of a different type as appropriate. # Examples @@ -255,10 +258,22 @@ julia> 'j' * "ulia" "julia" ``` """ -(*)(s1::Union{AbstractChar, AbstractString}, ss::Union{AbstractChar, AbstractString}...) = string(s1, ss...) +function (*)(s1::Union{AbstractChar, AbstractString}, ss::Union{AbstractChar, AbstractString}...) + if _isannotated(s1) || any(_isannotated, ss) + annotatedstring(s1, ss...) + else + string(s1, ss...) + end +end one(::Union{T,Type{T}}) where {T<:AbstractString} = convert(T, "") +# This could be written as a single statement with three ||-clauses, however then effect +# analysis thinks it may throw and runtime checks are added. +# Also see `substring.jl` for the `::SubString{T}` method. +_isannotated(S::Type) = S != Union{} && (S <: AnnotatedString || S <: AnnotatedChar) +_isannotated(s) = _isannotated(typeof(s)) + ## generic string comparison ## """ @@ -309,7 +324,8 @@ end ==(a::AbstractString, b::AbstractString) -> Bool Test whether two strings are equal character by character (technically, Unicode -code point by code point). +code point by code point). Should either string be a [`AnnotatedString`](@ref) the +string properties must match too. # Examples ```jldoctest @@ -790,8 +806,8 @@ IndexStyle(::Type{<:CodeUnits}) = IndexLinear() write(io::IO, s::CodeUnits) = write(io, s.s) -unsafe_convert(::Type{Ptr{T}}, s::CodeUnits{T}) where {T} = unsafe_convert(Ptr{T}, s.s) -unsafe_convert(::Type{Ptr{Int8}}, s::CodeUnits{UInt8}) = unsafe_convert(Ptr{Int8}, s.s) +cconvert(::Type{Ptr{T}}, s::CodeUnits{T}) where {T} = cconvert(Ptr{T}, s.s) +cconvert(::Type{Ptr{Int8}}, s::CodeUnits{UInt8}) = cconvert(Ptr{Int8}, s.s) """ codeunits(s::AbstractString) diff --git a/base/strings/cstring.jl b/base/strings/cstring.jl new file mode 100644 index 0000000000000..3a377ab0e7b1e --- /dev/null +++ b/base/strings/cstring.jl @@ -0,0 +1,314 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +import Core.Intrinsics: bitcast + +""" + Cwstring + +A C-style string composed of the native wide character type +[`Cwchar_t`](@ref)s. `Cwstring`s are NUL-terminated. For +C-style strings composed of the native character +type, see [`Cstring`](@ref). For more information +about string interoperability with C, see the +[manual](@ref man-bits-types). + +""" +Cwstring + +""" + Cstring + +A C-style string composed of the native character type +[`Cchar`](@ref)s. `Cstring`s are NUL-terminated. For +C-style strings composed of the native wide character +type, see [`Cwstring`](@ref). For more information +about string interoperability with C, see the +[manual](@ref man-bits-types). +""" +Cstring + +# construction from pointers +Cstring(p::Union{Ptr{Int8},Ptr{UInt8},Ptr{Cvoid}}) = bitcast(Cstring, p) +Cwstring(p::Union{Ptr{Cwchar_t},Ptr{Cvoid}}) = bitcast(Cwstring, p) +Ptr{T}(p::Cstring) where {T<:Union{Int8,UInt8,Cvoid}} = bitcast(Ptr{T}, p) +Ptr{T}(p::Cwstring) where {T<:Union{Cwchar_t,Cvoid}} = bitcast(Ptr{Cwchar_t}, p) + +convert(::Type{Cstring}, p::Union{Ptr{Int8},Ptr{UInt8},Ptr{Cvoid}}) = Cstring(p) +convert(::Type{Cwstring}, p::Union{Ptr{Cwchar_t},Ptr{Cvoid}}) = Cwstring(p) +convert(::Type{Ptr{T}}, p::Cstring) where {T<:Union{Int8,UInt8,Cvoid}} = Ptr{T}(p) +convert(::Type{Ptr{T}}, p::Cwstring) where {T<:Union{Cwchar_t,Cvoid}} = Ptr{T}(p) + +""" + pointer(array [, index]) + +Get the native address of an array or string, optionally at a given location `index`. + +This function is "unsafe". Be careful to ensure that a Julia reference to +`array` exists as long as this pointer will be used. The [`GC.@preserve`](@ref) +macro should be used to protect the `array` argument from garbage collection +within a given block of code. + +Calling [`Ref(array[, index])`](@ref Ref) is generally preferable to this function as it guarantees validity. +""" +function pointer end + +pointer(p::Cstring) = convert(Ptr{Cchar}, p) +pointer(p::Cwstring) = convert(Ptr{Cwchar_t}, p) + +# comparisons against pointers (mainly to support `cstr==C_NULL`) +==(x::Union{Cstring,Cwstring}, y::Ptr) = pointer(x) == y +==(x::Ptr, y::Union{Cstring,Cwstring}) = x == pointer(y) + +unsafe_string(s::Cstring) = unsafe_string(convert(Ptr{UInt8}, s)) + +# convert strings to String etc. to pass as pointers +cconvert(::Type{Cstring}, s::String) = s +cconvert(::Type{Cstring}, s::AbstractString) = + cconvert(Cstring, String(s)::String) + +function cconvert(::Type{Cwstring}, s::AbstractString) + v = transcode(Cwchar_t, String(s)) + push!(v, 0) + return cconvert(Cwstring, v) +end + +eltype(::Type{Cstring}) = Cchar +eltype(::Type{Cwstring}) = Cwchar_t + +containsnul(p::Ptr, len) = + C_NULL != ccall(:memchr, Ptr{Cchar}, (Ptr{Cchar}, Cint, Csize_t), p, 0, len) +containsnul(s::String) = containsnul(unsafe_convert(Ptr{Cchar}, s), sizeof(s)) +containsnul(s::AbstractString) = '\0' in s + +function unsafe_convert(::Type{Cstring}, s::String) + p = unsafe_convert(Ptr{Cchar}, s) + containsnul(p, sizeof(s)) && + throw(ArgumentError("embedded NULs are not allowed in C strings: $(repr(s))")) + return Cstring(p) +end + +unsafe_convert(::Type{Cstring}, s::Union{Memory{UInt8},Memory{Int8}}) = Cstring(unsafe_convert(Ptr{Cvoid}, s)) + +function cconvert(::Type{Cwstring}, v::Vector{Cwchar_t}) + for i = 1:length(v)-1 + v[i] == 0 && + throw(ArgumentError("embedded NULs are not allowed in C strings: $(repr(v))")) + end + v[end] == 0 || + throw(ArgumentError("C string data must be NUL terminated: $(repr(v))")) + return cconvert(Ptr{Cwchar_t}, v) +end +unsafe_convert(::Type{Cwstring}, s) = Cwstring(unsafe_convert(Ptr{Cwchar_t}, s)) +unsafe_convert(::Type{Cwstring}, s::Cwstring) = s + +# symbols are guaranteed not to contain embedded NUL +cconvert(::Type{Cstring}, s::Symbol) = s +unsafe_convert(::Type{Cstring}, s::Symbol) = Cstring(unsafe_convert(Ptr{Cchar}, s)) + +if ccall(:jl_get_UNAME, Any, ()) === :NT +""" + Base.cwstring(s) + +Converts a string `s` to a NUL-terminated `Vector{Cwchar_t}`, suitable for passing to C +functions expecting a `Ptr{Cwchar_t}`. The main advantage of using this over the implicit +conversion provided by [`Cwstring`](@ref) is if the function is called multiple times with the +same argument. + +This is only available on Windows. +""" +function cwstring(s::AbstractString) + bytes = codeunits(String(s)) + 0 in bytes && throw(ArgumentError("embedded NULs are not allowed in C strings: $(repr(s))")) + return push!(transcode(UInt16, bytes), 0) +end +end + +# transcoding between data in UTF-8 and UTF-16 for Windows APIs, +# and also UTF-32 for APIs using Cwchar_t on other platforms. + +""" + transcode(T, src) + +Convert string data between Unicode encodings. `src` is either a +`String` or a `Vector{UIntXX}` of UTF-XX code units, where +`XX` is 8, 16, or 32. `T` indicates the encoding of the return value: +`String` to return a (UTF-8 encoded) `String` or `UIntXX` +to return a `Vector{UIntXX}` of UTF-`XX` data. (The alias [`Cwchar_t`](@ref) +can also be used as the integer type, for converting `wchar_t*` strings +used by external C libraries.) + +The `transcode` function succeeds as long as the input data can be +reasonably represented in the target encoding; it always succeeds for +conversions between UTF-XX encodings, even for invalid Unicode data. + +Only conversion to/from UTF-8 is currently supported. + +# Examples +```jldoctest +julia> str = "αβγ" +"αβγ" + +julia> transcode(UInt16, str) +3-element Vector{UInt16}: + 0x03b1 + 0x03b2 + 0x03b3 + +julia> transcode(String, transcode(UInt16, str)) +"αβγ" +``` +""" +function transcode end + +transcode(::Type{T}, src::AbstractVector{T}) where {T<:Union{UInt8,UInt16,UInt32,Int32}} = src +transcode(::Type{T}, src::String) where {T<:Union{Int32,UInt32}} = T[T(c) for c in src] +transcode(::Type{T}, src::AbstractVector{UInt8}) where {T<:Union{Int32,UInt32}} = + transcode(T, String(Vector(src))) +transcode(::Type{T}, src::CodeUnits{UInt8,String}) where {T<:Union{Int32,UInt32}} = + transcode(T, String(src)) + +function transcode(::Type{UInt8}, src::Vector{<:Union{Int32,UInt32}}) + buf = IOBuffer() + for c in src + print(buf, Char(c)) + end + take!(buf) +end +transcode(::Type{String}, src::String) = src +transcode(T, src::String) = transcode(T, codeunits(src)) +transcode(::Type{String}, src) = String(transcode(UInt8, src)) + +function transcode(::Type{UInt16}, src::AbstractVector{UInt8}) + require_one_based_indexing(src) + dst = UInt16[] + i, n = 1, length(src) + n > 0 || return dst + sizehint!(dst, 2n) + a = src[1] + while true + if i < n && -64 <= a % Int8 <= -12 # multi-byte character + b = src[i += 1] + if -64 <= (b % Int8) || a == 0xf4 && 0x8f < b + # invalid UTF-8 (non-continuation or too-high code point) + push!(dst, a) + a = b; continue + elseif a < 0xe0 # 2-byte UTF-8 + push!(dst, xor(0x3080, UInt16(a) << 6, b)) + elseif i < n # 3/4-byte character + c = src[i += 1] + if -64 <= (c % Int8) # invalid UTF-8 (non-continuation) + push!(dst, a, b) + a = c; continue + elseif a < 0xf0 # 3-byte UTF-8 + push!(dst, xor(0x2080, UInt16(a) << 12, UInt16(b) << 6, c)) + elseif i < n + d = src[i += 1] + if -64 <= (d % Int8) # invalid UTF-8 (non-continuation) + push!(dst, a, b, c) + a = d; continue + elseif a == 0xf0 && b < 0x90 # overlong encoding + push!(dst, xor(0x2080, UInt16(b) << 12, UInt16(c) << 6, d)) + else # 4-byte UTF-8 + push!(dst, 0xe5b8 + (UInt16(a) << 8) + (UInt16(b) << 2) + (c >> 4), + xor(0xdc80, UInt16(c & 0xf) << 6, d)) + end + else # too short + push!(dst, a, b, c) + break + end + else # too short + push!(dst, a, b) + break + end + else # ASCII or invalid UTF-8 (continuation byte or too-high code point) + push!(dst, a) + end + i < n || break + a = src[i += 1] + end + return dst +end + +function transcode(::Type{UInt8}, src::AbstractVector{UInt16}) + require_one_based_indexing(src) + n = length(src) + n == 0 && return UInt8[] + + # Precompute m = sizeof(dst). This involves annoying duplication + # of the loop over the src array. However, this is not just an + # optimization: it is problematic for security reasons to grow + # dst dynamically, because Base.winprompt uses this function to + # convert passwords to UTF-8 and we don't want to make unintentional + # copies of the password data. + a = src[1] + i, m = 1, 0 + while true + if a < 0x80 + m += 1 + elseif a < 0x800 # 2-byte UTF-8 + m += 2 + elseif a & 0xfc00 == 0xd800 && i < length(src) + b = src[i += 1] + if (b & 0xfc00) == 0xdc00 # 2-unit UTF-16 sequence => 4-byte UTF-8 + m += 4 + else + m += 3 + a = b; continue + end + else + # 1-unit high UTF-16 or unpaired high surrogate + # either way, encode as 3-byte UTF-8 code point + m += 3 + end + i < n || break + a = src[i += 1] + end + + dst = StringVector(m) + a = src[1] + i, j = 1, 0 + while true + if a < 0x80 # ASCII + dst[j += 1] = a % UInt8 + elseif a < 0x800 # 2-byte UTF-8 + dst[j += 1] = 0xc0 | ((a >> 6) % UInt8) + dst[j += 1] = 0x80 | ((a % UInt8) & 0x3f) + elseif a & 0xfc00 == 0xd800 && i < n + b = src[i += 1] + if (b & 0xfc00) == 0xdc00 + # 2-unit UTF-16 sequence => 4-byte UTF-8 + a += 0x2840 + dst[j += 1] = 0xf0 | ((a >> 8) % UInt8) + dst[j += 1] = 0x80 | ((a % UInt8) >> 2) + dst[j += 1] = xor(0xf0, ((a % UInt8) << 4) & 0x3f, (b >> 6) % UInt8) + dst[j += 1] = 0x80 | ((b % UInt8) & 0x3f) + else + dst[j += 1] = 0xe0 | ((a >> 12) % UInt8) + dst[j += 1] = 0x80 | (((a >> 6) % UInt8) & 0x3f) + dst[j += 1] = 0x80 | ((a % UInt8) & 0x3f) + a = b; continue + end + else + # 1-unit high UTF-16 or unpaired high surrogate + # either way, encode as 3-byte UTF-8 code point + dst[j += 1] = 0xe0 | ((a >> 12) % UInt8) + dst[j += 1] = 0x80 | (((a >> 6) % UInt8) & 0x3f) + dst[j += 1] = 0x80 | ((a % UInt8) & 0x3f) + end + i < n || break + a = src[i += 1] + end + return dst +end + +function unsafe_string(p::Ptr{T}, length::Integer) where {T<:Union{UInt16,UInt32,Cwchar_t}} + transcode(String, unsafe_wrap(Array, p, length; own=false)) +end +function unsafe_string(cw::Cwstring) + p = convert(Ptr{Cwchar_t}, cw) + n = 1 + while unsafe_load(p, n) != 0 + n += 1 + end + return unsafe_string(p, n - 1) +end diff --git a/base/strings/io.jl b/base/strings/io.jl index 987a64798d3da..ba577b5e3eaaf 100644 --- a/base/strings/io.jl +++ b/base/strings/io.jl @@ -10,10 +10,10 @@ if `io` is not given) a canonical (un-decorated) text representation. The representation used by `print` includes minimal formatting and tries to avoid Julia-specific details. -`print` falls back to calling `show`, so most types should just define -`show`. Define `print` if your type has a separate "plain" representation. -For example, `show` displays strings with quotes, and `print` displays strings -without quotes. +`print` falls back to calling the 2-argument `show(io, x)` for each argument `x` in `xs`, +so most types should just define `show`. Define `print` if your type has a separate +"plain" representation. For example, `show` displays strings with quotes, and `print` +displays strings without quotes. See also [`println`](@ref), [`string`](@ref), [`printstyled`](@ref). @@ -252,8 +252,8 @@ print(io::IO, s::Union{String,SubString{String}}) = (write(io, s); nothing) """ repr(x; context=nothing) -Create a string from any value using the [`show`](@ref) function. -You should not add methods to `repr`; define a `show` method instead. +Create a string from any value using the 2-argument `show(io, x)` function. +You should not add methods to `repr`; define a [`show`](@ref) method instead. The optional keyword argument `context` can be set to a `:key=>value` pair, a tuple of `:key=>value` pairs, or an `IO` or [`IOContext`](@ref) object whose @@ -262,7 +262,7 @@ attributes are used for the I/O stream passed to `show`. Note that `repr(x)` is usually similar to how the value of `x` would be entered in Julia. See also [`repr(MIME("text/plain"), x)`](@ref) to instead return a "pretty-printed" version of `x` designed more for human consumption, -equivalent to the REPL display of `x`. +equivalent to the REPL display of `x`, using the 3-argument `show(io, mime, x)`. !!! compat "Julia 1.7" Passing a tuple to keyword `context` requires Julia 1.7 or later. @@ -353,9 +353,19 @@ function join(io::IO, iterator, delim="") end end -join(iterator) = sprint(join, iterator) -join(iterator, delim) = sprint(join, iterator, delim) -join(iterator, delim, last) = sprint(join, iterator, delim, last) +function _join_preserve_annotations(iterator, args...) + if _isannotated(eltype(iterator)) || any(_isannotated, args) + io = AnnotatedIOBuffer() + join(io, iterator, args...) + read(seekstart(io), AnnotatedString{String}) + else + sprint(join, iterator, args...) + end +end + +join(iterator) = _join_preserve_annotations(iterator) +join(iterator, delim) = _join_preserve_annotations(iterator, delim) +join(iterator, delim, last) = _join_preserve_annotations(iterator, delim, last) ## string escaping & unescaping ## @@ -590,14 +600,14 @@ julia> println(raw"\\\\x \\\\\\"") macro raw_str(s); s; end """ - escape_raw_string(s::AbstractString) - escape_raw_string(io, s::AbstractString) + escape_raw_string(s::AbstractString, delim='"') -> AbstractString + escape_raw_string(io, s::AbstractString, delim='"') Escape a string in the manner used for parsing raw string literals. -For each double-quote (`"`) character in input string `s`, this -function counts the number _n_ of preceding backslash (`\\`) characters, -and then increases there the number of backslashes from _n_ to 2_n_+1 -(even for _n_ = 0). It also doubles a sequence of backslashes at the end +For each double-quote (`"`) character in input string `s` (or `delim` if +specified), this function counts the number _n_ of preceding backslash (`\\`) +characters, and then increases there the number of backslashes from _n_ to +2_n_+1 (even for _n_ = 0). It also doubles a sequence of backslashes at the end of the string. This escaping convention is used in raw strings and other non-standard @@ -607,36 +617,41 @@ command-line string into the argv[] array.) See also [`escape_string`](@ref). """ -function escape_raw_string(io, str::AbstractString) +function escape_raw_string(io::IO, str::AbstractString, delim::Char='"') + total = 0 escapes = 0 for c in str if c == '\\' escapes += 1 else - if c == '"' + if c == delim # if one or more backslashes are followed by # a double quote then escape all backslashes # and the double quote - escapes = escapes * 2 + 1 - end - while escapes > 0 - write(io, '\\') - escapes -= 1 + escapes += 1 + total += escapes + while escapes > 0 + write(io, '\\') + escapes -= 1 + end end escapes = 0 - write(io, c) end + write(io, c) end # also escape any trailing backslashes, # so they do not affect the closing quote + total += escapes while escapes > 0 - write(io, '\\') write(io, '\\') escapes -= 1 end + total +end +function escape_raw_string(str::AbstractString, delim::Char='"') + total = escape_raw_string(devnull, str, delim) # check whether the string even needs to be copied and how much to allocate for it + return total == 0 ? str : sprint(escape_raw_string, str, delim; sizehint = sizeof(str) + total) end -escape_raw_string(str::AbstractString) = sprint(escape_raw_string, str; - sizehint = lastindex(str) + 2) ## multiline strings ## @@ -764,3 +779,26 @@ function String(chars::AbstractVector{<:AbstractChar}) end end end + +function AnnotatedString(chars::AbstractVector{C}) where {C<:AbstractChar} + str = if C <: AnnotatedChar + String(getfield.(chars, :char)) + else + sprint(sizehint=length(chars)) do io + for c in chars + print(io, c) + end + end + end + annots = Tuple{UnitRange{Int}, Pair{Symbol, Any}}[] + point = 1 + for c in chars + if c isa AnnotatedChar + for annot in c.annotations + push!(annots, (point:point, annot)) + end + end + point += ncodeunits(c) + end + AnnotatedString(str, annots) +end diff --git a/base/strings/search.jl b/base/strings/search.jl index 1a3085e084ccd..e2b3dc96b98cf 100644 --- a/base/strings/search.jl +++ b/base/strings/search.jl @@ -13,7 +13,7 @@ abstract type AbstractPattern end nothing_sentinel(i) = i == 0 ? nothing : i function findnext(pred::Fix2{<:Union{typeof(isequal),typeof(==)},<:AbstractChar}, - s::String, i::Integer) + s::Union{String, SubString{String}}, i::Integer) if i < 1 || i > sizeof(s) i == sizeof(s) + 1 && return nothing throw(BoundsError(s, i)) @@ -38,7 +38,7 @@ findnext(pred::Fix2{<:Union{typeof(isequal),typeof(==)},<:Union{Int8,UInt8}}, a: findfirst(::typeof(iszero), a::ByteArray) = nothing_sentinel(_search(a, zero(UInt8))) findnext(::typeof(iszero), a::ByteArray, i::Integer) = nothing_sentinel(_search(a, zero(UInt8), i)) -function _search(a::Union{String,ByteArray}, b::Union{Int8,UInt8}, i::Integer = 1) +function _search(a::Union{String,SubString{String},ByteArray}, b::Union{Int8,UInt8}, i::Integer = 1) if i < 1 throw(BoundsError(a, i)) end @@ -201,10 +201,10 @@ function _search_bloom_mask(c) UInt64(1) << (c & 63) end -_nthbyte(s::String, i) = codeunit(s, i) +_nthbyte(s::Union{String, SubString{String}}, i) = codeunit(s, i) _nthbyte(t::AbstractVector, index) = t[index + (firstindex(t)-1)] -function _searchindex(s::String, t::String, i::Integer) +function _searchindex(s::Union{String, SubString{String}}, t::Union{String, SubString{String}}, i::Integer) # Check for fast case of a single byte lastindex(t) == 1 && return something(findnext(isequal(t[1]), s, i), 0) _searchindex(codeunits(s), codeunits(t), i) diff --git a/base/strings/string.jl b/base/strings/string.jl index fefaf5d79ea3a..d091baeb6c663 100644 --- a/base/strings/string.jl +++ b/base/strings/string.jl @@ -63,8 +63,27 @@ by [`take!`](@ref) on a writable [`IOBuffer`](@ref) and by calls to In other cases, `Vector{UInt8}` data may be copied, but `v` is truncated anyway to guarantee consistent behavior. """ -String(v::AbstractVector{UInt8}) = String(copyto!(StringVector(length(v)), v)) -String(v::Vector{UInt8}) = ccall(:jl_array_to_string, Ref{String}, (Any,), v) +String(v::AbstractVector{UInt8}) = String(copyto!(StringMemory(length(v)), v)) +function String(v::Memory{UInt8}) + len = length(v) + len == 0 && return "" + return ccall(:jl_genericmemory_to_string, Ref{String}, (Any, Int), v, len) +end +function String(v::Vector{UInt8}) + #return ccall(:jl_array_to_string, Ref{String}, (Any,), v) + len = length(v) + len == 0 && return "" + ref = v.ref + if ref.ptr_or_offset == ref.mem.ptr + str = ccall(:jl_genericmemory_to_string, Ref{String}, (Any, Int), ref.mem, len) + else + str = ccall(:jl_pchar_to_string, Ref{String}, (Ptr{UInt8}, Int), ref, len) + end + # optimized empty!(v); sizehint!(v, 0) calls + setfield!(v, :size, (0,)) + setfield!(v, :ref, MemoryRef(Memory{UInt8}())) + return str +end """ unsafe_string(p::Ptr{UInt8}, [length::Integer]) @@ -87,7 +106,7 @@ end # This is @assume_effects :effect_free :nothrow :terminates_globally @ccall jl_alloc_string(n::Csize_t)::Ref{String}, # but the macro is not available at this time in bootstrap, so we write it manually. -@eval _string_n(n::Integer) = $(Expr(:foreigncall, QuoteNode(:jl_alloc_string), Ref{String}, Expr(:call, Expr(:core, :svec), :Csize_t), 1, QuoteNode((:ccall,0xe)), :(convert(Csize_t, n)))) +@eval _string_n(n::Integer) = $(Expr(:foreigncall, QuoteNode(:jl_alloc_string), Ref{String}, Expr(:call, Expr(:core, :svec), :Csize_t), 1, QuoteNode((:ccall,0x000e)), :(convert(Csize_t, n)))) """ String(s::AbstractString) @@ -97,8 +116,11 @@ Create a new `String` from an existing `AbstractString`. String(s::AbstractString) = print_to_string(s) @assume_effects :total String(s::Symbol) = unsafe_string(unsafe_convert(Ptr{UInt8}, s)) -unsafe_wrap(::Type{Vector{UInt8}}, s::String) = ccall(:jl_string_to_array, Ref{Vector{UInt8}}, (Any,), s) -unsafe_wrap(::Type{Vector{UInt8}}, s::FastContiguousSubArray{UInt8,1,Vector{UInt8}}) = unsafe_wrap(Vector{UInt8}, pointer(s), size(s)) +unsafe_wrap(::Type{Memory{UInt8}}, s::String) = ccall(:jl_string_to_genericmemory, Ref{Memory{UInt8}}, (Any,), s) +function unsafe_wrap(::Type{Vector{UInt8}}, s::String) + mem = unsafe_wrap(Memory{UInt8}, s) + view(mem, eachindex(mem)) +end Vector{UInt8}(s::CodeUnits{UInt8,String}) = copyto!(Vector{UInt8}(undef, length(s)), s) Vector{UInt8}(s::String) = Vector{UInt8}(codeunits(s)) @@ -157,15 +179,18 @@ typemin(::String) = typemin(String) @boundscheck between(i, 1, n) || throw(BoundsError(s, i)) @inbounds b = codeunit(s, i) (b & 0xc0 == 0x80) & (i-1 > 0) || return i - @inbounds b = codeunit(s, i-1) - between(b, 0b11000000, 0b11110111) && return i-1 - (b & 0xc0 == 0x80) & (i-2 > 0) || return i - @inbounds b = codeunit(s, i-2) - between(b, 0b11100000, 0b11110111) && return i-2 - (b & 0xc0 == 0x80) & (i-3 > 0) || return i - @inbounds b = codeunit(s, i-3) - between(b, 0b11110000, 0b11110111) && return i-3 - return i + (@noinline function _thisind_continued(s, i, n) # mark the rest of the function as a slow-path + local b + @inbounds b = codeunit(s, i-1) + between(b, 0b11000000, 0b11110111) && return i-1 + (b & 0xc0 == 0x80) & (i-2 > 0) || return i + @inbounds b = codeunit(s, i-2) + between(b, 0b11100000, 0b11110111) && return i-2 + (b & 0xc0 == 0x80) & (i-3 > 0) || return i + @inbounds b = codeunit(s, i-3) + between(b, 0b11110000, 0b11110111) && return i-3 + return i + end)(s, i, n) end @propagate_inbounds nextind(s::String, i::Int) = _nextind_str(s, i) @@ -176,23 +201,31 @@ end n = ncodeunits(s) @boundscheck between(i, 1, n) || throw(BoundsError(s, i)) @inbounds l = codeunit(s, i) - (l < 0x80) | (0xf8 ≤ l) && return i+1 - if l < 0xc0 - i′ = @inbounds thisind(s, i) - return i′ < i ? @inbounds(nextind(s, i′)) : i+1 - end - # first continuation byte - (i += 1) > n && return i - @inbounds b = codeunit(s, i) - b & 0xc0 ≠ 0x80 && return i - ((i += 1) > n) | (l < 0xe0) && return i - # second continuation byte - @inbounds b = codeunit(s, i) - b & 0xc0 ≠ 0x80 && return i - ((i += 1) > n) | (l < 0xf0) && return i - # third continuation byte - @inbounds b = codeunit(s, i) - ifelse(b & 0xc0 ≠ 0x80, i, i+1) + between(l, 0x80, 0xf7) || return i+1 + (@noinline function _nextind_continued(s, i, n, l) # mark the rest of the function as a slow-path + if l < 0xc0 + # handle invalid codeunit index by scanning back to the start of this index + # (which may be the same as this index) + i′ = @inbounds thisind(s, i) + i′ >= i && return i+1 + i = i′ + @inbounds l = codeunit(s, i) + (l < 0x80) | (0xf8 ≤ l) && return i+1 + @assert l >= 0xc0 + end + # first continuation byte + (i += 1) > n && return i + @inbounds b = codeunit(s, i) + b & 0xc0 ≠ 0x80 && return i + ((i += 1) > n) | (l < 0xe0) && return i + # second continuation byte + @inbounds b = codeunit(s, i) + b & 0xc0 ≠ 0x80 && return i + ((i += 1) > n) | (l < 0xf0) && return i + # third continuation byte + @inbounds b = codeunit(s, i) + return ifelse(b & 0xc0 ≠ 0x80, i, i+1) + end)(s, i, n, l) end ## checking UTF-8 & ACSII validity ## @@ -247,7 +280,7 @@ end Shifts | 0 4 10 14 18 24 8 20 12 26 - The shifts that represent each state were derived using teh SMT solver Z3, to ensure when encoded into + The shifts that represent each state were derived using the SMT solver Z3, to ensure when encoded into the rows the correct shift was a result. Each character class row is encoding 10 states with shifts as defined above. By shifting the bitsof a row by diff --git a/base/strings/strings.jl b/base/strings/strings.jl index d995d8535e24b..8dae311f475b4 100644 --- a/base/strings/strings.jl +++ b/base/strings/strings.jl @@ -1,5 +1,6 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +include("strings/annotated.jl") include("strings/search.jl") include("strings/unicode.jl") diff --git a/base/strings/substring.jl b/base/strings/substring.jl index 792925f24b12b..2a6b4ae7b9a22 100644 --- a/base/strings/substring.jl +++ b/base/strings/substring.jl @@ -36,9 +36,18 @@ struct SubString{T<:AbstractString} <: AbstractString end return new(s, i-1, nextind(s,j)-i) end + function SubString{T}(s::T, i::Int, j::Int, ::Val{:noshift}) where T<:AbstractString + @boundscheck if !(i == j == 0) + si, sj = i + 1, prevind(s, j + i + 1) + @inbounds isvalid(s, si) || string_index_err(s, si) + @inbounds isvalid(s, sj) || string_index_err(s, sj) + end + new(s, i, j) + end end @propagate_inbounds SubString(s::T, i::Int, j::Int) where {T<:AbstractString} = SubString{T}(s, i, j) +@propagate_inbounds SubString(s::T, i::Int, j::Int, v::Val{:noshift}) where {T<:AbstractString} = SubString{T}(s, i, j, v) @propagate_inbounds SubString(s::AbstractString, i::Integer, j::Integer=lastindex(s)) = SubString(s, Int(i), Int(j)) @propagate_inbounds SubString(s::AbstractString, r::AbstractUnitRange{<:Integer}) = SubString(s, first(r), last(r)) @@ -131,6 +140,8 @@ function hash(s::SubString{String}, h::UInt) ccall(memhash, UInt, (Ptr{UInt8}, Csize_t, UInt32), s, sizeof(s), h % UInt32) + h end +_isannotated(::SubString{T}) where {T} = _isannotated(T) + """ reverse(s::AbstractString) -> AbstractString diff --git a/base/strings/unicode.jl b/base/strings/unicode.jl index 17c5d66c160b6..a3b06063b98ac 100644 --- a/base/strings/unicode.jl +++ b/base/strings/unicode.jl @@ -4,7 +4,8 @@ module Unicode import Base: show, ==, hash, string, Symbol, isless, length, eltype, - convert, isvalid, ismalformed, isoverlong, iterate + convert, isvalid, ismalformed, isoverlong, iterate, + AnnotatedString, AnnotatedChar, annotated_chartransform # whether codepoints are valid Unicode scalar values, i.e. 0-0xd7ff, 0xe000-0x10ffff @@ -179,6 +180,7 @@ const _julia_charmap = Dict{UInt32,UInt32}( 0x00B7 => 0x22C5, 0x0387 => 0x22C5, 0x2212 => 0x002D, + 0x210F => 0x0127, ) utf8proc_map(s::AbstractString, flags::Integer, chartransform=identity) = utf8proc_map(String(s), flags, chartransform) @@ -270,6 +272,8 @@ julia> textwidth("March") """ textwidth(s::AbstractString) = mapreduce(textwidth, +, s; init=0) +textwidth(s::AnnotatedString) = textwidth(s.string) + """ lowercase(c::AbstractChar) @@ -289,6 +293,8 @@ julia> lowercase('Ö') lowercase(c::T) where {T<:AbstractChar} = isascii(c) ? ('A' <= c <= 'Z' ? c + 0x20 : c) : T(ccall(:utf8proc_tolower, UInt32, (UInt32,), c)) +lowercase(c::AnnotatedChar) = AnnotatedChar(lowercase(c.char), annotations(c)) + """ uppercase(c::AbstractChar) @@ -308,6 +314,8 @@ julia> uppercase('ê') uppercase(c::T) where {T<:AbstractChar} = isascii(c) ? ('a' <= c <= 'z' ? c - 0x20 : c) : T(ccall(:utf8proc_toupper, UInt32, (UInt32,), c)) +uppercase(c::AnnotatedChar) = AnnotatedChar(uppercase(c.char), annotations(c)) + """ titlecase(c::AbstractChar) @@ -331,6 +339,8 @@ julia> uppercase('dž') titlecase(c::T) where {T<:AbstractChar} = isascii(c) ? ('a' <= c <= 'z' ? c - 0x20 : c) : T(ccall(:utf8proc_totitle, UInt32, (UInt32,), c)) +titlecase(c::AnnotatedChar) = AnnotatedChar(titlecase(c.char), annotations(c)) + ############################################################################ # returns UTF8PROC_CATEGORY code in 0:30 giving Unicode category @@ -605,6 +615,7 @@ julia> uppercase("Julia") ``` """ uppercase(s::AbstractString) = map(uppercase, s) +uppercase(s::AnnotatedString) = annotated_chartransform(uppercase, s) """ lowercase(s::AbstractString) @@ -620,6 +631,7 @@ julia> lowercase("STRINGS AND THINGS") ``` """ lowercase(s::AbstractString) = map(lowercase, s) +lowercase(s::AnnotatedString) = annotated_chartransform(lowercase, s) """ titlecase(s::AbstractString; [wordsep::Function], strict::Bool=true) -> String @@ -668,6 +680,23 @@ function titlecase(s::AbstractString; wordsep::Function = !isletter, strict::Boo return String(take!(b)) end +# TODO: improve performance characteristics, room for a ~10x improvement. +function titlecase(s::AnnotatedString; wordsep::Function = !isletter, strict::Bool=true) + initial_state = (; startword = true, state = Ref{Int32}(0), + c0 = eltype(s)(zero(UInt32)), wordsep, strict) + annotated_chartransform(s, initial_state) do c, state + if isgraphemebreak!(state.state, state.c0, c) && state.wordsep(c) + state = Base.setindex(state, true, :startword) + cnew = c + else + cnew = state.startword ? titlecase(c) : state.strict ? lowercase(c) : c + state = Base.setindex(state, false, :startword) + end + state = Base.setindex(state, c, :c0) + cnew, state + end +end + """ uppercasefirst(s::AbstractString) -> String @@ -692,6 +721,17 @@ function uppercasefirst(s::AbstractString) string(c′, SubString(s, nextind(s, 1))) end +# TODO: improve performance characteristics, room for a ~5x improvement. +function uppercasefirst(s::AnnotatedString) + annotated_chartransform(s, true) do c, state + if state + (titlecase(c), false) + else + (c, state) + end + end +end + """ lowercasefirst(s::AbstractString) @@ -714,6 +754,17 @@ function lowercasefirst(s::AbstractString) string(c′, SubString(s, nextind(s, 1))) end +# TODO: improve performance characteristics, room for a ~5x improvement. +function lowercasefirst(s::AnnotatedString) + annotated_chartransform(s, true) do c, state + if state + (lowercase(c), false) + else + (c, state) + end + end +end + ############################################################################ # iterators for grapheme segmentation diff --git a/base/strings/util.jl b/base/strings/util.jl index 890afaf62b2ee..4b701001a8676 100644 --- a/base/strings/util.jl +++ b/base/strings/util.jl @@ -1,13 +1,23 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -const Chars = Union{AbstractChar,Tuple{Vararg{AbstractChar}},AbstractVector{<:AbstractChar},Set{<:AbstractChar}} +""" + Base.Chars = Union{AbstractChar,Tuple{Vararg{AbstractChar}},AbstractVector{<:AbstractChar},AbstractSet{<:AbstractChar}} + +An alias type for a either single character or a tuple/vector/set of characters, used to describe arguments +of several string-matching functions such as [`startswith`](@ref) and [`strip`](@ref). + +!!! compat "Julia 1.11" + Julia versions prior to 1.11 only included `Set`, not `AbstractSet`, in `Base.Chars` types. +""" +const Chars = Union{AbstractChar,Tuple{Vararg{AbstractChar}},AbstractVector{<:AbstractChar},AbstractSet{<:AbstractChar}} # starts with and ends with predicates """ - startswith(s::AbstractString, prefix::AbstractString) + startswith(s::AbstractString, prefix::Union{AbstractString,Base.Chars}) -Return `true` if `s` starts with `prefix`. If `prefix` is a vector or set +Return `true` if `s` starts with `prefix`, which can be a string, a character, +or a tuple/vector/set of characters. If `prefix` is a tuple/vector/set of characters, test whether the first character of `s` belongs to that set. See also [`endswith`](@ref), [`contains`](@ref). @@ -30,10 +40,11 @@ end startswith(str::AbstractString, chars::Chars) = !isempty(str) && first(str)::AbstractChar in chars """ - endswith(s::AbstractString, suffix::AbstractString) + endswith(s::AbstractString, suffix::Union{AbstractString,Base.Chars}) -Return `true` if `s` ends with `suffix`. If `suffix` is a vector or set of -characters, test whether the last character of `s` belongs to that set. +Return `true` if `s` ends with `suffix`, which can be a string, a character, +or a tuple/vector/set of characters. If `suffix` is a tuple/vector/set +of characters, test whether the last character of `s` belongs to that set. See also [`startswith`](@ref), [`contains`](@ref). @@ -70,7 +81,8 @@ end """ startswith(io::IO, prefix::Union{AbstractString,Base.Chars}) -Check if an `IO` object starts with a prefix. See also [`peek`](@ref). +Check if an `IO` object starts with a prefix, which can be either a string, a +character, or a tuple/vector/set of characters. See also [`peek`](@ref). """ function Base.startswith(io::IO, prefix::Base.Chars) mark(io) @@ -458,13 +470,15 @@ function lpad( s::Union{AbstractChar,AbstractString}, n::Integer, p::Union{AbstractChar,AbstractString}=' ', -) :: String +) + stringfn = if _isannotated(s) || _isannotated(p) + annotatedstring else string end n = Int(n)::Int m = signed(n) - Int(textwidth(s))::Int - m ≤ 0 && return string(s) + m ≤ 0 && return stringfn(s) l = textwidth(p) q, r = divrem(m, l) - r == 0 ? string(p^q, s) : string(p^q, first(p, r), s) + r == 0 ? stringfn(p^q, s) : stringfn(p^q, first(p, r), s) end """ @@ -488,13 +502,15 @@ function rpad( s::Union{AbstractChar,AbstractString}, n::Integer, p::Union{AbstractChar,AbstractString}=' ', -) :: String +) + stringfn = if _isannotated(s) || _isannotated(p) + annotatedstring else string end n = Int(n)::Int m = signed(n) - Int(textwidth(s))::Int - m ≤ 0 && return string(s) + m ≤ 0 && return stringfn(s) l = textwidth(p) q, r = divrem(m, l) - r == 0 ? string(s, p^q) : string(s, p^q, first(p, r)) + r == 0 ? stringfn(s, p^q) : stringfn(s, p^q, first(p, r)) end """ @@ -571,6 +587,8 @@ end # Specialization for partition(s,n) to return a SubString eltype(::Type{PartitionIterator{T}}) where {T<:AbstractString} = SubString{T} +# SubStrings do not nest +eltype(::Type{PartitionIterator{T}}) where {T<:SubString} = T function iterate(itr::PartitionIterator{<:AbstractString}, state = firstindex(itr.c)) state > ncodeunits(itr.c) && return nothing @@ -1043,7 +1061,7 @@ function bytes2hex end function bytes2hex(itr) eltype(itr) === UInt8 || throw(ArgumentError("eltype of iterator not UInt8")) - b = Base.StringVector(2*length(itr)) + b = Base.StringMemory(2*length(itr)) @inbounds for (i, x) in enumerate(itr) b[2i - 1] = hex_chars[1 + x >> 4] b[2i ] = hex_chars[1 + x & 0xf] diff --git a/base/subarray.jl b/base/subarray.jl index 901410e908d1e..656628bb40382 100644 --- a/base/subarray.jl +++ b/base/subarray.jl @@ -52,8 +52,10 @@ viewindexing(I::Tuple{Slice, Slice, Vararg{Any}}) = (@inline; viewindexing(tail( # A UnitRange can follow Slices, but only if all other indices are scalar viewindexing(I::Tuple{Slice, AbstractUnitRange, Vararg{ScalarIndex}}) = IndexLinear() viewindexing(I::Tuple{Slice, Slice, Vararg{ScalarIndex}}) = IndexLinear() # disambiguate -# In general, ranges are only fast if all other indices are scalar -viewindexing(I::Tuple{AbstractRange, Vararg{ScalarIndex}}) = IndexLinear() +# In general, scalar ranges are only fast if all other indices are scalar +# Other ranges, such as those of `CartesianIndex`es, are not fast even if these +# are followed by `ScalarIndex`es +viewindexing(I::Tuple{AbstractRange{<:ScalarIndex}, Vararg{ScalarIndex}}) = IndexLinear() # All other index combinations are slow viewindexing(I::Tuple{Vararg{Any}}) = IndexCartesian() # Of course, all other array types are slow @@ -108,16 +110,44 @@ unaliascopy(A::SubArray) = typeof(A)(unaliascopy(A.parent), map(unaliascopy, A.i # When the parent is an Array we can trim the size down a bit. In the future this # could possibly be extended to any mutable array. -function unaliascopy(V::SubArray{T,N,A,I,LD}) where {T,N,A<:Array,I<:Tuple{Vararg{Union{Real,AbstractRange,Array}}},LD} - dest = Array{T}(undef, index_lengths(V.indices...)) - copyto!(dest, V) - SubArray{T,N,A,I,LD}(dest, map(_trimmedindex, V.indices), 0, Int(LD)) -end +function unaliascopy(V::SubArray{T,N,A,I,LD}) where {T,N,A<:Array,I<:Tuple{Vararg{Union{ScalarIndex,AbstractRange{<:ScalarIndex},Array{<:Union{ScalarIndex,AbstractCartesianIndex}}}}},LD} + dest = Array{T}(undef, _trimmedshape(V.indices...)) + trimmedpind = _trimmedpind(V.indices...) + vdest = trimmedpind isa Tuple{Vararg{Union{Slice,Colon}}} ? dest : view(dest, trimmedpind...) + copyto!(vdest, view(V, _trimmedvind(V.indices...)...)) + indices = map(_trimmedindex, V.indices) + stride1 = LD ? compute_stride1(dest, indices) : 0 + offset1 = LD ? compute_offset1(dest, stride1, indices) : 0 + SubArray{T,N,A,I,LD}(dest, indices, offset1, stride1) +end +# Get the proper trimmed shape +_trimmedshape(::ScalarIndex, rest...) = (1, _trimmedshape(rest...)...) +_trimmedshape(i::AbstractRange, rest...) = (isempty(i) ? zero(eltype(i)) : maximum(i), _trimmedshape(rest...)...) +_trimmedshape(i::Union{UnitRange,StepRange,OneTo}, rest...) = (length(i), _trimmedshape(rest...)...) +_trimmedshape(i::AbstractArray{<:ScalarIndex}, rest...) = (length(i), _trimmedshape(rest...)...) +_trimmedshape(i::AbstractArray{<:AbstractCartesianIndex{0}}, rest...) = _trimmedshape(rest...) +_trimmedshape(i::AbstractArray{<:AbstractCartesianIndex{N}}, rest...) where {N} = (length(i), ntuple(Returns(1), Val(N - 1))..., _trimmedshape(rest...)...) +_trimmedshape() = () +# We can avoid the repeation from `AbstractArray{CartesianIndex{0}}` +_trimmedpind(i, rest...) = (map(Returns(:), axes(i))..., _trimmedpind(rest...)...) +_trimmedpind(i::AbstractRange, rest...) = (i, _trimmedpind(rest...)...) +_trimmedpind(i::Union{UnitRange,StepRange,OneTo}, rest...) = ((:), _trimmedpind(rest...)...) +_trimmedpind(i::AbstractArray{<:AbstractCartesianIndex{0}}, rest...) = _trimmedpind(rest...) +_trimmedpind() = () +_trimmedvind(i, rest...) = (map(Returns(:), axes(i))..., _trimmedvind(rest...)...) +_trimmedvind(i::AbstractArray{<:AbstractCartesianIndex{0}}, rest...) = (map(first, axes(i))..., _trimmedvind(rest...)...) +_trimmedvind() = () # Transform indices to be "dense" -_trimmedindex(i::Real) = oftype(i, 1) -_trimmedindex(i::AbstractUnitRange) = oftype(i, oneto(length(i))) -_trimmedindex(i::AbstractArray) = oftype(i, reshape(eachindex(IndexLinear(), i), axes(i))) - +_trimmedindex(i::ScalarIndex) = oftype(i, 1) +_trimmedindex(i::AbstractRange) = i +_trimmedindex(i::Union{UnitRange,StepRange,OneTo}) = oftype(i, oneto(length(i))) +_trimmedindex(i::AbstractArray{<:ScalarIndex}) = oftype(i, reshape(eachindex(IndexLinear(), i), axes(i))) +_trimmedindex(i::AbstractArray{<:AbstractCartesianIndex{0}}) = oftype(i, copy(i)) +function _trimmedindex(i::AbstractArray{<:AbstractCartesianIndex{N}}) where {N} + padding = ntuple(Returns(1), Val(N - 1)) + ax1 = eachindex(IndexLinear(), i) + return oftype(i, reshape(CartesianIndices((ax1, padding...)), axes(i))) +end ## SubArray creation # We always assume that the dimensionality of the parent matches the number of # indices that end up getting passed to it, so we store the parent as a @@ -127,6 +157,11 @@ _maybe_reshape_parent(A::AbstractArray, ::NTuple{1, Bool}) = reshape(A, Val(1)) _maybe_reshape_parent(A::AbstractArray{<:Any,1}, ::NTuple{1, Bool}) = reshape(A, Val(1)) _maybe_reshape_parent(A::AbstractArray{<:Any,N}, ::NTuple{N, Bool}) where {N} = A _maybe_reshape_parent(A::AbstractArray, ::NTuple{N, Bool}) where {N} = reshape(A, Val(N)) +# The trailing singleton indices could be eliminated after bounds checking. +rm_singleton_indices(ndims::Tuple, J1, Js...) = (J1, rm_singleton_indices(IteratorsMD._splitrest(ndims, index_ndims(J1)), Js...)...) +rm_singleton_indices(::Tuple{}, ::ScalarIndex, Js...) = rm_singleton_indices((), Js...) +rm_singleton_indices(::Tuple) = () + """ view(A, inds...) @@ -173,22 +208,15 @@ julia> view(2:5, 2:3) # returns a range as type is immutable 3:4 ``` """ -function view(A::AbstractArray{<:Any,N}, I::Vararg{Any,M}) where {N,M} +function view(A::AbstractArray, I::Vararg{Any,M}) where {M} @inline J = map(i->unalias(A,i), to_indices(A, I)) @boundscheck checkbounds(A, J...) - if length(J) > ndims(A) && J[N+1:end] isa Tuple{Vararg{Int}} - # view([1,2,3], :, 1) does not need to reshape - return unsafe_view(A, J[1:N]...) - end - unsafe_view(_maybe_reshape_parent(A, index_ndims(J...)), J...) + J′ = rm_singleton_indices(ntuple(Returns(true), Val(ndims(A))), J...) + unsafe_view(_maybe_reshape_parent(A, index_ndims(J′...)), J′...) end # Ranges implement getindex to return recomputed ranges; use that for views, too (when possible) -function view(r1::OneTo, r2::OneTo) - @_propagate_inbounds_meta - getindex(r1, r2) -end function view(r1::AbstractUnitRange, r2::AbstractUnitRange{<:Integer}) @_propagate_inbounds_meta getindex(r1, r2) @@ -263,18 +291,18 @@ reindex(idxs::Tuple{Slice, Vararg{Any}}, subidxs::Tuple{Any, Vararg{Any}}) = # Re-index into parent vectors with one subindex reindex(idxs::Tuple{AbstractVector, Vararg{Any}}, subidxs::Tuple{Any, Vararg{Any}}) = - (@_propagate_inbounds_meta; (idxs[1][subidxs[1]], reindex(tail(idxs), tail(subidxs))...)) + (@_propagate_inbounds_meta; (maybeview(idxs[1], subidxs[1]), reindex(tail(idxs), tail(subidxs))...)) # Parent matrices are re-indexed with two sub-indices reindex(idxs::Tuple{AbstractMatrix, Vararg{Any}}, subidxs::Tuple{Any, Any, Vararg{Any}}) = - (@_propagate_inbounds_meta; (idxs[1][subidxs[1], subidxs[2]], reindex(tail(idxs), tail(tail(subidxs)))...)) + (@_propagate_inbounds_meta; (maybeview(idxs[1], subidxs[1], subidxs[2]), reindex(tail(idxs), tail(tail(subidxs)))...)) # In general, we index N-dimensional parent arrays with N indices @generated function reindex(idxs::Tuple{AbstractArray{T,N}, Vararg{Any}}, subidxs::Tuple{Vararg{Any}}) where {T,N} if length(subidxs.parameters) >= N subs = [:(subidxs[$d]) for d in 1:N] tail = [:(subidxs[$d]) for d in N+1:length(subidxs.parameters)] - :(@_propagate_inbounds_meta; (idxs[1][$(subs...)], reindex(tail(idxs), ($(tail...),))...)) + :(@_propagate_inbounds_meta; (maybeview(idxs[1], $(subs...)), reindex(tail(idxs), ($(tail...),))...)) else :(throw(ArgumentError("cannot re-index SubArray with fewer indices than dimensions\nThis should not occur; please submit a bug report."))) end @@ -291,37 +319,48 @@ end # But SubArrays with fast linear indexing pre-compute a stride and offset FastSubArray{T,N,P,I} = SubArray{T,N,P,I,true} +# We define a convenience functions to compute the shifted parent index +# This differs from reindex as this accepts the view directly, instead of its indices +@inline _reindexlinear(V::FastSubArray, i::Int) = V.offset1 + V.stride1*i +@inline _reindexlinear(V::FastSubArray, i::AbstractUnitRange{Int}) = V.offset1 .+ V.stride1 .* i + function getindex(V::FastSubArray, i::Int) @inline @boundscheck checkbounds(V, i) - @inbounds r = V.parent[V.offset1 + V.stride1*i] - r -end -# We can avoid a multiplication if the first parent index is a Colon or AbstractUnitRange, -# or if all the indices are scalars, i.e. the view is for a single value only -FastContiguousSubArray{T,N,P,I<:Union{Tuple{Union{Slice, AbstractUnitRange}, Vararg{Any}}, - Tuple{Vararg{ScalarIndex}}}} = SubArray{T,N,P,I,true} -function getindex(V::FastContiguousSubArray, i::Int) - @inline - @boundscheck checkbounds(V, i) - @inbounds r = V.parent[V.offset1 + i] + @inbounds r = V.parent[_reindexlinear(V, i)] r end + # For vector views with linear indexing, we disambiguate to favor the stride/offset # computation as that'll generally be faster than (or just as fast as) re-indexing into a range. function getindex(V::FastSubArray{<:Any, 1}, i::Int) @inline @boundscheck checkbounds(V, i) - @inbounds r = V.parent[V.offset1 + V.stride1*i] + @inbounds r = V.parent[_reindexlinear(V, i)] r end -function getindex(V::FastContiguousSubArray{<:Any, 1}, i::Int) + +# We can avoid a multiplication if the first parent index is a Colon or AbstractUnitRange, +# or if all the indices are scalars, i.e. the view is for a single value only +FastContiguousSubArray{T,N,P,I<:Union{Tuple{Union{Slice, AbstractUnitRange}, Vararg{Any}}, + Tuple{Vararg{ScalarIndex}}}} = SubArray{T,N,P,I,true} + +@inline _reindexlinear(V::FastContiguousSubArray, i::Int) = V.offset1 + i +@inline _reindexlinear(V::FastContiguousSubArray, i::AbstractUnitRange{Int}) = V.offset1 .+ i + +# parents of FastContiguousSubArrays may support fast indexing with AbstractUnitRanges, +# so we may just forward the indexing to the parent +# This may only be done for non-offset ranges, as the result would otherwise have offset axes +const OneBasedRanges = Union{OneTo{Int}, UnitRange{Int}, Slice{OneTo{Int}}, IdentityUnitRange{OneTo{Int}}} +function getindex(V::FastContiguousSubArray, i::OneBasedRanges) @inline @boundscheck checkbounds(V, i) - @inbounds r = V.parent[V.offset1 + i] + @inbounds r = V.parent[_reindexlinear(V, i)] r end +@inline getindex(V::FastContiguousSubArray, i::Colon) = getindex(V, to_indices(V, (:,))...) + # Indexed assignment follows the same pattern as `getindex` above function setindex!(V::SubArray{T,N}, x, I::Vararg{Int,N}) where {T,N} @inline @@ -332,28 +371,25 @@ end function setindex!(V::FastSubArray, x, i::Int) @inline @boundscheck checkbounds(V, i) - @inbounds V.parent[V.offset1 + V.stride1*i] = x - V -end -function setindex!(V::FastContiguousSubArray, x, i::Int) - @inline - @boundscheck checkbounds(V, i) - @inbounds V.parent[V.offset1 + i] = x + @inbounds V.parent[_reindexlinear(V, i)] = x V end function setindex!(V::FastSubArray{<:Any, 1}, x, i::Int) @inline @boundscheck checkbounds(V, i) - @inbounds V.parent[V.offset1 + V.stride1*i] = x + @inbounds V.parent[_reindexlinear(V, i)] = x V end -function setindex!(V::FastContiguousSubArray{<:Any, 1}, x, i::Int) + +function setindex!(V::FastSubArray, x, i::AbstractUnitRange{Int}) @inline @boundscheck checkbounds(V, i) - @inbounds V.parent[V.offset1 + i] = x + @inbounds V.parent[_reindexlinear(V, i)] = x V end +@inline setindex!(V::FastSubArray, x, i::Colon) = setindex!(V, x, to_indices(V, (i,))...) + function isassigned(V::SubArray{T,N}, I::Vararg{Int,N}) where {T,N} @inline @boundscheck checkbounds(Bool, V, I...) || return false @@ -363,30 +399,36 @@ end function isassigned(V::FastSubArray, i::Int) @inline @boundscheck checkbounds(Bool, V, i) || return false - @inbounds r = isassigned(V.parent, V.offset1 + V.stride1*i) + @inbounds r = isassigned(V.parent, _reindexlinear(V, i)) r end -function isassigned(V::FastContiguousSubArray, i::Int) +function isassigned(V::FastSubArray{<:Any, 1}, i::Int) @inline @boundscheck checkbounds(Bool, V, i) || return false - @inbounds r = isassigned(V.parent, V.offset1 + i) + @inbounds r = isassigned(V.parent, _reindexlinear(V, i)) r end -function isassigned(V::FastSubArray{<:Any, 1}, i::Int) + +function _unsetindex!(V::FastSubArray, i::Int) @inline - @boundscheck checkbounds(Bool, V, i) || return false - @inbounds r = isassigned(V.parent, V.offset1 + V.stride1*i) - r + @boundscheck checkbounds(V, i) + @inbounds _unsetindex!(V.parent, _reindexlinear(V, i)) + return V end -function isassigned(V::FastContiguousSubArray{<:Any, 1}, i::Int) +function _unsetindex!(V::FastSubArray{<:Any,1}, i::Int) @inline - @boundscheck checkbounds(Bool, V, i) || return false - @inbounds r = isassigned(V.parent, V.offset1 + i) - r + @boundscheck checkbounds(V, i) + @inbounds _unsetindex!(V.parent, _reindexlinear(V, i)) + return V +end +function _unsetindex!(V::SubArray{T,N}, i::Vararg{Int,N}) where {T,N} + @inline + @boundscheck checkbounds(V, i...) + @inbounds _unsetindex!(V.parent, reindex(V.indices, i)...) + return V end IndexStyle(::Type{<:FastSubArray}) = IndexLinear() -IndexStyle(::Type{<:SubArray}) = IndexCartesian() # Strides are the distance in memory between adjacent elements in a given dimension # which we determine from the strides of the parent @@ -416,12 +458,8 @@ iscontiguous(A::SubArray) = iscontiguous(typeof(A)) iscontiguous(::Type{<:SubArray}) = false iscontiguous(::Type{<:FastContiguousSubArray}) = true -first_index(V::FastSubArray) = V.offset1 + V.stride1 # cached for fast linear SubArrays -function first_index(V::SubArray) - P, I = parent(V), V.indices - s1 = compute_stride1(P, I) - s1 + compute_offset1(P, s1, I) -end +first_index(V::FastSubArray) = V.offset1 + V.stride1 * firstindex(V) # cached for fast linear SubArrays +first_index(V::SubArray) = compute_linindex(parent(V), V.indices) # Computing the first index simply steps through the indices, accumulating the # sum of index each multiplied by the parent's stride. @@ -447,11 +485,6 @@ function compute_linindex(parent, I::NTuple{N,Any}) where N IP = fill_to_length(axes(parent), OneTo(1), Val(N)) compute_linindex(first(LinearIndices(parent)), 1, IP, I) end -function compute_linindex(f, s, IP::Tuple, I::Tuple{ScalarIndex, Vararg{Any}}) - @inline - Δi = I[1]-first(IP[1]) - compute_linindex(f + Δi*s, s*length(IP[1]), tail(IP), tail(I)) -end function compute_linindex(f, s, IP::Tuple, I::Tuple{Any, Vararg{Any}}) @inline Δi = first(I[1])-first(IP[1]) @@ -466,10 +499,6 @@ find_extended_inds(::ScalarIndex, I...) = (@inline; find_extended_inds(I...)) find_extended_inds(i1, I...) = (@inline; (i1, find_extended_inds(I...)...)) find_extended_inds() = () -function unsafe_convert(::Type{Ptr{T}}, V::SubArray{T,N,P,<:Tuple{Vararg{RangeIndex}}}) where {T,N,P} - return unsafe_convert(Ptr{T}, V.parent) + _memory_offset(V.parent, map(first, V.indices)...) -end - pointer(V::FastSubArray, i::Int) = pointer(V.parent, V.offset1 + V.stride1*i) pointer(V::FastContiguousSubArray, i::Int) = pointer(V.parent, V.offset1 + i) @@ -494,3 +523,13 @@ function _indices_sub(i1::AbstractArray, I...) end has_offset_axes(S::SubArray) = has_offset_axes(S.indices...) + +function replace_in_print_matrix(S::SubArray{<:Any,2,<:AbstractMatrix}, i::Integer, j::Integer, s::AbstractString) + replace_in_print_matrix(S.parent, to_indices(S.parent, reindex(S.indices, (i,j)))..., s) +end +function replace_in_print_matrix(S::SubArray{<:Any,1,<:AbstractVector}, i::Integer, j::Integer, s::AbstractString) + replace_in_print_matrix(S.parent, to_indices(S.parent, reindex(S.indices, (i,)))..., j, s) +end + +# XXX: this is considerably more unsafe than the other similarly named methods +unsafe_wrap(::Type{Vector{UInt8}}, s::FastContiguousSubArray{UInt8,1,Vector{UInt8}}) = unsafe_wrap(Vector{UInt8}, pointer(s), size(s)) diff --git a/base/summarysize.jl b/base/summarysize.jl index 9bbae187cab12..2505824768099 100644 --- a/base/summarysize.jl +++ b/base/summarysize.jl @@ -26,7 +26,7 @@ julia> Base.summarysize(1.0) 8 julia> Base.summarysize(Ref(rand(100))) -848 +864 julia> sizeof(Ref(rand(100))) 8 @@ -49,9 +49,9 @@ function summarysize(obj; if isassigned(x, i) val = x[i] end - elseif isa(x, Array) + elseif isa(x, GenericMemory) nf = length(x) - if ccall(:jl_array_isassigned, Cint, (Any, UInt), x, i - 1) != 0 + if @inbounds @inline isassigned(x, i) val = x[i] end else @@ -126,14 +126,14 @@ function (ss::SummarySize)(obj::Core.TypeName) return Core.sizeof(obj) + (isdefined(obj, :mt) ? ss(obj.mt) : 0) end -function (ss::SummarySize)(obj::Array) +function (ss::SummarySize)(obj::GenericMemory) haskey(ss.seen, obj) ? (return 0) : (ss.seen[obj] = true) - headersize = 4*sizeof(Int) + 8 + max(0, ndims(obj)-2)*sizeof(Int) + headersize = 2*sizeof(Int) size::Int = headersize datakey = unsafe_convert(Ptr{Cvoid}, obj) if !haskey(ss.seen, datakey) ss.seen[datakey] = true - dsize = Core.sizeof(obj) + dsize = sizeof(obj) T = eltype(obj) if isbitsunion(T) # add 1 union selector byte for each element diff --git a/base/sysimg.jl b/base/sysimg.jl index 1bdbe60479e91..62241de9ffd4a 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -6,7 +6,46 @@ using .Base # Set up Main module using Base.MainInclude # ans, err, and sometimes Out -import Base.MainInclude: eval, include + +# These definitions calls Base._include rather than Base.include to get +# one-frame stacktraces for the common case of using include(fname) in Main. + +""" + include([mapexpr::Function,] path::AbstractString) + +Evaluate the contents of the input source file in the global scope of the containing module. +Every module (except those defined with `baremodule`) has its own +definition of `include`, which evaluates the file in that module. +Returns the result of the last evaluated expression of the input file. During including, +a task-local include path is set to the directory containing the file. Nested calls to +`include` will search relative to that path. This function is typically used to load source +interactively, or to combine files in packages that are broken into multiple source files. +The argument `path` is normalized using [`normpath`](@ref) which will resolve +relative path tokens such as `..` and convert `/` to the appropriate path separator. + +The optional first argument `mapexpr` can be used to transform the included code before +it is evaluated: for each parsed expression `expr` in `path`, the `include` function +actually evaluates `mapexpr(expr)`. If it is omitted, `mapexpr` defaults to [`identity`](@ref). + +Use [`Base.include`](@ref) to evaluate a file into another module. + +!!! compat "Julia 1.5" + Julia 1.5 is required for passing the `mapexpr` argument. +""" +include(mapexpr::Function, fname::AbstractString) = Base._include(mapexpr, Main, fname) +function include(fname::AbstractString) + isa(fname, String) || (fname = Base.convert(String, fname)::String) + Base._include(identity, Main, fname) +end + +""" + eval(expr) + +Evaluate an expression in the global scope of the containing module. +Every `Module` (except those defined with `baremodule`) has its own 1-argument +definition of `eval`, which evaluates expressions in that module. +""" +eval(x) = Core.eval(Main, x) # Ensure this file is also tracked pushfirst!(Base._included_files, (@__MODULE__, abspath(@__FILE__))) @@ -51,7 +90,7 @@ let tot_time_stdlib = 0.0 # use a temp module to avoid leaving the type of this closure in Main push!(empty!(LOAD_PATH), "@stdlib") - m = Module() + m = Core.Module() GC.@preserve m begin print_time = @eval m (mod, t) -> (print(rpad(string(mod) * " ", $maxlen + 3, "─")); Base.time_print(stdout, t * 10^9); println()) @@ -63,8 +102,9 @@ let print_time(stdlib, tt) end for dep in Base._require_dependencies - dep[3] == 0.0 && continue - push!(Base._included_files, dep[1:2]) + mod, path, fsize, mtime = dep[1], dep[2], dep[3], dep[5] + (fsize == 0 || mtime == 0.0) && continue + push!(Base._included_files, (mod, path)) end empty!(Base._require_dependencies) Base._track_dependencies[] = false @@ -79,6 +119,7 @@ let Base.init_load_path() # want to be able to find external packages in userimg.jl ccall(:jl_clear_implicit_imports, Cvoid, (Any,), Main) + tot_time_userimg = @elapsed (isfile("userimg.jl") && Base.include(Main, "userimg.jl")) tot_time_base = (Base.end_base_include - Base.start_base_include) * 10.0^(-9) diff --git a/base/sysinfo.jl b/base/sysinfo.jl index 2c962088484e7..484ac4d01d104 100644 --- a/base/sysinfo.jl +++ b/base/sysinfo.jl @@ -33,6 +33,9 @@ export BINDIR, iswindows, isjsvm, isexecutable, + isreadable, + iswritable, + username, which import ..Base: show @@ -97,7 +100,45 @@ Standard word size on the current machine, in bits. """ const WORD_SIZE = Core.sizeof(Int) * 8 -global SC_CLK_TCK::Clong, CPU_NAME::String, JIT::String +""" + Sys.SC_CLK_TCK: + +The number of system "clock ticks" per second, corresponding to `sysconf(_SC_CLK_TCK)` on +POSIX systems, or `0` if it is unknown. + +CPU times, e.g. as returned by `Sys.cpu_info()`, are in units of ticks, i.e. units of `1 / Sys.SC_CLK_TCK` seconds if `Sys.SC_CLK_TCK > 0`. +""" +global SC_CLK_TCK::Clong + +""" + Sys.CPU_NAME::String + +A string representing the name of CPU. + +# Examples +For example, `Sys.CPU_NAME` might equal `"tigerlake"` on an +[Intel Core "Tiger Lake" CPU](https://en.wikipedia.org/wiki/Tiger_Lake), +or `"apple-m1"` on an [Apple M1 CPU](https://en.wikipedia.org/wiki/Apple_M1). + +Note: Included in the detailed system information via `versioninfo(verbose=true)`. +""" +global CPU_NAME::String + +""" + Sys.JIT::String + +A string representing the specific Just-In-Time (JIT) compiler being utilized in the current runtime. + +# Examples +Currently, this equals `"ORCJIT"` for the LLVM "ORC" ("On-Request Compilation") JIT library: +```jldoctest +julia> Sys.JIT +"ORCJIT" +``` + +Note: Included in the detailed system information via `versioninfo(verbose=true)`. +""" +global JIT::String function __init__() env_threads = nothing @@ -138,6 +179,24 @@ mutable struct UV_cpu_info_t cpu_times!idle::UInt64 cpu_times!irq::UInt64 end + +""" + Sys.CPUinfo + +The `CPUinfo` type is a mutable struct with the following fields: +- `model::String`: CPU model information. +- `speed::Int32`: CPU speed (in MHz). +- `cpu_times!user::UInt64`: Time spent in user mode. CPU state shows CPU time used by user space processes. +- `cpu_times!nice::UInt64`: Time spent in nice mode. CPU state is a subset of the "user" state and shows the CPU time used by processes that have a positive niceness, meaning a lower priority than other tasks. +- `cpu_times!sys::UInt64`: Time spent in system mode. CPU state shows the amount of CPU time used by the kernel. +- `cpu_times!idle::UInt64`: Time spent in idle mode. CPU state shows the CPU time that's not actively being used. +- `cpu_times!irq::UInt64`: Time spent handling interrupts. CPU state shows the amount of time the CPU has been servicing hardware interrupts. + +The times are in units of `1/Sys.SC_CLK_TCK` seconds if `Sys.SC_CLK_TCK > 0`; otherwise they are in +unknown units. + +Note: Included in the detailed system information via `versioninfo(verbose=true)`. +""" mutable struct CPUinfo model::String speed::Int32 @@ -152,6 +211,8 @@ CPUinfo(info::UV_cpu_info_t) = CPUinfo(unsafe_string(info.model), info.speed, info.cpu_times!user, info.cpu_times!nice, info.cpu_times!sys, info.cpu_times!idle, info.cpu_times!irq) +public CPUinfo + function _show_cpuinfo(io::IO, info::Sys.CPUinfo, header::Bool=true, prefix::AbstractString=" ") tck = SC_CLK_TCK if header @@ -173,7 +234,7 @@ function _show_cpuinfo(io::IO, info::Sys.CPUinfo, header::Bool=true, prefix::Abs end end -show(io::IO, info::CPUinfo) = _show_cpuinfo(io, info, true, " ") +show(io::IO, ::MIME"text/plain", info::CPUinfo) = _show_cpuinfo(io, info, true, " ") function _cpu_summary(io::IO, cpu::AbstractVector{CPUinfo}, i, j) if j-i < 9 @@ -200,6 +261,17 @@ function _cpu_summary(io::IO, cpu::AbstractVector{CPUinfo}, i, j) println(io) end +""" + Sys.cpu_summary(io::IO=stdout, cpu::AbstractVector{CPUinfo}=cpu_info()) + +Print a summary of CPU information to the `io` stream (defaulting to [`stdout`](@ref)), organizing and displaying aggregated data for CPUs with the same model, for a given array of `CPUinfo` data structures +describing a set of CPUs (which defaults to the return value of the [`Sys.cpu_info`](@ref) function). + +The summary includes aggregated information for each distinct CPU model, +providing details such as average CPU speed and total time spent in different modes (user, nice, sys, idle, irq) across all cores with the same model. + +Note: Included in the detailed system information via `versioninfo(verbose=true)`. +""" function cpu_summary(io::IO=stdout, cpu::AbstractVector{CPUinfo} = cpu_info()) model = cpu[1].model first = 1 @@ -212,6 +284,18 @@ function cpu_summary(io::IO=stdout, cpu::AbstractVector{CPUinfo} = cpu_info()) _cpu_summary(io, cpu, first, length(cpu)) end +""" + Sys.cpu_info() + +Return a vector of `CPUinfo` objects, where each object represents information about a CPU core. + +This is pretty-printed in a tabular format by `Sys.cpu_summary`, which is included in the output +of `versioninfo(verbose=true)`, so most users will not need to access the `CPUinfo` +data structures directly. + +The function provides information about each CPU, including model, speed, and usage statistics such as user time, nice time, system time, idle time, and interrupt time. + +""" function cpu_info() UVcpus = Ref{Ptr{UV_cpu_info_t}}() count = Ref{Int32}() @@ -276,7 +360,7 @@ free_memory() = ccall(:uv_get_available_memory, UInt64, ()) Get the total memory in RAM (including that which is currently used) in bytes. This amount may be constrained, e.g., by Linux control groups. For the unconstrained -amount, see `Sys.physical_memory()`. +amount, see `Sys.total_physical_memory()`. """ function total_memory() constrained = ccall(:uv_get_constrained_memory, UInt64, ()) @@ -315,7 +399,7 @@ end Get the maximum resident set size utilized in bytes. See also: - - man page of `getrusage`(2) on Linux and FreeBSD. + - man page of `getrusage`(2) on Linux and BSD. - Windows API `GetProcessMemoryInfo`. """ maxrss() = ccall(:jl_maxrss, Csize_t, ()) @@ -469,24 +553,9 @@ windows_version const WINDOWS_VISTA_VER = v"6.0" -""" - Sys.isexecutable(path::String) - -Return `true` if the given `path` has executable permissions. - -!!! note - Prior to Julia 1.6, this did not correctly interrogate filesystem - ACLs on Windows, therefore it would return `true` for any - file. From Julia 1.6 on, it correctly determines whether the - file is marked as executable or not. -""" -function isexecutable(path::String) - # We use `access()` and `X_OK` to determine if a given path is - # executable by the current user. `X_OK` comes from `unistd.h`. - X_OK = 0x01 - return ccall(:jl_fs_access, Cint, (Ptr{UInt8}, Cint), path, X_OK) == 0 -end -isexecutable(path::AbstractString) = isexecutable(String(path)) +const isexecutable = Base.isexecutable +const isreadable = Base.isreadable +const iswritable = Base.iswritable """ Sys.which(program_name::String) @@ -567,4 +636,27 @@ function which(program_name::String) end which(program_name::AbstractString) = which(String(program_name)) +""" + Sys.username() -> String + +Return the username for the current user. If the username cannot be determined +or is empty, this function throws an error. + +To retrieve a username that is overridable via an environment variable, +e.g., `USER`, consider using +```julia +user = get(Sys.username, ENV, "USER") +``` + +!!! compat "Julia 1.11" + This function requires at least Julia 1.11. + +See also [`homedir`](@ref). +""" +function username() + pw = Libc.getpw() + isempty(pw.username) && Base.uv_error("username", Base.UV_ENOENT) + return pw.username +end + end # module Sys diff --git a/base/task.jl b/base/task.jl index 09b40f19f5913..7b19fc5f2a3d1 100644 --- a/base/task.jl +++ b/base/task.jl @@ -154,8 +154,7 @@ const _state_index = findfirst(==(:_state), fieldnames(Task)) @eval function load_state_acquire(t) # TODO: Replace this by proper atomic operations when available @GC.preserve t llvmcall($(""" - %ptr = inttoptr i$(Sys.WORD_SIZE) %0 to i8* - %rv = load atomic i8, i8* %ptr acquire, align 8 + %rv = load atomic i8, i8* %0 acquire, align 8 ret i8 %rv """), UInt8, Tuple{Ptr{UInt8}}, Ptr{UInt8}(pointer_from_objref(t) + fieldoffset(Task, _state_index))) @@ -180,11 +179,20 @@ end elseif field === :exception # TODO: this field name should be deprecated in 2.0 return t._isexception ? t.result : nothing + elseif field === :scope + error("Querying `scope` is disallowed. Use `current_scope` instead.") else return getfield(t, field) end end +@inline function setproperty!(t::Task, field::Symbol, @nospecialize(v)) + if field === :scope + istaskstarted(t) && error("Setting scope on a started task directly is disallowed.") + end + return @invoke setproperty!(t::Any, field::Symbol, v::Any) +end + """ istaskdone(t::Task) -> Bool @@ -347,15 +355,167 @@ function _wait2(t::Task, waiter::Task) nothing end -function wait(t::Task) - t === current_task() && error("deadlock detected: cannot wait on current task") +""" + wait(t::Task; throw=true) + +Wait for a `Task` to finish. + +The keyword `throw` (defaults to `true`) controls whether a failed task results +in an error, thrown as a [`TaskFailedException`](@ref) which wraps the failed task. + +Throws a `ConcurrencyViolationError` if `t` is the currently running task, to prevent deadlocks. +""" +function wait(t::Task; throw=true) + t === current_task() && Core.throw(ConcurrencyViolationError("deadlock detected: cannot wait on current task")) _wait(t) - if istaskfailed(t) - throw(TaskFailedException(t)) + if throw && istaskfailed(t) + Core.throw(TaskFailedException(t)) end nothing end +# Wait multiple tasks + +""" + waitany(tasks; throw=true) -> (done_tasks, remaining_tasks) + +Wait until at least one of the given tasks have been completed. + +If `throw` is `true`, throw `CompositeException` when one of the +completed tasks completes with an exception. + +The return value consists of two task vectors. The first one consists of +completed tasks, and the other consists of uncompleted tasks. + +!!! warning + This may scale poorly compared to writing code that uses multiple individual tasks that + each runs serially, since this needs to scan the list of `tasks` each time and + synchronize with each one every time this is called. Or consider using + [`waitall(tasks; failfast=true)`](@ref waitall) instead. +""" +waitany(tasks; throw=true) = _wait_multiple(tasks, throw) + +""" + waitall(tasks; failfast=true, throw=true) -> (done_tasks, remaining_tasks) + +Wait until all the given tasks have been completed. + +If `failfast` is `true`, the function will return when at least one of the +given tasks is finished by exception. If `throw` is `true`, throw +`CompositeException` when one of the completed tasks has failed. + +`failfast` and `throw` keyword arguments work independently; when only +`throw=true` is specified, this function waits for all the tasks to complete. + +The return value consists of two task vectors. The first one consists of +completed tasks, and the other consists of uncompleted tasks. +""" +waitall(tasks; failfast=true, throw=true) = _wait_multiple(tasks, throw, true, failfast) + +function _wait_multiple(waiting_tasks, throwexc=false, all=false, failfast=false) + tasks = Task[] + + for t in waiting_tasks + t isa Task || error("Expected an iterator of `Task` object") + push!(tasks, t) + end + + if (all && !failfast) || length(tasks) <= 1 + exception = false + # Force everything to finish synchronously for the case of waitall + # with failfast=false + for t in tasks + _wait(t) + exception |= istaskfailed(t) + end + if exception && throwexc + exceptions = [TaskFailedException(t) for t in tasks if istaskfailed(t)] + throw(CompositeException(exceptions)) + else + return tasks, Task[] + end + end + + exception = false + nremaining::Int = length(tasks) + done_mask = falses(nremaining) + for (i, t) in enumerate(tasks) + if istaskdone(t) + done_mask[i] = true + exception |= istaskfailed(t) + nremaining -= 1 + else + done_mask[i] = false + end + end + + if nremaining == 0 + return tasks, Task[] + elseif any(done_mask) && (!all || (failfast && exception)) + if throwexc && (!all || failfast) && exception + exceptions = [TaskFailedException(t) for t in tasks[done_mask] if istaskfailed(t)] + throw(CompositeException(exceptions)) + else + return tasks[done_mask], tasks[.~done_mask] + end + end + + chan = Channel{Int}(Inf) + sentinel = current_task() + waiter_tasks = fill(sentinel, length(tasks)) + + for (i, done) in enumerate(done_mask) + done && continue + t = tasks[i] + if istaskdone(t) + done_mask[i] = true + exception |= istaskfailed(t) + nremaining -= 1 + exception && failfast && break + else + waiter = @task put!(chan, i) + waiter.sticky = false + _wait2(t, waiter) + waiter_tasks[i] = waiter + end + end + + while nremaining > 0 + i = take!(chan) + t = tasks[i] + waiter_tasks[i] = sentinel + done_mask[i] = true + exception |= istaskfailed(t) + nremaining -= 1 + + # stop early if requested, unless there is something immediately + # ready to consume from the channel (using a race-y check) + if (!all || (failfast && exception)) && !isready(chan) + break + end + end + + close(chan) + + if nremaining == 0 + return tasks, Task[] + else + remaining_mask = .~done_mask + for i in findall(remaining_mask) + waiter = waiter_tasks[i] + donenotify = tasks[i].donenotify::ThreadSynchronizer + @lock donenotify Base.list_deletefirst!(donenotify.waitq, waiter) + end + done_tasks = tasks[done_mask] + if throwexc && exception + exceptions = [TaskFailedException(t) for t in done_tasks if istaskfailed(t)] + throw(CompositeException(exceptions)) + else + return done_tasks, tasks[remaining_mask] + end + end +end + """ fetch(x::Any) @@ -890,11 +1050,15 @@ end A fast, unfair-scheduling version of `schedule(t, arg); yield()` which immediately yields to `t` before calling the scheduler. + +Throws a `ConcurrencyViolationError` if `t` is the currently running task. """ function yield(t::Task, @nospecialize(x=nothing)) - (t._state === task_state_runnable && t.queue === nothing) || error("yield: Task not runnable") + current = current_task() + t === current && throw(ConcurrencyViolationError("Cannot yield to currently running task!")) + (t._state === task_state_runnable && t.queue === nothing) || throw(ConcurrencyViolationError("yield: Task not runnable")) t.result = x - enq_work(current_task()) + enq_work(current) set_next_task(t) return try_yieldto(ensure_rescheduled) end diff --git a/base/terminfo.jl b/base/terminfo.jl index ff7e6fab7f1f7..6f1d1ca8015f0 100644 --- a/base/terminfo.jl +++ b/base/terminfo.jl @@ -1,5 +1,8 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +# Since this code is in the startup-path, we go to some effort to +# be easier on the compiler, such as using `map` over broadcasting. + include("terminfo_data.jl") """ @@ -15,7 +18,7 @@ particular capabilities, solely based on `term(5)`. - `names::Vector{String}`: The names this terminal is known by. - `flags::BitVector`: A list of 0–$(length(TERM_FLAGS)) flag values. -- `numbers::Union{Vector{UInt16}, Vector{UInt32}}`: A list of 0–$(length(TERM_NUMBERS)) +- `numbers::Union{Vector{Int16}, Vector{Int32}}`: A list of 0–$(length(TERM_NUMBERS)) number values. A value of `typemax(eltype(numbers))` is used to skip over unspecified capabilities while ensuring value indices are correct. - `strings::Vector{Union{String, Nothing}}`: A list of 0–$(length(TERM_STRINGS)) @@ -30,9 +33,9 @@ See also: `TermInfo` and `TermCapability`. struct TermInfoRaw names::Vector{String} flags::BitVector - numbers::Union{Vector{UInt16}, Vector{UInt32}} + numbers::Vector{Int} strings::Vector{Union{String, Nothing}} - extended::Union{Nothing, Dict{Symbol, Union{Bool, Int, String}}} + extended::Union{Nothing, Dict{Symbol, Union{Bool, Int, String, Nothing}}} end """ @@ -59,59 +62,49 @@ See also: `TermInfoRaw` and `TermCapability`. """ struct TermInfo names::Vector{String} - flags::Int - numbers::BitVector - strings::BitVector - extensions::Vector{Symbol} - capabilities::Dict{Symbol, Union{Bool, Int, String}} + flags::Dict{Symbol, Bool} + numbers::Dict{Symbol, Int} + strings::Dict{Symbol, String} + extensions::Union{Nothing, Set{Symbol}} + aliases::Dict{Symbol, Symbol} end -TermInfo() = TermInfo([], 0, [], [], [], Dict()) +TermInfo() = TermInfo([], Dict(), Dict(), Dict(), nothing, Dict()) function read(data::IO, ::Type{TermInfoRaw}) # Parse according to `term(5)` # Header magic = read(data, UInt16) |> ltoh NumInt = if magic == 0o0432 - UInt16 + Int16 elseif magic == 0o01036 - UInt32 + Int32 else throw(ArgumentError("Terminfo data did not start with the magic number 0o0432 or 0o01036")) end - name_bytes = read(data, UInt16) |> ltoh - flag_bytes = read(data, UInt16) |> ltoh - numbers_count = read(data, UInt16) |> ltoh - string_count = read(data, UInt16) |> ltoh - table_bytes = read(data, UInt16) |> ltoh + name_bytes, flag_bytes, numbers_count, string_count, table_bytes = + @ntuple 5 _->read(data, Int16) |> ltoh # Terminal Names - term_names = split(String(read(data, name_bytes - 1)), '|') .|> String + term_names = map(String, split(String(read(data, name_bytes - 1)), '|')) 0x00 == read(data, UInt8) || throw(ArgumentError("Terminfo data did not contain a null byte after the terminal names section")) # Boolean Flags - flags = read(data, flag_bytes) .== 0x01 + flags = map(==(0x01), read(data, flag_bytes)) if position(data) % 2 != 0 0x00 == read(data, UInt8) || throw(ArgumentError("Terminfo did not contain a null byte after the flag section, expected to position the start of the numbers section on an even byte")) end # Numbers, Strings, Table - numbers = map(ltoh, reinterpret(NumInt, read(data, numbers_count * sizeof(NumInt)))) - string_indices = map(ltoh, reinterpret(UInt16, read(data, string_count * sizeof(UInt16)))) + numbers = map(Int ∘ ltoh, reinterpret(NumInt, read(data, numbers_count * sizeof(NumInt)))) + string_indices = map(ltoh, reinterpret(Int16, read(data, string_count * sizeof(Int16)))) strings_table = read(data, table_bytes) - strings = map(string_indices) do idx - if idx ∉ (0xffff, 0xfffe) - len = findfirst(==(0x00), view(strings_table, 1+idx:length(strings_table))) - !isnothing(len) || - throw(ArgumentError("Terminfo string table entry does not terminate with a null byte")) - String(strings_table[1+idx:idx+len-1]) - end - end + strings = _terminfo_read_strings(strings_table, string_indices) TermInfoRaw(term_names, flags, numbers, strings, if !eof(data) extendedterminfo(data, NumInt) end) end """ - extendedterminfo(data::IO; NumInt::Union{Type{UInt16}, Type{UInt32}}) + extendedterminfo(data::IO; NumInt::Union{Type{Int16}, Type{Int32}}) Read an extended terminfo section from `data`, with `NumInt` as the numbers type. @@ -119,34 +112,56 @@ This will accept any terminfo content that conforms with `term(5)`. See also: `read(::IO, ::Type{TermInfoRaw})` """ -function extendedterminfo(data::IO, NumInt::Union{Type{UInt16}, Type{UInt32}}) +function extendedterminfo(data::IO, NumInt::Union{Type{Int16}, Type{Int32}}) # Extended info if position(data) % 2 != 0 0x00 == read(data, UInt8) || - throw(ArgumentError("Terminfo did not contain a null byte before the extended section, expected to position the start on an even byte")) + throw(ArgumentError("Terminfo did not contain a null byte before the extended section; expected to position the start on an even byte")) end # Extended header - flag_bytes = read(data, UInt16) |> ltoh - numbers_count = read(data, UInt16) |> ltoh - string_count = read(data, UInt16) |> ltoh - table_count = read(data, UInt16) |> ltoh - table_bytes = read(data, UInt16) |> ltoh + flag_bytes, numbers_count, string_count, table_count, table_bytes = + @ntuple 5 _->read(data, Int16) |> ltoh # Extended flags/numbers/strings - flags = read(data, flag_bytes) .== 0x01 + flags = map(==(0x01), read(data, flag_bytes)) if flag_bytes % 2 != 0 0x00 == read(data, UInt8) || - throw(ArgumentError("Terminfo did not contain a null byte after the extended flag section, expected to position the start of the numbers section on an even byte")) + throw(ArgumentError("Terminfo did not contain a null byte after the extended flag section; expected to position the start of the numbers section on an even byte")) + end + numbers = map(Int ∘ ltoh, reinterpret(NumInt, read(data, numbers_count * sizeof(NumInt)))) + table_indices = map(ltoh, reinterpret(Int16, read(data, table_count * sizeof(Int16)))) + table_data = read(data, table_bytes) + strings = _terminfo_read_strings(table_data, table_indices[1:string_count]) + table_halfoffset = Int16(get(table_indices, string_count, 0) + + ncodeunits(something(get(strings, length(strings), ""), "")) + 1) + for index in string_count+1:lastindex(table_indices) + table_indices[index] += table_halfoffset end - numbers = map(n -> Int(ltoh(n)), reinterpret(NumInt, read(data, numbers_count * sizeof(NumInt)))) - table_indices = map(ltoh, reinterpret(UInt16, read(data, table_count * sizeof(UInt16)))) - table_strings = [String(readuntil(data, 0x00)) for _ in 1:length(table_indices)] - info = Dict{Symbol, Union{Bool, Int, String}}() - strings = table_strings[1:string_count] - labels = table_strings[string_count+1:end] - for (label, val) in zip(labels, vcat(flags, numbers, strings)) - info[Symbol(label)] = val + labels = map(Symbol, _terminfo_read_strings(table_data, table_indices[string_count+1:end])) + Dict{Symbol, Union{Bool, Int, String, Nothing}}( + zip(labels, Iterators.flatten((flags, numbers, strings)))) +end + +""" + _terminfo_read_strings(table::Vector{UInt8}, indices::Vector{Int16}) + +From `table`, read a string starting at each position in `indices`. Each string +must be null-terminated. Should an index be -1 or -2, `nothing` is given instead +of a string. +""" +function _terminfo_read_strings(table::Vector{UInt8}, indices::Vector{Int16}) + strings = Vector{Union{Nothing, String}}(undef, length(indices)) + map!(strings, indices) do idx + if idx >= 0 + len = findfirst(==(0x00), view(table, 1+idx:length(table))) + !isnothing(len) || + throw(ArgumentError("Terminfo table entry @$idx does not terminate with a null byte")) + String(table[1+idx:idx+len-1]) + elseif idx ∈ (-1, -2) + else + throw(ArgumentError("Terminfo table index is invalid: -2 ≰ $idx")) + end end - return info + strings end """ @@ -158,45 +173,68 @@ NCurses 6.3, see `TERM_FLAGS`, `TERM_NUMBERS`, and `TERM_STRINGS`). function TermInfo(raw::TermInfoRaw) capabilities = Dict{Symbol, Union{Bool, Int, String}}() sizehint!(capabilities, 2 * (length(raw.flags) + length(raw.numbers) + length(raw.strings))) + flags = Dict{Symbol, Bool}() + numbers = Dict{Symbol, Int}() + strings = Dict{Symbol, String}() + aliases = Dict{Symbol, Symbol}() + extensions = nothing for (flag, value) in zip(TERM_FLAGS, raw.flags) - capabilities[flag.short] = value - capabilities[flag.long] = value + flags[flag.name] = value + aliases[flag.capname] = flag.name end for (num, value) in zip(TERM_NUMBERS, raw.numbers) - if value != typemax(eltype(raw.numbers)) - capabilities[num.short] = Int(value) - capabilities[num.long] = Int(value) - end + numbers[num.name] = Int(value) + aliases[num.capname] = num.name end for (str, value) in zip(TERM_STRINGS, raw.strings) if !isnothing(value) - capabilities[str.short] = value - capabilities[str.long] = value + strings[str.name] = value + aliases[str.capname] = str.name end end - extensions = if !isnothing(raw.extended) - capabilities = merge(capabilities, raw.extended) - keys(raw.extended) |> collect - else - Symbol[] + if !isnothing(raw.extended) + extensions = Set{Symbol}() + longalias(key, value) = first(get(TERM_USER, (typeof(value), key), (nothing, ""))) + for (short, value) in raw.extended + long = longalias(short, value) + key = something(long, short) + push!(extensions, key) + if value isa Bool + flags[key] = value + elseif value isa Int + numbers[key] = value + elseif value isa String + strings[key] = value + end + if !isnothing(long) + aliases[short] = long + end + end end - TermInfo(raw.names, length(raw.flags), - map(n-> n != typemax(typeof(n)), raw.numbers), - map(!isnothing, raw.strings), - extensions, capabilities) + TermInfo(raw.names, flags, numbers, strings, extensions, aliases) end -getindex(ti::TermInfo, key::Symbol) = ti.capabilities[key] -get(ti::TermInfo, key::Symbol, default::D) where D<:Union{Bool, Int, String} = - get(ti.capabilities, key, default)::D -get(ti::TermInfo, key::Symbol, default) = get(ti.capabilities, key, default) -keys(ti::TermInfo) = keys(ti.capabilities) -haskey(ti::TermInfo, key::Symbol) = haskey(ti.capabilities, key) +get(ti::TermInfo, key::Symbol, default::Bool) = get(ti.flags, get(ti.aliases, key, key), default) +get(ti::TermInfo, key::Symbol, default::Int) = get(ti.numbers, get(ti.aliases, key, key), default) +get(ti::TermInfo, key::Symbol, default::String) = get(ti.strings, get(ti.aliases, key, key), default) + +haskey(ti::TermInfo, key::Symbol) = + haskey(ti.flags, key) || haskey(ti.numbers, key) || haskey(ti.strings, key) || haskey(ti.aliases, key) + +function getindex(ti::TermInfo, key::Symbol) + haskey(ti.flags, key) && return ti.flags[key] + haskey(ti.numbers, key) && return ti.numbers[key] + haskey(ti.strings, key) && return ti.strings[key] + haskey(ti.aliases, key) && return getindex(ti, ti.aliases[key]) + throw(KeyError(key)) +end + +keys(ti::TermInfo) = keys(ti.flags) ∪ keys(ti.numbers) ∪ keys(ti.strings) ∪ keys(ti.aliases) function show(io::IO, ::MIME"text/plain", ti::TermInfo) - print(io, "TermInfo(", ti.names, "; ", ti.flags, " flags, ", - sum(ti.numbers), " numbers, ", sum(ti.strings), " strings") - !isempty(ti.extensions) > 0 && + print(io, "TermInfo(", ti.names, "; ", length(ti.flags), " flags, ", + length(ti.numbers), " numbers, ", length(ti.strings), " strings") + !isnothing(ti.extensions) && print(io, ", ", length(ti.extensions), " extended capabilities") print(io, ')') end @@ -216,13 +254,15 @@ function find_terminfo_file(term::String) [ENV["TERMINFO"]] elseif isdir(joinpath(homedir(), ".terminfo")) [joinpath(homedir(), ".terminfo")] - elseif haskey(ENV, "TERMINFO_DIRS") - split(ENV["TERMINFO_DIRS"], ':') - elseif Sys.isunix() - ["/usr/share/terminfo"] else String[] end + haskey(ENV, "TERMINFO_DIRS") && + append!(terminfo_dirs, + replace(split(ENV["TERMINFO_DIRS"], ':'), + "" => "/usr/share/terminfo")) + Sys.isunix() && + push!(terminfo_dirs, "/etc/terminfo", "/lib/terminfo", "/usr/share/terminfo") for dir in terminfo_dirs if isfile(joinpath(dir, chr, term)) return joinpath(dir, chr, term) @@ -272,19 +312,50 @@ end Return a boolean signifying whether the current terminal supports 24-bit colors. -This uses the `COLORTERM` environment variable if possible, returning true if it -is set to either `"truecolor"` or `"24bit"`. - -As a fallback, first on unix systems the `colors` terminal capability is checked -— should more than 256 colors be reported, this is taken to signify 24-bit -support. +Multiple conditions are taken as signifying truecolor support, specifically any of the following: +- The `COLORTERM` environment variable is set to `"truecolor"` or `"24bit"` +- The current terminfo sets the [`RGB`[^1] + capability](https://invisible-island.net/ncurses/man/user_caps.5.html#h3-Recognized-Capabilities) + (or the legacy `Tc` capability[^2]) flag +- The current terminfo provides `setrgbf` and `setrgbb` strings[^3] +- The current terminfo has a `colors` number greater that `256`, on a unix system +- The VTE version is at least 3600 (detected via the `VTE_VERSION` environment variable) +- The current terminal has the `XTERM_VERSION` environment variable set +- The current terminal appears to be iTerm according to the `TERMINAL_PROGRAM` environment variable +- The `TERM` environment variable corresponds to: linuxvt, rxvt, or st + +[^1]: Added to Ncurses 6.1, and used in `TERM=*-direct` terminfos. +[^2]: Convention [added to tmux in 2016](https://github.com/tmux/tmux/commit/427b8204268af5548d09b830e101c59daa095df9), + superseded by `RGB`. +[^3]: Proposed by [Rüdiger Sonderfeld in 2013](https://lists.gnu.org/archive/html/bug-ncurses/2013-10/msg00007.html), + adopted by a few terminal emulators. + +!!! note + The set of conditions is messy, because the situation is a mess, and there's + no resolution in sight. `COLORTERM` is widely accepted, but an imperfect + solution because only `TERM` is passed across `ssh` sessions. Terminfo is + the obvious place for a terminal to declare capabilities, but it's taken + enough years for ncurses/terminfo to declare a standard capability (`RGB`) + that a number of other approaches have taken root. Furthermore, the official + `RGB` capability is *incompatible* with 256-color operation, and so is + unable to resolve the fragmentation in the terminal ecosystem. """ function ttyhastruecolor() + # Lasciate ogne speranza, voi ch'intrate get(ENV, "COLORTERM", "") ∈ ("truecolor", "24bit") || - @static if Sys.isunix() - get(current_terminfo, :colors, 0) > 256 - else - false + get(current_terminfo, :RGB, false) || get(current_terminfo, :Tc, false) || + (haskey(current_terminfo, :setrgbf) && haskey(current_terminfo, :setrgbb)) || + @static if Sys.isunix() get(current_terminfo, :colors, 0) > 256 else false end || + (Sys.iswindows() && Sys.windows_version() ≥ v"10.0.14931") || # See + something(tryparse(Int, get(ENV, "VTE_VERSION", "")), 0) >= 3600 || # Per GNOME bug #685759 + haskey(ENV, "XTERM_VERSION") || + get(ENV, "TERMINAL_PROGRAM", "") == "iTerm.app" || # Why does Apple need to be special? + haskey(ENV, "KONSOLE_PROFILE_NAME") || # Per commentary in VT102Emulation.cpp + haskey(ENV, "KONSOLE_DBUS_SESSION") || + let term = get(ENV, "TERM", "") + startswith(term, "linux") || # Linux 4.8+ supports true-colour SGR. + startswith(term, "rxvt") || # See + startswith(term, "st") # From experimentation end end diff --git a/base/terminfo_data.jl b/base/terminfo_data.jl index 38c058f414f07..caf2ff528d3e1 100644 --- a/base/terminfo_data.jl +++ b/base/terminfo_data.jl @@ -1,5 +1,15 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +# Updating this listing is fairly easy, assuming existence of a unix system, +# posix shell, and `awk`. Just update the version string in the commented out +# `NCURSES_VERSION` variable, and run this file. This works because this file is +# a bit of a quine. + +#= +awk '/^#=run/{flag=1;next}/=#/{flag=0}flag{gsub(/__FILE__/,"\"'"$0"'\"");print}' "$0" | \ + julia --startup-file=no -E 'readchomp("/dev/fd/0") |> Meta.parse |> eval' && echo "Done"; exit +=# + """ struct TermCapability @@ -10,531 +20,777 @@ Specification of a single terminal capability. # Fields -- `short::Symbol`: The *Cap-name* of the capability -- `long::Symbol`: The name of the terminfo capability variable +- `name::Symbol`: The name of the terminfo capability variable +- `capname::Symbol`: The *Cap-name* of the capability - `description::String`: A description of the purpose of the capability See also: `TermInfo`, `TERM_FLAGS`, `TERM_NUMBERS`, and `TERM_STRINGS`. """ struct TermCapability - short::Symbol - long::Symbol + name::Symbol + capname::Symbol description::String end -# Terminfo Capabilities as of NCurses 6.3 +#=run +begin + +using Downloads + +version_info = IOBuffer() +standard_caps = IOBuffer() +user_caps = IOBuffer() + +Downloads.download("https://raw.githubusercontent.com/mirror/ncurses/master/VERSION", version_info) +Downloads.download("https://raw.githubusercontent.com/mirror/ncurses/master/include/Caps", standard_caps) +Downloads.download("https://raw.githubusercontent.com/mirror/ncurses/master/include/Caps-ncurses", user_caps) + +const TERM_FLAGS = NTuple{3, String}[] +const TERM_NUMBERS = NTuple{3, String}[] +const TERM_STRINGS = NTuple{3, String}[] +const TERM_USER = NTuple{3, String}[] + +_, ncurses_version, ncurses_date = split(read(seekstart(version_info), String)) + +for line in eachline(seekstart(standard_caps)) + startswith(line, '#') && continue + components = split(line, '\t', keepempty=false) + if length(components) ∉ 8:9 + @warn "Malformed line: $(sprint(show, line))" + continue + end + name, shortcode, type, _, _, _, _, description, _... = components + caplist = if type == "bool" TERM_FLAGS + elseif type == "num" TERM_NUMBERS + elseif type == "str" TERM_STRINGS + else + @warn "Unrecognised capability type: $type" + continue + end + push!(caplist, (name, shortcode, description)) +end + +for line in eachline(seekstart(user_caps)) + startswith(line, '#') && continue + !startswith(line, "userdef") && continue + line = line[1+ncodeunits("userdef "):end] + components = split(line, '\t', keepempty=false) + if length(components) ∉ 4:5 + @warn "Malformed line: $(sprint(show, line))" + continue + end + code, type, _, description, _... = components + if code == "xm" + components[3] == "-" || continue + description = "mouse response" + end + dtype = get(Dict("bool" => "Bool", "num" => "Int", "str" => "String"), type, nothing) + if isnothing(dtype) + @warn "Unrecognised data type: $type" + continue + end + push!(TERM_USER, (dtype, code, description)) +end + +push!(TERM_USER, ("Bool", "Tc", "tmux extension to indicate 24-bit truecolor support")) +push!(TERM_USER, ("Bool", "Su", "kitty extension to indicate styled underline support")) + +const SENTINEL = "\n## GENERATED CODE BEYOND THIS POINT ##" +const PREAMBLE = readuntil(__FILE__, SENTINEL, keep=true) + +out = IOBuffer() +write(out, PREAMBLE, "\n\n# Terminfo Capabilities as of NCurses $ncurses_version-$ncurses_date\n", + "const NCURSES_VERSION = v\"$ncurses_version.$ncurses_date\"\n") + +for (ftype, list) in [("flag", TERM_FLAGS), ("number", TERM_NUMBERS), ("string", TERM_STRINGS)] + print(out, "\n\"\"\"\n\ + Ordered list of known terminal capability $ftype fields, as of NCurses $ncurses_version-$ncurses_date.\n\ + \"\"\"\n\ + const TERM_$(uppercase(ftype))S = [") + namepad = maximum(textwidth, getindex.(list, 1)) + 1 + codepad = maximum(textwidth, getindex.(list, 2)) + 1 + for (name, shortcode, description) in list + print(out, "\n TermCapability(:", name, ',', ' '^(namepad - textwidth(name)), + ':', shortcode, ',', ' '^(codepad - textwidth(shortcode)), + '"', escape_string(description), "\"),") + end + println(out, "\n]") +end + +function getcustomalias(allterms::Vector{NTuple{3, String}}, type, short, description) + specific_aliases = Dict{String, String}( + "smxx" => ":enter_strikeout_mode", + "rmxx" => ":exit_strikeout_mode", + "Smol" => ":enter_overline_mode", + "Rmol" => ":exit_overline_mode", + "Cs" => ":set_cursor_color", + "Cr" => ":reset_cursor_color", + "Ss" => ":set_cursor_style", + "Se" => ":reset_cursor_style", + "Smulx" => ":set_underline_style", + "Su" => ":can_style_underline", + "csl" => ":clear_status_line", + "Ms" => ":set_host_clipboard", + "Tc" => ":truecolor", + "XF" => ":xterm_focus") + if startswith(short, 'k') && !occursin("keypad", description) + return ":key_" * replace(lowercase(description), r"[^a-z]" => '_') + end + return get(specific_aliases, short, "nothing") +end + +print(out, "\n\"\"\"\nTerminfo extensions that NCurses $ncurses_version-$ncurses_date is aware of.\n\"\"\"", + "\nconst TERM_USER = Dict{Tuple{DataType, Symbol}, Union{Tuple{Nothing, String}, Tuple{Symbol, String}}}(") +shortpad = maximum(textwidth, getindex.(TERM_USER, 2)) + 1 +for (type, short, description) in TERM_USER + print(out, "\n ($(rpad(type * ',', 7)) :$short)", ' '^(shortpad - textwidth(short)), + "=> (", getcustomalias(TERM_USER, type, short, description), ", \"", + escape_string(description), "\"),") +end +println(out, "\n)") + +open(io -> write(io, seekstart(out)), __FILE__, "w") + +end +=# + +## GENERATED CODE BEYOND THIS POINT ## + +# Terminfo Capabilities as of NCurses 6.4-20230311 +const NCURSES_VERSION = v"6.4.20230311" """ -Ordered list of known terminal capability flag fields, as of NCurses 6.3. +Ordered list of known terminal capability flag fields, as of NCurses 6.4-20230311. """ const TERM_FLAGS = [ - TermCapability(:bw, :auto_left_margin, "cub1 wraps from column 0 to last column"), - TermCapability(:am, :auto_right_margin, "terminal has automatic margins"), - TermCapability(:xsb, :no_esc_ctlc, "beehive (f1=escape, f2=ctrl C)"), - TermCapability(:xhp, :ceol_standout_glitch, "standout not erased by overwriting (hp)"), - TermCapability(:xenl, :eat_newline_glitch, "newline ignored after 80 cols (concept)"), - TermCapability(:eo, :erase_overstrike, "can erase overstrikes with a blank"), - TermCapability(:gn, :generic_type, "generic line type"), - TermCapability(:hc, :hard_copy, "hardcopy terminal"), - TermCapability(:km, :has_meta_key, "Has a meta key (i.e., sets 8th-bit)"), - TermCapability(:hs, :has_status_line, "has extra status line"), - TermCapability(:in, :insert_null_glitch, "insert mode distinguishes nulls"), - TermCapability(:db, :memory_below, "display may be retained below the screen"), - TermCapability(:da, :memory_above, "display may be retained above the screen"), - TermCapability(:mir, :move_insert_mode, "safe to move while in insert mode"), - TermCapability(:msgr, :move_standout_mode, "safe to move while in standout mode"), - TermCapability(:os, :over_strike, "terminal can overstrike"), - TermCapability(:eslok, :status_line_esc_ok, "escape can be used on the status line"), - TermCapability(:xt, :dest_tabs_magic_smso, "tabs destructive, magic so char (t1061)"), - TermCapability(:hz, :tilde_glitch, "cannot print ~'s (Hazeltine)"), - TermCapability(:ul, :transparent_underline, "underline character overstrikes"), - TermCapability(:xon, :xon_xoff, "terminal uses xon/xoff handshaking"), - TermCapability(:nxon, :needs_xon_xoff, "padding will not work, xon/xoff required"), - TermCapability(:mc5i, :prtr_silent, "printer will not echo on screen"), - TermCapability(:chts, :hard_cursor, "cursor is hard to see"), - TermCapability(:nrrmc, :non_rev_rmcup, "smcup does not reverse rmcup"), - TermCapability(:npc, :no_pad_char, "pad character does not exist"), - TermCapability(:ndscr, :non_dest_scroll_region, "scrolling region is non-destructive"), - TermCapability(:ccc, :can_change, "terminal can re-define existing colors"), - TermCapability(:bce, :back_color_erase, "screen erased with background color"), - TermCapability(:hls, :hue_lightness_saturation, "terminal uses only HLS color notation (Tektronix)"), - TermCapability(:xhpa, :col_addr_glitch, "only positive motion for hpa/mhpa caps"), - TermCapability(:crxm, :cr_cancels_micro_mode, "using cr turns off micro mode"), - TermCapability(:daisy, :has_print_wheel, "printer needs operator to change character set"), - TermCapability(:xvpa, :row_addr_glitch, "only positive motion for vpa/mvpa caps"), - TermCapability(:sam, :semi_auto_right_margin, "printing in last column causes cr"), - TermCapability(:cpix, :cpi_changes_res, "changing character pitch changes resolution"), - TermCapability(:lpix, :lpi_changes_res, "changing line pitch changes resolution"), - TermCapability(:OTbs, :backspaces_with_bs, "uses ^H to move left"), - TermCapability(:OTns, :crt_no_scrolling, "crt cannot scroll"), - TermCapability(:OTnc, :no_correctly_working_cr, "no way to go to start of line"), - TermCapability(:OTMT, :gnu_has_meta_key, "has meta key"), - TermCapability(:OTNL, :linefeed_is_newline, "move down with \n"), - TermCapability(:OTpt, :has_hardware_tabs, "has 8-char tabs invoked with ^I"), - TermCapability(:OTxr, :return_does_clr_eol, "return clears the line"), + TermCapability(:auto_left_margin, :bw, "cub1 wraps from column 0 to last column"), + TermCapability(:auto_right_margin, :am, "terminal has automatic margins"), + TermCapability(:no_esc_ctlc, :xsb, "beehive (f1=escape, f2=ctrl C)"), + TermCapability(:ceol_standout_glitch, :xhp, "standout not erased by overwriting (hp)"), + TermCapability(:eat_newline_glitch, :xenl, "newline ignored after 80 cols (concept)"), + TermCapability(:erase_overstrike, :eo, "can erase overstrikes with a blank"), + TermCapability(:generic_type, :gn, "generic line type"), + TermCapability(:hard_copy, :hc, "hardcopy terminal"), + TermCapability(:has_meta_key, :km, "Has a meta key (i.e., sets 8th-bit)"), + TermCapability(:has_status_line, :hs, "has extra status line"), + TermCapability(:insert_null_glitch, :in, "insert mode distinguishes nulls"), + TermCapability(:memory_above, :da, "display may be retained above the screen"), + TermCapability(:memory_below, :db, "display may be retained below the screen"), + TermCapability(:move_insert_mode, :mir, "safe to move while in insert mode"), + TermCapability(:move_standout_mode, :msgr, "safe to move while in standout mode"), + TermCapability(:over_strike, :os, "terminal can overstrike"), + TermCapability(:status_line_esc_ok, :eslok, "escape can be used on the status line"), + TermCapability(:dest_tabs_magic_smso, :xt, "tabs destructive, magic so char (t1061)"), + TermCapability(:tilde_glitch, :hz, "cannot print ~'s (Hazeltine)"), + TermCapability(:transparent_underline, :ul, "underline character overstrikes"), + TermCapability(:xon_xoff, :xon, "terminal uses xon/xoff handshaking"), + TermCapability(:needs_xon_xoff, :nxon, "padding will not work, xon/xoff required"), + TermCapability(:prtr_silent, :mc5i, "printer will not echo on screen"), + TermCapability(:hard_cursor, :chts, "cursor is hard to see"), + TermCapability(:non_rev_rmcup, :nrrmc, "smcup does not reverse rmcup"), + TermCapability(:no_pad_char, :npc, "pad character does not exist"), + TermCapability(:non_dest_scroll_region, :ndscr, "scrolling region is non-destructive"), + TermCapability(:can_change, :ccc, "terminal can re-define existing colors"), + TermCapability(:back_color_erase, :bce, "screen erased with background color"), + TermCapability(:hue_lightness_saturation, :hls, "terminal uses only HLS color notation (Tektronix)"), + TermCapability(:col_addr_glitch, :xhpa, "only positive motion for hpa/mhpa caps"), + TermCapability(:cr_cancels_micro_mode, :crxm, "using cr turns off micro mode"), + TermCapability(:has_print_wheel, :daisy, "printer needs operator to change character set"), + TermCapability(:row_addr_glitch, :xvpa, "only positive motion for vpa/mvpa caps"), + TermCapability(:semi_auto_right_margin, :sam, "printing in last column causes cr"), + TermCapability(:cpi_changes_res, :cpix, "changing character pitch changes resolution"), + TermCapability(:lpi_changes_res, :lpix, "changing line pitch changes resolution"), + TermCapability(:backspaces_with_bs, :OTbs, "uses ^H to move left"), + TermCapability(:crt_no_scrolling, :OTns, "crt cannot scroll"), + TermCapability(:no_correctly_working_cr, :OTnc, "no way to go to start of line"), + TermCapability(:gnu_has_meta_key, :OTMT, "has meta key"), + TermCapability(:linefeed_is_newline, :OTNL, "move down with \\n"), + TermCapability(:has_hardware_tabs, :OTpt, "has 8-char tabs invoked with ^I"), + TermCapability(:return_does_clr_eol, :OTxr, "return clears the line"), ] """ -Ordered list of known terminal capability number fields, as of NCurses 6.3. +Ordered list of known terminal capability number fields, as of NCurses 6.4-20230311. """ const TERM_NUMBERS = [ - TermCapability(:cols, :columns, "number of columns in a line"), - TermCapability(:it, :init_tabs, "tabs initially every # spaces"), - TermCapability(:lines, :lines, "number of lines on screen or page"), - TermCapability(:lm, :lines_of_memory, "lines of memory if > line. 0 means varies"), - TermCapability(:xmc, :magic_cookie_glitch, "number of blank characters left by smso or rmso"), - TermCapability(:pb, :padding_baud_rate, "lowest baud rate where padding needed"), - TermCapability(:vt, :virtual_terminal, "virtual terminal number (CB/unix)"), - TermCapability(:wsl, :width_status_line, "number of columns in status line"), - TermCapability(:nlab, :num_labels, "number of labels on screen"), - TermCapability(:lh, :label_height, "rows in each label"), - TermCapability(:lw, :label_width, "columns in each label"), - TermCapability(:ma, :max_attributes, "maximum combined attributes terminal can handle"), - TermCapability(:wnum, :maximum_windows, "maximum number of definable windows"), - TermCapability(:colors, :max_colors, "maximum number of colors on screen"), - TermCapability(:pairs, :max_pairs, "maximum number of color-pairs on the screen"), - TermCapability(:ncv, :no_color_video, "video attributes that cannot be used with colors"), - TermCapability(:bufsz, :buffer_capacity, "numbers of bytes buffered before printing"), - TermCapability(:spinv, :dot_vert_spacing, "spacing of pins vertically in pins per inch"), - TermCapability(:spinh, :dot_horz_spacing, "spacing of dots horizontally in dots per inch"), - TermCapability(:maddr, :max_micro_address, "maximum value in micro_..._address"), - TermCapability(:mjump, :max_micro_jump, "maximum value in parm_..._micro"), - TermCapability(:mcs, :micro_col_size, "character step size when in micro mode"), - TermCapability(:mls, :micro_line_size, "line step size when in micro mode"), - TermCapability(:npins, :number_of_pins, "numbers of pins in print-head"), - TermCapability(:orc, :output_res_char, "horizontal resolution in units per line"), - TermCapability(:orl, :output_res_line, "vertical resolution in units per line"), - TermCapability(:orhi, :output_res_horz_inch, "horizontal resolution in units per inch"), - TermCapability(:orvi, :output_res_vert_inch, "vertical resolution in units per inch"), - TermCapability(:cps, :print_rate, "print rate in characters per second"), - TermCapability(:widcs, :wide_char_size, "character step size when in double wide mode"), - TermCapability(:btns, :buttons, "number of buttons on mouse"), - TermCapability(:bitwin, :bit_image_entwining, "number of passes for each bit-image row"), - TermCapability(:bitype, :bit_image_type, "type of bit-image device"), - TermCapability(:OTug, :magic_cookie_glitch_ul, "number of blanks left by ul"), - TermCapability(:OTdC, :carriage_return_delay, "pad needed for CR"), - TermCapability(:OTdN, :new_line_delay, "pad needed for LF"), - TermCapability(:OTdB, :backspace_delay, "padding required for ^H"), - TermCapability(:OTdT, :horizontal_tab_delay, "padding required for ^I"), - TermCapability(:OTkn, :number_of_function_keys, "count of function keys"), + TermCapability(:columns, :cols, "number of columns in a line"), + TermCapability(:init_tabs, :it, "tabs initially every # spaces"), + TermCapability(:lines, :lines, "number of lines on screen or page"), + TermCapability(:lines_of_memory, :lm, "lines of memory if > line. 0 means varies"), + TermCapability(:magic_cookie_glitch, :xmc, "number of blank characters left by smso or rmso"), + TermCapability(:padding_baud_rate, :pb, "lowest baud rate where padding needed"), + TermCapability(:virtual_terminal, :vt, "virtual terminal number (CB/unix)"), + TermCapability(:width_status_line, :wsl, "number of columns in status line"), + TermCapability(:num_labels, :nlab, "number of labels on screen"), + TermCapability(:label_height, :lh, "rows in each label"), + TermCapability(:label_width, :lw, "columns in each label"), + TermCapability(:max_attributes, :ma, "maximum combined attributes terminal can handle"), + TermCapability(:maximum_windows, :wnum, "maximum number of definable windows"), + TermCapability(:max_colors, :colors, "maximum number of colors on screen"), + TermCapability(:max_pairs, :pairs, "maximum number of color-pairs on the screen"), + TermCapability(:no_color_video, :ncv, "video attributes that cannot be used with colors"), + TermCapability(:buffer_capacity, :bufsz, "numbers of bytes buffered before printing"), + TermCapability(:dot_vert_spacing, :spinv, "spacing of pins vertically in pins per inch"), + TermCapability(:dot_horz_spacing, :spinh, "spacing of dots horizontally in dots per inch"), + TermCapability(:max_micro_address, :maddr, "maximum value in micro_..._address"), + TermCapability(:max_micro_jump, :mjump, "maximum value in parm_..._micro"), + TermCapability(:micro_col_size, :mcs, "character step size when in micro mode"), + TermCapability(:micro_line_size, :mls, "line step size when in micro mode"), + TermCapability(:number_of_pins, :npins, "numbers of pins in print-head"), + TermCapability(:output_res_char, :orc, "horizontal resolution in units per line"), + TermCapability(:output_res_line, :orl, "vertical resolution in units per line"), + TermCapability(:output_res_horz_inch, :orhi, "horizontal resolution in units per inch"), + TermCapability(:output_res_vert_inch, :orvi, "vertical resolution in units per inch"), + TermCapability(:print_rate, :cps, "print rate in characters per second"), + TermCapability(:wide_char_size, :widcs, "character step size when in double wide mode"), + TermCapability(:buttons, :btns, "number of buttons on mouse"), + TermCapability(:bit_image_entwining, :bitwin, "number of passes for each bit-image row"), + TermCapability(:bit_image_type, :bitype, "type of bit-image device"), + TermCapability(:magic_cookie_glitch_ul, :OTug, "number of blanks left by ul"), + TermCapability(:carriage_return_delay, :OTdC, "pad needed for CR"), + TermCapability(:new_line_delay, :OTdN, "pad needed for LF"), + TermCapability(:backspace_delay, :OTdB, "padding required for ^H"), + TermCapability(:horizontal_tab_delay, :OTdT, "padding required for ^I"), + TermCapability(:number_of_function_keys, :OTkn, "count of function keys"), ] """ -Ordered list of known terminal capability string fields, as of NCurses 6.3. +Ordered list of known terminal capability string fields, as of NCurses 6.4-20230311. """ const TERM_STRINGS = [ - TermCapability(:cbt, :back_tab, "back tab (P)"), - TermCapability(:bel, :bell, "audible signal (bell) (P)"), - TermCapability(:cr, :carriage_return, "carriage return (P*) (P*)"), - TermCapability(:csr, :change_scroll_region, "change region to line #1 to line #2 (P)"), - TermCapability(:tbc, :clear_all_tabs, "clear all tab stops (P)"), - TermCapability(:clear, :clear_screen, "clear screen and home cursor (P*)"), - TermCapability(:el, :clr_eol, "clear to end of line (P)"), - TermCapability(:ed, :clr_eos, "clear to end of screen (P*)"), - TermCapability(:hpa, :column_address, "horizontal position #1, absolute (P)"), - TermCapability(:cmdch, :command_character, "terminal settable cmd character in prototype !?"), - TermCapability(:cup, :cursor_address, "move to row #1 columns #2"), - TermCapability(:cud1, :cursor_down, "down one line"), - TermCapability(:home, :cursor_home, "home cursor (if no cup)"), - TermCapability(:civis, :cursor_invisible, "make cursor invisible"), - TermCapability(:cub1, :cursor_left, "move left one space"), - TermCapability(:mrcup, :cursor_mem_address, "memory relative cursor addressing, move to row #1 columns #2"), - TermCapability(:cnorm, :cursor_normal, "make cursor appear normal (undo civis/cvvis)"), - TermCapability(:cuf1, :cursor_right, "non-destructive space (move right one space)"), - TermCapability(:ll, :cursor_to_ll, "last line, first column (if no cup)"), - TermCapability(:cuu1, :cursor_up, "up one line"), - TermCapability(:cvvis, :cursor_visible, "make cursor very visible"), - TermCapability(:dch1, :delete_character, "delete character (P*)"), - TermCapability(:dl1, :delete_line, "delete line (P*)"), - TermCapability(:dsl, :dis_status_line, "disable status line"), - TermCapability(:hd, :down_half_line, "half a line down"), - TermCapability(:smacs, :enter_alt_charset_mode, "start alternate character set (P)"), - TermCapability(:blink, :enter_blink_mode, "turn on blinking"), - TermCapability(:bold, :enter_bold_mode, "turn on bold (extra bright) mode"), - TermCapability(:smcup, :enter_ca_mode, "string to start programs using cup"), - TermCapability(:smdc, :enter_delete_mode, "enter delete mode"), - TermCapability(:dim, :enter_dim_mode, "turn on half-bright mode"), - TermCapability(:smir, :enter_insert_mode, "enter insert mode"), - TermCapability(:invis, :enter_secure_mode, "turn on blank mode (characters invisible)"), - TermCapability(:prot, :enter_protected_mode, "turn on protected mode"), - TermCapability(:rev, :enter_reverse_mode, "turn on reverse video mode"), - TermCapability(:smso, :enter_standout_mode, "begin standout mode"), - TermCapability(:smul, :enter_underline_mode, "begin underline mode"), - TermCapability(:ech, :erase_chars, "erase #1 characters (P)"), - TermCapability(:rmacs, :exit_alt_charset_mode, "end alternate character set (P)"), - TermCapability(:sgr0, :exit_attribute_mode, "turn off all attributes"), - TermCapability(:rmcup, :exit_ca_mode, "strings to end programs using cup"), - TermCapability(:rmdc, :exit_delete_mode, "end delete mode"), - TermCapability(:rmir, :exit_insert_mode, "exit insert mode"), - TermCapability(:rmso, :exit_standout_mode, "exit standout mode"), - TermCapability(:rmul, :exit_underline_mode, "exit underline mode"), - TermCapability(:flash, :flash_screen, "visible bell (may not move cursor)"), - TermCapability(:ff, :form_feed, "hardcopy terminal page eject (P*)"), - TermCapability(:fsl, :from_status_line, "return from status line"), - TermCapability(:is1, :init_1string, "initialization string"), - TermCapability(:is2, :init_2string, "initialization string"), - TermCapability(:is3, :init_3string, "initialization string"), - TermCapability(:if, :init_file, "name of initialization file"), - TermCapability(:ich1, :insert_character, "insert character (P)"), - TermCapability(:il1, :insert_line, "insert line (P*)"), - TermCapability(:ip, :insert_padding, "insert padding after inserted character"), - TermCapability(:kbs, :key_backspace, "backspace key"), - TermCapability(:ktbc, :key_catab, "clear-all-tabs key"), - TermCapability(:kclr, :key_clear, "clear-screen or erase key"), - TermCapability(:kctab, :key_ctab, "clear-tab key"), - TermCapability(:kdch1, :key_dc, "delete-character key"), - TermCapability(:kdl1, :key_dl, "delete-line key"), - TermCapability(:kcud1, :key_down, "down-arrow key"), - TermCapability(:krmir, :key_eic, "sent by rmir or smir in insert mode"), - TermCapability(:kel, :key_eol, "clear-to-end-of-line key"), - TermCapability(:ked, :key_eos, "clear-to-end-of-screen key"), - TermCapability(:kf0, :key_f0, "F0 function key"), - TermCapability(:kf1, :key_f1, "F1 function key"), - TermCapability(:kf10, :key_f10, "F10 function key"), - TermCapability(:kf2, :key_f2, "F2 function key"), - TermCapability(:kf3, :key_f3, "F3 function key"), - TermCapability(:kf4, :key_f4, "F4 function key"), - TermCapability(:kf5, :key_f5, "F5 function key"), - TermCapability(:kf6, :key_f6, "F6 function key"), - TermCapability(:kf7, :key_f7, "F7 function key"), - TermCapability(:kf8, :key_f8, "F8 function key"), - TermCapability(:kf9, :key_f9, "F9 function key"), - TermCapability(:khome, :key_home, "home key"), - TermCapability(:kich1, :key_ic, "insert-character key"), - TermCapability(:kil1, :key_il, "insert-line key"), - TermCapability(:kcub1, :key_left, "left-arrow key"), - TermCapability(:kll, :key_ll, "lower-left key (home down)"), - TermCapability(:knp, :key_npage, "next-page key"), - TermCapability(:kpp, :key_ppage, "previous-page key"), - TermCapability(:kcuf1, :key_right, "right-arrow key"), - TermCapability(:kind, :key_sf, "scroll-forward key"), - TermCapability(:kri, :key_sr, "scroll-backward key"), - TermCapability(:khts, :key_stab, "set-tab key"), - TermCapability(:kcuu1, :key_up, "up-arrow key"), - TermCapability(:rmkx, :keypad_local, "leave 'keyboard_transmit' mode"), - TermCapability(:smkx, :keypad_xmit, "enter 'keyboard_transmit' mode"), - TermCapability(:lf0, :lab_f0, "label on function key f0 if not f0"), - TermCapability(:lf1, :lab_f1, "label on function key f1 if not f1"), - TermCapability(:lf10, :lab_f10, "label on function key f10 if not f10"), - TermCapability(:lf2, :lab_f2, "label on function key f2 if not f2"), - TermCapability(:lf3, :lab_f3, "label on function key f3 if not f3"), - TermCapability(:lf4, :lab_f4, "label on function key f4 if not f4"), - TermCapability(:lf5, :lab_f5, "label on function key f5 if not f5"), - TermCapability(:lf6, :lab_f6, "label on function key f6 if not f6"), - TermCapability(:lf7, :lab_f7, "label on function key f7 if not f7"), - TermCapability(:lf8, :lab_f8, "label on function key f8 if not f8"), - TermCapability(:lf9, :lab_f9, "label on function key f9 if not f9"), - TermCapability(:rmm, :meta_off, "turn off meta mode"), - TermCapability(:smm, :meta_on, "turn on meta mode (8th-bit on)"), - TermCapability(:nel, :newline, "newline (behave like cr followed by lf)"), - TermCapability(:pad, :pad_char, "padding char (instead of null)"), - TermCapability(:dch, :parm_dch, "delete #1 characters (P*)"), - TermCapability(:dl, :parm_delete_line, "delete #1 lines (P*)"), - TermCapability(:cud, :parm_down_cursor, "down #1 lines (P*)"), - TermCapability(:ich, :parm_ich, "insert #1 characters (P*)"), - TermCapability(:indn, :parm_index, "scroll forward #1 lines (P)"), - TermCapability(:il, :parm_insert_line, "insert #1 lines (P*)"), - TermCapability(:cub, :parm_left_cursor, "move #1 characters to the left (P)"), - TermCapability(:cuf, :parm_right_cursor, "move #1 characters to the right (P*)"), - TermCapability(:rin, :parm_rindex, "scroll back #1 lines (P)"), - TermCapability(:cuu, :parm_up_cursor, "up #1 lines (P*)"), - TermCapability(:pfkey, :pkey_key, "program function key #1 to type string #2"), - TermCapability(:pfloc, :pkey_local, "program function key #1 to execute string #2"), - TermCapability(:pfx, :pkey_xmit, "program function key #1 to transmit string #2"), - TermCapability(:mc0, :print_screen, "print contents of screen"), - TermCapability(:mc4, :prtr_off, "turn off printer"), - TermCapability(:mc5, :prtr_on, "turn on printer"), - TermCapability(:rep, :repeat_char, "repeat char #1 #2 times (P*)"), - TermCapability(:rs1, :reset_1string, "reset string"), - TermCapability(:rs2, :reset_2string, "reset string"), - TermCapability(:rs3, :reset_3string, "reset string"), - TermCapability(:rf, :reset_file, "name of reset file"), - TermCapability(:rc, :restore_cursor, "restore cursor to position of last save_cursor"), - TermCapability(:vpa, :row_address, "vertical position #1 absolute (P)"), - TermCapability(:sc, :save_cursor, "save current cursor position (P)"), - TermCapability(:ind, :scroll_forward, "scroll text up (P)"), - TermCapability(:ri, :scroll_reverse, "scroll text down (P)"), - TermCapability(:sgr, :set_attributes, "define video attributes #1-#9 (PG9)"), - TermCapability(:hts, :set_tab, "set a tab in every row, current columns"), - TermCapability(:wind, :set_window, "current window is lines #1-#2 cols #3-#4"), - TermCapability(:ht, :tab, "tab to next 8-space hardware tab stop"), - TermCapability(:tsl, :to_status_line, "move to status line, column #1"), - TermCapability(:uc, :underline_char, "underline char and move past it"), - TermCapability(:hu, :up_half_line, "half a line up"), - TermCapability(:iprog, :init_prog, "path name of program for initialization"), - TermCapability(:ka1, :key_a1, "upper left of keypad"), - TermCapability(:ka3, :key_a3, "upper right of keypad"), - TermCapability(:kb2, :key_b2, "center of keypad"), - TermCapability(:kc1, :key_c1, "lower left of keypad"), - TermCapability(:kc3, :key_c3, "lower right of keypad"), - TermCapability(:mc5p, :prtr_non, "turn on printer for #1 bytes"), - TermCapability(:rmp, :char_padding, "like ip but when in insert mode"), - TermCapability(:acsc, :acs_chars, "graphics charset pairs, based on vt100"), - TermCapability(:pln, :plab_norm, "program label #1 to show string #2"), - TermCapability(:kcbt, :key_btab, "back-tab key"), - TermCapability(:smxon, :enter_xon_mode, "turn on xon/xoff handshaking"), - TermCapability(:rmxon, :exit_xon_mode, "turn off xon/xoff handshaking"), - TermCapability(:smam, :enter_am_mode, "turn on automatic margins"), - TermCapability(:rmam, :exit_am_mode, "turn off automatic margins"), - TermCapability(:xonc, :xon_character, "XON character"), - TermCapability(:xoffc, :xoff_character, "XOFF character"), - TermCapability(:enacs, :ena_acs, "enable alternate char set"), - TermCapability(:smln, :label_on, "turn on soft labels"), - TermCapability(:rmln, :label_off, "turn off soft labels"), - TermCapability(:kbeg, :key_beg, "begin key"), - TermCapability(:kcan, :key_cancel, "cancel key"), - TermCapability(:kclo, :key_close, "close key"), - TermCapability(:kcmd, :key_command, "command key"), - TermCapability(:kcpy, :key_copy, "copy key"), - TermCapability(:kcrt, :key_create, "create key"), - TermCapability(:kend, :key_end, "end key"), - TermCapability(:kent, :key_enter, "enter/send key"), - TermCapability(:kext, :key_exit, "exit key"), - TermCapability(:kfnd, :key_find, "find key"), - TermCapability(:khlp, :key_help, "help key"), - TermCapability(:kmrk, :key_mark, "mark key"), - TermCapability(:kmsg, :key_message, "message key"), - TermCapability(:kmov, :key_move, "move key"), - TermCapability(:knxt, :key_next, "next key"), - TermCapability(:kopn, :key_open, "open key"), - TermCapability(:kopt, :key_options, "options key"), - TermCapability(:kprv, :key_previous, "previous key"), - TermCapability(:kprt, :key_print, "print key"), - TermCapability(:krdo, :key_redo, "redo key"), - TermCapability(:kref, :key_reference, "reference key"), - TermCapability(:krfr, :key_refresh, "refresh key"), - TermCapability(:krpl, :key_replace, "replace key"), - TermCapability(:krst, :key_restart, "restart key"), - TermCapability(:kres, :key_resume, "resume key"), - TermCapability(:ksav, :key_save, "save key"), - TermCapability(:kspd, :key_suspend, "suspend key"), - TermCapability(:kund, :key_undo, "undo key"), - TermCapability(:kBEG, :key_sbeg, "shifted begin key"), - TermCapability(:kCAN, :key_scancel, "shifted cancel key"), - TermCapability(:kCMD, :key_scommand, "shifted command key"), - TermCapability(:kCPY, :key_scopy, "shifted copy key"), - TermCapability(:kCRT, :key_screate, "shifted create key"), - TermCapability(:kDC, :key_sdc, "shifted delete-character key"), - TermCapability(:kDL, :key_sdl, "shifted delete-line key"), - TermCapability(:kslt, :key_select, "select key"), - TermCapability(:kEND, :key_send, "shifted end key"), - TermCapability(:kEOL, :key_seol, "shifted clear-to-end-of-line key"), - TermCapability(:kEXT, :key_sexit, "shifted exit key"), - TermCapability(:kFND, :key_sfind, "shifted find key"), - TermCapability(:kHLP, :key_shelp, "shifted help key"), - TermCapability(:kHOM, :key_shome, "shifted home key"), - TermCapability(:kIC, :key_sic, "shifted insert-character key"), - TermCapability(:kLFT, :key_sleft, "shifted left-arrow key"), - TermCapability(:kMSG, :key_smessage, "shifted message key"), - TermCapability(:kMOV, :key_smove, "shifted move key"), - TermCapability(:kNXT, :key_snext, "shifted next key"), - TermCapability(:kOPT, :key_soptions, "shifted options key"), - TermCapability(:kPRV, :key_sprevious, "shifted previous key"), - TermCapability(:kPRT, :key_sprint, "shifted print key"), - TermCapability(:kRDO, :key_sredo, "shifted redo key"), - TermCapability(:kRPL, :key_sreplace, "shifted replace key"), - TermCapability(:kRIT, :key_sright, "shifted right-arrow key"), - TermCapability(:kRES, :key_srsume, "shifted resume key"), - TermCapability(:kSAV, :key_ssave, "shifted save key"), - TermCapability(:kSPD, :key_ssuspend, "shifted suspend key"), - TermCapability(:kUND, :key_sundo, "shifted undo key"), - TermCapability(:rfi, :req_for_input, "send next input char (for ptys)"), - TermCapability(:kf11, :key_f11, "F11 function key"), - TermCapability(:kf12, :key_f12, "F12 function key"), - TermCapability(:kf13, :key_f13, "F13 function key"), - TermCapability(:kf14, :key_f14, "F14 function key"), - TermCapability(:kf15, :key_f15, "F15 function key"), - TermCapability(:kf16, :key_f16, "F16 function key"), - TermCapability(:kf17, :key_f17, "F17 function key"), - TermCapability(:kf18, :key_f18, "F18 function key"), - TermCapability(:kf19, :key_f19, "F19 function key"), - TermCapability(:kf20, :key_f20, "F20 function key"), - TermCapability(:kf21, :key_f21, "F21 function key"), - TermCapability(:kf22, :key_f22, "F22 function key"), - TermCapability(:kf23, :key_f23, "F23 function key"), - TermCapability(:kf24, :key_f24, "F24 function key"), - TermCapability(:kf25, :key_f25, "F25 function key"), - TermCapability(:kf26, :key_f26, "F26 function key"), - TermCapability(:kf27, :key_f27, "F27 function key"), - TermCapability(:kf28, :key_f28, "F28 function key"), - TermCapability(:kf29, :key_f29, "F29 function key"), - TermCapability(:kf30, :key_f30, "F30 function key"), - TermCapability(:kf31, :key_f31, "F31 function key"), - TermCapability(:kf32, :key_f32, "F32 function key"), - TermCapability(:kf33, :key_f33, "F33 function key"), - TermCapability(:kf34, :key_f34, "F34 function key"), - TermCapability(:kf35, :key_f35, "F35 function key"), - TermCapability(:kf36, :key_f36, "F36 function key"), - TermCapability(:kf37, :key_f37, "F37 function key"), - TermCapability(:kf38, :key_f38, "F38 function key"), - TermCapability(:kf39, :key_f39, "F39 function key"), - TermCapability(:kf40, :key_f40, "F40 function key"), - TermCapability(:kf41, :key_f41, "F41 function key"), - TermCapability(:kf42, :key_f42, "F42 function key"), - TermCapability(:kf43, :key_f43, "F43 function key"), - TermCapability(:kf44, :key_f44, "F44 function key"), - TermCapability(:kf45, :key_f45, "F45 function key"), - TermCapability(:kf46, :key_f46, "F46 function key"), - TermCapability(:kf47, :key_f47, "F47 function key"), - TermCapability(:kf48, :key_f48, "F48 function key"), - TermCapability(:kf49, :key_f49, "F49 function key"), - TermCapability(:kf50, :key_f50, "F50 function key"), - TermCapability(:kf51, :key_f51, "F51 function key"), - TermCapability(:kf52, :key_f52, "F52 function key"), - TermCapability(:kf53, :key_f53, "F53 function key"), - TermCapability(:kf54, :key_f54, "F54 function key"), - TermCapability(:kf55, :key_f55, "F55 function key"), - TermCapability(:kf56, :key_f56, "F56 function key"), - TermCapability(:kf57, :key_f57, "F57 function key"), - TermCapability(:kf58, :key_f58, "F58 function key"), - TermCapability(:kf59, :key_f59, "F59 function key"), - TermCapability(:kf60, :key_f60, "F60 function key"), - TermCapability(:kf61, :key_f61, "F61 function key"), - TermCapability(:kf62, :key_f62, "F62 function key"), - TermCapability(:kf63, :key_f63, "F63 function key"), - TermCapability(:el1, :clr_bol, "Clear to beginning of line"), - TermCapability(:mgc, :clear_margins, "clear right and left soft margins"), - TermCapability(:smgl, :set_left_margin, "set left soft margin at current column. (ML is not in BSD termcap)."), - TermCapability(:smgr, :set_right_margin, "set right soft margin at current column"), - TermCapability(:fln, :label_format, "label format"), - TermCapability(:sclk, :set_clock, "set clock, #1 hrs #2 mins #3 secs"), - TermCapability(:dclk, :display_clock, "display clock"), - TermCapability(:rmclk, :remove_clock, "remove clock"), - TermCapability(:cwin, :create_window, "define a window #1 from #2, #3 to #4, #5"), - TermCapability(:wingo, :goto_window, "go to window #1"), - TermCapability(:hup, :hangup, "hang-up phone"), - TermCapability(:dial, :dial_phone, "dial number #1"), - TermCapability(:qdial, :quick_dial, "dial number #1 without checking"), - TermCapability(:tone, :tone, "select touch tone dialing"), - TermCapability(:pulse, :pulse, "select pulse dialing"), - TermCapability(:hook, :flash_hook, "flash switch hook"), - TermCapability(:pause, :fixed_pause, "pause for 2-3 seconds"), - TermCapability(:wait, :wait_tone, "wait for dial-tone"), - TermCapability(:u0, :user0, "User string #0"), - TermCapability(:u1, :user1, "User string #1"), - TermCapability(:u2, :user2, "User string #2"), - TermCapability(:u3, :user3, "User string #3"), - TermCapability(:u4, :user4, "User string #4"), - TermCapability(:u5, :user5, "User string #5"), - TermCapability(:u6, :user6, "User string #6"), - TermCapability(:u7, :user7, "User string #7"), - TermCapability(:u8, :user8, "User string #8"), - TermCapability(:u9, :user9, "User string #9"), - TermCapability(:op, :orig_pair, "Set default pair to its original value"), - TermCapability(:oc, :orig_colors, "Set all color pairs to the original ones"), - TermCapability(:initc, :initialize_color, "Initialize color #1 to (#2, #3, #4)"), - TermCapability(:initp, :initialize_pair, "Initialize color pair #1 to fg=(#2, #3, #4), bg=(#5,#6,#7)"), - TermCapability(:scp, :set_color_pair, "Set current color pair to #1"), - TermCapability(:setf, :set_foreground, "Set foreground color #1"), - TermCapability(:setb, :set_background, "Set background color #1"), - TermCapability(:cpi, :change_char_pitch, "Change number of characters per inch to #1"), - TermCapability(:lpi, :change_line_pitch, "Change number of lines per inch to #1"), - TermCapability(:chr, :change_res_horz, "Change horizontal resolution to #1"), - TermCapability(:cvr, :change_res_vert, "Change vertical resolution to #1"), - TermCapability(:defc, :define_char, "Define a character #1, #2 dots wide, descender #3"), - TermCapability(:swidm, :enter_doublewide_mode, "Enter double-wide mode"), - TermCapability(:sdrfq, :enter_draft_quality, "Enter draft-quality mode"), - TermCapability(:sitm, :enter_italics_mode, "Enter italic mode"), - TermCapability(:slm, :enter_leftward_mode, "Start leftward carriage motion"), - TermCapability(:smicm, :enter_micro_mode, "Start micro-motion mode"), - TermCapability(:snlq, :enter_near_letter_quality, "Enter NLQ mode"), - TermCapability(:snrmq, :enter_normal_quality, "Enter normal-quality mode"), - TermCapability(:sshm, :enter_shadow_mode, "Enter shadow-print mode"), - TermCapability(:ssubm, :enter_subscript_mode, "Enter subscript mode"), - TermCapability(:ssupm, :enter_superscript_mode, "Enter superscript mode"), - TermCapability(:sum, :enter_upward_mode, "Start upward carriage motion"), - TermCapability(:rwidm, :exit_doublewide_mode, "End double-wide mode"), - TermCapability(:ritm, :exit_italics_mode, "End italic mode"), - TermCapability(:rlm, :exit_leftward_mode, "End left-motion mode"), - TermCapability(:rmicm, :exit_micro_mode, "End micro-motion mode"), - TermCapability(:rshm, :exit_shadow_mode, "End shadow-print mode"), - TermCapability(:rsubm, :exit_subscript_mode, "End subscript mode"), - TermCapability(:rsupm, :exit_superscript_mode, "End superscript mode"), - TermCapability(:rum, :exit_upward_mode, "End reverse character motion"), - TermCapability(:mhpa, :micro_column_address, "Like column_address in micro mode"), - TermCapability(:mcud1, :micro_down, "Like cursor_down in micro mode"), - TermCapability(:mcub1, :micro_left, "Like cursor_left in micro mode"), - TermCapability(:mcuf1, :micro_right, "Like cursor_right in micro mode"), - TermCapability(:mvpa, :micro_row_address, "Like row_address #1 in micro mode"), - TermCapability(:mcuu1, :micro_up, "Like cursor_up in micro mode"), - TermCapability(:porder, :order_of_pins, "Match software bits to print-head pins"), - TermCapability(:mcud, :parm_down_micro, "Like parm_down_cursor in micro mode"), - TermCapability(:mcub, :parm_left_micro, "Like parm_left_cursor in micro mode"), - TermCapability(:mcuf, :parm_right_micro, "Like parm_right_cursor in micro mode"), - TermCapability(:mcuu, :parm_up_micro, "Like parm_up_cursor in micro mode"), - TermCapability(:scs, :select_char_set, "Select character set, #1"), - TermCapability(:smgb, :set_bottom_margin, "Set bottom margin at current line"), - TermCapability(:smgbp, :set_bottom_margin_parm, "Set bottom margin at line #1 or (if smgtp is not given) #2 lines from bottom"), - TermCapability(:smglp, :set_left_margin_parm, "Set left (right) margin at column #1"), - TermCapability(:smgrp, :set_right_margin_parm, "Set right margin at column #1"), - TermCapability(:smgt, :set_top_margin, "Set top margin at current line"), - TermCapability(:smgtp, :set_top_margin_parm, "Set top (bottom) margin at row #1"), - TermCapability(:sbim, :start_bit_image, "Start printing bit image graphics"), - TermCapability(:scsd, :start_char_set_def, "Start character set definition #1, with #2 characters in the set"), - TermCapability(:rbim, :stop_bit_image, "Stop printing bit image graphics"), - TermCapability(:rcsd, :stop_char_set_def, "End definition of character set #1"), - TermCapability(:subcs, :subscript_characters, "List of subscriptable characters"), - TermCapability(:supcs, :superscript_characters, "List of superscriptable characters"), - TermCapability(:docr, :these_cause_cr, "Printing any of these characters causes CR"), - TermCapability(:zerom, :zero_motion, "No motion for subsequent character"), - TermCapability(:csnm, :char_set_names, "Produce #1'th item from list of character set names"), - TermCapability(:kmous, :key_mouse, "Mouse event has occurred"), - TermCapability(:minfo, :mouse_info, "Mouse status information"), - TermCapability(:reqmp, :req_mouse_pos, "Request mouse position"), - TermCapability(:getm, :get_mouse, "Curses should get button events, parameter #1 not documented."), - TermCapability(:setaf, :set_a_foreground, "Set foreground color to #1, using ANSI escape"), - TermCapability(:setab, :set_a_background, "Set background color to #1, using ANSI escape"), - TermCapability(:pfxl, :pkey_plab, "Program function key #1 to type string #2 and show string #3"), - TermCapability(:devt, :device_type, "Indicate language/codeset support"), - TermCapability(:csin, :code_set_init, "Init sequence for multiple codesets"), - TermCapability(:s0ds, :set0_des_seq, "Shift to codeset 0 (EUC set 0, ASCII)"), - TermCapability(:s1ds, :set1_des_seq, "Shift to codeset 1"), - TermCapability(:s2ds, :set2_des_seq, "Shift to codeset 2"), - TermCapability(:s3ds, :set3_des_seq, "Shift to codeset 3"), - TermCapability(:smglr, :set_lr_margin, "Set both left and right margins to #1, #2. (ML is not in BSD termcap)."), - TermCapability(:smgtb, :set_tb_margin, "Sets both top and bottom margins to #1, #2"), - TermCapability(:birep, :bit_image_repeat, "Repeat bit image cell #1 #2 times"), - TermCapability(:binel, :bit_image_newline, "Move to next row of the bit image"), - TermCapability(:bicr, :bit_image_carriage_return, "Move to beginning of same row"), - TermCapability(:colornm, :color_names, "Give name for color #1"), - TermCapability(:defbi, :define_bit_image_region, "Define rectangular bit image region"), - TermCapability(:endbi, :end_bit_image_region, "End a bit-image region"), - TermCapability(:setcolor, :set_color_band, "Change to ribbon color #1"), - TermCapability(:slines, :set_page_length, "Set page length to #1 lines"), - TermCapability(:dispc, :display_pc_char, "Display PC character #1"), - TermCapability(:smpch, :enter_pc_charset_mode, "Enter PC character display mode"), - TermCapability(:rmpch, :exit_pc_charset_mode, "Exit PC character display mode"), - TermCapability(:smsc, :enter_scancode_mode, "Enter PC scancode mode"), - TermCapability(:rmsc, :exit_scancode_mode, "Exit PC scancode mode"), - TermCapability(:pctrm, :pc_term_options, "PC terminal options"), - TermCapability(:scesc, :scancode_escape, "Escape for scancode emulation"), - TermCapability(:scesa, :alt_scancode_esc, "Alternate escape for scancode emulation"), - TermCapability(:ehhlm, :enter_horizontal_hl_mode, "Enter horizontal highlight mode"), - TermCapability(:elhlm, :enter_left_hl_mode, "Enter left highlight mode"), - TermCapability(:elohlm, :enter_low_hl_mode, "Enter low highlight mode"), - TermCapability(:erhlm, :enter_right_hl_mode, "Enter right highlight mode"), - TermCapability(:ethlm, :enter_top_hl_mode, "Enter top highlight mode"), - TermCapability(:evhlm, :enter_vertical_hl_mode, "Enter vertical highlight mode"), - TermCapability(:sgr1, :set_a_attributes, "Define second set of video attributes #1-#6"), - TermCapability(:slength, :set_pglen_inch, "Set page length to #1 hundredth of an inch (some implementations use sL for termcap)."), - TermCapability(:OTi2, :termcap_init2, "secondary initialization string"), - TermCapability(:OTrs, :termcap_reset, "terminal reset string"), - TermCapability(:OTnl, :linefeed_if_not_lf, "use to move down"), - TermCapability(:OTbs, :backspaces_with_bs, "uses ^H to move left"), - TermCapability(:OTko, :other_non_function_keys, "list of self-mapped keycaps"), - TermCapability(:OTma, :arrow_key_map, "map motion-keys for vi version 2"), - TermCapability(:OTG2, :acs_ulcorner, "single upper left"), - TermCapability(:OTG3, :acs_llcorner, "single lower left"), - TermCapability(:OTG1, :acs_urcorner, "single upper right"), - TermCapability(:OTG4, :acs_lrcorner, "single lower right"), - TermCapability(:OTGR, :acs_ltee, "tee pointing right"), - TermCapability(:OTGL, :acs_rtee, "tee pointing left"), - TermCapability(:OTGU, :acs_btee, "tee pointing up"), - TermCapability(:OTGD, :acs_ttee, "tee pointing down"), - TermCapability(:OTGH, :acs_hline, "single horizontal line"), - TermCapability(:OTGV, :acs_vline, "single vertical line"), - TermCapability(:OTGC, :acs_plus, "single intersection"), - TermCapability(:meml, :memory_lock, "lock memory above cursor"), - TermCapability(:memu, :memory_unlock, "unlock memory"), - TermCapability(:box1, :box_chars_1, "box characters primary set"), + TermCapability(:back_tab, :cbt, "back tab (P)"), + TermCapability(:bell, :bel, "audible signal (bell) (P)"), + TermCapability(:carriage_return, :cr, "carriage return (P*) (P*)"), + TermCapability(:change_scroll_region, :csr, "change region to line #1 to line #2 (P)"), + TermCapability(:clear_all_tabs, :tbc, "clear all tab stops (P)"), + TermCapability(:clear_screen, :clear, "clear screen and home cursor (P*)"), + TermCapability(:clr_eol, :el, "clear to end of line (P)"), + TermCapability(:clr_eos, :ed, "clear to end of screen (P*)"), + TermCapability(:column_address, :hpa, "horizontal position #1, absolute (P)"), + TermCapability(:command_character, :cmdch, "terminal settable cmd character in prototype !?"), + TermCapability(:cursor_address, :cup, "move to row #1 columns #2"), + TermCapability(:cursor_down, :cud1, "down one line"), + TermCapability(:cursor_home, :home, "home cursor (if no cup)"), + TermCapability(:cursor_invisible, :civis, "make cursor invisible"), + TermCapability(:cursor_left, :cub1, "move left one space"), + TermCapability(:cursor_mem_address, :mrcup, "memory relative cursor addressing, move to row #1 columns #2"), + TermCapability(:cursor_normal, :cnorm, "make cursor appear normal (undo civis/cvvis)"), + TermCapability(:cursor_right, :cuf1, "non-destructive space (move right one space)"), + TermCapability(:cursor_to_ll, :ll, "last line, first column (if no cup)"), + TermCapability(:cursor_up, :cuu1, "up one line"), + TermCapability(:cursor_visible, :cvvis, "make cursor very visible"), + TermCapability(:delete_character, :dch1, "delete character (P*)"), + TermCapability(:delete_line, :dl1, "delete line (P*)"), + TermCapability(:dis_status_line, :dsl, "disable status line"), + TermCapability(:down_half_line, :hd, "half a line down"), + TermCapability(:enter_alt_charset_mode, :smacs, "start alternate character set (P)"), + TermCapability(:enter_blink_mode, :blink, "turn on blinking"), + TermCapability(:enter_bold_mode, :bold, "turn on bold (extra bright) mode"), + TermCapability(:enter_ca_mode, :smcup, "string to start programs using cup"), + TermCapability(:enter_delete_mode, :smdc, "enter delete mode"), + TermCapability(:enter_dim_mode, :dim, "turn on half-bright mode"), + TermCapability(:enter_insert_mode, :smir, "enter insert mode"), + TermCapability(:enter_secure_mode, :invis, "turn on blank mode (characters invisible)"), + TermCapability(:enter_protected_mode, :prot, "turn on protected mode"), + TermCapability(:enter_reverse_mode, :rev, "turn on reverse video mode"), + TermCapability(:enter_standout_mode, :smso, "begin standout mode"), + TermCapability(:enter_underline_mode, :smul, "begin underline mode"), + TermCapability(:erase_chars, :ech, "erase #1 characters (P)"), + TermCapability(:exit_alt_charset_mode, :rmacs, "end alternate character set (P)"), + TermCapability(:exit_attribute_mode, :sgr0, "turn off all attributes"), + TermCapability(:exit_ca_mode, :rmcup, "strings to end programs using cup"), + TermCapability(:exit_delete_mode, :rmdc, "end delete mode"), + TermCapability(:exit_insert_mode, :rmir, "exit insert mode"), + TermCapability(:exit_standout_mode, :rmso, "exit standout mode"), + TermCapability(:exit_underline_mode, :rmul, "exit underline mode"), + TermCapability(:flash_screen, :flash, "visible bell (may not move cursor)"), + TermCapability(:form_feed, :ff, "hardcopy terminal page eject (P*)"), + TermCapability(:from_status_line, :fsl, "return from status line"), + TermCapability(:init_1string, :is1, "initialization string"), + TermCapability(:init_2string, :is2, "initialization string"), + TermCapability(:init_3string, :is3, "initialization string"), + TermCapability(:init_file, :if, "name of initialization file"), + TermCapability(:insert_character, :ich1, "insert character (P)"), + TermCapability(:insert_line, :il1, "insert line (P*)"), + TermCapability(:insert_padding, :ip, "insert padding after inserted character"), + TermCapability(:key_backspace, :kbs, "backspace key"), + TermCapability(:key_catab, :ktbc, "clear-all-tabs key"), + TermCapability(:key_clear, :kclr, "clear-screen or erase key"), + TermCapability(:key_ctab, :kctab, "clear-tab key"), + TermCapability(:key_dc, :kdch1, "delete-character key"), + TermCapability(:key_dl, :kdl1, "delete-line key"), + TermCapability(:key_down, :kcud1, "down-arrow key"), + TermCapability(:key_eic, :krmir, "sent by rmir or smir in insert mode"), + TermCapability(:key_eol, :kel, "clear-to-end-of-line key"), + TermCapability(:key_eos, :ked, "clear-to-end-of-screen key"), + TermCapability(:key_f0, :kf0, "F0 function key"), + TermCapability(:key_f1, :kf1, "F1 function key"), + TermCapability(:key_f10, :kf10, "F10 function key"), + TermCapability(:key_f2, :kf2, "F2 function key"), + TermCapability(:key_f3, :kf3, "F3 function key"), + TermCapability(:key_f4, :kf4, "F4 function key"), + TermCapability(:key_f5, :kf5, "F5 function key"), + TermCapability(:key_f6, :kf6, "F6 function key"), + TermCapability(:key_f7, :kf7, "F7 function key"), + TermCapability(:key_f8, :kf8, "F8 function key"), + TermCapability(:key_f9, :kf9, "F9 function key"), + TermCapability(:key_home, :khome, "home key"), + TermCapability(:key_ic, :kich1, "insert-character key"), + TermCapability(:key_il, :kil1, "insert-line key"), + TermCapability(:key_left, :kcub1, "left-arrow key"), + TermCapability(:key_ll, :kll, "lower-left key (home down)"), + TermCapability(:key_npage, :knp, "next-page key"), + TermCapability(:key_ppage, :kpp, "previous-page key"), + TermCapability(:key_right, :kcuf1, "right-arrow key"), + TermCapability(:key_sf, :kind, "scroll-forward key"), + TermCapability(:key_sr, :kri, "scroll-backward key"), + TermCapability(:key_stab, :khts, "set-tab key"), + TermCapability(:key_up, :kcuu1, "up-arrow key"), + TermCapability(:keypad_local, :rmkx, "leave 'keyboard_transmit' mode"), + TermCapability(:keypad_xmit, :smkx, "enter 'keyboard_transmit' mode"), + TermCapability(:lab_f0, :lf0, "label on function key f0 if not f0"), + TermCapability(:lab_f1, :lf1, "label on function key f1 if not f1"), + TermCapability(:lab_f10, :lf10, "label on function key f10 if not f10"), + TermCapability(:lab_f2, :lf2, "label on function key f2 if not f2"), + TermCapability(:lab_f3, :lf3, "label on function key f3 if not f3"), + TermCapability(:lab_f4, :lf4, "label on function key f4 if not f4"), + TermCapability(:lab_f5, :lf5, "label on function key f5 if not f5"), + TermCapability(:lab_f6, :lf6, "label on function key f6 if not f6"), + TermCapability(:lab_f7, :lf7, "label on function key f7 if not f7"), + TermCapability(:lab_f8, :lf8, "label on function key f8 if not f8"), + TermCapability(:lab_f9, :lf9, "label on function key f9 if not f9"), + TermCapability(:meta_off, :rmm, "turn off meta mode"), + TermCapability(:meta_on, :smm, "turn on meta mode (8th-bit on)"), + TermCapability(:newline, :nel, "newline (behave like cr followed by lf)"), + TermCapability(:pad_char, :pad, "padding char (instead of null)"), + TermCapability(:parm_dch, :dch, "delete #1 characters (P*)"), + TermCapability(:parm_delete_line, :dl, "delete #1 lines (P*)"), + TermCapability(:parm_down_cursor, :cud, "down #1 lines (P*)"), + TermCapability(:parm_ich, :ich, "insert #1 characters (P*)"), + TermCapability(:parm_index, :indn, "scroll forward #1 lines (P)"), + TermCapability(:parm_insert_line, :il, "insert #1 lines (P*)"), + TermCapability(:parm_left_cursor, :cub, "move #1 characters to the left (P)"), + TermCapability(:parm_right_cursor, :cuf, "move #1 characters to the right (P*)"), + TermCapability(:parm_rindex, :rin, "scroll back #1 lines (P)"), + TermCapability(:parm_up_cursor, :cuu, "up #1 lines (P*)"), + TermCapability(:pkey_key, :pfkey, "program function key #1 to type string #2"), + TermCapability(:pkey_local, :pfloc, "program function key #1 to execute string #2"), + TermCapability(:pkey_xmit, :pfx, "program function key #1 to transmit string #2"), + TermCapability(:print_screen, :mc0, "print contents of screen"), + TermCapability(:prtr_off, :mc4, "turn off printer"), + TermCapability(:prtr_on, :mc5, "turn on printer"), + TermCapability(:repeat_char, :rep, "repeat char #1 #2 times (P*)"), + TermCapability(:reset_1string, :rs1, "reset string"), + TermCapability(:reset_2string, :rs2, "reset string"), + TermCapability(:reset_3string, :rs3, "reset string"), + TermCapability(:reset_file, :rf, "name of reset file"), + TermCapability(:restore_cursor, :rc, "restore cursor to position of last save_cursor"), + TermCapability(:row_address, :vpa, "vertical position #1 absolute (P)"), + TermCapability(:save_cursor, :sc, "save current cursor position (P)"), + TermCapability(:scroll_forward, :ind, "scroll text up (P)"), + TermCapability(:scroll_reverse, :ri, "scroll text down (P)"), + TermCapability(:set_attributes, :sgr, "define video attributes #1-#9 (PG9)"), + TermCapability(:set_tab, :hts, "set a tab in every row, current columns"), + TermCapability(:set_window, :wind, "current window is lines #1-#2 cols #3-#4"), + TermCapability(:tab, :ht, "tab to next 8-space hardware tab stop"), + TermCapability(:to_status_line, :tsl, "move to status line, column #1"), + TermCapability(:underline_char, :uc, "underline char and move past it"), + TermCapability(:up_half_line, :hu, "half a line up"), + TermCapability(:init_prog, :iprog, "path name of program for initialization"), + TermCapability(:key_a1, :ka1, "upper left of keypad"), + TermCapability(:key_a3, :ka3, "upper right of keypad"), + TermCapability(:key_b2, :kb2, "center of keypad"), + TermCapability(:key_c1, :kc1, "lower left of keypad"), + TermCapability(:key_c3, :kc3, "lower right of keypad"), + TermCapability(:prtr_non, :mc5p, "turn on printer for #1 bytes"), + TermCapability(:char_padding, :rmp, "like ip but when in insert mode"), + TermCapability(:acs_chars, :acsc, "graphics charset pairs, based on vt100"), + TermCapability(:plab_norm, :pln, "program label #1 to show string #2"), + TermCapability(:key_btab, :kcbt, "back-tab key"), + TermCapability(:enter_xon_mode, :smxon, "turn on xon/xoff handshaking"), + TermCapability(:exit_xon_mode, :rmxon, "turn off xon/xoff handshaking"), + TermCapability(:enter_am_mode, :smam, "turn on automatic margins"), + TermCapability(:exit_am_mode, :rmam, "turn off automatic margins"), + TermCapability(:xon_character, :xonc, "XON character"), + TermCapability(:xoff_character, :xoffc, "XOFF character"), + TermCapability(:ena_acs, :enacs, "enable alternate char set"), + TermCapability(:label_on, :smln, "turn on soft labels"), + TermCapability(:label_off, :rmln, "turn off soft labels"), + TermCapability(:key_beg, :kbeg, "begin key"), + TermCapability(:key_cancel, :kcan, "cancel key"), + TermCapability(:key_close, :kclo, "close key"), + TermCapability(:key_command, :kcmd, "command key"), + TermCapability(:key_copy, :kcpy, "copy key"), + TermCapability(:key_create, :kcrt, "create key"), + TermCapability(:key_end, :kend, "end key"), + TermCapability(:key_enter, :kent, "enter/send key"), + TermCapability(:key_exit, :kext, "exit key"), + TermCapability(:key_find, :kfnd, "find key"), + TermCapability(:key_help, :khlp, "help key"), + TermCapability(:key_mark, :kmrk, "mark key"), + TermCapability(:key_message, :kmsg, "message key"), + TermCapability(:key_move, :kmov, "move key"), + TermCapability(:key_next, :knxt, "next key"), + TermCapability(:key_open, :kopn, "open key"), + TermCapability(:key_options, :kopt, "options key"), + TermCapability(:key_previous, :kprv, "previous key"), + TermCapability(:key_print, :kprt, "print key"), + TermCapability(:key_redo, :krdo, "redo key"), + TermCapability(:key_reference, :kref, "reference key"), + TermCapability(:key_refresh, :krfr, "refresh key"), + TermCapability(:key_replace, :krpl, "replace key"), + TermCapability(:key_restart, :krst, "restart key"), + TermCapability(:key_resume, :kres, "resume key"), + TermCapability(:key_save, :ksav, "save key"), + TermCapability(:key_suspend, :kspd, "suspend key"), + TermCapability(:key_undo, :kund, "undo key"), + TermCapability(:key_sbeg, :kBEG, "shifted begin key"), + TermCapability(:key_scancel, :kCAN, "shifted cancel key"), + TermCapability(:key_scommand, :kCMD, "shifted command key"), + TermCapability(:key_scopy, :kCPY, "shifted copy key"), + TermCapability(:key_screate, :kCRT, "shifted create key"), + TermCapability(:key_sdc, :kDC, "shifted delete-character key"), + TermCapability(:key_sdl, :kDL, "shifted delete-line key"), + TermCapability(:key_select, :kslt, "select key"), + TermCapability(:key_send, :kEND, "shifted end key"), + TermCapability(:key_seol, :kEOL, "shifted clear-to-end-of-line key"), + TermCapability(:key_sexit, :kEXT, "shifted exit key"), + TermCapability(:key_sfind, :kFND, "shifted find key"), + TermCapability(:key_shelp, :kHLP, "shifted help key"), + TermCapability(:key_shome, :kHOM, "shifted home key"), + TermCapability(:key_sic, :kIC, "shifted insert-character key"), + TermCapability(:key_sleft, :kLFT, "shifted left-arrow key"), + TermCapability(:key_smessage, :kMSG, "shifted message key"), + TermCapability(:key_smove, :kMOV, "shifted move key"), + TermCapability(:key_snext, :kNXT, "shifted next key"), + TermCapability(:key_soptions, :kOPT, "shifted options key"), + TermCapability(:key_sprevious, :kPRV, "shifted previous key"), + TermCapability(:key_sprint, :kPRT, "shifted print key"), + TermCapability(:key_sredo, :kRDO, "shifted redo key"), + TermCapability(:key_sreplace, :kRPL, "shifted replace key"), + TermCapability(:key_sright, :kRIT, "shifted right-arrow key"), + TermCapability(:key_srsume, :kRES, "shifted resume key"), + TermCapability(:key_ssave, :kSAV, "shifted save key"), + TermCapability(:key_ssuspend, :kSPD, "shifted suspend key"), + TermCapability(:key_sundo, :kUND, "shifted undo key"), + TermCapability(:req_for_input, :rfi, "send next input char (for ptys)"), + TermCapability(:key_f11, :kf11, "F11 function key"), + TermCapability(:key_f12, :kf12, "F12 function key"), + TermCapability(:key_f13, :kf13, "F13 function key"), + TermCapability(:key_f14, :kf14, "F14 function key"), + TermCapability(:key_f15, :kf15, "F15 function key"), + TermCapability(:key_f16, :kf16, "F16 function key"), + TermCapability(:key_f17, :kf17, "F17 function key"), + TermCapability(:key_f18, :kf18, "F18 function key"), + TermCapability(:key_f19, :kf19, "F19 function key"), + TermCapability(:key_f20, :kf20, "F20 function key"), + TermCapability(:key_f21, :kf21, "F21 function key"), + TermCapability(:key_f22, :kf22, "F22 function key"), + TermCapability(:key_f23, :kf23, "F23 function key"), + TermCapability(:key_f24, :kf24, "F24 function key"), + TermCapability(:key_f25, :kf25, "F25 function key"), + TermCapability(:key_f26, :kf26, "F26 function key"), + TermCapability(:key_f27, :kf27, "F27 function key"), + TermCapability(:key_f28, :kf28, "F28 function key"), + TermCapability(:key_f29, :kf29, "F29 function key"), + TermCapability(:key_f30, :kf30, "F30 function key"), + TermCapability(:key_f31, :kf31, "F31 function key"), + TermCapability(:key_f32, :kf32, "F32 function key"), + TermCapability(:key_f33, :kf33, "F33 function key"), + TermCapability(:key_f34, :kf34, "F34 function key"), + TermCapability(:key_f35, :kf35, "F35 function key"), + TermCapability(:key_f36, :kf36, "F36 function key"), + TermCapability(:key_f37, :kf37, "F37 function key"), + TermCapability(:key_f38, :kf38, "F38 function key"), + TermCapability(:key_f39, :kf39, "F39 function key"), + TermCapability(:key_f40, :kf40, "F40 function key"), + TermCapability(:key_f41, :kf41, "F41 function key"), + TermCapability(:key_f42, :kf42, "F42 function key"), + TermCapability(:key_f43, :kf43, "F43 function key"), + TermCapability(:key_f44, :kf44, "F44 function key"), + TermCapability(:key_f45, :kf45, "F45 function key"), + TermCapability(:key_f46, :kf46, "F46 function key"), + TermCapability(:key_f47, :kf47, "F47 function key"), + TermCapability(:key_f48, :kf48, "F48 function key"), + TermCapability(:key_f49, :kf49, "F49 function key"), + TermCapability(:key_f50, :kf50, "F50 function key"), + TermCapability(:key_f51, :kf51, "F51 function key"), + TermCapability(:key_f52, :kf52, "F52 function key"), + TermCapability(:key_f53, :kf53, "F53 function key"), + TermCapability(:key_f54, :kf54, "F54 function key"), + TermCapability(:key_f55, :kf55, "F55 function key"), + TermCapability(:key_f56, :kf56, "F56 function key"), + TermCapability(:key_f57, :kf57, "F57 function key"), + TermCapability(:key_f58, :kf58, "F58 function key"), + TermCapability(:key_f59, :kf59, "F59 function key"), + TermCapability(:key_f60, :kf60, "F60 function key"), + TermCapability(:key_f61, :kf61, "F61 function key"), + TermCapability(:key_f62, :kf62, "F62 function key"), + TermCapability(:key_f63, :kf63, "F63 function key"), + TermCapability(:clr_bol, :el1, "Clear to beginning of line"), + TermCapability(:clear_margins, :mgc, "clear right and left soft margins"), + TermCapability(:set_left_margin, :smgl, "set left soft margin at current column."), + TermCapability(:set_right_margin, :smgr, "set right soft margin at current column"), + TermCapability(:label_format, :fln, "label format"), + TermCapability(:set_clock, :sclk, "set clock, #1 hrs #2 mins #3 secs"), + TermCapability(:display_clock, :dclk, "display clock"), + TermCapability(:remove_clock, :rmclk, "remove clock"), + TermCapability(:create_window, :cwin, "define a window #1 from #2,#3 to #4,#5"), + TermCapability(:goto_window, :wingo, "go to window #1"), + TermCapability(:hangup, :hup, "hang-up phone"), + TermCapability(:dial_phone, :dial, "dial number #1"), + TermCapability(:quick_dial, :qdial, "dial number #1 without checking"), + TermCapability(:tone, :tone, "select touch tone dialing"), + TermCapability(:pulse, :pulse, "select pulse dialing"), + TermCapability(:flash_hook, :hook, "flash switch hook"), + TermCapability(:fixed_pause, :pause, "pause for 2-3 seconds"), + TermCapability(:wait_tone, :wait, "wait for dial-tone"), + TermCapability(:user0, :u0, "User string #0"), + TermCapability(:user1, :u1, "User string #1"), + TermCapability(:user2, :u2, "User string #2"), + TermCapability(:user3, :u3, "User string #3"), + TermCapability(:user4, :u4, "User string #4"), + TermCapability(:user5, :u5, "User string #5"), + TermCapability(:user6, :u6, "User string #6"), + TermCapability(:user7, :u7, "User string #7"), + TermCapability(:user8, :u8, "User string #8"), + TermCapability(:user9, :u9, "User string #9"), + TermCapability(:orig_pair, :op, "Set default pair to its original value"), + TermCapability(:orig_colors, :oc, "Set all color pairs to the original ones"), + TermCapability(:initialize_color, :initc, "initialize color #1 to (#2,#3,#4)"), + TermCapability(:initialize_pair, :initp, "Initialize color pair #1 to fg=(#2,#3,#4), bg=(#5,#6,#7)"), + TermCapability(:set_color_pair, :scp, "Set current color pair to #1"), + TermCapability(:set_foreground, :setf, "Set foreground color #1"), + TermCapability(:set_background, :setb, "Set background color #1"), + TermCapability(:change_char_pitch, :cpi, "Change number of characters per inch to #1"), + TermCapability(:change_line_pitch, :lpi, "Change number of lines per inch to #1"), + TermCapability(:change_res_horz, :chr, "Change horizontal resolution to #1"), + TermCapability(:change_res_vert, :cvr, "Change vertical resolution to #1"), + TermCapability(:define_char, :defc, "Define a character #1, #2 dots wide, descender #3"), + TermCapability(:enter_doublewide_mode, :swidm, "Enter double-wide mode"), + TermCapability(:enter_draft_quality, :sdrfq, "Enter draft-quality mode"), + TermCapability(:enter_italics_mode, :sitm, "Enter italic mode"), + TermCapability(:enter_leftward_mode, :slm, "Start leftward carriage motion"), + TermCapability(:enter_micro_mode, :smicm, "Start micro-motion mode"), + TermCapability(:enter_near_letter_quality, :snlq, "Enter NLQ mode"), + TermCapability(:enter_normal_quality, :snrmq, "Enter normal-quality mode"), + TermCapability(:enter_shadow_mode, :sshm, "Enter shadow-print mode"), + TermCapability(:enter_subscript_mode, :ssubm, "Enter subscript mode"), + TermCapability(:enter_superscript_mode, :ssupm, "Enter superscript mode"), + TermCapability(:enter_upward_mode, :sum, "Start upward carriage motion"), + TermCapability(:exit_doublewide_mode, :rwidm, "End double-wide mode"), + TermCapability(:exit_italics_mode, :ritm, "End italic mode"), + TermCapability(:exit_leftward_mode, :rlm, "End left-motion mode"), + TermCapability(:exit_micro_mode, :rmicm, "End micro-motion mode"), + TermCapability(:exit_shadow_mode, :rshm, "End shadow-print mode"), + TermCapability(:exit_subscript_mode, :rsubm, "End subscript mode"), + TermCapability(:exit_superscript_mode, :rsupm, "End superscript mode"), + TermCapability(:exit_upward_mode, :rum, "End reverse character motion"), + TermCapability(:micro_column_address, :mhpa, "Like column_address in micro mode"), + TermCapability(:micro_down, :mcud1, "Like cursor_down in micro mode"), + TermCapability(:micro_left, :mcub1, "Like cursor_left in micro mode"), + TermCapability(:micro_right, :mcuf1, "Like cursor_right in micro mode"), + TermCapability(:micro_row_address, :mvpa, "Like row_address #1 in micro mode"), + TermCapability(:micro_up, :mcuu1, "Like cursor_up in micro mode"), + TermCapability(:order_of_pins, :porder, "Match software bits to print-head pins"), + TermCapability(:parm_down_micro, :mcud, "Like parm_down_cursor in micro mode"), + TermCapability(:parm_left_micro, :mcub, "Like parm_left_cursor in micro mode"), + TermCapability(:parm_right_micro, :mcuf, "Like parm_right_cursor in micro mode"), + TermCapability(:parm_up_micro, :mcuu, "Like parm_up_cursor in micro mode"), + TermCapability(:select_char_set, :scs, "Select character set, #1"), + TermCapability(:set_bottom_margin, :smgb, "Set bottom margin at current line"), + TermCapability(:set_bottom_margin_parm, :smgbp, "Set bottom margin at line #1 or (if smgtp is not given) #2 lines from bottom"), + TermCapability(:set_left_margin_parm, :smglp, "Set left (right) margin at column #1"), + TermCapability(:set_right_margin_parm, :smgrp, "Set right margin at column #1"), + TermCapability(:set_top_margin, :smgt, "Set top margin at current line"), + TermCapability(:set_top_margin_parm, :smgtp, "Set top (bottom) margin at row #1"), + TermCapability(:start_bit_image, :sbim, "Start printing bit image graphics"), + TermCapability(:start_char_set_def, :scsd, "Start character set definition #1, with #2 characters in the set"), + TermCapability(:stop_bit_image, :rbim, "Stop printing bit image graphics"), + TermCapability(:stop_char_set_def, :rcsd, "End definition of character set #1"), + TermCapability(:subscript_characters, :subcs, "List of subscriptable characters"), + TermCapability(:superscript_characters, :supcs, "List of superscriptable characters"), + TermCapability(:these_cause_cr, :docr, "Printing any of these characters causes CR"), + TermCapability(:zero_motion, :zerom, "No motion for subsequent character"), + TermCapability(:char_set_names, :csnm, "Produce #1'th item from list of character set names"), + TermCapability(:key_mouse, :kmous, "Mouse event has occurred"), + TermCapability(:mouse_info, :minfo, "Mouse status information"), + TermCapability(:req_mouse_pos, :reqmp, "Request mouse position"), + TermCapability(:get_mouse, :getm, "Curses should get button events, parameter #1 not documented."), + TermCapability(:set_a_foreground, :setaf, "Set foreground color to #1, using ANSI escape"), + TermCapability(:set_a_background, :setab, "Set background color to #1, using ANSI escape"), + TermCapability(:pkey_plab, :pfxl, "Program function key #1 to type string #2 and show string #3"), + TermCapability(:device_type, :devt, "Indicate language/codeset support"), + TermCapability(:code_set_init, :csin, "Init sequence for multiple codesets"), + TermCapability(:set0_des_seq, :s0ds, "Shift to codeset 0 (EUC set 0, ASCII)"), + TermCapability(:set1_des_seq, :s1ds, "Shift to codeset 1"), + TermCapability(:set2_des_seq, :s2ds, "Shift to codeset 2"), + TermCapability(:set3_des_seq, :s3ds, "Shift to codeset 3"), + TermCapability(:set_lr_margin, :smglr, "Set both left and right margins to #1, #2. (ML is not in BSD termcap)."), + TermCapability(:set_tb_margin, :smgtb, "Sets both top and bottom margins to #1, #2"), + TermCapability(:bit_image_repeat, :birep, "Repeat bit image cell #1 #2 times"), + TermCapability(:bit_image_newline, :binel, "Move to next row of the bit image"), + TermCapability(:bit_image_carriage_return, :bicr, "Move to beginning of same row"), + TermCapability(:color_names, :colornm, "Give name for color #1"), + TermCapability(:define_bit_image_region, :defbi, "Define rectangular bit image region"), + TermCapability(:end_bit_image_region, :endbi, "End a bit-image region"), + TermCapability(:set_color_band, :setcolor, "Change to ribbon color #1"), + TermCapability(:set_page_length, :slines, "Set page length to #1 lines"), + TermCapability(:display_pc_char, :dispc, "Display PC character #1"), + TermCapability(:enter_pc_charset_mode, :smpch, "Enter PC character display mode"), + TermCapability(:exit_pc_charset_mode, :rmpch, "Exit PC character display mode"), + TermCapability(:enter_scancode_mode, :smsc, "Enter PC scancode mode"), + TermCapability(:exit_scancode_mode, :rmsc, "Exit PC scancode mode"), + TermCapability(:pc_term_options, :pctrm, "PC terminal options"), + TermCapability(:scancode_escape, :scesc, "Escape for scancode emulation"), + TermCapability(:alt_scancode_esc, :scesa, "Alternate escape for scancode emulation"), + TermCapability(:enter_horizontal_hl_mode, :ehhlm, "Enter horizontal highlight mode"), + TermCapability(:enter_left_hl_mode, :elhlm, "Enter left highlight mode"), + TermCapability(:enter_low_hl_mode, :elohlm, "Enter low highlight mode"), + TermCapability(:enter_right_hl_mode, :erhlm, "Enter right highlight mode"), + TermCapability(:enter_top_hl_mode, :ethlm, "Enter top highlight mode"), + TermCapability(:enter_vertical_hl_mode, :evhlm, "Enter vertical highlight mode"), + TermCapability(:set_a_attributes, :sgr1, "Define second set of video attributes #1-#6"), + TermCapability(:set_pglen_inch, :slength, "Set page length to #1 hundredth of an inch (some implementations use sL for termcap)."), + TermCapability(:termcap_init2, :OTi2, "secondary initialization string"), + TermCapability(:termcap_reset, :OTrs, "terminal reset string"), + TermCapability(:linefeed_if_not_lf, :OTnl, "use to move down"), + TermCapability(:backspace_if_not_bs, :OTbc, "move left, if not ^H"), + TermCapability(:other_non_function_keys, :OTko, "list of self-mapped keycaps"), + TermCapability(:arrow_key_map, :OTma, "map motion-keys for vi version 2"), + TermCapability(:acs_ulcorner, :OTG2, "single upper left"), + TermCapability(:acs_llcorner, :OTG3, "single lower left"), + TermCapability(:acs_urcorner, :OTG1, "single upper right"), + TermCapability(:acs_lrcorner, :OTG4, "single lower right"), + TermCapability(:acs_ltee, :OTGR, "tee pointing right"), + TermCapability(:acs_rtee, :OTGL, "tee pointing left"), + TermCapability(:acs_btee, :OTGU, "tee pointing up"), + TermCapability(:acs_ttee, :OTGD, "tee pointing down"), + TermCapability(:acs_hline, :OTGH, "single horizontal line"), + TermCapability(:acs_vline, :OTGV, "single vertical line"), + TermCapability(:acs_plus, :OTGC, "single intersection"), + TermCapability(:memory_lock, :meml, "lock memory above cursor"), + TermCapability(:memory_unlock, :memu, "unlock memory"), + TermCapability(:box_chars_1, :box1, "box characters primary set"), ] + +""" +Terminfo extensions that NCurses 6.4-20230311 is aware of. +""" +const TERM_USER = Dict{Tuple{DataType, Symbol}, Union{Tuple{Nothing, String}, Tuple{Symbol, String}}}( + (Int, :CO ) => (nothing, "number of indexed colors overlaying RGB space"), + (String, :E3) => (nothing, "clears the terminal's scrollback buffer."), + (Bool, :NQ) => (nothing, "terminal does not support query/response"), + (Bool, :RGB) => (nothing, "use direct colors with 1/3 of color-pair bits per color."), + (Int, :RGB) => (nothing, "use direct colors with given number of bits per color."), + (String, :RGB) => (nothing, "use direct colors with given bit-layout."), + (String, :TS) => (nothing, "like \"tsl\", but uses no parameter."), + (Int, :U8) => (nothing, "terminal does/does not support VT100 SI/SO when processing UTF-8 encoding."), + (String, :XM) => (nothing, "initialize alternate xterm mouse mode"), + (String, :grbom) => (nothing, "disable real bold (not intensity bright) mode."), + (String, :gsbom) => (nothing, "enable real bold (not intensity bright) mode."), + (String, :xm) => (nothing, "mouse response"), + (String, :Rmol) => (:exit_overline_mode, "remove overline-mode"), + (String, :Smol) => (:enter_overline_mode, "set overline-mode"), + (String, :blink2) => (nothing, "turn on rapid blinking"), + (String, :norm) => (nothing, "turn off bold and half-bright mode"), + (String, :opaq) => (nothing, "turn off blank mode"), + (String, :setal) => (nothing, "set underline-color"), + (String, :smul2) => (nothing, "begin double underline mode"), + (Bool, :AN) => (nothing, "turn on autonuke."), + (Bool, :AX) => (nothing, "understands ANSI set default fg/bg color (\\E[39m / \\E[49m)."), + (String, :C0) => (nothing, "use the string as a conversion table for font '0', like acsc."), + (Bool, :C8) => (nothing, "terminal shows bold as high-intensity colors."), + (String, :CE) => (nothing, "switch cursor-keys back to normal mode."), + (String, :CS) => (nothing, "switch cursor-keys to application mode."), + (String, :E0) => (nothing, "switch charset 'G0' back to standard charset. Default is '\\E(B'."), + (Bool, :G0) => (nothing, "terminal can deal with ISO 2022 font selection sequences."), + (String, :KJ) => (nothing, "set the encoding of the terminal."), + (Int, :OL) => (nothing, "set the screen program's output buffer limit."), + (String, :S0) => (nothing, "switch charset 'G0' to the specified charset. Default is '\\E(%.'."), + (Bool, :TF) => (nothing, "add missing capabilities to screen's termcap/info entry. (Set by default)."), + (String, :WS) => (nothing, "resize display. This capability has the desired width and height as arguments. SunView(tm) example: '\\E[8;%d;%dt'."), + (String, :XC) => (nothing, "describe a translation of characters to strings depending on the current font."), + (Bool, :XT) => (nothing, "terminal understands special xterm sequences (OSC, mouse tracking)."), + (String, :Z0) => (nothing, "change width to 132 columns."), + (String, :Z1) => (nothing, "change width to 80 columns."), + (String, :Cr) => (:reset_cursor_color, "restore the default cursor color."), + (String, :Cs) => (:set_cursor_color, "set the cursor color."), + (String, :Csr) => (nothing, "change the cursor style, overriding Ss."), + (String, :Ms) => (:set_host_clipboard, "store the current buffer in the host terminal's selection (clipboard)."), + (String, :Se) => (:reset_cursor_style, "reset the cursor style to the terminal initial state."), + (String, :Smulx) => (:set_underline_style, "modify the appearance of underlines in VTE."), + (String, :Ss) => (:set_cursor_style, "change the cursor style."), + (String, :rmxx) => (:exit_strikeout_mode, "reset ECMA-48 strikeout/crossed-out attributes."), + (String, :smxx) => (:enter_strikeout_mode, "set ECMA-48 strikeout/crossed-out attributes."), + (String, :BD) => (nothing, "disables bracketed paste"), + (String, :BE) => (nothing, "enables bracketed paste"), + (String, :PE) => (nothing, "is sent after pasted text"), + (String, :PS) => (nothing, "is sent before pasted text"), + (String, :RV) => (nothing, "report terminal secondary device attributes"), + (String, :XR) => (nothing, "report terminal version as a free-format string."), + (Bool, :XF) => (:xterm_focus, "terminal supports xterm focus in/out"), + (String, :rv) => (nothing, "response to RV, regular expression"), + (String, :xr) => (nothing, "response to XR, regular expression"), + (String, :csl) => (:clear_status_line, "clear status line"), + (String, :kDC3) => (:key_alt_delete_character, "alt delete-character"), + (String, :kDC4) => (:key_shift_alt_delete_character, "shift+alt delete-character"), + (String, :kDC5) => (:key_control_delete_character, "control delete-character"), + (String, :kDC6) => (:key_shift_control_delete_character, "shift+control delete-character"), + (String, :kDC7) => (:key_alt_control_delete_character, "alt+control delete-character"), + (String, :kDN) => (:key_shift_down_cursor, "shift down-cursor"), + (String, :kDN3) => (:key_alt_down_cursor, "alt down-cursor"), + (String, :kDN4) => (:key_shift_alt_down_cursor, "shift+alt down-cursor"), + (String, :kDN5) => (:key_control_down_cursor, "control down-cursor"), + (String, :kDN6) => (:key_shift_control_down_cursor, "shift+control down-cursor"), + (String, :kDN7) => (:key_alt_control_down_cursor, "alt+control down-cursor"), + (String, :kEND3) => (:key_alt_end, "alt end"), + (String, :kEND4) => (:key_shift_alt_end, "shift+alt end"), + (String, :kEND5) => (:key_control_end, "control end"), + (String, :kEND6) => (:key_shift_control_end, "shift+control end"), + (String, :kEND7) => (:key_alt_control_end, "alt+control end"), + (String, :kHOM3) => (:key_alt_home, "alt home"), + (String, :kHOM4) => (:key_shift_alt_home, "shift+alt home"), + (String, :kHOM5) => (:key_control_home, "control home"), + (String, :kHOM6) => (:key_shift_control_home, "shift+control home"), + (String, :kHOM7) => (:key_alt_control_home, "alt+control home"), + (String, :kIC3) => (:key_alt_insert_character, "alt insert-character"), + (String, :kIC4) => (:key_shift_alt_insert_character, "shift+alt insert-character"), + (String, :kIC5) => (:key_control_insert_character, "control insert-character"), + (String, :kIC6) => (:key_shift_control_insert_character, "shift+control insert-character"), + (String, :kIC7) => (:key_alt_control_insert_character, "alt+control insert-character"), + (String, :kLFT3) => (:key_alt_left_cursor, "alt left-cursor"), + (String, :kLFT4) => (:key_shift_alt_left_cursor, "shift+alt left-cursor"), + (String, :kLFT5) => (:key_control_left_cursor, "control left-cursor"), + (String, :kLFT6) => (:key_shift_control_left_cursor, "shift+control left-cursor"), + (String, :kLFT7) => (:key_alt_control_left_cursor, "alt+control left-cursor"), + (String, :kNXT3) => (:key_alt_next, "alt next"), + (String, :kNXT4) => (:key_shift_alt_next, "shift+alt next"), + (String, :kNXT5) => (:key_control_next, "control next"), + (String, :kNXT6) => (:key_shift_control_next, "shift+control next"), + (String, :kNXT7) => (:key_alt_control_next, "alt+control next"), + (String, :kPRV3) => (:key_alt_previous, "alt previous"), + (String, :kPRV4) => (:key_shift_alt_previous, "shift+alt previous"), + (String, :kPRV5) => (:key_control_previous, "control previous"), + (String, :kPRV6) => (:key_shift_control_previous, "shift+control previous"), + (String, :kPRV7) => (:key_alt_control_previous, "alt+control previous"), + (String, :kRIT3) => (:key_alt_right_cursor, "alt right-cursor"), + (String, :kRIT4) => (:key_shift_alt_right_cursor, "shift+alt right-cursor"), + (String, :kRIT5) => (:key_control_right_cursor, "control right-cursor"), + (String, :kRIT6) => (:key_shift_control_right_cursor, "shift+control right-cursor"), + (String, :kRIT7) => (:key_alt_control_right_cursor, "alt+control right-cursor"), + (String, :kUP) => (:key_shift_up_cursor, "shift up-cursor"), + (String, :kUP3) => (:key_alt_up_cursor, "alt up-cursor"), + (String, :kUP4) => (:key_shift_alt_up_cursor, "shift+alt up-cursor"), + (String, :kUP5) => (:key_control_up_cursor, "control up-cursor"), + (String, :kUP6) => (:key_shift_control_up_cursor, "shift+control up-cursor"), + (String, :kUP7) => (:key_alt_control_up_cursor, "alt+control up-cursor"), + (String, :ka2) => (nothing, "vt220-keypad extensions"), + (String, :kb1) => (nothing, "vt220-keypad extensions"), + (String, :kb3) => (nothing, "vt220-keypad extensions"), + (String, :kc2) => (nothing, "vt220-keypad extensions"), + (String, :kxIN) => (:key_mouse_response_on_focus_in, "mouse response on focus-in"), + (String, :kxOUT) => (:key_mouse_response_on_focus_out, "mouse response on focus-out"), + (Bool, :Tc) => (:truecolor, "tmux extension to indicate 24-bit truecolor support"), + (Bool, :Su) => (:can_style_underline, "kitty extension to indicate styled underline support"), +) diff --git a/base/threadingconstructs.jl b/base/threadingconstructs.jl index a5a1294be049b..acab7cf7b8299 100644 --- a/base/threadingconstructs.jl +++ b/base/threadingconstructs.jl @@ -44,8 +44,9 @@ maxthreadid() = Int(Core.Intrinsics.atomic_pointerref(cglobal(:jl_n_threads, Cin """ Threads.nthreads(:default | :interactive) -> Int -Get the current number of threads within the specified thread pool. The threads in default -have id numbers `1:nthreads(:default)`. +Get the current number of threads within the specified thread pool. The threads in `:interactive` +have id numbers `1:nthreads(:interactive)`, and the threads in `:default` have id numbers in +`nthreads(:interactive) .+ (1:nthreads(:default))`. See also `BLAS.get_num_threads` and `BLAS.set_num_threads` in the [`LinearAlgebra`](@ref man-linalg) standard library, and `nprocs()` in the [`Distributed`](@ref man-distributed) @@ -78,7 +79,7 @@ function _sym_to_tpid(tp::Symbol) elseif tp == :foreign return Int8(-1) else - throw(ArgumentError("Unrecognized threadpool name `$(repr(tp))`")) + throw(ArgumentError("Unrecognized threadpool name `$tp`")) end end @@ -157,7 +158,8 @@ function threading_run(fun, static) else # TODO: this should be the current pool (except interactive) if there # are ever more than two pools. - @assert ccall(:jl_set_task_threadpoolid, Cint, (Any, Int8), t, _sym_to_tpid(:default)) == 1 + _result = ccall(:jl_set_task_threadpoolid, Cint, (Any, Int8), t, _sym_to_tpid(:default)) + @assert _result == 1 end tasks[i] = t schedule(t) @@ -175,9 +177,46 @@ end function _threadsfor(iter, lbody, schedule) lidx = iter.args[1] # index range = iter.args[2] + esc_range = esc(range) + func = if schedule === :greedy + greedy_func(esc_range, lidx, lbody) + else + default_func(esc_range, lidx, lbody) + end quote local threadsfor_fun - let range = $(esc(range)) + $func + if $(schedule === :greedy || schedule === :dynamic || schedule === :default) + threading_run(threadsfor_fun, false) + elseif ccall(:jl_in_threaded_region, Cint, ()) != 0 # :static + error("`@threads :static` cannot be used concurrently or nested") + else # :static + threading_run(threadsfor_fun, true) + end + nothing + end +end + +function greedy_func(itr, lidx, lbody) + quote + let c = Channel{eltype($itr)}(0,spawn=true) do ch + for item in $itr + put!(ch, item) + end + end + function threadsfor_fun(tid) + for item in c + local $(esc(lidx)) = item + $(esc(lbody)) + end + end + end + end +end + +function default_func(itr, lidx, lbody) + quote + let range = $itr function threadsfor_fun(tid = 1; onethread = false) r = range # Load into local variable lenr = length(r) @@ -215,14 +254,6 @@ function _threadsfor(iter, lbody, schedule) end end end - if $(schedule === :dynamic || schedule === :default) - threading_run(threadsfor_fun, false) - elseif ccall(:jl_in_threaded_region, Cint, ()) != 0 # :static - error("`@threads :static` cannot be used concurrently or nested") - else # :static - threading_run(threadsfor_fun, true) - end - nothing end end @@ -288,6 +319,20 @@ microseconds). !!! compat "Julia 1.8" The `:dynamic` option for the `schedule` argument is available and the default as of Julia 1.8. +### `:greedy` + +`:greedy` scheduler spawns up to [`Threads.threadpoolsize()`](@ref) tasks, each greedily working on +the given iterated values as they are produced. As soon as one task finishes its work, it takes +the next value from the iterator. Work done by any individual task is not necessarily on +contiguous values from the iterator. The given iterator may produce values forever, only the +iterator interface is required (no indexing). + +This scheduling option is generally a good choice if the workload of individual iterations +is not uniform/has a large spread. + +!!! compat "Julia 1.11" + The `:greedy` option for the `schedule` argument is available as of Julia 1.11. + ### `:static` `:static` scheduler creates one task per thread and divides the iterations equally among @@ -301,7 +346,7 @@ thread other than 1. In newly written library functions, `:static` scheduling is discouraged because the functions using this option cannot be called from arbitrary worker threads. -## Example +## Examples To illustrate of the different scheduling strategies, consider the following function `busywait` containing a non-yielding timed loop that runs for a given number of seconds. @@ -343,7 +388,7 @@ macro threads(args...) # for now only allow quoted symbols sched = nothing end - if sched !== :static && sched !== :dynamic + if sched !== :static && sched !== :dynamic && sched !== :greedy throw(ArgumentError("unsupported schedule argument in @threads")) end elseif na == 1 @@ -366,7 +411,8 @@ function _spawn_set_thrpool(t::Task, tp::Symbol) if tpid == -1 || _nthreads_in_pool(tpid) == 0 tpid = _sym_to_tpid(:default) end - @assert ccall(:jl_set_task_threadpoolid, Cint, (Any, Int8), t, tpid) == 1 + _result = ccall(:jl_set_task_threadpoolid, Cint, (Any, Int8), t, tpid) + @assert _result == 1 nothing end diff --git a/base/timing.jl b/base/timing.jl index bc4bd73d927b2..ecb67a2375d92 100644 --- a/base/timing.jl +++ b/base/timing.jl @@ -98,6 +98,13 @@ function gc_live_bytes() Int(ccall(:jl_gc_live_bytes, Int64, ())) + num.allocd + num.deferred_alloc end +# must be kept in sync with the value from `src/julia_threads.h`` +const JL_GC_N_MAX_POOLS = 51 +function gc_page_utilization_data() + page_utilization_raw = cglobal(:jl_gc_page_utilization_stats, Float64) + return Base.unsafe_wrap(Array, page_utilization_raw, JL_GC_N_MAX_POOLS, own=false) +end + """ Base.jit_total_bytes() @@ -128,18 +135,43 @@ function padded_nonzero_print(value, str, always_print = true) end end +""" + format_bytes(bytes; binary=true) + +Format a given number of bytes into a human-readable string. + +# Arguments +- `bytes`: The number of bytes to format. +- `binary=true`: If `true`, formats the bytes in binary units (powers of 1024). If `false`, uses decimal units (powers of 1000). + +# Returns +`String`: A human-readable string representation of the bytes, formatted in either binary or decimal units based on the `binary` argument. + +# Examples +```jldoctest +julia> Base.format_bytes(1024) +"1024 bytes" + +julia> Base.format_bytes(10000) +"9.766 KiB" + +julia> Base.format_bytes(10000, binary=false) +"10.000 kB" +``` +""" function format_bytes(bytes; binary=true) # also used by InteractiveUtils units = binary ? _mem_units : _cnt_units factor = binary ? 1024 : 1000 bytes, mb = prettyprint_getunits(bytes, length(units), Int64(factor)) if mb == 1 - return string(Int(bytes), " ", units[mb], bytes==1 ? "" : "s") + return string(Int(bytes), " ", _mem_units[mb], bytes==1 ? "" : "s") else return string(Ryu.writefixed(Float64(bytes), 3), binary ? " $(units[mb])" : "$(units[mb])B") end end -function time_print(io::IO, elapsedtime, bytes=0, gctime=0, allocs=0, compile_time=0, recompile_time=0, newline=false; msg::Union{String,Nothing}=nothing) +function time_print(io::IO, elapsedtime, bytes=0, gctime=0, allocs=0, lock_conflicts=0, compile_time=0, recompile_time=0, newline=false; + msg::Union{String,Nothing}=nothing) timestr = Ryu.writefixed(Float64(elapsedtime/1e9), 6) str = sprint() do io if msg isa String @@ -165,6 +197,10 @@ function time_print(io::IO, elapsedtime, bytes=0, gctime=0, allocs=0, compile_ti end print(io, Ryu.writefixed(Float64(100*gctime/elapsedtime), 2), "% gc time") end + if lock_conflicts > 0 + plural = lock_conflicts == 1 ? "" : "s" + print(io, ", ", lock_conflicts, " lock conflict$plural") + end if compile_time > 0 if bytes != 0 || allocs != 0 || gctime > 0 print(io, ", ") @@ -183,11 +219,11 @@ function time_print(io::IO, elapsedtime, bytes=0, gctime=0, allocs=0, compile_ti nothing end -function timev_print(elapsedtime, diff::GC_Diff, compile_times; msg::Union{String,Nothing}=nothing) +function timev_print(elapsedtime, diff::GC_Diff, lock_conflicts, compile_times; msg::Union{String,Nothing}=nothing) allocs = gc_alloc_count(diff) compile_time = first(compile_times) recompile_time = last(compile_times) - time_print(stdout, elapsedtime, diff.allocd, diff.total_time, allocs, compile_time, recompile_time, true; msg) + time_print(stdout, elapsedtime, diff.allocd, diff.total_time, allocs, lock_conflicts, compile_time, recompile_time, true; msg) padded_nonzero_print(elapsedtime, "elapsed time (ns)") padded_nonzero_print(diff.total_time, "gc time (ns)") padded_nonzero_print(diff.allocd, "bytes allocated") @@ -219,7 +255,8 @@ end A macro to execute an expression, printing the time it took to execute, the number of allocations, and the total number of bytes its execution caused to be allocated, before returning the value of the expression. Any time spent garbage collecting (gc), compiling -new code, or recompiling invalidated code is shown as a percentage. +new code, or recompiling invalidated code is shown as a percentage. Any lock conflicts +where a [`ReentrantLock`](@ref) had to wait are shown as a count. Optionally provide a description string to print before the time report. @@ -240,6 +277,9 @@ See also [`@showtime`](@ref), [`@timev`](@ref), [`@timed`](@ref), [`@elapsed`](@ Recompilation time being shown separately from compilation time was introduced in Julia 1.8 +!!! compat "Julia 1.11" + The reporting of any lock conflicts was added in Julia 1.11. + ```julia-repl julia> x = rand(10,10); @@ -274,20 +314,10 @@ macro time(ex) end macro time(msg, ex) quote - Experimental.@force_compile - local stats = gc_num() - local elapsedtime = time_ns() - cumulative_compile_timing(true) - local compile_elapsedtimes = cumulative_compile_time_ns() - local val = @__tryfinally($(esc(ex)), - (elapsedtime = time_ns() - elapsedtime; - cumulative_compile_timing(false); - compile_elapsedtimes = cumulative_compile_time_ns() .- compile_elapsedtimes) - ) - local diff = GC_Diff(gc_num(), stats) + local ret = @timed $(esc(ex)) local _msg = $(esc(msg)) - time_print(stdout, elapsedtime, diff.allocd, diff.total_time, gc_alloc_count(diff), first(compile_elapsedtimes), last(compile_elapsedtimes), true; msg=_msg) - val + time_print(stdout, ret.time*1e9, ret.gcstats.allocd, ret.gcstats.total_time, gc_alloc_count(ret.gcstats), ret.lock_conflicts, ret.compile_time*1e9, ret.recompile_time*1e9, true; msg=_msg) + ret.value end end @@ -356,20 +386,10 @@ macro timev(ex) end macro timev(msg, ex) quote - Experimental.@force_compile - local stats = gc_num() - local elapsedtime = time_ns() - cumulative_compile_timing(true) - local compile_elapsedtimes = cumulative_compile_time_ns() - local val = @__tryfinally($(esc(ex)), - (elapsedtime = time_ns() - elapsedtime; - cumulative_compile_timing(false); - compile_elapsedtimes = cumulative_compile_time_ns() .- compile_elapsedtimes) - ) - local diff = GC_Diff(gc_num(), stats) + local ret = @timed $(esc(ex)) local _msg = $(esc(msg)) - timev_print(elapsedtime, diff, compile_elapsedtimes; msg=_msg) - val + timev_print(ret.time*1e9, ret.gcstats, ret.lock_conflicts, (ret.compile_time*1e9, ret.recompile_time*1e9); msg=_msg) + ret.value end end @@ -462,19 +482,57 @@ macro allocations(ex) end end +""" + @lock_conflicts + +A macro to evaluate an expression, discard the resulting value, and instead return the +total number of lock conflicts during evaluation, where a lock attempt on a [`ReentrantLock`](@ref) +resulted in a wait because the lock was already held. + +See also [`@time`](@ref), [`@timev`](@ref) and [`@timed`](@ref). + +```julia-repl +julia> @lock_conflicts begin + l = ReentrantLock() + Threads.@threads for i in 1:Threads.nthreads() + lock(l) do + sleep(1) + end + end +end +5 +``` + +!!! compat "Julia 1.11" + This macro was added in Julia 1.11. +""" +macro lock_conflicts(ex) + quote + Threads.lock_profiling(true) + local lock_conflicts = Threads.LOCK_CONFLICT_COUNT[] + try + $(esc(ex)) + finally + Threads.lock_profiling(false) + end + Threads.LOCK_CONFLICT_COUNT[] - lock_conflicts + end +end + """ @timed -A macro to execute an expression, and return the value of the expression, elapsed time, -total bytes allocated, garbage collection time, and an object with various memory allocation -counters. +A macro to execute an expression, and return the value of the expression, elapsed time in seconds, +total bytes allocated, garbage collection time, an object with various memory allocation +counters, compilation time in seconds, and recompilation time in seconds. Any lock conflicts +where a [`ReentrantLock`](@ref) had to wait are shown as a count. In some cases the system will look inside the `@timed` expression and compile some of the called code before execution of the top-level expression begins. When that happens, some compilation time will not be counted. To include this time you can run `@timed @eval ...`. See also [`@time`](@ref), [`@timev`](@ref), [`@elapsed`](@ref), -[`@allocated`](@ref), and [`@allocations`](@ref). +[`@allocated`](@ref), [`@allocations`](@ref), and [`@lock_conflicts`](@ref). ```julia-repl julia> stats = @timed rand(10^6); @@ -493,19 +551,47 @@ julia> propertynames(stats.gcstats) julia> stats.gcstats.total_time 5576500 + +julia> stats.compile_time +0.0 + +julia> stats.recompile_time +0.0 + ``` !!! compat "Julia 1.5" The return type of this macro was changed from `Tuple` to `NamedTuple` in Julia 1.5. + +!!! compat "Julia 1.11" + The `lock_conflicts`, `compile_time`, and `recompile_time` fields were added in Julia 1.11. """ macro timed(ex) quote Experimental.@force_compile + Threads.lock_profiling(true) + local lock_conflicts = Threads.LOCK_CONFLICT_COUNT[] local stats = gc_num() local elapsedtime = time_ns() - local val = $(esc(ex)) - elapsedtime = time_ns() - elapsedtime + cumulative_compile_timing(true) + local compile_elapsedtimes = cumulative_compile_time_ns() + local val = @__tryfinally($(esc(ex)), + (elapsedtime = time_ns() - elapsedtime; + cumulative_compile_timing(false); + compile_elapsedtimes = cumulative_compile_time_ns() .- compile_elapsedtimes; + lock_conflicts = Threads.LOCK_CONFLICT_COUNT[] - lock_conflicts; + Threads.lock_profiling(false)) + ) local diff = GC_Diff(gc_num(), stats) - (value=val, time=elapsedtime/1e9, bytes=diff.allocd, gctime=diff.total_time/1e9, gcstats=diff) + ( + value=val, + time=elapsedtime/1e9, + bytes=diff.allocd, + gctime=diff.total_time/1e9, + gcstats=diff, + lock_conflicts=lock_conflicts, + compile_time=compile_elapsedtimes[1]/1e9, + recompile_time=compile_elapsedtimes[2]/1e9 + ) end end diff --git a/base/toml_parser.jl b/base/toml_parser.jl index 18166b03e23b3..b31e256517a1e 100644 --- a/base/toml_parser.jl +++ b/base/toml_parser.jl @@ -1,5 +1,10 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +""" +`Base.TOML` is an undocumented internal part of Julia's TOML parser +implementation. Users should call the documented interface in the +TOML.jl standard library instead (by `import TOML` or `using TOML`). +""" module TOML using Base: IdSet @@ -80,7 +85,7 @@ mutable struct Parser # Filled in in case we are parsing a file to improve error messages filepath::Union{String, Nothing} - # Get's populated with the Dates stdlib if it exists + # Gets populated with the Dates stdlib if it exists Dates::Union{Module, Nothing} end @@ -367,7 +372,7 @@ end @inline peek(l::Parser) = l.current_char # Return true if the character was accepted. When a character -# is accepted it get's eaten and we move to the next character +# is accepted it gets eaten and we move to the next character @inline function accept(l::Parser, f::Union{Function, Char})::Bool c = peek(l) c == EOF_CHAR && return false @@ -665,7 +670,7 @@ end ######### function push!!(v::Vector, el) - # Since these types are typically non-inferrable, they are a big invalidation risk, + # Since these types are typically non-inferable, they are a big invalidation risk, # and since it's used by the package-loading infrastructure the cost of invalidation # is high. Therefore, this is written to reduce the "exposed surface area": e.g., rather # than writing `T[el]` we write it as `push!(Vector{T}(undef, 1), el)` so that there @@ -849,7 +854,7 @@ function parse_number_or_date_start(l::Parser) ate, contains_underscore = @try accept_batch_underscore(l, isdigit, readed_zero) read_underscore |= contains_underscore if (read_digit || ate) && ok_end_value(peek(l)) - return parse_int(l, contains_underscore) + return parse_integer(l, contains_underscore) end # Done with integers here @@ -899,7 +904,18 @@ function parse_float(l::Parser, contains_underscore)::Err{Float64} return v end -for (name, T1, T2, n1, n2) in (("int", Int64, Int128, 17, 33), +function parse_int(l::Parser, contains_underscore, base=nothing)::Err{Int64} + s = take_string_or_substring(l, contains_underscore) + v = try + Base.parse(Int64, s; base=base) + catch e + e isa Base.OverflowError && return(ParserError(ErrOverflowError)) + error("internal parser error: did not correctly discredit $(repr(s)) as an int") + end + return v +end + +for (name, T1, T2, n1, n2) in (("integer", Int64, Int128, 17, 33), ("hex", UInt64, UInt128, 18, 34), ("oct", UInt64, UInt128, 24, 45), ("bin", UInt64, UInt128, 66, 130), @@ -1098,7 +1114,7 @@ function _parse_local_time(l::Parser, skip_hour=false)::Err{NTuple{4, Int64}} end # DateTime in base only manages 3 significant digits in fractional # second - fractional_second = parse_int(l, false) + fractional_second = parse_int(l, false)::Int64 # Truncate off the rest eventual digits accept_batch(l, isdigit) end diff --git a/base/tuple.jl b/base/tuple.jl index 339aafac171b1..a3fadb5c86e78 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -28,10 +28,9 @@ firstindex(@nospecialize t::Tuple) = 1 lastindex(@nospecialize t::Tuple) = length(t) size(@nospecialize(t::Tuple), d::Integer) = (d == 1) ? length(t) : throw(ArgumentError("invalid tuple dimension $d")) axes(@nospecialize t::Tuple) = (OneTo(length(t)),) -@eval getindex(@nospecialize(t::Tuple), i::Int) = getfield(t, i, $(Expr(:boundscheck))) -@eval getindex(@nospecialize(t::Tuple), i::Integer) = getfield(t, convert(Int, i), $(Expr(:boundscheck))) -__inbounds_getindex(@nospecialize(t::Tuple), i::Int) = getfield(t, i, false) -__inbounds_getindex(@nospecialize(t::Tuple), i::Integer) = getfield(t, convert(Int, i), false) +getindex(@nospecialize(t::Tuple), i::Int) = getfield(t, i, @_boundscheck) +getindex(@nospecialize(t::Tuple), i::Integer) = getfield(t, convert(Int, i), @_boundscheck) +__safe_getindex(@nospecialize(t::Tuple), i::Int) = (@_nothrow_noub_meta; getfield(t, i, false)) getindex(t::Tuple, r::AbstractArray{<:Any,1}) = (eltype(t)[t[ri] for ri in r]...,) getindex(t::Tuple, b::AbstractArray{Bool,1}) = length(b) == length(t) ? getindex(t, findall(b)) : throw(BoundsError(t, b)) getindex(t::Tuple, c::Colon) = t @@ -74,6 +73,74 @@ end keys(@nospecialize t::Tuple) = OneTo(length(t)) +""" + prevind(A, i) + +Return the index before `i` in `A`. The returned index is often equivalent to `i +- 1` for an integer `i`. This function can be useful for generic code. + +!!! warning + The returned index might be out of bounds. Consider using + [`checkbounds`](@ref). + +See also: [`nextind`](@ref). + +# Examples +```jldoctest +julia> x = [1 2; 3 4] +2×2 Matrix{Int64}: + 1 2 + 3 4 + +julia> prevind(x, 4) # valid result +3 + +julia> prevind(x, 1) # invalid result +0 + +julia> prevind(x, CartesianIndex(2, 2)) # valid result +CartesianIndex(1, 2) + +julia> prevind(x, CartesianIndex(1, 1)) # invalid result +CartesianIndex(2, 0) +``` +""" +function prevind end + +""" + nextind(A, i) + +Return the index after `i` in `A`. The returned index is often equivalent to `i ++ 1` for an integer `i`. This function can be useful for generic code. + +!!! warning + The returned index might be out of bounds. Consider using + [`checkbounds`](@ref). + +See also: [`prevind`](@ref). + +# Examples +```jldoctest +julia> x = [1 2; 3 4] +2×2 Matrix{Int64}: + 1 2 + 3 4 + +julia> nextind(x, 1) # valid result +2 + +julia> nextind(x, 4) # invalid result +5 + +julia> nextind(x, CartesianIndex(1, 1)) # valid result +CartesianIndex(2, 1) + +julia> nextind(x, CartesianIndex(2, 2)) # invalid result +CartesianIndex(1, 3) +``` +""" +function nextind end + prevind(@nospecialize(t::Tuple), i::Integer) = Int(i)-1 nextind(@nospecialize(t::Tuple), i::Integer) = Int(i)+1 @@ -241,6 +308,13 @@ end # @ tuple.jl:209 typeof(function eltype end).name.max_methods = UInt8(4) +# key/val types +keytype(@nospecialize t::Tuple) = keytype(typeof(t)) +keytype(@nospecialize T::Type{<:Tuple}) = Int + +valtype(@nospecialize t::Tuple) = valtype(typeof(t)) +valtype(@nospecialize T::Type{<:Tuple}) = eltype(T) + # version of tail that doesn't throw on empty tuples (used in array indexing) safe_tail(t::Tuple) = tail(t) safe_tail(t::Tuple{}) = () @@ -322,7 +396,7 @@ end # n argument function heads(ts::Tuple...) = map(t -> t[1], ts) tails(ts::Tuple...) = map(tail, ts) -map(f, ::Tuple{}...) = () +map(f, ::Tuple{}, ::Tuple{}...) = () anyempty(x::Tuple{}, xs...) = true anyempty(x::Tuple, xs...) = anyempty(xs...) anyempty() = false @@ -418,6 +492,7 @@ _totuple(::Type{Tuple}, itr, s...) = (collect(Iterators.rest(itr,s...))...,) _totuple(::Type{Tuple}, itr::Array) = (itr...,) _totuple(::Type{Tuple}, itr::SimpleVector) = (itr...,) _totuple(::Type{Tuple}, itr::NamedTuple) = (itr...,) +_totuple(::Type{Tuple}, p::Pair) = (p.first, p.second) _totuple(::Type{Tuple}, x::Number) = (x,) # to make Tuple(x) inferable end @@ -615,4 +690,10 @@ Return an empty tuple, `()`. empty(@nospecialize x::Tuple) = () foreach(f, itr::Tuple) = foldl((_, x) -> (f(x); nothing), itr, init=nothing) -foreach(f, itrs::Tuple...) = foldl((_, xs) -> (f(xs...); nothing), zip(itrs...), init=nothing) +foreach(f, itr::Tuple, itrs::Tuple...) = foldl((_, xs) -> (f(xs...); nothing), zip(itr, itrs...), init=nothing) + +function circshift(x::Tuple, shift::Integer) + @inline + j = mod1(shift, length(x)) + ntuple(k -> getindex(x, k-j+ifelse(k>j,0,length(x))), Val(length(x))) +end diff --git a/base/twiceprecision.jl b/base/twiceprecision.jl index 955bfc97b16ff..6928d420a3860 100644 --- a/base/twiceprecision.jl +++ b/base/twiceprecision.jl @@ -278,6 +278,7 @@ big(x::TwicePrecision) = big(x.hi) + big(x.lo) -(x::TwicePrecision) = TwicePrecision(-x.hi, -x.lo) +zero(x::TwicePrecision) = zero(typeof(x)) function zero(::Type{TwicePrecision{T}}) where {T} z = zero(T) TwicePrecision{T}(z, z) @@ -785,3 +786,19 @@ _tp_prod(t::TwicePrecision) = t x.hi < y.hi || ((x.hi == y.hi) & (x.lo < y.lo)) isbetween(a, x, b) = a <= x <= b || b <= x <= a + +# These functions exist for use in LogRange: + +_exp_allowing_twice64(x::Number) = exp(x) +_exp_allowing_twice64(x::TwicePrecision{Float64}) = Math.exp_impl(x.hi, x.lo, Val(:ℯ)) + +# No error on negative x, and for NaN/Inf this returns junk: +function _log_twice64_unchecked(x::Float64) + xu = reinterpret(UInt64, x) + if xu < (UInt64(1)<<52) # x is subnormal + xu = reinterpret(UInt64, x * 0x1p52) # normalize x + xu &= ~sign_mask(Float64) + xu -= UInt64(52) << 52 # mess with the exponent + end + TwicePrecision(Math._log_ext(xu)...) +end diff --git a/base/util.jl b/base/util.jl index fe04eaf04bd47..5e86f026f8f9a 100644 --- a/base/util.jl +++ b/base/util.jl @@ -144,7 +144,7 @@ See also [`print`](@ref), [`println`](@ref), [`show`](@ref). printstyled(stdout, msg...; bold=bold, italic=italic, underline=underline, blink=blink, reverse=reverse, hidden=hidden, color=color) """ - Base.julia_cmd(juliapath=joinpath(Sys.BINDIR, julia_exename()); cpu_target) + Base.julia_cmd(juliapath=joinpath(Sys.BINDIR, julia_exename()); cpu_target::Union{Nothing,String}=nothing) Return a julia command similar to the one of the running process. Propagates any of the `--cpu-target`, `--sysimage`, `--compile`, `--sysimage-native-code`, @@ -154,6 +154,8 @@ command line arguments that are not at their default values. Among others, `--math-mode`, `--warn-overwrite`, and `--trace-compile` are notably not propagated currently. +Unless set to `nothing`, the `cpu_target` keyword argument can be used to override the CPU target set for the running process. + To get the julia command without propagated command line arguments, `julia_cmd()[1]` can be used. !!! compat "Julia 1.1" @@ -163,8 +165,7 @@ To get the julia command without propagated command line arguments, `julia_cmd() The flags `--color` and `--startup-file` were added in Julia 1.5. !!! compat "Julia 1.9" - The keyword argument `cpu_target` was added. - + The keyword argument `cpu_target` was added in 1.9. The flag `--pkgimages` was added in Julia 1.9. """ function julia_cmd(julia=joinpath(Sys.BINDIR, julia_exename()); cpu_target::Union{Nothing,String} = nothing) @@ -206,6 +207,9 @@ function julia_cmd(julia=joinpath(Sys.BINDIR, julia_exename()); cpu_target::Unio opts.can_inline == 0 && push!(addflags, "--inline=no") opts.use_compiled_modules == 0 && push!(addflags, "--compiled-modules=no") opts.use_compiled_modules == 2 && push!(addflags, "--compiled-modules=existing") + opts.use_compiled_modules == 3 && push!(addflags, "--compiled-modules=strict") + opts.use_pkgimages == 0 && push!(addflags, "--pkgimages=no") + opts.use_pkgimages == 2 && push!(addflags, "--pkgimages=existing") opts.opt_level == 2 || push!(addflags, "-O$(opts.opt_level)") opts.opt_level_min == 0 || push!(addflags, "--min-optlevel=$(opts.opt_level_min)") push!(addflags, "-g$(opts.debug_level)") @@ -241,13 +245,7 @@ function julia_cmd(julia=joinpath(Sys.BINDIR, julia_exename()); cpu_target::Unio if opts.use_sysimage_native_code == 0 push!(addflags, "--sysimage-native-code=no") end - if opts.use_pkgimages == 0 - push!(addflags, "--pkgimages=no") - else - # If pkgimage is set, malloc_log and code_coverage should not - @assert opts.malloc_log == 0 && opts.code_coverage == 0 - end - return `$julia -C$cpu_target -J$image_file $addflags` + return `$julia -C $cpu_target -J$image_file $addflags` end function julia_exename() @@ -377,7 +375,7 @@ then the user can enter just a newline character to select the `default`. See also `Base.winprompt` (for Windows) and `Base.getpass` for secure entry of passwords. -# Example +# Examples ```julia-repl julia> your_name = Base.prompt("Enter your name"); @@ -566,24 +564,31 @@ Stacktrace: macro kwdef(expr) expr = macroexpand(__module__, expr) # to expand @static isexpr(expr, :struct) || error("Invalid usage of @kwdef") - T = expr.args[2] + _, T, fieldsblock = expr.args if T isa Expr && T.head === :<: T = T.args[1] end - params_ex = Expr(:parameters) - call_args = Any[] + fieldnames = Any[] + defvals = Any[] + extract_names_and_defvals_from_kwdef_fieldblock!(fieldsblock, fieldnames, defvals) + parameters = map(fieldnames, defvals) do fieldname, defval + if isnothing(defval) + return fieldname + else + return Expr(:kw, fieldname, esc(defval)) + end + end - _kwdef!(expr.args[3], params_ex.args, call_args) # Only define a constructor if the type has fields, otherwise we'll get a stack # overflow on construction - if !isempty(params_ex.args) - if T isa Symbol - sig = :(($(esc(T)))($params_ex)) - call = :(($(esc(T)))($(call_args...))) - body = Expr(:block, __source__, call) + if !isempty(parameters) + T_no_esc = Meta.unescape(T) + if T_no_esc isa Symbol + sig = Expr(:call, esc(T), Expr(:parameters, parameters...)) + body = Expr(:block, __source__, Expr(:call, esc(T), fieldnames...)) kwdefs = Expr(:function, sig, body) - elseif isexpr(T, :curly) + elseif isexpr(T_no_esc, :curly) # if T == S{A<:AA,B<:BB}, define two methods # S(...) = ... # S{A,B}(...) where {A<:AA,B<:BB} = ... @@ -591,11 +596,11 @@ macro kwdef(expr) P = T.args[2:end] Q = Any[isexpr(U, :<:) ? U.args[1] : U for U in P] SQ = :($S{$(Q...)}) - body1 = Expr(:block, __source__, :(($(esc(S)))($(call_args...)))) - sig1 = :(($(esc(S)))($params_ex)) + body1 = Expr(:block, __source__, Expr(:call, esc(S), fieldnames...)) + sig1 = Expr(:call, esc(S), Expr(:parameters, parameters...)) def1 = Expr(:function, sig1, body1) - body2 = Expr(:block, __source__, :(($(esc(SQ)))($(call_args...)))) - sig2 = :(($(esc(SQ)))($params_ex) where {$(esc.(P)...)}) + body2 = Expr(:block, __source__, Expr(:call, esc(SQ), fieldnames...)) + sig2 = :($(Expr(:call, esc(SQ), Expr(:parameters, parameters...))) where {$(esc.(P)...)}) def2 = Expr(:function, sig2, body2) kwdefs = Expr(:block, def1, def2) else @@ -612,54 +617,44 @@ end # @kwdef helper function # mutates arguments inplace -function _kwdef!(blk, params_args, call_args) - for i in eachindex(blk.args) - ei = blk.args[i] - if ei isa Symbol - # var - push!(params_args, ei) - push!(call_args, ei) - elseif ei isa Expr - is_atomic = ei.head === :atomic - ei = is_atomic ? first(ei.args) : ei # strip "@atomic" and add it back later - is_const = ei.head === :const - ei = is_const ? first(ei.args) : ei # strip "const" and add it back later - # Note: `@atomic const ..` isn't valid, but reconstruct it anyway to serve a nice error - if ei isa Symbol - # const var - push!(params_args, ei) - push!(call_args, ei) - elseif ei.head === :(=) - lhs = ei.args[1] - if lhs isa Symbol - # var = defexpr - var = lhs - elseif lhs isa Expr && lhs.head === :(::) && lhs.args[1] isa Symbol - # var::T = defexpr - var = lhs.args[1] - else - # something else, e.g. inline inner constructor - # F(...) = ... - continue +function extract_names_and_defvals_from_kwdef_fieldblock!(block, names, defvals) + for (i, item) in pairs(block.args) + if isexpr(item, :block) + extract_names_and_defvals_from_kwdef_fieldblock!(item, names, defvals) + elseif item isa Expr && item.head in (:escape, :var"hygienic-scope") + n = length(names) + extract_names_and_defvals_from_kwdef_fieldblock!(item, names, defvals) + for j in n+1:length(defvals) + if !isnothing(defvals[j]) + defvals[j] = Expr(item.head, defvals[j]) end - defexpr = ei.args[2] # defexpr - push!(params_args, Expr(:kw, var, esc(defexpr))) - push!(call_args, var) - lhs = is_const ? Expr(:const, lhs) : lhs - lhs = is_atomic ? Expr(:atomic, lhs) : lhs - blk.args[i] = lhs # overrides arg - elseif ei.head === :(::) && ei.args[1] isa Symbol - # var::Typ - var = ei.args[1] - push!(params_args, var) - push!(call_args, var) - elseif ei.head === :block - # can arise with use of @static inside type decl - _kwdef!(ei, params_args, call_args) end + else + def, name, defval = @something(def_name_defval_from_kwdef_fielddef(item), continue) + block.args[i] = def + push!(names, name) + push!(defvals, defval) end end - blk +end + +function def_name_defval_from_kwdef_fielddef(kwdef) + if kwdef isa Symbol + return kwdef, kwdef, nothing + elseif isexpr(kwdef, :(::)) + name, _ = kwdef.args + return kwdef, Meta.unescape(name), nothing + elseif isexpr(kwdef, :(=)) + lhs, rhs = kwdef.args + def, name, _ = @something(def_name_defval_from_kwdef_fielddef(lhs), return nothing) + return def, name, rhs + elseif kwdef isa Expr && kwdef.head in (:const, :atomic) + def, name, defval = @something(def_name_defval_from_kwdef_fielddef(kwdef.args[1]), return nothing) + return Expr(kwdef.head, def), name, defval + elseif kwdef isa Expr && kwdef.head in (:escape, :var"hygienic-scope") + def, name, defval = @something(def_name_defval_from_kwdef_fielddef(kwdef.args[1]), return nothing) + return Expr(kwdef.head, def), name, isnothing(defval) ? defval : Expr(kwdef.head, defval) + end end # testing @@ -691,7 +686,8 @@ function runtests(tests = ["all"]; ncores::Int = ceil(Int, Sys.CPU_THREADS / 2), ENV2["JULIA_CPU_THREADS"] = "$ncores" pathsep = Sys.iswindows() ? ";" : ":" ENV2["JULIA_DEPOT_PATH"] = string(mktempdir(; cleanup = true), pathsep) # make sure the default depots can be loaded - delete!(ENV2, "JULIA_LOAD_PATH") + ENV2["JULIA_LOAD_PATH"] = string("@", pathsep, "@stdlib") + ENV2["JULIA_TESTS"] = "true" delete!(ENV2, "JULIA_PROJECT") try run(setenv(`$(julia_cmd()) $(joinpath(Sys.BINDIR, @@ -699,11 +695,9 @@ function runtests(tests = ["all"]; ncores::Int = ceil(Int, Sys.CPU_THREADS / 2), nothing catch buf = PipeBuffer() - original_load_path = copy(Base.LOAD_PATH); empty!(Base.LOAD_PATH); pushfirst!(Base.LOAD_PATH, "@stdlib") - let InteractiveUtils = Base.require(Base, :InteractiveUtils) + let InteractiveUtils = Base.require_stdlib(PkgId(UUID(0xb77e0a4c_d291_57a0_90e8_8db25a27a240), "InteractiveUtils")) @invokelatest InteractiveUtils.versioninfo(buf) end - empty!(Base.LOAD_PATH); append!(Base.LOAD_PATH, original_load_path) error("A test has failed. Please submit a bug report (https://github.com/JuliaLang/julia/issues)\n" * "including error messages above and the output of versioninfo():\n$(read(buf, String))") end diff --git a/base/uuid.jl b/base/uuid.jl index ff4df68ddb7c8..9b2da3c6409db 100644 --- a/base/uuid.jl +++ b/base/uuid.jl @@ -90,7 +90,7 @@ let groupings = [36:-1:25; 23:-1:20; 18:-1:15; 13:-1:10; 8:-1:1] global string function string(u::UUID) u = u.value - a = Base.StringVector(36) + a = Base.StringMemory(36) for i in groupings @inbounds a[i] = hex_chars[1 + u & 0xf] u >>= 4 @@ -101,7 +101,7 @@ let groupings = [36:-1:25; 23:-1:20; 18:-1:15; 13:-1:10; 8:-1:1] end print(io::IO, u::UUID) = print(io, string(u)) -show(io::IO, u::UUID) = print(io, "UUID(\"", u, "\")") +show(io::IO, u::UUID) = print(io, UUID, "(\"", u, "\")") isless(a::UUID, b::UUID) = isless(a.value, b.value) diff --git a/base/version.jl b/base/version.jl index a966135a071e2..9fa4edb32e4dd 100644 --- a/base/version.jl +++ b/base/version.jl @@ -9,12 +9,19 @@ const VInt = UInt32 VersionNumber Version number type which follows the specifications of -[semantic versioning (semver)](https://semver.org/), composed of major, minor +[semantic versioning (semver)](https://semver.org/spec/v2.0.0-rc.2.html), composed of major, minor and patch numeric values, followed by pre-release and build -alpha-numeric annotations. +alphanumeric annotations. `VersionNumber` objects can be compared with all of the standard comparison -operators (`==`, `<`, `<=`, etc.), with the result following semver rules. +operators (`==`, `<`, `<=`, etc.), with the result following semver v2.0.0-rc.2 rules. + +`VersionNumber` has the following public fields: +- `v.major::Integer` +- `v.minor::Integer` +- `v.patch::Integer` +- `v.prerelease::Tuple{Vararg{Union{Integer, AbstractString}}}` +- `v.build::Tuple{Vararg{Union{Integer, AbstractString}}}` See also [`@v_str`](@ref) to efficiently construct `VersionNumber` objects from semver-format literal strings, [`VERSION`](@ref) for the `VersionNumber` @@ -266,73 +273,3 @@ else end libllvm_path() = ccall(:jl_get_libllvm, Any, ()) - -function banner(io::IO = stdout; short = false) - if GIT_VERSION_INFO.tagged_commit - commit_string = TAGGED_RELEASE_BANNER - elseif isempty(GIT_VERSION_INFO.commit) - commit_string = "" - else - days = Int(floor((ccall(:jl_clock_now, Float64, ()) - GIT_VERSION_INFO.fork_master_timestamp) / (60 * 60 * 24))) - days = max(0, days) - unit = days == 1 ? "day" : "days" - distance = GIT_VERSION_INFO.fork_master_distance - commit = GIT_VERSION_INFO.commit_short - - if distance == 0 - commit_string = "Commit $(commit) ($(days) $(unit) old master)" - else - branch = GIT_VERSION_INFO.branch - commit_string = "$(branch)/$(commit) (fork: $(distance) commits, $(days) $(unit))" - end - end - - commit_date = isempty(Base.GIT_VERSION_INFO.date_string) ? "" : " ($(split(Base.GIT_VERSION_INFO.date_string)[1]))" - - if get(io, :color, false)::Bool - c = text_colors - tx = c[:normal] # text - jl = c[:normal] # julia - d1 = c[:bold] * c[:blue] # first dot - d2 = c[:bold] * c[:red] # second dot - d3 = c[:bold] * c[:green] # third dot - d4 = c[:bold] * c[:magenta] # fourth dot - - if short - print(io,""" - $(d3)o$(tx) | Version $(VERSION)$(commit_date) - $(d2)o$(tx) $(d4)o$(tx) | $(commit_string) - """) - else - print(io,""" $(d3)_$(tx) - $(d1)_$(tx) $(jl)_$(tx) $(d2)_$(d3)(_)$(d4)_$(tx) | Documentation: https://docs.julialang.org - $(d1)(_)$(jl) | $(d2)(_)$(tx) $(d4)(_)$(tx) | - $(jl)_ _ _| |_ __ _$(tx) | Type \"?\" for help, \"]?\" for Pkg help. - $(jl)| | | | | | |/ _` |$(tx) | - $(jl)| | |_| | | | (_| |$(tx) | Version $(VERSION)$(commit_date) - $(jl)_/ |\\__'_|_|_|\\__'_|$(tx) | $(commit_string) - $(jl)|__/$(tx) | - - """) - end - else - if short - print(io,""" - o | Version $(VERSION)$(commit_date) - o o | $(commit_string) - """) - else - print(io,""" - _ - _ _ _(_)_ | Documentation: https://docs.julialang.org - (_) | (_) (_) | - _ _ _| |_ __ _ | Type \"?\" for help, \"]?\" for Pkg help. - | | | | | | |/ _` | | - | | |_| | | | (_| | | Version $(VERSION)$(commit_date) - _/ |\\__'_|_|_|\\__'_| | $(commit_string) - |__/ | - - """) - end - end -end diff --git a/base/views.jl b/base/views.jl index 70d4c1d9110ee..6898abdda1471 100644 --- a/base/views.jl +++ b/base/views.jl @@ -123,20 +123,21 @@ julia> A ``` """ macro view(ex) + Meta.isexpr(ex, :ref) || throw(ArgumentError( + "Invalid use of @view macro: argument must be a reference expression A[...].")) + ex = replace_ref_begin_end!(ex) + # NOTE We embed `view` as a function object itself directly into the AST. + # By doing this, we prevent the creation of function definitions like + # `view(A, idx) = xxx` in cases such as `@view(A[idx]) = xxx.` if Meta.isexpr(ex, :ref) - ex = replace_ref_begin_end!(ex) - if Meta.isexpr(ex, :ref) - ex = Expr(:call, view, ex.args...) - else # ex replaced by let ...; foo[...]; end - if !(Meta.isexpr(ex, :let) && Meta.isexpr(ex.args[2], :ref)) - error("invalid expression") - end - ex.args[2] = Expr(:call, view, ex.args[2].args...) - end - Expr(:&&, true, esc(ex)) + ex = Expr(:call, view, ex.args...) + elseif Meta.isexpr(ex, :let) && (arg2 = ex.args[2]; Meta.isexpr(arg2, :ref)) + # ex replaced by let ...; foo[...]; end + ex.args[2] = Expr(:call, view, arg2.args...) else - throw(ArgumentError("Invalid use of @view macro: argument must be a reference expression A[...].")) + error("invalid expression") end + return esc(ex) end ############################################################################ @@ -224,16 +225,16 @@ Similarly, `@views` converts string slices into [`SubString`](@ref) views. occurs in functions called by that code. !!! compat "Julia 1.5" - Using `begin` in an indexing expression to refer to the first index requires at least - Julia 1.5. + Using `begin` in an indexing expression to refer to the first index was implemented + in Julia 1.4, but was only supported by `@views` starting in Julia 1.5. # Examples ```jldoctest julia> A = zeros(3, 3); julia> @views for row in 1:3 - b = A[row, :] - b[:] .= row + b = A[row, :] # b is a view, not a copy + b .= row # assign every element to the row index end julia> A diff --git a/base/weakkeydict.jl b/base/weakkeydict.jl index 328f368c80b71..1a98bf1ee4333 100644 --- a/base/weakkeydict.jl +++ b/base/weakkeydict.jl @@ -54,17 +54,7 @@ WeakKeyDict(ps::Pair{K}...) where {K} = WeakKeyDict{K,Any}(ps) WeakKeyDict(ps::(Pair{K,V} where K)...) where {V} = WeakKeyDict{Any,V}(ps) WeakKeyDict(ps::Pair...) = WeakKeyDict{Any,Any}(ps) -function WeakKeyDict(kv) - try - Base.dict_with_eltype((K, V) -> WeakKeyDict{K, V}, kv, eltype(kv)) - catch - if !isiterable(typeof(kv)) || !all(x->isa(x,Union{Tuple,Pair}),kv) - throw(ArgumentError("WeakKeyDict(kv): kv needs to be an iterator of tuples or pairs")) - else - rethrow() - end - end -end +WeakKeyDict(kv) = Base.dict_with_eltype((K, V) -> WeakKeyDict{K, V}, kv, eltype(kv)) function _cleanup_locked(h::WeakKeyDict) if h.dirty @@ -80,7 +70,7 @@ function _cleanup_locked(h::WeakKeyDict) return h end -sizehint!(d::WeakKeyDict, newsz) = sizehint!(d.ht, newsz) +sizehint!(d::WeakKeyDict, newsz; shrink::Bool = true) = @lock d sizehint!(d.ht, newsz; shrink = shrink) empty(d::WeakKeyDict, ::Type{K}, ::Type{V}) where {K, V} = WeakKeyDict{K, V}() IteratorSize(::Type{<:WeakKeyDict}) = SizeUnknown() @@ -213,4 +203,6 @@ function iterate(t::WeakKeyDict{K,V}, state...) where {K, V} end end +@propagate_inbounds Iterators.only(d::WeakKeyDict) = Iterators._only(d, first) + filter!(f, d::WeakKeyDict) = filter_in_one_pass!(f, d) diff --git a/cli/Makefile b/cli/Makefile index b6a2b48ebf044..6e2696538e216 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -20,9 +20,12 @@ ifeq ($(OS),WINNT) LOADER_LDFLAGS += -municode -mconsole -nostdlib --disable-auto-import \ --disable-runtime-pseudo-reloc -lntdll -lkernel32 -lpsapi else ifeq ($(OS),Linux) -LOADER_LDFLAGS += -Wl,--no-as-needed -ldl -lpthread -rdynamic -lc -Wl,--as-needed +# textoff and notext are aliases to the same option which suppress the TEXTREL warning for i686 +LOADER_LDFLAGS += -Wl,--no-as-needed -ldl -lpthread -rdynamic -lc -Wl,--as-needed -Wl,-z,notext else ifeq ($(OS),FreeBSD) LOADER_LDFLAGS += -Wl,--no-as-needed -ldl -lpthread -rdynamic -lc -Wl,--as-needed +else ifeq ($(OS),OpenBSD) +LOADER_LDFLAGS += -Wl,--no-as-needed -lpthread -rdynamic -lc -Wl,--as-needed else ifeq ($(OS),Darwin) LOADER_LDFLAGS += -lSystem endif @@ -47,7 +50,7 @@ LIB_DOBJS := $(BUILDDIR)/loader_lib.dbg.obj # If this is an architecture that supports dynamic linking, link in a trampoline definition ifneq (,$(wildcard $(SRCDIR)/trampolines/trampolines_$(ARCH).S)) LIB_OBJS += $(BUILDDIR)/loader_trampolines.o -LIB_DOBJS += $(BUILDDIR)/loader_trampolines.o +LIB_DOBJS += $(BUILDDIR)/loader_trampolines.dbg.obj endif default: release @@ -64,6 +67,8 @@ $(BUILDDIR)/loader_exe.dbg.obj : $(SRCDIR)/loader_exe.c $(HEADERS) $(JULIAHOME)/ @$(call PRINT_CC, $(CC) $(DEBUGFLAGS) $(LOADER_CFLAGS) -c $< -o $@) $(BUILDDIR)/loader_trampolines.o : $(SRCDIR)/trampolines/trampolines_$(ARCH).S $(HEADERS) $(SRCDIR)/trampolines/common.h @$(call PRINT_CC, $(CC) $(SHIPFLAGS) $(LOADER_CFLAGS) $< -c -o $@) +$(BUILDDIR)/loader_trampolines.dbg.obj : $(SRCDIR)/trampolines/trampolines_$(ARCH).S $(HEADERS) $(SRCDIR)/trampolines/common.h + @$(call PRINT_CC, $(CC) $(DEBUGFLAGS) $(LOADER_CFLAGS) $< -c -o $@) # Debugging target to help us see what kind of code is being generated for our trampolines dump-trampolines: $(SRCDIR)/trampolines/trampolines_$(ARCH).S @@ -104,7 +109,7 @@ julia-debug: $(build_bindir)/julia-debug$(EXE) libjulia-release: $(build_shlibdir)/libjulia.$(SHLIB_EXT) libjulia-debug: $(build_shlibdir)/libjulia-debug.$(SHLIB_EXT) -ifneq (,$(filter $(OS), Linux FreeBSD)) +ifneq (,$(filter $(OS), Linux FreeBSD OpenBSD)) VERSIONSCRIPT := -Wl,--version-script=$(BUILDDIR)/julia.expmap endif @@ -114,7 +119,7 @@ STRIP_EXPORTED_FUNCS := $(shell $(CPP_STDOUT) -I$(JULIAHOME)/src $(SRCDIR)/list_ endif $(build_shlibdir)/libjulia.$(JL_MAJOR_MINOR_SHLIB_EXT): $(LIB_OBJS) $(SRCDIR)/list_strip_symbols.h $(BUILDDIR)/julia.expmap | $(build_shlibdir) $(build_libdir) - @$(call PRINT_LINK, $(CC) $(call IMPLIB_FLAGS,$@.tmp) $(LOADER_CFLAGS) -shared $(SHIPFLAGS) $(LIB_OBJS) -o $@ \ + @$(call PRINT_LINK, $(CC) $(call IMPLIB_FLAGS,$@.tmp) $(LOADER_CFLAGS) -shared $(SHIPFLAGS) $(LIB_OBJS) $(RPATH_LIB) -o $@ \ $(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(VERSIONSCRIPT) $(call SONAME_FLAGS,libjulia.$(JL_MAJOR_SHLIB_EXT))) @$(INSTALL_NAME_CMD)libjulia.$(JL_MAJOR_SHLIB_EXT) $@ @$(DSYMUTIL) $@ @@ -125,7 +130,7 @@ ifeq ($(OS), WINNT) endif $(build_shlibdir)/libjulia-debug.$(JL_MAJOR_MINOR_SHLIB_EXT): $(LIB_DOBJS) $(SRCDIR)/list_strip_symbols.h $(BUILDDIR)/julia.expmap | $(build_shlibdir) $(build_libdir) - @$(call PRINT_LINK, $(CC) $(call IMPLIB_FLAGS,$@.tmp) $(LOADER_CFLAGS) -shared $(DEBUGFLAGS) $(LIB_DOBJS) -o $@ \ + @$(call PRINT_LINK, $(CC) $(call IMPLIB_FLAGS,$@.tmp) $(LOADER_CFLAGS) -shared $(DEBUGFLAGS) $(LIB_DOBJS) $(RPATH_LIB) -o $@ \ $(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(VERSIONSCRIPT) $(call SONAME_FLAGS,libjulia-debug.$(JL_MAJOR_SHLIB_EXT))) @$(INSTALL_NAME_CMD)libjulia-debug.$(JL_MAJOR_SHLIB_EXT) $@ @$(DSYMUTIL) $@ diff --git a/cli/loader_lib.c b/cli/loader_lib.c index 12feed0c508a0..d09d21e10abd1 100644 --- a/cli/loader_lib.c +++ b/cli/loader_lib.c @@ -281,6 +281,7 @@ static char *libstdcxxprobe(void) // See if the version is compatible char *dlerr = dlerror(); // clear out dlerror void *sym = dlsym(handle, GLIBCXX_LEAST_VERSION_SYMBOL); + (void)sym; dlerr = dlerror(); if (dlerr) { // We can't use the library that was found, so don't write anything. @@ -375,7 +376,6 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) { const char *lib_dir = jl_get_libdir(); // Pre-load libraries that libjulia-internal needs. - int deps_len = strlen(&dep_libs[1]); char *curr_dep = &dep_libs[1]; // We keep track of "special" libraries names (ones whose name is prefixed with `@`) @@ -451,6 +451,7 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) { char *cxxpath = libstdcxxprobe(); if (cxxpath) { void *cxx_handle = dlopen(cxxpath, RTLD_LAZY); + (void)cxx_handle; const char *dlr = dlerror(); if (dlr) { jl_loader_print_stderr("ERROR: Unable to dlopen(cxxpath) in parent!\n"); @@ -519,7 +520,7 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) { (*jl_codegen_exported_func_addrs[symbol_idx]) = addr; } // Next, if we're on Linux/FreeBSD, set up fast TLS. -#if !defined(_OS_WINDOWS_) && !defined(_OS_DARWIN_) +#if !defined(_OS_WINDOWS_) && !defined(_OS_DARWIN_) && !defined(_OS_OPENBSD_) void (*jl_pgcstack_setkey)(void*, void*(*)(void)) = lookup_symbol(libjulia_internal, "jl_pgcstack_setkey"); if (jl_pgcstack_setkey == NULL) { jl_loader_print_stderr("ERROR: Cannot find jl_pgcstack_setkey() function within libjulia-internal!\n"); diff --git a/cli/trampolines/trampolines_i686.S b/cli/trampolines/trampolines_i686.S index 3d9cacf0ce652..f6c46fd6ee49b 100644 --- a/cli/trampolines/trampolines_i686.S +++ b/cli/trampolines/trampolines_i686.S @@ -3,13 +3,41 @@ #include "common.h" #include "../../src/jl_exported_funcs.inc" +// set this option to 1 to get very slightly slower trampolines which however do not trigger +// this linker warning: +// ld: ./loader_trampolines.o: warning: relocation against `jl_***_addr' in read-only section `.text' +// ld: warning: creating DT_TEXTREL in a shared object +// If you have a large libjulia.so file or other restrictions on using TEXTREL for some +// reason, this may be worthwhile. +// This is not relevant on Windows (though it is valid there), since it always uses +// DT_TEXTREL anyways, and does not support this notion of PIC. +#define USE_PC32 0 + +#if USE_PC32 +.cfi_startproc +julia__x86.get_pc_thunk.ax: + mov (%esp),%eax + ret +.cfi_endproc + +#define CALL(name) \ + call julia__x86.get_pc_thunk.ax; \ + jmpl *(CNAMEADDR(name) - .)(%eax); \ + +#else + +#define CALL(name) \ + jmpl *(CNAMEADDR(name)); \ + +#endif + #define XX(name) \ DEBUGINFO(CNAME(name)); \ .global CNAME(name); \ .cfi_startproc; \ CNAME(name)##:; \ CET_START(); \ - jmpl *(CNAMEADDR(name)); \ + CALL(name); \ ud2; \ .cfi_endproc; \ EXPORT(name); \ diff --git a/contrib/asan/Make.user.asan b/contrib/asan/Make.user.asan index 96ed13b54e0f9..a97631f7c8080 100644 --- a/contrib/asan/Make.user.asan +++ b/contrib/asan/Make.user.asan @@ -6,6 +6,7 @@ TOOLDIR=$(TOOLCHAIN)/usr/tools USECLANG=1 override CC=$(TOOLDIR)/clang override CXX=$(TOOLDIR)/clang++ +override PATCHELF=$(TOOLDIR)/patchelf export ASAN_SYMBOLIZER_PATH=$(TOOLDIR)/llvm-symbolizer USE_BINARYBUILDER_LLVM=1 diff --git a/contrib/asan/build.sh b/contrib/asan/build.sh index 77f3078b35c42..2e7f243772c81 100755 --- a/contrib/asan/build.sh +++ b/contrib/asan/build.sh @@ -40,7 +40,7 @@ if [ ! -d "$TOOLCHAIN" ]; then cp "$HERE/Make.user.tools" "$TOOLCHAIN/Make.user" fi -make -C "$TOOLCHAIN/deps" install-clang install-llvm-tools +make -C "$TOOLCHAIN/deps" install-clang install-llvm-tools install-patchelf echo echo "Building Julia..." diff --git a/contrib/check-whitespace.jl b/contrib/check-whitespace.jl index d5473ab4c7c62..e178ec8a02a38 100755 --- a/contrib/check-whitespace.jl +++ b/contrib/check-whitespace.jl @@ -18,51 +18,57 @@ const patterns = split(""" *Makefile """) +# Note: `git ls-files` gives `/` as a path separator on Windows, +# so we just use `/` for all platforms. allow_tabs(path) = path == "Make.inc" || endswith(path, "Makefile") || endswith(path, ".make") || endswith(path, ".mk") || - startswith(path, joinpath("src", "support")) || - startswith(path, joinpath("src", "flisp")) || - endswith(path, joinpath("test", "syntax.jl")) || - endswith(path, joinpath("test", "triplequote.jl")) + startswith(path, "src/support") || + startswith(path, "src/flisp") || + endswith(path, "test/syntax.jl") || + endswith(path, "test/triplequote.jl") const errors = Set{Tuple{String,Int,String}}() -for path in eachline(`git ls-files -- $patterns`) - lineno = 0 - non_blank = 0 +function check_whitespace() + for path in eachline(`git ls-files -- $patterns`) + lineno = 0 + non_blank = 0 - file_err(msg) = push!(errors, (path, 0, msg)) - line_err(msg) = push!(errors, (path, lineno, msg)) + file_err(msg) = push!(errors, (path, 0, msg)) + line_err(msg) = push!(errors, (path, lineno, msg)) - isfile(path) || continue - for line in eachline(path, keep=true) - lineno += 1 - contains(line, '\r') && file_err("non-UNIX line endings") - contains(line, '\ua0') && line_err("non-breaking space") - allow_tabs(path) || - contains(line, '\t') && line_err("tab") - endswith(line, '\n') || line_err("no trailing newline") - line = chomp(line) - endswith(line, r"\s") && line_err("trailing whitespace") - contains(line, r"\S") && (non_blank = lineno) + isfile(path) || continue + for line in eachline(path, keep=true) + lineno += 1 + contains(line, '\r') && file_err("non-UNIX line endings") + contains(line, '\ua0') && line_err("non-breaking space") + allow_tabs(path) || + contains(line, '\t') && line_err("tab") + endswith(line, '\n') || line_err("no trailing newline") + line = chomp(line) + endswith(line, r"\s") && line_err("trailing whitespace") + contains(line, r"\S") && (non_blank = lineno) + end + non_blank < lineno && line_err("trailing blank lines") end - non_blank < lineno && line_err("trailing blank lines") -end -if isempty(errors) - println(stderr, "Whitespace check found no issues.") - exit(0) -else - println(stderr, "Whitespace check found $(length(errors)) issues:") - for (path, lineno, msg) in sort!(collect(errors)) - if lineno == 0 - println(stderr, "$path -- $msg") - else - println(stderr, "$path:$lineno -- $msg") + if isempty(errors) + println(stderr, "Whitespace check found no issues.") + exit(0) + else + println(stderr, "Whitespace check found $(length(errors)) issues:") + for (path, lineno, msg) in sort!(collect(errors)) + if lineno == 0 + println(stderr, "$path -- $msg") + else + println(stderr, "$path:$lineno -- $msg") + end end + exit(1) end - exit(1) end + +check_whitespace() diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 582e5410a4b3d..1152e20d16842 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -1,7 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -# Prevent this from putting anyting into the Main namespace -@eval Module() begin +# Prevent this from putting anything into the Main namespace +@eval Core.Module() begin if Threads.maxthreadid() != 1 @warn "Running this file with multiple Julia threads may lead to a build error" Threads.maxthreadid() @@ -39,6 +39,17 @@ precompile(Base.unsafe_string, (Ptr{Int8},)) # loading.jl precompile(Base.__require_prelocked, (Base.PkgId, Nothing)) precompile(Base._require, (Base.PkgId, Nothing)) +precompile(Base.indexed_iterate, (Pair{Symbol, Union{Nothing, String}}, Int)) +precompile(Base.indexed_iterate, (Pair{Symbol, Union{Nothing, String}}, Int, Int)) + +# Pkg loading +precompile(Tuple{typeof(Base.Filesystem.normpath), String, String, Vararg{String}}) +precompile(Tuple{typeof(Base.append!), Array{String, 1}, Array{String, 1}}) +precompile(Tuple{typeof(Base.join), Array{String, 1}, Char}) +precompile(Tuple{typeof(Base.getindex), Base.Dict{Any, Any}, Char}) +precompile(Tuple{typeof(Base.delete!), Base.Set{Any}, Char}) +precompile(Tuple{typeof(Base.convert), Type{Base.Dict{String, Base.Dict{String, String}}}, Base.Dict{String, Any}}) +precompile(Tuple{typeof(Base.convert), Type{Base.Dict{String, Array{String, 1}}}, Base.Dict{String, Any}}) # REPL precompile(isequal, (String, String)) @@ -46,10 +57,16 @@ precompile(Base.check_open, (Base.TTY,)) precompile(Base.getproperty, (Base.TTY, Symbol)) precompile(write, (Base.TTY, String)) precompile(Tuple{typeof(Base.get), Base.TTY, Symbol, Bool}) -precompile(Tuple{typeof(Base.hashindex), String, Int64}) +precompile(Tuple{typeof(Base.hashindex), String, Int}) precompile(Tuple{typeof(Base.write), Base.GenericIOBuffer{Array{UInt8, 1}}, String}) -precompile(Tuple{typeof(Base.indexed_iterate), Tuple{Nothing, Int64}, Int64}) -precompile(Tuple{typeof(Base.indexed_iterate), Tuple{Nothing, Int64}, Int64, Int64}) +precompile(Tuple{typeof(Base.indexed_iterate), Tuple{Nothing, Int}, Int}) +precompile(Tuple{typeof(Base.indexed_iterate), Tuple{Nothing, Int}, Int, Int}) +precompile(Tuple{typeof(Base._typeddict), Base.Dict{String, Any}, Base.Dict{String, Any}, Vararg{Base.Dict{String, Any}}}) +precompile(Tuple{typeof(Base.promoteK), Type, Base.Dict{String, Any}, Base.Dict{String, Any}}) +precompile(Tuple{typeof(Base.promoteK), Type, Base.Dict{String, Any}}) +precompile(Tuple{typeof(Base.promoteV), Type, Base.Dict{String, Any}, Base.Dict{String, Any}}) +precompile(Tuple{typeof(Base.eval_user_input), Base.PipeEndpoint, Any, Bool}) +precompile(Tuple{typeof(Base.get), Base.PipeEndpoint, Symbol, Bool}) # used by Revise.jl precompile(Tuple{typeof(Base.parse_cache_header), String}) @@ -61,13 +78,17 @@ precompile(Tuple{typeof(haskey), Dict{Base.PkgId,Vector{Function}}, Base.PkgId}) precompile(Tuple{typeof(delete!), Dict{Base.PkgId,Vector{Function}}, Base.PkgId}) precompile(Tuple{typeof(push!), Vector{Function}, Function}) +# preferences +precompile(Base.get_preferences, (Base.UUID,)) +precompile(Base.record_compiletime_preference, (Base.UUID, String)) + # miscellaneous precompile(Tuple{typeof(Base.exit)}) precompile(Tuple{typeof(Base.require), Base.PkgId}) precompile(Tuple{typeof(Base.recursive_prefs_merge), Base.Dict{String, Any}}) precompile(Tuple{typeof(Base.recursive_prefs_merge), Base.Dict{String, Any}, Base.Dict{String, Any}, Vararg{Base.Dict{String, Any}}}) -precompile(Tuple{typeof(Base.hashindex), Tuple{Base.PkgId, Nothing}, Int64}) -precompile(Tuple{typeof(Base.hashindex), Tuple{Base.PkgId, String}, Int64}) +precompile(Tuple{typeof(Base.hashindex), Tuple{Base.PkgId, Nothing}, Int}) +precompile(Tuple{typeof(Base.hashindex), Tuple{Base.PkgId, String}, Int}) precompile(Tuple{typeof(isassigned), Core.SimpleVector, Int}) precompile(Tuple{typeof(getindex), Core.SimpleVector, Int}) precompile(Tuple{typeof(Base.Experimental.register_error_hint), Any, Type}) @@ -78,6 +99,23 @@ precompile(Base.CoreLogging.current_logger_for_env, (Base.CoreLogging.LogLevel, precompile(Base.CoreLogging.env_override_minlevel, (Symbol, Module)) precompile(Base.StackTraces.lookup, (Ptr{Nothing},)) precompile(Tuple{typeof(Base.run_module_init), Module, Int}) + +# precompilepkgs +precompile(Tuple{typeof(Base.get), Type{Array{String, 1}}, Base.Dict{String, Any}, String}) +precompile(Tuple{typeof(Base.get), Type{Base.Dict{String, Any}}, Base.Dict{String, Any}, String}) +precompile(Tuple{typeof(Base.haskey), Base.Dict{String, Any}, String}) +precompile(Tuple{typeof(Base.indexed_iterate), Tuple{Base.TTY, Bool}, Int, Int}) +precompile(Tuple{typeof(Base.indexed_iterate), Tuple{Base.TTY, Bool}, Int}) +precompile(Tuple{typeof(Base.open), Base.CmdRedirect, String, Base.TTY}) +precompile(Tuple{typeof(Base.Precompilation.precompilepkgs)}) +precompile(Tuple{typeof(Base.Precompilation.printpkgstyle), Base.TTY, Symbol, String}) +precompile(Tuple{typeof(Base.rawhandle), Base.TTY}) +precompile(Tuple{typeof(Base.setindex!), Base.Dict{String, Array{String, 1}}, Array{String, 1}, String}) +precompile(Tuple{typeof(Base.setindex!), GenericMemory{:not_atomic, Union{Base.Libc.RawFD, Base.SyncCloseFD, IO}, Core.AddrSpace{Core}(0x00)}, Base.TTY, Int}) +precompile(Tuple{typeof(Base.setup_stdio), Base.TTY, Bool}) +precompile(Tuple{typeof(Base.spawn_opts_inherit), Base.DevNull, Base.TTY, Base.TTY}) +precompile(Tuple{typeof(Core.kwcall), NamedTuple{(:context,), Tuple{Base.TTY}}, typeof(Base.sprint), Function}) +precompile(Tuple{Type{Base.UUID}, Base.UUID}) """ for T in (Float16, Float32, Float64), IO in (IOBuffer, IOContext{IOBuffer}, Base.TTY, IOContext{Base.TTY}) @@ -85,39 +123,59 @@ for T in (Float16, Float32, Float64), IO in (IOBuffer, IOContext{IOBuffer}, Base hardcoded_precompile_statements *= "precompile(Tuple{typeof(show), $IO, $T})\n" end +# Precompiles for Revise and other packages precompile_script = """ -# NOTE: these were moved to the end of Base.jl. TODO: move back here. -# # Used by Revise & its dependencies -# while true # force inference -# delete!(push!(Set{Module}(), Base), Main) -# m = first(methods(+)) -# delete!(push!(Set{Method}(), m), m) -# empty!(Set()) -# push!(push!(Set{Union{GlobalRef,Symbol}}(), :two), GlobalRef(Base, :two)) -# (setindex!(Dict{String,Base.PkgId}(), Base.PkgId(Base), "file.jl"))["file.jl"] -# (setindex!(Dict{Symbol,Vector{Int}}(), [1], :two))[:two] -# (setindex!(Dict{Base.PkgId,String}(), "file.jl", Base.PkgId(Base)))[Base.PkgId(Base)] -# (setindex!(Dict{Union{GlobalRef,Symbol}, Vector{Int}}(), [1], :two))[:two] -# (setindex!(IdDict{Type, Union{Missing, Vector{Tuple{LineNumberNode, Expr}}}}(), missing, Int))[Int] -# Dict{Symbol, Union{Nothing, Bool, Symbol}}(:one => false)[:one] -# Dict(Base => [:(1+1)])[Base] -# Dict(:one => [1])[:one] -# Dict("abc" => Set())["abc"] -# pushfirst!([], sum) -# get(Base.pkgorigins, Base.PkgId(Base), nothing) -# sort!([1,2,3]) -# unique!([1,2,3]) -# cumsum([1,2,3]) -# append!(Int[], BitSet()) -# isempty(BitSet()) -# delete!(BitSet([1,2]), 3) -# deleteat!(Int32[1,2,3], [1,3]) -# deleteat!(Any[1,2,3], [1,3]) -# Core.svec(1, 2) == Core.svec(3, 4) -# # copy(Core.Compiler.retrieve_code_info(Core.Compiler.specialize_method(which(+, (Int, Int)), [Int, Int], Core.svec()))) -# any(t->t[1].line > 1, [(LineNumberNode(2,:none),:(1+1))]) -# break # end force inference -# end +for match = Base._methods(+, (Int, Int), -1, Base.get_world_counter()) + m = match.method + delete!(push!(Set{Method}(), m), m) + copy(Core.Compiler.retrieve_code_info(Core.Compiler.specialize_method(match), typemax(UInt))) + + empty!(Set()) + push!(push!(Set{Union{GlobalRef,Symbol}}(), :two), GlobalRef(Base, :two)) + (setindex!(Dict{String,Base.PkgId}(), Base.PkgId(Base), "file.jl"))["file.jl"] + (setindex!(Dict{Symbol,Vector{Int}}(), [1], :two))[:two] + (setindex!(Dict{Base.PkgId,String}(), "file.jl", Base.PkgId(Base)))[Base.PkgId(Base)] + (setindex!(Dict{Union{GlobalRef,Symbol}, Vector{Int}}(), [1], :two))[:two] + (setindex!(IdDict{Type, Union{Missing, Vector{Tuple{LineNumberNode, Expr}}}}(), missing, Int))[Int] + Dict{Symbol, Union{Nothing, Bool, Symbol}}(:one => false)[:one] + Dict(Base => [:(1+1)])[Base] + Dict(:one => [1])[:one] + Dict("abc" => Set())["abc"] + pushfirst!([], sum) + get(Base.pkgorigins, Base.PkgId(Base), nothing) + sort!([1,2,3]) + unique!([1,2,3]) + cumsum([1,2,3]) + append!(Int[], BitSet()) + isempty(BitSet()) + delete!(BitSet([1,2]), 3) + deleteat!(Int32[1,2,3], [1,3]) + deleteat!(Any[1,2,3], [1,3]) + Core.svec(1, 2) == Core.svec(3, 4) + any(t->t[1].line > 1, [(LineNumberNode(2,:none), :(1+1))]) + + # Code loading uses this + sortperm(mtime.(readdir(".")), rev=true) + # JLLWrappers uses these + Dict{Base.UUID,Set{String}}()[Base.UUID("692b3bcd-3c85-4b1f-b108-f13ce0eb3210")] = Set{String}() + get!(Set{String}, Dict{Base.UUID,Set{String}}(), Base.UUID("692b3bcd-3c85-4b1f-b108-f13ce0eb3210")) + eachindex(IndexLinear(), Expr[]) + push!(Expr[], Expr(:return, false)) + vcat(String[], String[]) + k, v = (:hello => nothing) + + # Preferences uses these + get(Dict{String,Any}(), "missing", nothing) + delete!(Dict{String,Any}(), "missing") + for (k, v) in Dict{String,Any}() + println(k) + end + + # interactive startup uses this + write(IOBuffer(), "") + + break # only actually need to do this once +end """ julia_exepath() = joinpath(Sys.BINDIR, Base.julia_exename()) @@ -197,10 +255,10 @@ ansi_disablecursor = "\e[?25l" blackhole = Sys.isunix() ? "/dev/null" : "nul" procenv = Dict{String,Any}( "JULIA_HISTORY" => blackhole, - "JULIA_PROJECT" => nothing, # remove from environment - "JULIA_LOAD_PATH" => "@stdlib", + "JULIA_LOAD_PATH" => "@$(Sys.iswindows() ? ";" : ":")@stdlib", "JULIA_DEPOT_PATH" => Sys.iswindows() ? ";" : ":", "TERM" => "", + # "JULIA_DEBUG" => "precompilation", "JULIA_FALLBACK_REPL" => "true") generate_precompile_statements() = try # Make sure `ansi_enablecursor` is printed @@ -237,24 +295,32 @@ generate_precompile_statements() = try # Make sure `ansi_enablecursor` is printe print_state("step1" => "R") # Also precompile a package here pkgname = "__PackagePrecompilationStatementModule" - mkpath(joinpath(prec_path, pkgname, "src")) - path = joinpath(prec_path, pkgname, "src", "$pkgname.jl") - write(path, - """ - module $pkgname - end - """) + pkguuid = "824efdaf-a0e9-431c-8ee7-3d356b2531c2" + pkgpath = joinpath(prec_path, pkgname) + mkpath(joinpath(pkgpath, "src")) + write(joinpath(pkgpath, "src", "$pkgname.jl"), + """ + module $pkgname + println("Precompiling $pkgname") + end + """) + write(joinpath(pkgpath, "Project.toml"), + """ + name = "$pkgname" + uuid = "$pkguuid" + """) + touch(joinpath(pkgpath, "Manifest.toml")) tmp_prec = tempname(prec_path) tmp_proc = tempname(prec_path) s = """ - pushfirst!(DEPOT_PATH, $(repr(prec_path))); + pushfirst!(DEPOT_PATH, $(repr(joinpath(prec_path,"depot")))); Base.PRECOMPILE_TRACE_COMPILE[] = $(repr(tmp_prec)); - Base.compilecache(Base.PkgId($(repr(pkgname))), $(repr(path))) + Base.Precompilation.precompilepkgs(;fancyprint=true); $precompile_script """ p = run(pipeline(addenv(`$(julia_exepath()) -O0 --trace-compile=$tmp_proc --sysimage $sysimg - --cpu-target=native --startup-file=no --color=yes`, procenv), - stdin=IOBuffer(s), stdout=debug_output)) + --cpu-target=native --startup-file=no --color=yes --project=$(pkgpath)`, procenv), + stdin=IOBuffer(s), stderr=debug_output, stdout=debug_output)) n_step1 = 0 for f in (tmp_prec, tmp_proc) isfile(f) || continue @@ -309,7 +375,7 @@ generate_precompile_statements() = try # Make sure `ansi_enablecursor` is printe print_state("step3" => string("R$n_succeeded", failed > 0 ? " ($failed failed)" : "")) catch ex # See #28808 - @warn "Failed to precompile expression" form=statement exception=ex _module=nothing _file=nothing _line=0 + @warn "Failed to precompile expression" form=statement exception=(ex,catch_backtrace()) _module=nothing _file=nothing _line=0 end end wait(clock) # Stop asynchronous printing @@ -333,10 +399,6 @@ end generate_precompile_statements() -# As a last step in system image generation, -# remove some references to build time environment for a more reproducible build. -Base.Filesystem.temp_cleanup_purge(force=true) - let stdout = Ref{IO}(stdout) Base.PROGRAM_FILE = "" Sys.BINDIR = "" diff --git a/contrib/mac/frameworkapp/JuliaLauncher/AppDelegate.m b/contrib/mac/frameworkapp/JuliaLauncher/AppDelegate.m index db2f13b485189..1d20d6ed3efa1 100644 --- a/contrib/mac/frameworkapp/JuliaLauncher/AppDelegate.m +++ b/contrib/mac/frameworkapp/JuliaLauncher/AppDelegate.m @@ -51,7 +51,7 @@ + (ExecSandboxController *)sharedController { @end -/// Location of an installed variant of Julia (frameowrk or nix hier). +/// Location of an installed variant of Julia (framework or nix hier). @interface JuliaVariant : NSObject @property(readonly, nullable) NSBundle *bundle; @property(readonly, nonnull) NSURL *juliaexe; diff --git a/contrib/normalize_triplet.py b/contrib/normalize_triplet.py index 77c047b360b76..b1bab29487b8f 100755 --- a/contrib/normalize_triplet.py +++ b/contrib/normalize_triplet.py @@ -19,6 +19,7 @@ platform_mapping = { 'darwin': "-apple-darwin[\\d\\.]*", 'freebsd': "-(.*-)?freebsd[\\d\\.]*", + 'openbsd': "-(.*-)?openbsd[\\d\\.]*", 'windows': "-w64-mingw32", 'linux': "-(.*-)?linux", } @@ -96,6 +97,7 @@ def p(x): 'darwin': 'apple-darwin', 'windows': 'w64-mingw32', 'freebsd': 'unknown-freebsd', + 'openbsd': 'unknown-openbsd', } x = r(x) if x: diff --git a/contrib/pgo-lto/.gitignore b/contrib/pgo-lto/.gitignore new file mode 100644 index 0000000000000..978d8f2ca86dd --- /dev/null +++ b/contrib/pgo-lto/.gitignore @@ -0,0 +1,4 @@ +profiles +stage0* +stage1* +stage2* diff --git a/contrib/pgo-lto/Makefile b/contrib/pgo-lto/Makefile new file mode 100644 index 0000000000000..a73825e182561 --- /dev/null +++ b/contrib/pgo-lto/Makefile @@ -0,0 +1,80 @@ +.PHONY: top clean clean-profiles + +STAGE0_BUILD:=$(CURDIR)/stage0.build +STAGE1_BUILD:=$(CURDIR)/stage1.build +STAGE2_BUILD:=$(CURDIR)/stage2.build + +STAGE0_TOOLS:=$(STAGE0_BUILD)/usr/tools/ + +PROFILE_DIR:=$(CURDIR)/profiles +PROFILE_FILE:=$(PROFILE_DIR)/merged.prof +PROFRAW_FILES:=$(wildcard $(PROFILE_DIR)/*.profraw) +JULIA_ROOT:=$(CURDIR)/../.. + +LLVM_CXXFILT:=$(STAGE0_TOOLS)llvm-cxxfilt +LLVM_PROFDATA:=$(STAGE0_TOOLS)llvm-profdata +LLVM_OBJCOPY:=$(STAGE0_TOOLS)llvm-objcopy + +# When building a single libLLVM.so we need to increase -vp-counters-per-site +# significantly +COUNTERS_PER_SITE:=6 + +AFTER_STAGE1_MESSAGE:='Run `make clean-profiles` to start with a clean slate. $\ + Then run Julia to collect realistic profile data, for example: `$(STAGE1_BUILD)/julia -O3 -e $\ + '\''using Pkg; Pkg.add("LoopVectorization"); Pkg.test("LoopVectorization")'\''`. This $\ + should produce about 15MB of data in $(PROFILE_DIR). Note that running extensive $\ + scripts may result in counter overflows, which can be detected by running $\ + `make top`. Afterwards run `make stage2`.' + +TOOLCHAIN_FLAGS = $\ + "CC=$(STAGE0_TOOLS)clang" $\ + "CXX=$(STAGE0_TOOLS)clang++" $\ + "LD=$(STAGE0_TOOLS)ld.lld" $\ + "AR=$(STAGE0_TOOLS)llvm-ar" $\ + "RANLIB=$(STAGE0_TOOLS)llvm-ranlib" $\ + "CFLAGS+=$(PGO_CFLAGS)" $\ + "CXXFLAGS+=$(PGO_CXXFLAGS)" $\ + "LDFLAGS+=$(PGO_LDFLAGS)" + +$(STAGE0_BUILD) $(STAGE1_BUILD) $(STAGE2_BUILD): + $(MAKE) -C $(JULIA_ROOT) O=$@ configure + +stage0: export USE_BINARYBUILDER_LLVM=1 +stage0: | $(STAGE0_BUILD) + # Turn [cd]tors into init/fini_array sections in libclang_rt, since lld + # doesn't do that, and otherwise the profile constructor is not executed + $(MAKE) -C $(STAGE0_BUILD)/deps install-clang install-llvm install-lld install-llvm-tools && \ + find $< -name 'libclang_rt.profile-*.a' -exec $(LLVM_OBJCOPY) --rename-section .ctors=.init_array --rename-section .dtors=.fini_array {} + && \ + touch $@ + +$(STAGE1_BUILD): stage0 +stage1: PGO_CFLAGS:=-fprofile-generate=$(PROFILE_DIR) -Xclang -mllvm -Xclang -vp-counters-per-site=$(COUNTERS_PER_SITE) +stage1: PGO_CXXFLAGS:=-fprofile-generate=$(PROFILE_DIR) -Xclang -mllvm -Xclang -vp-counters-per-site=$(COUNTERS_PER_SITE) +stage1: PGO_LDFLAGS:=-fuse-ld=lld -flto=thin -fprofile-generate=$(PROFILE_DIR) +stage1: export USE_BINARYBUILDER_LLVM=0 +stage1: | $(STAGE1_BUILD) + $(MAKE) -C $(STAGE1_BUILD) $(TOOLCHAIN_FLAGS) && touch $@ + @echo $(AFTER_STAGE1_MESSAGE) + +stage2: PGO_CFLAGS:=-fprofile-use=$(PROFILE_FILE) +stage2: PGO_CXXFLAGS:=-fprofile-use=$(PROFILE_FILE) +stage2: PGO_LDFLAGS:=-fuse-ld=lld -flto=thin -fprofile-use=$(PROFILE_FILE) -Wl,--icf=safe +stage2: export USE_BINARYBUILDER_LLVM=0 +stage2: $(PROFILE_FILE) | $(STAGE2_BUILD) + $(MAKE) -C $(STAGE2_BUILD) $(TOOLCHAIN_FLAGS) && touch $@ + +install: stage2 + $(MAKE) -C $(STAGE2_BUILD) USE_BINARYBUILDER_LLVM=0 install + +$(PROFILE_FILE): stage1 $(PROFRAW_FILES) + $(LLVM_PROFDATA) merge -output=$@ $(PROFRAW_FILES) + +# show top 50 functions +top: $(PROFILE_FILE) + $(LLVM_PROFDATA) show --topn=50 $< | $(LLVM_CXXFILT) + +clean-profiles: + rm -rf $(PROFILE_DIR) + +clean: + rm -f stage0 stage1 stage2 $(PROFILE_FILE) diff --git a/etc/write_base_cache.jl b/contrib/write_base_cache.jl similarity index 100% rename from etc/write_base_cache.jl rename to contrib/write_base_cache.jl diff --git a/deps/JuliaSyntax.version b/deps/JuliaSyntax.version index 1d7dfd8efd096..7f124715024ce 100644 --- a/deps/JuliaSyntax.version +++ b/deps/JuliaSyntax.version @@ -1,4 +1,4 @@ JULIASYNTAX_BRANCH = main -JULIASYNTAX_SHA1 = a9110fa8ecbe79943bb9525b4ccd99a3976cfcb7 +JULIASYNTAX_SHA1 = 4f1731d6ce7c2465fc21ea245110b7a39f34658a JULIASYNTAX_GIT_URL := https://github.com/JuliaLang/JuliaSyntax.jl.git JULIASYNTAX_TAR_URL = https://api.github.com/repos/JuliaLang/JuliaSyntax.jl/tarball/$1 diff --git a/deps/Makefile b/deps/Makefile index 27f5fdbb693d5..fd8f5267e9d7e 100644 --- a/deps/Makefile +++ b/deps/Makefile @@ -64,13 +64,15 @@ ifeq ($(OS), Linux) DEP_LIBS += unwind else ifeq ($(OS), FreeBSD) DEP_LIBS += unwind +else ifeq ($(OS), OpenBSD) +DEP_LIBS += llvmunwind else ifeq ($(OS), Darwin) DEP_LIBS += llvmunwind endif endif endif -ifneq (,$(findstring $(OS),Linux FreeBSD)) +ifneq (,$(findstring $(OS),Linux FreeBSD OpenBSD)) ifeq ($(USE_SYSTEM_PATCHELF), 0) DEP_LIBS += patchelf PATCHELF:=$(build_depsbindir)/patchelf diff --git a/deps/checksums/ArgTools-4eccde45ddc27e4f7fc9094b2861c684e062adb2.tar.gz/md5 b/deps/checksums/ArgTools-4eccde45ddc27e4f7fc9094b2861c684e062adb2.tar.gz/md5 deleted file mode 100644 index e0fdf5c76bcc5..0000000000000 --- a/deps/checksums/ArgTools-4eccde45ddc27e4f7fc9094b2861c684e062adb2.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -87d5afd4bf8c66b6e598da521dafad41 diff --git a/deps/checksums/ArgTools-4eccde45ddc27e4f7fc9094b2861c684e062adb2.tar.gz/sha512 b/deps/checksums/ArgTools-4eccde45ddc27e4f7fc9094b2861c684e062adb2.tar.gz/sha512 deleted file mode 100644 index 3339d6d582c3e..0000000000000 --- a/deps/checksums/ArgTools-4eccde45ddc27e4f7fc9094b2861c684e062adb2.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -0586684ecb31c68840643fa0006a3bb5c042845b89182ca9c67dd6c92863e73c045f5c5dabe9e2d758c9c42288c957935dab1b48c91820cfcf2b240b6902f015 diff --git a/deps/checksums/ArgTools-997089b9cd56404b40ff766759662e16dc1aab4b.tar.gz/md5 b/deps/checksums/ArgTools-997089b9cd56404b40ff766759662e16dc1aab4b.tar.gz/md5 new file mode 100644 index 0000000000000..3e85638390011 --- /dev/null +++ b/deps/checksums/ArgTools-997089b9cd56404b40ff766759662e16dc1aab4b.tar.gz/md5 @@ -0,0 +1 @@ +44175a2843f243a8f2e01cd1e781ecc9 diff --git a/deps/checksums/ArgTools-997089b9cd56404b40ff766759662e16dc1aab4b.tar.gz/sha512 b/deps/checksums/ArgTools-997089b9cd56404b40ff766759662e16dc1aab4b.tar.gz/sha512 new file mode 100644 index 0000000000000..f5690055f63f4 --- /dev/null +++ b/deps/checksums/ArgTools-997089b9cd56404b40ff766759662e16dc1aab4b.tar.gz/sha512 @@ -0,0 +1 @@ +8a9d0f20cd8fa0ed072966debc691597bb09b34836465f52595b9b2525e80b194c7176781495573dd2f9a02b142e832e59f0dccef15afa184543775d58dc7987 diff --git a/deps/checksums/Distributed-6a07d9853ab7686df7440a47d1b585c6c9f3be35.tar.gz/md5 b/deps/checksums/Distributed-6a07d9853ab7686df7440a47d1b585c6c9f3be35.tar.gz/md5 new file mode 100644 index 0000000000000..f6ae9d2d77aeb --- /dev/null +++ b/deps/checksums/Distributed-6a07d9853ab7686df7440a47d1b585c6c9f3be35.tar.gz/md5 @@ -0,0 +1 @@ +316bc519c49cd54685cfceb76a4dab5d diff --git a/deps/checksums/Distributed-6a07d9853ab7686df7440a47d1b585c6c9f3be35.tar.gz/sha512 b/deps/checksums/Distributed-6a07d9853ab7686df7440a47d1b585c6c9f3be35.tar.gz/sha512 new file mode 100644 index 0000000000000..275997e84e6d5 --- /dev/null +++ b/deps/checksums/Distributed-6a07d9853ab7686df7440a47d1b585c6c9f3be35.tar.gz/sha512 @@ -0,0 +1 @@ +e71c269c5ca92f4b5b709b871dd1bb06f9e23c5b1444a30f780ccb37f4aa19b50668a5f81c85269f7cea5ebe740b453c1b3fe1569629f441d01d8964cd185e54 diff --git a/deps/checksums/Distributed-fdf56f429cd44da6d5d08cb5208bbf41d5b3d0a5.tar.gz/md5 b/deps/checksums/Distributed-fdf56f429cd44da6d5d08cb5208bbf41d5b3d0a5.tar.gz/md5 deleted file mode 100644 index 9c68ca7817a54..0000000000000 --- a/deps/checksums/Distributed-fdf56f429cd44da6d5d08cb5208bbf41d5b3d0a5.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -f9d2bec2893aab7d37cab00496eea3ac diff --git a/deps/checksums/Distributed-fdf56f429cd44da6d5d08cb5208bbf41d5b3d0a5.tar.gz/sha512 b/deps/checksums/Distributed-fdf56f429cd44da6d5d08cb5208bbf41d5b3d0a5.tar.gz/sha512 deleted file mode 100644 index fb068b6893321..0000000000000 --- a/deps/checksums/Distributed-fdf56f429cd44da6d5d08cb5208bbf41d5b3d0a5.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -88068d98571f5ec72e952fa01520daabf7a4955f291e373d59735f4cbe048231d23f54fd08c37675421c005c69abf304318f06ba72eb85b1fc64f483e1978731 diff --git a/deps/checksums/Downloads-8a614d592810b15d17885838dec61da244a12e09.tar.gz/md5 b/deps/checksums/Downloads-8a614d592810b15d17885838dec61da244a12e09.tar.gz/md5 deleted file mode 100644 index d59a1fcc37890..0000000000000 --- a/deps/checksums/Downloads-8a614d592810b15d17885838dec61da244a12e09.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -e88a4630ab5cd62b1d2a6213cd2942b8 diff --git a/deps/checksums/Downloads-8a614d592810b15d17885838dec61da244a12e09.tar.gz/sha512 b/deps/checksums/Downloads-8a614d592810b15d17885838dec61da244a12e09.tar.gz/sha512 deleted file mode 100644 index af6d2ecf264ac..0000000000000 --- a/deps/checksums/Downloads-8a614d592810b15d17885838dec61da244a12e09.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -988a20fdd50c11dd66318d00032f14ae4c8b7809b507639d9907744cfdd0cff75c299ac67f99e62c25f527de1557ea6f736ee54ef18f2c54fbee76035de88aa5 diff --git a/deps/checksums/Downloads-a9d274ff6588cc5dbfa90e908ee34c2408bab84a.tar.gz/md5 b/deps/checksums/Downloads-a9d274ff6588cc5dbfa90e908ee34c2408bab84a.tar.gz/md5 new file mode 100644 index 0000000000000..fc3bce951cafb --- /dev/null +++ b/deps/checksums/Downloads-a9d274ff6588cc5dbfa90e908ee34c2408bab84a.tar.gz/md5 @@ -0,0 +1 @@ +97bb33510fadec7f4cc4c718c739e9a0 diff --git a/deps/checksums/Downloads-a9d274ff6588cc5dbfa90e908ee34c2408bab84a.tar.gz/sha512 b/deps/checksums/Downloads-a9d274ff6588cc5dbfa90e908ee34c2408bab84a.tar.gz/sha512 new file mode 100644 index 0000000000000..bf2821e8252b0 --- /dev/null +++ b/deps/checksums/Downloads-a9d274ff6588cc5dbfa90e908ee34c2408bab84a.tar.gz/sha512 @@ -0,0 +1 @@ +a362aaf762f42deebb8632a7a7980cd22b2777e8c4dc629e418580269e24a64217ad846d61acad70438cfdc190e47ba2ff7716edd4e04d8d10c1d765efce604d diff --git a/deps/checksums/JuliaSyntax-4f1731d6ce7c2465fc21ea245110b7a39f34658a.tar.gz/md5 b/deps/checksums/JuliaSyntax-4f1731d6ce7c2465fc21ea245110b7a39f34658a.tar.gz/md5 new file mode 100644 index 0000000000000..c2663955ec773 --- /dev/null +++ b/deps/checksums/JuliaSyntax-4f1731d6ce7c2465fc21ea245110b7a39f34658a.tar.gz/md5 @@ -0,0 +1 @@ +8c9d9579eeab1ba40f978a32c9db9900 diff --git a/deps/checksums/JuliaSyntax-4f1731d6ce7c2465fc21ea245110b7a39f34658a.tar.gz/sha512 b/deps/checksums/JuliaSyntax-4f1731d6ce7c2465fc21ea245110b7a39f34658a.tar.gz/sha512 new file mode 100644 index 0000000000000..46647cb3e432b --- /dev/null +++ b/deps/checksums/JuliaSyntax-4f1731d6ce7c2465fc21ea245110b7a39f34658a.tar.gz/sha512 @@ -0,0 +1 @@ +1bdad624f61482b55deba8727fea1c087bfaea9e1f8afa3b44b984441fb7e663dac067baa4a96ae2d4cbd4a46ae8c87e9d20d2dfcd17046ad194711304184e57 diff --git a/deps/checksums/JuliaSyntax-a9110fa8ecbe79943bb9525b4ccd99a3976cfcb7.tar.gz/md5 b/deps/checksums/JuliaSyntax-a9110fa8ecbe79943bb9525b4ccd99a3976cfcb7.tar.gz/md5 deleted file mode 100644 index 25f5adf71897d..0000000000000 --- a/deps/checksums/JuliaSyntax-a9110fa8ecbe79943bb9525b4ccd99a3976cfcb7.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -a42bbb42babbbd727556f6bc01455826 diff --git a/deps/checksums/JuliaSyntax-a9110fa8ecbe79943bb9525b4ccd99a3976cfcb7.tar.gz/sha512 b/deps/checksums/JuliaSyntax-a9110fa8ecbe79943bb9525b4ccd99a3976cfcb7.tar.gz/sha512 deleted file mode 100644 index cd3f3f3043d45..0000000000000 --- a/deps/checksums/JuliaSyntax-a9110fa8ecbe79943bb9525b4ccd99a3976cfcb7.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -d542766e72b57418b9b4e17743f89d8535c1f36497346b57538bd0cb451e64af9493015692179f80ec4ee8cf18349c1e0888f5710db895e19f9bb0322f0f7464 diff --git a/deps/checksums/JuliaSyntaxHighlighting-4110caaf4fcdf0c614fd3ecd7c5bf589ca82ac63.tar.gz/md5 b/deps/checksums/JuliaSyntaxHighlighting-4110caaf4fcdf0c614fd3ecd7c5bf589ca82ac63.tar.gz/md5 new file mode 100644 index 0000000000000..5e99f7453cfe2 --- /dev/null +++ b/deps/checksums/JuliaSyntaxHighlighting-4110caaf4fcdf0c614fd3ecd7c5bf589ca82ac63.tar.gz/md5 @@ -0,0 +1 @@ +08230d0801fda3c81927d558452215e4 diff --git a/deps/checksums/JuliaSyntaxHighlighting-4110caaf4fcdf0c614fd3ecd7c5bf589ca82ac63.tar.gz/sha512 b/deps/checksums/JuliaSyntaxHighlighting-4110caaf4fcdf0c614fd3ecd7c5bf589ca82ac63.tar.gz/sha512 new file mode 100644 index 0000000000000..16d15cdef3104 --- /dev/null +++ b/deps/checksums/JuliaSyntaxHighlighting-4110caaf4fcdf0c614fd3ecd7c5bf589ca82ac63.tar.gz/sha512 @@ -0,0 +1 @@ +0386841dcf30ee53f7f95dd3206e1208482507d157aa09739873de2a56e5ca3d7bbf27eccd9f4ed81c1c0fea229673475f6454fe94df0ff960563ca4c29ed36c diff --git a/deps/checksums/Pkg-8f772ffa72d86c81f03cddce2e779d6b330414e3.tar.gz/md5 b/deps/checksums/Pkg-8f772ffa72d86c81f03cddce2e779d6b330414e3.tar.gz/md5 new file mode 100644 index 0000000000000..89baf14afbcc2 --- /dev/null +++ b/deps/checksums/Pkg-8f772ffa72d86c81f03cddce2e779d6b330414e3.tar.gz/md5 @@ -0,0 +1 @@ +b1b74c19867026be19c77c3ab94b11e4 diff --git a/deps/checksums/Pkg-8f772ffa72d86c81f03cddce2e779d6b330414e3.tar.gz/sha512 b/deps/checksums/Pkg-8f772ffa72d86c81f03cddce2e779d6b330414e3.tar.gz/sha512 new file mode 100644 index 0000000000000..cb6d2098d0f10 --- /dev/null +++ b/deps/checksums/Pkg-8f772ffa72d86c81f03cddce2e779d6b330414e3.tar.gz/sha512 @@ -0,0 +1 @@ +c1834ec2c41c201224d58928053d94fdb1972dbafa59b5d80bc43ac60f22eeb4287d57d519fe7d0e9f69c430e39e7eafc26aa0c2994ea417cab32725301cb39d diff --git a/deps/checksums/Pkg-b02fb95979c71dc5834aad739ad61622cccf4a16.tar.gz/md5 b/deps/checksums/Pkg-b02fb95979c71dc5834aad739ad61622cccf4a16.tar.gz/md5 deleted file mode 100644 index 141318a4aa0eb..0000000000000 --- a/deps/checksums/Pkg-b02fb95979c71dc5834aad739ad61622cccf4a16.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -540f9de36245fc3d52ff40a97b4d73d9 diff --git a/deps/checksums/Pkg-b02fb95979c71dc5834aad739ad61622cccf4a16.tar.gz/sha512 b/deps/checksums/Pkg-b02fb95979c71dc5834aad739ad61622cccf4a16.tar.gz/sha512 deleted file mode 100644 index 4b2a864ef3951..0000000000000 --- a/deps/checksums/Pkg-b02fb95979c71dc5834aad739ad61622cccf4a16.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -207d82ea9f376c30e5d8680dfaf82f3a9f8f40789313e46fcedf3f6911608be2b6e7cb63ab115eb45549413ba03e2da0d08d5fadd47ed1b46d0a544877a67ce9 diff --git a/deps/checksums/SparseArrays-4e6776a825f2a26c3c580f9b77ba230a6598d7dd.tar.gz/md5 b/deps/checksums/SparseArrays-4e6776a825f2a26c3c580f9b77ba230a6598d7dd.tar.gz/md5 deleted file mode 100644 index fe0a7d8a02a91..0000000000000 --- a/deps/checksums/SparseArrays-4e6776a825f2a26c3c580f9b77ba230a6598d7dd.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -610ecdfc1624f4dba575d1ca78c02f01 diff --git a/deps/checksums/SparseArrays-4e6776a825f2a26c3c580f9b77ba230a6598d7dd.tar.gz/sha512 b/deps/checksums/SparseArrays-4e6776a825f2a26c3c580f9b77ba230a6598d7dd.tar.gz/sha512 deleted file mode 100644 index 5626ac97ada1f..0000000000000 --- a/deps/checksums/SparseArrays-4e6776a825f2a26c3c580f9b77ba230a6598d7dd.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -fb6fc223f2bc0abfdc082805100351a46fb1c56d16079ea1d608d114f86a701243f5aa448b7e251f735125389baed94e632385f3e560c789136a6848e5f39c81 diff --git a/deps/checksums/SparseArrays-cb602d7b7cf46057ddc87d23cda2bdd168a548ac.tar.gz/md5 b/deps/checksums/SparseArrays-cb602d7b7cf46057ddc87d23cda2bdd168a548ac.tar.gz/md5 new file mode 100644 index 0000000000000..5234c20cb4ff7 --- /dev/null +++ b/deps/checksums/SparseArrays-cb602d7b7cf46057ddc87d23cda2bdd168a548ac.tar.gz/md5 @@ -0,0 +1 @@ +47cb7d9dd6f3d8ae3cb497c202ae6411 diff --git a/deps/checksums/SparseArrays-cb602d7b7cf46057ddc87d23cda2bdd168a548ac.tar.gz/sha512 b/deps/checksums/SparseArrays-cb602d7b7cf46057ddc87d23cda2bdd168a548ac.tar.gz/sha512 new file mode 100644 index 0000000000000..1aa2d8146d78e --- /dev/null +++ b/deps/checksums/SparseArrays-cb602d7b7cf46057ddc87d23cda2bdd168a548ac.tar.gz/sha512 @@ -0,0 +1 @@ +5ff47ea50564375e5e926c3f592a9708b1d9862e4090a53c6b02fc09bc1872578e016a4231564a10dd17be174beed54fd0b8821430828e7148f09556f8034ed9 diff --git a/deps/checksums/Statistics-04e5d8916fae616f5ad328cf6a0b94cf883b8ba6.tar.gz/md5 b/deps/checksums/Statistics-04e5d8916fae616f5ad328cf6a0b94cf883b8ba6.tar.gz/md5 deleted file mode 100644 index 546b021cc2afb..0000000000000 --- a/deps/checksums/Statistics-04e5d8916fae616f5ad328cf6a0b94cf883b8ba6.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -1ed739f3d108cfe880fd97fe8b4359e7 diff --git a/deps/checksums/Statistics-04e5d8916fae616f5ad328cf6a0b94cf883b8ba6.tar.gz/sha512 b/deps/checksums/Statistics-04e5d8916fae616f5ad328cf6a0b94cf883b8ba6.tar.gz/sha512 deleted file mode 100644 index c4501f163b216..0000000000000 --- a/deps/checksums/Statistics-04e5d8916fae616f5ad328cf6a0b94cf883b8ba6.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -f83e44af718c5dbe8346762f869796cf80ca6bb10d97c7855e5ea2863198ae650ba8462b46a92a7f6d0941f52d2739c3e10b0a7cbbc6f28e0ad3adb13a89a874 diff --git a/deps/checksums/Statistics-68869af06e8cdeb7aba1d5259de602da7328057f.tar.gz/md5 b/deps/checksums/Statistics-68869af06e8cdeb7aba1d5259de602da7328057f.tar.gz/md5 new file mode 100644 index 0000000000000..9ba42f555d535 --- /dev/null +++ b/deps/checksums/Statistics-68869af06e8cdeb7aba1d5259de602da7328057f.tar.gz/md5 @@ -0,0 +1 @@ +01b84d67052d1558e51619d5159e7a8b diff --git a/deps/checksums/Statistics-68869af06e8cdeb7aba1d5259de602da7328057f.tar.gz/sha512 b/deps/checksums/Statistics-68869af06e8cdeb7aba1d5259de602da7328057f.tar.gz/sha512 new file mode 100644 index 0000000000000..31c9c6ca42cec --- /dev/null +++ b/deps/checksums/Statistics-68869af06e8cdeb7aba1d5259de602da7328057f.tar.gz/sha512 @@ -0,0 +1 @@ +6ab55ba6f93d2e8b34b19f53cb51a4bfc97b336d451b98f7b95ff81f04fee4fb90a2e4d04aa4bbf3ccffc99c36d9c82c9d00dbae283474308de4a27a91c2e0b7 diff --git a/deps/checksums/StyledStrings-bfdb4c3f73a93a956ad48b0f06f89eb1cd40ff6b.tar.gz/md5 b/deps/checksums/StyledStrings-bfdb4c3f73a93a956ad48b0f06f89eb1cd40ff6b.tar.gz/md5 new file mode 100644 index 0000000000000..511b60c7e2217 --- /dev/null +++ b/deps/checksums/StyledStrings-bfdb4c3f73a93a956ad48b0f06f89eb1cd40ff6b.tar.gz/md5 @@ -0,0 +1 @@ +8fc4fd7e90d35e7d8d06a6b7c312ec03 diff --git a/deps/checksums/StyledStrings-bfdb4c3f73a93a956ad48b0f06f89eb1cd40ff6b.tar.gz/sha512 b/deps/checksums/StyledStrings-bfdb4c3f73a93a956ad48b0f06f89eb1cd40ff6b.tar.gz/sha512 new file mode 100644 index 0000000000000..d5e2302499a2c --- /dev/null +++ b/deps/checksums/StyledStrings-bfdb4c3f73a93a956ad48b0f06f89eb1cd40ff6b.tar.gz/sha512 @@ -0,0 +1 @@ +137360872c9b75276426efa9e9096e442115a554b7e00dc98ce02904fa1a535f76e48ba1366fc517794490a494cccc3238d006ebb43dadb5594e5099a2c36f55 diff --git a/deps/checksums/SuiteSparse-e8285dd13a6d5b5cf52d8124793fc4d622d07554.tar.gz/md5 b/deps/checksums/SuiteSparse-e8285dd13a6d5b5cf52d8124793fc4d622d07554.tar.gz/md5 new file mode 100644 index 0000000000000..2f81a0d9191b5 --- /dev/null +++ b/deps/checksums/SuiteSparse-e8285dd13a6d5b5cf52d8124793fc4d622d07554.tar.gz/md5 @@ -0,0 +1 @@ +46541001073d1c3c85e18d910f8308f3 diff --git a/deps/checksums/SuiteSparse-e8285dd13a6d5b5cf52d8124793fc4d622d07554.tar.gz/sha512 b/deps/checksums/SuiteSparse-e8285dd13a6d5b5cf52d8124793fc4d622d07554.tar.gz/sha512 new file mode 100644 index 0000000000000..e2eb44845e276 --- /dev/null +++ b/deps/checksums/SuiteSparse-e8285dd13a6d5b5cf52d8124793fc4d622d07554.tar.gz/sha512 @@ -0,0 +1 @@ +f7470a447b934ca9315e216a07b97e363f11bc93186f9aa057b20b2d05092c58ae4f1b733de362de4a0730861c00be4ca5588d0b3ba65f018c1798b9122b9672 diff --git a/deps/checksums/cacert-2023-01-10.pem/md5 b/deps/checksums/cacert-2023-01-10.pem/md5 deleted file mode 100644 index 92063050b50f3..0000000000000 --- a/deps/checksums/cacert-2023-01-10.pem/md5 +++ /dev/null @@ -1 +0,0 @@ -e7cf471ba7c88f4e313f492a76e624b3 diff --git a/deps/checksums/cacert-2023-01-10.pem/sha512 b/deps/checksums/cacert-2023-01-10.pem/sha512 deleted file mode 100644 index d3322e5890f81..0000000000000 --- a/deps/checksums/cacert-2023-01-10.pem/sha512 +++ /dev/null @@ -1 +0,0 @@ -08cd35277bf2260cb3232d7a7ca3cce6b2bd58af9221922d2c6e9838a19c2f96d1ca6d77f3cc2a3ab611692f9fec939e9b21f67442282e867a487b0203ee0279 diff --git a/deps/checksums/cacert-2024-03-11.pem/md5 b/deps/checksums/cacert-2024-03-11.pem/md5 new file mode 100644 index 0000000000000..618b6c74efdd4 --- /dev/null +++ b/deps/checksums/cacert-2024-03-11.pem/md5 @@ -0,0 +1 @@ +594084120d27f482b1dc48f558d12d48 diff --git a/deps/checksums/cacert-2024-03-11.pem/sha512 b/deps/checksums/cacert-2024-03-11.pem/sha512 new file mode 100644 index 0000000000000..441b8e84707b0 --- /dev/null +++ b/deps/checksums/cacert-2024-03-11.pem/sha512 @@ -0,0 +1 @@ +31f03cc19566d007c4cffdad2ada71d99b4734ad7b13bc4f30d73d321f40cbe13b87a801aa61d9788207a851cc1f95a8af8ac732a372d45edb932f204bce3744 diff --git a/deps/checksums/clang b/deps/checksums/clang index 685d7403b5815..f27067bf6cb85 100644 --- a/deps/checksums/clang +++ b/deps/checksums/clang @@ -1,108 +1,108 @@ -Clang.v15.0.7+8.aarch64-apple-darwin-llvm_version+15.asserts.tar.gz/md5/3994ef8cee2fe488709909f5fffdc091 -Clang.v15.0.7+8.aarch64-apple-darwin-llvm_version+15.asserts.tar.gz/sha512/0713994930b87768dbddeaf5ce5143380e83bfdf3e0e10aa1ba0337fa0ccfc3c84fa05786cccc2b2461d0bf3bf7dee45e18b4f7146c62ae624705f4d3e43df1e -Clang.v15.0.7+8.aarch64-apple-darwin-llvm_version+15.tar.gz/md5/dd097ca34e16abfcee18ec6b955f0d3b -Clang.v15.0.7+8.aarch64-apple-darwin-llvm_version+15.tar.gz/sha512/446f31dbca35a307faec0a779737129c7878ce8c879637265120114f4c68365a6593e1f692c76ff160f5b264a0bb0e9b9f157315ee138ddebdcda1e6e9d3c030 -Clang.v15.0.7+8.aarch64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/96a5a51c74d4d74ce9a5f7701f7da325 -Clang.v15.0.7+8.aarch64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/d27e8931b89aaea97bf10a7bb9d9608a14537e352bbfdb496f0ef9537599f47ce11e519f77912a6befe96189a26c423a8359b1543b39558ec9648a82196c9057 -Clang.v15.0.7+8.aarch64-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/2bf9499f0016a6d3f192e9b982587fdc -Clang.v15.0.7+8.aarch64-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/956cc2c49b49a0d56575c8192e8bc8a3378d484ffafb3fc78f2f8d4331f1070a6fa8111c6bcd63d579a0e54546a3d5134a3c760c609a962e2a586d7b0d48646c -Clang.v15.0.7+8.aarch64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/28ea22de73f4c555edeeb4ff22ce6099 -Clang.v15.0.7+8.aarch64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/88491ec5f0c51e3a6b4b2b6877e9e8437f8b3145a22f8dfb05269f9ddf72c1b8eca496b650ce2e9a26101ad48d2e79d01b4b4a48883fb547d4445b24ab79bfd5 -Clang.v15.0.7+8.aarch64-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/9058d021eb893fb11313a04359d1d2b0 -Clang.v15.0.7+8.aarch64-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/5fd93a2278b3d3c6f6a1836722b97ccd099f17a36da900b8bd54f0b399bc9ee42e847bc094058c3f3ce77dfc6950b5cef9b4571f5941857280a370af16c8b7b3 -Clang.v15.0.7+8.aarch64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/md5/8ac220c9570fa66c2f13d9247dcfb324 -Clang.v15.0.7+8.aarch64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/sha512/977077fd338912c2fdbb88f4bcf1591b6b4f648d6e7c9ba2783b331eb67da2e6b9b29f85446500f6dfb15c9e05af8fe9128dbf3e77dfa69bd597999873769e8d -Clang.v15.0.7+8.aarch64-linux-musl-cxx03-llvm_version+15.tar.gz/md5/f017a86ee8803f2099b04ecf254e8157 -Clang.v15.0.7+8.aarch64-linux-musl-cxx03-llvm_version+15.tar.gz/sha512/c773d0c07234b03c71a1c2862f373f76d4c898fdbad35342c2e687a2afadaa220daad759afa5f88aa7eaab3d0d05f1e8eefcfd374bc1c92ddf638d42d20b74fa -Clang.v15.0.7+8.aarch64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/md5/80af25bf64203daaccc72f2274b60681 -Clang.v15.0.7+8.aarch64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/sha512/913f8ee4c7f9618227673db0aad7e5e98f0fccb8a1faa84778ec84ab10ee8fe4d59a8115582420459de67b8667f3f4681bde59dab6dfa70ddeb8ff8b7fd1add2 -Clang.v15.0.7+8.aarch64-linux-musl-cxx11-llvm_version+15.tar.gz/md5/5b79fba21be10d03476c52ad0888d562 -Clang.v15.0.7+8.aarch64-linux-musl-cxx11-llvm_version+15.tar.gz/sha512/075e074a0f460be27fddd32069dcf4ee48aacbdf480ea74356692b3d98808868a18166ae5e689b17faa6b0ae1f1453bb63d98ed119afd3f44a8e662fa602a57e -Clang.v15.0.7+8.armv6l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/90a9224695634cc70cc39b603cc8bc57 -Clang.v15.0.7+8.armv6l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/c1e388b838723b5251e6bf068a1790f83ccfe618be2e138b397d0a40c4f85f717bf01e8a46bbab48a91f182caef8e3fc291ec71a56df95fa60446a8858ac904b -Clang.v15.0.7+8.armv6l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/md5/6f2ec0e8aaaeb7c9e0c1d826010886ba -Clang.v15.0.7+8.armv6l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/sha512/eb33e93ff5f3e32051c08c0aa42f1cc691dc364683ac484bb292f190911c879226282cea758224c9fea1bfcb934e4abbf4edd07666d9a2dee63d0ba40c2b3b47 -Clang.v15.0.7+8.armv6l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/2e8b81ef8017987c5ace824d524545cb -Clang.v15.0.7+8.armv6l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/76d1cfba99747038542946680dad02d9deb4fb6dafde6def13e95c393da5d5f5f2aa30889377c6f3a869963075b9181b3d7652016202767768e880f2fdea827d -Clang.v15.0.7+8.armv6l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/md5/7556ca721d4e90e64a266c4b59fefc48 -Clang.v15.0.7+8.armv6l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/sha512/8df1651aaa87364fe47da4fc993551a9f9ba575ca88c749b92d6586390e19c8b059c20081e4b76426b23cc1140d1bd38c216855d968f3dc01505e29dc5760367 -Clang.v15.0.7+8.armv6l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/fd294a76698157d71a3a64f4f4e442e5 -Clang.v15.0.7+8.armv6l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/71466cd9d0b46686af10e124bb026e02af240b5ad5f3844fa5c0bb152ee334a56d41e5c82c8b2e3778ea409f5dbc1074afe5a8121fcb157745420a2fcc3465f3 -Clang.v15.0.7+8.armv6l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/md5/a2ad561f1a41ed993fc2edebbe1a6b3b -Clang.v15.0.7+8.armv6l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/sha512/51ed85190a1bdf89d0f5fe361381b0e0b773be05f510ac7b77747948f49dea49807d3d46cb3e948d22785884b8b92dcbf36d202087d2c2628fce6c73c6cd9789 -Clang.v15.0.7+8.armv6l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/98829294d5c6327582a8f9b1d7232083 -Clang.v15.0.7+8.armv6l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/d570b28e6a11d02467075e82931f8529671acd5e61992f462729d690810953535be31dcfe6fbdb3add0e00799809bf3afb529d227820c7f176e6193c24da7853 -Clang.v15.0.7+8.armv6l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/md5/bd173830434f85d17e4857b7ffdb2dd1 -Clang.v15.0.7+8.armv6l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/sha512/ff66a8b1085d67521aa58f766b7bfad2c19ec89add9a67be2c5984a7a5ff5aacf60d54e0448ce70ca80d9a7d972624b9a2be530f767c87ed6839f709a482a7aa -Clang.v15.0.7+8.armv7l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/34b020d23cd5b34ae47b2b0473822f14 -Clang.v15.0.7+8.armv7l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/250d55dfb02be3cfc4eeca0da386590d9fb0b9d6759360732b72b8663f4a3c843cd9e23ea08b8d272945fa64230e57f9a5bcdc4399dda2db54e51961e7c9f3e6 -Clang.v15.0.7+8.armv7l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/md5/b3c76e91d6b43c7794b6ddb9d300d8f4 -Clang.v15.0.7+8.armv7l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/sha512/efbbcf2cfff1918e58fd850d99eec1e28cdaa11730b722a416e5da233a8fd0be685259052d5e9adf32c2c2364a4f96540a0175a44b8dc8d6ec872ef12394b9f2 -Clang.v15.0.7+8.armv7l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/81ae8ac294b1656b188dc437a7de6b10 -Clang.v15.0.7+8.armv7l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/1eac2968698b759bba3467205843183ed7279f24ae06f2d47b1cd4965c115d0b9f578ff7e0a6006883a462ad97c1829f3d706767de77a05d159bafa183064f84 -Clang.v15.0.7+8.armv7l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/md5/581efd97263d337031ee761326320489 -Clang.v15.0.7+8.armv7l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/sha512/ae28a7743146eba68831e0f1929f87596117ebf64d9284585c1909ed1b78a60e102618e4232b37017543351208e0d1183d81e0bbf737c7930bd35dd146c9a2bb -Clang.v15.0.7+8.armv7l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/34342cc1e94435ddac6ffc7ec89471f9 -Clang.v15.0.7+8.armv7l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/2e06c7a4b79a45d2139cadc3c10c4dbc086e6cf20b5f2ce90b50aad9bab6ffa4b55eef3dc9e40b495329a2a1eb9d0948b29a4495dc8a3cd56ce500087b9e63c6 -Clang.v15.0.7+8.armv7l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/md5/de880e4c673351c777509ccfdc0a2579 -Clang.v15.0.7+8.armv7l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/sha512/e1becf694f1edbce097eaff7349c8a2ca8a1dda265c01a40549acf0dbcbbdf3c23d515236b114e6ec124f85e6bde380850ba72029de37cd36d59789936a21e83 -Clang.v15.0.7+8.armv7l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/93a4565b236d905021ab84b0a6c9fd34 -Clang.v15.0.7+8.armv7l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/d7c5b5e6fb627c2e19c72e42dcbcd8de9784dee522057d9fcdb03b0bba127de5517d72da8ca120c3cafabcfe6ac016526a7fbc090258119f5f6252d26d2c0c17 -Clang.v15.0.7+8.armv7l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/md5/31d9e5992d969548f67b9478abfb1575 -Clang.v15.0.7+8.armv7l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/sha512/28ed4cdf1a289ece1defb2d4f19cb03ad07963f8e9df62073fbde3adaf943e78085a6f2d03b6cbd3f00f10f61a3499f964e104c2c1ee73167965b84be0bca901 -Clang.v15.0.7+8.i686-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/8b6ffb09acc5d80408362b2705629db4 -Clang.v15.0.7+8.i686-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/d69bd543be8848c9349014f216af7ff1cb32401cf1b0a060a3687259cb56518933f65ac8ce24eca74e2bfc79a1900bc638ed1428e7823672d088efee8aa70387 -Clang.v15.0.7+8.i686-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/6dc4752e4987fbf9afc49b0a5a0b62f4 -Clang.v15.0.7+8.i686-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/7617ac0ad1496549f6a38d33414cbbd74b72dbb54758f24ffc88a42226cfb607babba73b7e33d9558bff2d208bac3ee8f3b2594990d5e5fda49819eff3ad0d05 -Clang.v15.0.7+8.i686-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/47cccd1ef2565df7196afe4da7659380 -Clang.v15.0.7+8.i686-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/9d25f2d0634e2b4f5b8d9e4fb57c65c7d5956cadc24bbdb67a62095f2e6378aba3a8f16a0bc3aea6656c97aa035b3179645e352ff1a0b01cb89a773e9edc0b89 -Clang.v15.0.7+8.i686-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/4e3fdf880b91a5fdbdcdff34386ee8de -Clang.v15.0.7+8.i686-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/b02b3a7db9796d45d9d1bf1fc95495c71f6575a70ebf7f4d9d249bf117eb8bdbaa100b2d4e867f82da1b9f9d13ae0d7f68097d1b085b0b6f3826ed21723cc709 -Clang.v15.0.7+8.i686-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/md5/859e942a9b7d0ee2fb626552e87586ad -Clang.v15.0.7+8.i686-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/sha512/23f4ecd6a2b4967d41a7a7ee18d3930b4c5e9dd4894a75c2f85e59be0c66bc0fccc7931e051258f36290affd64b6fa37b925ed53a68a0d0cb937a66a6ebfd263 -Clang.v15.0.7+8.i686-w64-mingw32-cxx03-llvm_version+15.tar.gz/md5/f1c4a569ea5705e9eb5c1807f4053c4a -Clang.v15.0.7+8.i686-w64-mingw32-cxx03-llvm_version+15.tar.gz/sha512/73cbb461b39b25b9af334487cea4f16ede9d82ea615671a72d9cce28f9069c3f872f8caa85a3ca61d75919424cd03259a9ac7cbc6c245e1aee2f9afbeef37d7a -Clang.v15.0.7+8.i686-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/md5/f1750649bc2102333fee60be64cfcebc -Clang.v15.0.7+8.i686-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/sha512/9c512417ebdc0662a1003978f454d28ec5ddb059270172878a0ca62cc1b85a4e7b7a873d8361f52d0e89372f625f9238404f4b850618ff808592107803e80632 -Clang.v15.0.7+8.i686-w64-mingw32-cxx11-llvm_version+15.tar.gz/md5/d88610eb9c37cd5e438087b41960658c -Clang.v15.0.7+8.i686-w64-mingw32-cxx11-llvm_version+15.tar.gz/sha512/79b241c911a38d363e86761500fd2fba3a439feda270132ec364bcf02189c3a97cfafdf73ef41e00364f8bf5e2ead602cdf5c5a699126e8a372bd3696d7f4b1f -Clang.v15.0.7+8.powerpc64le-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/7b0878064a5f8ba1953f5e76c4b63c18 -Clang.v15.0.7+8.powerpc64le-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/542f057b2c3d6e829f34a615d7c9c021d793ce3723512b70f7e140640175b237a9669bd9f24563eede1dc5e25e37fa8910b1769ed344af9ea4d21bebc6638801 -Clang.v15.0.7+8.powerpc64le-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/e797f5d917b08bdae66fdcab4f8367b5 -Clang.v15.0.7+8.powerpc64le-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/40a379b2de5eafa8f202b258b8620bf82fb3eae384337120149ae17c7affa7e166a6b4fb77fb4ce94808c152efdf270ec22f03a2ca57aee27799ccc3c684b88c -Clang.v15.0.7+8.powerpc64le-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/1fb0bda6cad1591b1b40752efaa1f198 -Clang.v15.0.7+8.powerpc64le-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/0ff8a3d9fc53e5733b9ea9ea30a4c0023fc926704d2dc747a1802343e5c45f6cabed9fd3207cb146218bf8f71b4de79d708c3f169fa13e3eb35dde591509d84a -Clang.v15.0.7+8.powerpc64le-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/7e2922fc9c5e3f93b73af4b2f8502f7b -Clang.v15.0.7+8.powerpc64le-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/b7656c67cbe9d6cfcd0837a4646819d9dcad83ab0907a570f8e66894f2650cbaa43c78796435b16bf28878b39e1c9254772b5aaa996f6a5629159c7c63caf26b -Clang.v15.0.7+8.x86_64-apple-darwin-llvm_version+15.asserts.tar.gz/md5/795b7f564282b22c2ef2cece8162044c -Clang.v15.0.7+8.x86_64-apple-darwin-llvm_version+15.asserts.tar.gz/sha512/035781d34cc8ddb7e41eb3d46651589ab519cc4fbe0ba0de8d3602b1353027ad4826b619a33de5c033b85f4261471f6f4bd1e62164c07f945a731bd579919e08 -Clang.v15.0.7+8.x86_64-apple-darwin-llvm_version+15.tar.gz/md5/4430f9cbe83a8d97a993dfe7a4b3b044 -Clang.v15.0.7+8.x86_64-apple-darwin-llvm_version+15.tar.gz/sha512/19e4ae8d491734b15fac101cbdbaf652cc37d420f64dcf127bd0fac6eb381fa040312f9c3b836f80fd9d6cb67a1951598dacd9abd826a227e639a685e086637f -Clang.v15.0.7+8.x86_64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/9ccb6fc99b35c72b6e566f3d2336c686 -Clang.v15.0.7+8.x86_64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/af926692bcca26ea35450bb2794b94f6694ffbffbf54342f403dc47270e983ee697c06dbaefc143ecd0954100736586838987f51f8259e160b0ca06688514889 -Clang.v15.0.7+8.x86_64-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/ce36f48d88d7dad1aef44c11b30aebf8 -Clang.v15.0.7+8.x86_64-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/fbbf65977c5924830ff4c1c2c6d824a3635d09d4ee9304888030b8eca1bd7c06d8c053521068f663df932379cb3b7f0772589f7cca56156dd7053e6722d29953 -Clang.v15.0.7+8.x86_64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/2b68c2057aeca1564106dab565fe876b -Clang.v15.0.7+8.x86_64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/64c8be7c227fa52c9d90353d5ab72c179c8974b0bc048d6f5793d0ac08110b98c54e2e3f65416986b7049904cf86b5a865cf3de4423d7b0906b4e85a0dcacd21 -Clang.v15.0.7+8.x86_64-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/04a0a842bc490c440ec65927c285059f -Clang.v15.0.7+8.x86_64-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/82dc5eba9a795fe8d0e5c660aad1d902cde8c304ddfeb794d55a7b8decc97d27b648f658bb749b3bc25d2d50771febb0b481198459fc739c1d6944542c2b64f0 -Clang.v15.0.7+8.x86_64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/md5/1742d997df9bc95ddc7791e0f3ba49c8 -Clang.v15.0.7+8.x86_64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/sha512/5187a9fddf31ea260d6d46f613d96c79b3c77bb8c7dd2752bc8d7becb7b7210a3d9ce195b2f03d466760bb4e52dee0beda22f238097e1472dbed04170b14e2c4 -Clang.v15.0.7+8.x86_64-linux-musl-cxx03-llvm_version+15.tar.gz/md5/79e68396face36fc162ab27b99714b34 -Clang.v15.0.7+8.x86_64-linux-musl-cxx03-llvm_version+15.tar.gz/sha512/80f22d1bca302debb3e01213da099e5378eb3c1ff8684dbc8efca9145a27d1542a9a065e328c76148465dbb42268dad028004f38215289bbd862fc2cb12abf81 -Clang.v15.0.7+8.x86_64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/md5/e2f7246f9e8ebd73e21186f18985755d -Clang.v15.0.7+8.x86_64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/sha512/6dcaa68abe3abff6f3e288aa34ce3591a8e92d933187715cb9bc7362947fefeba3ce1b3629efdcdcb1fa3be111f7614a10b4f0b73f3ed54f46d6d9791612305e -Clang.v15.0.7+8.x86_64-linux-musl-cxx11-llvm_version+15.tar.gz/md5/15a21dc979f82c71a2d743f76a539c5d -Clang.v15.0.7+8.x86_64-linux-musl-cxx11-llvm_version+15.tar.gz/sha512/684b9d596c4e0c4567841cc913ba91671030765edea4642c1883d87c12ddc2a2e4ec82931b2024a66e37c48820aeedd1fc41982930396cf657596d8d3970bf09 -Clang.v15.0.7+8.x86_64-unknown-freebsd-llvm_version+15.asserts.tar.gz/md5/82d172f6be5b1ae34fb92f85f962b9d5 -Clang.v15.0.7+8.x86_64-unknown-freebsd-llvm_version+15.asserts.tar.gz/sha512/62c39f038d7a616986314ec3da0ebc59cbed5a6cbac73becfae5a037e734922f4ab59e45a25c8c31242660fe4409023a1191c7311d0af1303d6170ec87d97413 -Clang.v15.0.7+8.x86_64-unknown-freebsd-llvm_version+15.tar.gz/md5/89c040d2773aa3f40fc1b0965d19b201 -Clang.v15.0.7+8.x86_64-unknown-freebsd-llvm_version+15.tar.gz/sha512/adb704fc2545d766da18766b2e3b6353f9865dac7e46a54e5d83b6886ca5ba8a091a8ceed86fe12bbfc0b3dc12b990dcf1389ba8d27ffe1bf8291c9855ca69b0 -Clang.v15.0.7+8.x86_64-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/md5/b4c8315bcdb815f012e8c31913a06367 -Clang.v15.0.7+8.x86_64-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/sha512/0bf975521b50c2049bc24b363ae1aa7273471ff20ffbb3d6084561c6c83fd7f08d5ce882ab976d6ec5cffac74cc5ca1c0bf888afdbd8c0c42abc3a6d61a73969 -Clang.v15.0.7+8.x86_64-w64-mingw32-cxx03-llvm_version+15.tar.gz/md5/bbb5fd33559714a6cdc89472a78279e3 -Clang.v15.0.7+8.x86_64-w64-mingw32-cxx03-llvm_version+15.tar.gz/sha512/d5635f4b34a21a39c52a494c8baa138d15b9455b08ca7d0882cb8920b5d272ae3b6f3a5f5f1ab7cafb53fd4c93d2bf9b7f1028b09c40e4991ae2e2b04669bad2 -Clang.v15.0.7+8.x86_64-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/md5/f0a7ba2ba8ba4ef2a75e4e2d9fe5e6ca -Clang.v15.0.7+8.x86_64-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/sha512/0148d0fa525f35a1b8835bfcb7f7ddfb498d118762f960b678d236d5c6b2b16b104d6a56ec2763617ada8faba68d3572d61b73d263fd7dbbbc1111359262828d -Clang.v15.0.7+8.x86_64-w64-mingw32-cxx11-llvm_version+15.tar.gz/md5/7c1a2ade54354864e5d7064e6192a111 -Clang.v15.0.7+8.x86_64-w64-mingw32-cxx11-llvm_version+15.tar.gz/sha512/abc8121210650568f4c3e84902f2ffb43f2d660c0b0efeebdcf15d11a87d6a6d0409f4db74d58acf4dea58e3167729e34c82170f65ee1735f4db47c487f723e1 +Clang.v16.0.6+4.aarch64-apple-darwin-llvm_version+16.asserts.tar.gz/md5/669e6bc49ff1715e259ea4d60dd3f0b5 +Clang.v16.0.6+4.aarch64-apple-darwin-llvm_version+16.asserts.tar.gz/sha512/e81c11d4d3c5a96a646a535362dc7f18d1be25b11304faf65b002ca68255c57e067f3022d53e3ae19e2319b1267e71da66bbb8afc0be53632c558bf08aa47f52 +Clang.v16.0.6+4.aarch64-apple-darwin-llvm_version+16.tar.gz/md5/22e32a15d23de5a4b5fa01f56c48e8b3 +Clang.v16.0.6+4.aarch64-apple-darwin-llvm_version+16.tar.gz/sha512/4228e5a66f21058d181b67e4b959f84aceb2195e90af0f28908c537e42b72b32cbd3e813e9261476597ba04d76549cedf6b5aa72eddf6cda63a34df00106a8c5 +Clang.v16.0.6+4.aarch64-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/md5/2defb4a5181b381201a6d16c6711e89b +Clang.v16.0.6+4.aarch64-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/sha512/3386402e8fcfd1c0f6d154cd8b966f308759e13919a38d148a1ad3539e046330982eefaff074f7ab454cd1d396cb92d992dda6589fa73e00fdcb237ec1cfc843 +Clang.v16.0.6+4.aarch64-linux-gnu-cxx03-llvm_version+16.tar.gz/md5/f96d1684861a5b78ec1ce22c68659907 +Clang.v16.0.6+4.aarch64-linux-gnu-cxx03-llvm_version+16.tar.gz/sha512/09327b84e86cf117a0e492d39b531690c700f4dbd3e1469ecd3d269fa5e788c173d62f972d474194999a12a3cf88e3151cc07ac418511207c8d2494c4e493f41 +Clang.v16.0.6+4.aarch64-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/md5/dd27b64b1c8a4b66647de4dda29e76bb +Clang.v16.0.6+4.aarch64-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/sha512/1fc68f4c2132eca09bb8cf9d51a1d6c223c4c705de181d973dbc5d818222da1a68eb4dc8f944750c0a715ddd73e61e7da6a4aa44c8a682bb549f0bcfe83a4044 +Clang.v16.0.6+4.aarch64-linux-gnu-cxx11-llvm_version+16.tar.gz/md5/e343df2dcacf98d7366ea9d3206f8655 +Clang.v16.0.6+4.aarch64-linux-gnu-cxx11-llvm_version+16.tar.gz/sha512/75ad869bd6ff350c37300ed6f0ceb2815023a8132c3025eb6992f33728b3c67556a503a05613a36bb6f3be39d06256bf51ba2b710f68ac2ed18004e2cd4ed133 +Clang.v16.0.6+4.aarch64-linux-musl-cxx03-llvm_version+16.asserts.tar.gz/md5/71f7ffde41325bf4946fb71429e641c9 +Clang.v16.0.6+4.aarch64-linux-musl-cxx03-llvm_version+16.asserts.tar.gz/sha512/bbeedf07ee304016c23b57b30bb9e3d9de6186805c38ada808c2f742d61b0650a40626333fe452d2491ff758a07e4ff7e438e9bb66b6a93627381ee5a772e2e5 +Clang.v16.0.6+4.aarch64-linux-musl-cxx03-llvm_version+16.tar.gz/md5/02a5e1d72a7707c4a8dff484f7b06127 +Clang.v16.0.6+4.aarch64-linux-musl-cxx03-llvm_version+16.tar.gz/sha512/591265593d59938744b7753ccff22622bcef5dac9779228ec599db0da250e39082a4dcdbf76cab6c4b79e3b9b8dcc458a426d410c70f739678e4abbd02dbcf42 +Clang.v16.0.6+4.aarch64-linux-musl-cxx11-llvm_version+16.asserts.tar.gz/md5/6e23a222df648d83a871a9e70092be22 +Clang.v16.0.6+4.aarch64-linux-musl-cxx11-llvm_version+16.asserts.tar.gz/sha512/2ab4e861762ebd9c6e269b8cb10b3238cc0535620956ce63da0b34dd2faf4a925622ee879807ca6af6ff808fdd7f8a1d69dcfbdd499da2cb9d35daf4111b5406 +Clang.v16.0.6+4.aarch64-linux-musl-cxx11-llvm_version+16.tar.gz/md5/a9a8572068bb9ba3d4fff517e4141158 +Clang.v16.0.6+4.aarch64-linux-musl-cxx11-llvm_version+16.tar.gz/sha512/addc84d7245b000e13fd128856fe7125c9cd7d66b943867b17623d73f77634638e7a4d5417ddbd441204fe0e7c26c6668d661e7f336d8e83af93f37ea92c7ce9 +Clang.v16.0.6+4.armv6l-linux-gnueabihf-cxx03-llvm_version+16.asserts.tar.gz/md5/82466ead7526e9f7e676d5281618592b +Clang.v16.0.6+4.armv6l-linux-gnueabihf-cxx03-llvm_version+16.asserts.tar.gz/sha512/6dc81a93532d7c039e115173de8d6dd28cb1dbc39a41426eb477d3c9172baa5ba0a30e1a5d4db74a9793a32fb19c691daf99794a6339d47b250ab4ea9a042dfb +Clang.v16.0.6+4.armv6l-linux-gnueabihf-cxx03-llvm_version+16.tar.gz/md5/9add4c5f6f4649baecdeb16ce8429c64 +Clang.v16.0.6+4.armv6l-linux-gnueabihf-cxx03-llvm_version+16.tar.gz/sha512/48276bd860514c27458c2d02ec047c6eabc7364c2b6f429abc081ac78bdd2ad4c9536c501029818676d8d7b1347e900f39ad58a01ba75a1cd8c14ccb78ba995c +Clang.v16.0.6+4.armv6l-linux-gnueabihf-cxx11-llvm_version+16.asserts.tar.gz/md5/4a4a38af087d005b713348ac1290fd2f +Clang.v16.0.6+4.armv6l-linux-gnueabihf-cxx11-llvm_version+16.asserts.tar.gz/sha512/380975fdecfe0a33b71dcadaa652402764f08af0bd5d9d2cffbedc34a9acc7b5a5e2c92169f35bfd7427d9a431002bbcb4381d0662c58c2d46db9e5709d0c8ae +Clang.v16.0.6+4.armv6l-linux-gnueabihf-cxx11-llvm_version+16.tar.gz/md5/602d51818dbe23cc36ddc60083ebcbb0 +Clang.v16.0.6+4.armv6l-linux-gnueabihf-cxx11-llvm_version+16.tar.gz/sha512/4593cc80d9ff046961f7f68c30502429ef7b270556b4153c5e47945e8032100470d74e9c9b62c34dd97fc9c81001615b10c31b171bfef2c67c1cd0574d0d2c95 +Clang.v16.0.6+4.armv6l-linux-musleabihf-cxx03-llvm_version+16.asserts.tar.gz/md5/e7aba5ab085a0893c4b46a4a8f0d963b +Clang.v16.0.6+4.armv6l-linux-musleabihf-cxx03-llvm_version+16.asserts.tar.gz/sha512/690b7623af2ca8c1be696b80ac42aa99ec006a5c96a2560250425bcc1c41ceec31d1e6396174ef5fa071ea1d6ecc19bfce9f5606cfaba2176ecb0993e2beb901 +Clang.v16.0.6+4.armv6l-linux-musleabihf-cxx03-llvm_version+16.tar.gz/md5/6544aa4614c5f95fa26194b4812a2ab3 +Clang.v16.0.6+4.armv6l-linux-musleabihf-cxx03-llvm_version+16.tar.gz/sha512/4b4c5aa1449416d807cbb9546c31bba655f413f6f5cffd26b20ad53d452ada64235342c430aae5ac1faaa52496bcd097d6b7ec6027b9f6dcb6ea5cb6d0c77a0d +Clang.v16.0.6+4.armv6l-linux-musleabihf-cxx11-llvm_version+16.asserts.tar.gz/md5/296309f5d25708e632866c688b5abce6 +Clang.v16.0.6+4.armv6l-linux-musleabihf-cxx11-llvm_version+16.asserts.tar.gz/sha512/29082d6a4b1c9f0d2ab650fe0fd3e9d2085ad7225ea7c4cb7dc38ee8dfc03189a5c7b060af75022f81cca17aa71e3ff96b67255398d06db77a429b2cd0bc2017 +Clang.v16.0.6+4.armv6l-linux-musleabihf-cxx11-llvm_version+16.tar.gz/md5/de7837fe7342a448586a5a176bd98549 +Clang.v16.0.6+4.armv6l-linux-musleabihf-cxx11-llvm_version+16.tar.gz/sha512/194df7bbf67fa70bf78baffecfe5438307804743a4093a5bc123cafc8085d61be9f48c5c1bda47d5b9ca65e7614119c3a235c2ddb1d311623ac92135ebcc3f0d +Clang.v16.0.6+4.armv7l-linux-gnueabihf-cxx03-llvm_version+16.asserts.tar.gz/md5/71036efacbec706ea6bb4124ac42aa14 +Clang.v16.0.6+4.armv7l-linux-gnueabihf-cxx03-llvm_version+16.asserts.tar.gz/sha512/f4f98648e8708b5053b7086d823651a1fd8df5333a0ea571f9a39f7cc4449ad19abf0556bf756d4e12ab5cbadca771f36bdece6087cf45e4377da9f572e4b4ed +Clang.v16.0.6+4.armv7l-linux-gnueabihf-cxx03-llvm_version+16.tar.gz/md5/52374b994418675552d0004c4c304329 +Clang.v16.0.6+4.armv7l-linux-gnueabihf-cxx03-llvm_version+16.tar.gz/sha512/51385ef025b1c3d06ed4db078d6458a92d3826b733051e1513c94908e9475b2a27d0a9d5df8ee3f442ac009d1bac4597c0338ed0cb06e2ec5e9d6267ad9c1be3 +Clang.v16.0.6+4.armv7l-linux-gnueabihf-cxx11-llvm_version+16.asserts.tar.gz/md5/4fbd519b4be6fc20180bfc770b38e1a8 +Clang.v16.0.6+4.armv7l-linux-gnueabihf-cxx11-llvm_version+16.asserts.tar.gz/sha512/7807b20b344186aa0fa54cebcab4371995f8d3a4e6fb1ae43fcceffd7d20be227f43346f6408375ecbfc40244be7d0cca690ba2000a8715deab19b729e2cf28f +Clang.v16.0.6+4.armv7l-linux-gnueabihf-cxx11-llvm_version+16.tar.gz/md5/3017cea85ad3f416ad0ad1131562ca38 +Clang.v16.0.6+4.armv7l-linux-gnueabihf-cxx11-llvm_version+16.tar.gz/sha512/0ad5f18a3d33d849af2055ea833764c2d77d81fc5a33d41987fb4c89ac9eaa647da10d32b3a3c86317e60bfc4af431e855ba0bc2e5ccd0800950ccb77bc0e03e +Clang.v16.0.6+4.armv7l-linux-musleabihf-cxx03-llvm_version+16.asserts.tar.gz/md5/e06e17cd995cb424628d02e0a40b7110 +Clang.v16.0.6+4.armv7l-linux-musleabihf-cxx03-llvm_version+16.asserts.tar.gz/sha512/e0466a2cbf651684ad51ee9f2a34f10b12c0f114ab795b0b16c8f18d944d956101a913e7762ddca36bfb146c5930d1e8b6a6a8508517e44276fd4e21663118d9 +Clang.v16.0.6+4.armv7l-linux-musleabihf-cxx03-llvm_version+16.tar.gz/md5/3a3357bc382a3e32f98ac557f47ee626 +Clang.v16.0.6+4.armv7l-linux-musleabihf-cxx03-llvm_version+16.tar.gz/sha512/bcc34b8ed6f3876c5f11b87f4779cc6106aa54055886c55d2f8d0ba420ddd5e498907b44b2fafcec1efd4cc9b513f26e09163cddefad60a1d07493b6a3c3d0ef +Clang.v16.0.6+4.armv7l-linux-musleabihf-cxx11-llvm_version+16.asserts.tar.gz/md5/2a938d18681235edbfb6ebb1868246f4 +Clang.v16.0.6+4.armv7l-linux-musleabihf-cxx11-llvm_version+16.asserts.tar.gz/sha512/6df520c1443b1750d5cb397389bde9478510042cdacead6420c08db9e80d69957a93e17c9918a83bf6c3b46d5b7fab71ba7efd52da641619ae3308ae510e9a54 +Clang.v16.0.6+4.armv7l-linux-musleabihf-cxx11-llvm_version+16.tar.gz/md5/304f130084074e3e4ac5d37f1c1bea57 +Clang.v16.0.6+4.armv7l-linux-musleabihf-cxx11-llvm_version+16.tar.gz/sha512/bf893e5ab3f572e1db081b484ffcddd7dc2f6c15060099a76ef00685885d8f71b9196272c5b973014f2492cd7f94cf1e3c32b9222e97d4b580baae9235539a29 +Clang.v16.0.6+4.i686-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/md5/c1f5add69bea21e906485c0267345084 +Clang.v16.0.6+4.i686-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/sha512/72b08d5001e713505a3185dc179bf1d2614e77518ad6439f92e6df69b6b328e48ba449865fdda20f5cd4a10482dd8c07398e28ffcec7ca29216c12170e34825b +Clang.v16.0.6+4.i686-linux-gnu-cxx03-llvm_version+16.tar.gz/md5/c58327d44901fb4c51bd98639be97dcd +Clang.v16.0.6+4.i686-linux-gnu-cxx03-llvm_version+16.tar.gz/sha512/21d92e946dd96e32000ab114b29db233d41e3731a6ebac9d9c876cc8e7b225be570d3751f74c98a0747121638fc6d6e63c3c3e8734cfd53a3c056dc4aec9f96a +Clang.v16.0.6+4.i686-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/md5/d8c025b9240ed4c8742ea8381b975d2c +Clang.v16.0.6+4.i686-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/sha512/2631a139162c4acdf990054eef55e68fcd83608ee708a85589a82bb4b4a2e0a0e91dfca6f4b7c2aad9b4fed61fedca9237c6f55cd1cb91a9d9bfa1c279b3cabb +Clang.v16.0.6+4.i686-linux-gnu-cxx11-llvm_version+16.tar.gz/md5/62f5a0d34e3e39ff0832f5566e77b43e +Clang.v16.0.6+4.i686-linux-gnu-cxx11-llvm_version+16.tar.gz/sha512/911c9e5a545500dac325faddbdbf33983862ebf2d2b55baf07f8003ce045710d15dc386f65c6a1c01a1c3500b831030ea686953b86631d1870ea4c6016784928 +Clang.v16.0.6+4.i686-w64-mingw32-cxx03-llvm_version+16.asserts.tar.gz/md5/aca138da6dbb96b923c3b444760901be +Clang.v16.0.6+4.i686-w64-mingw32-cxx03-llvm_version+16.asserts.tar.gz/sha512/21768ce38c29c012b36037ed7d5232b53c388d6fdfee81f13f7b04e4168957b2d8477b821452b8f602455899c779cd5d32e69a213fdaaa25e4e83d1e3c9621fd +Clang.v16.0.6+4.i686-w64-mingw32-cxx03-llvm_version+16.tar.gz/md5/10de75baa15af9f99a0a936a7c1fdcd0 +Clang.v16.0.6+4.i686-w64-mingw32-cxx03-llvm_version+16.tar.gz/sha512/686db38e54436b2816ca658cdd17b7f8de3c71d4f5bcca629968d5c2a11b91b55269aa506f8ba4a4f059f482804d7c622cdd92dbef445e2377f2cd1c6fe7265a +Clang.v16.0.6+4.i686-w64-mingw32-cxx11-llvm_version+16.asserts.tar.gz/md5/c1f92df83f99e7d920da36b470e39a4d +Clang.v16.0.6+4.i686-w64-mingw32-cxx11-llvm_version+16.asserts.tar.gz/sha512/8f67d8fc779ee738ceba20389bd68560696e94497b0455b3894027bb9773ea0f4933cac6a3c4d10fd7a391d934cff0487722be509f37654736a23c3b4f3d2dde +Clang.v16.0.6+4.i686-w64-mingw32-cxx11-llvm_version+16.tar.gz/md5/660393c8f29f5127bb32f5b1135028db +Clang.v16.0.6+4.i686-w64-mingw32-cxx11-llvm_version+16.tar.gz/sha512/a4366e839d1d4fbba30fa6599cc7cdee4d32474abb6ae5fa499e83546543917e9737ff38994bc7a9421d4fa88b8c466a50a49196482c2ddd4ab0b8a3f7556903 +Clang.v16.0.6+4.powerpc64le-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/md5/1689b18599e772b79c2bdd09bbf8f8fa +Clang.v16.0.6+4.powerpc64le-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/sha512/d7a74189addfe6ab7fca0dd7408b8b0476e3befe1cac65150a3558c6cd1cbb4b919c5f079fc892dee46b6776bad8d3251799ebb7d76d7fcaa4ef45830a551adc +Clang.v16.0.6+4.powerpc64le-linux-gnu-cxx03-llvm_version+16.tar.gz/md5/33c06a9914fea54f71d3a546583e43d1 +Clang.v16.0.6+4.powerpc64le-linux-gnu-cxx03-llvm_version+16.tar.gz/sha512/bcfbb536df4b0929a3cba2b6366d7cf0b3362c3eabd7f1e94f774a617f5134a26f3e4c41c44896c7a3e2f6c734b7efd2301de34d9883f6e2d42619d7fb9281bb +Clang.v16.0.6+4.powerpc64le-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/md5/bd983991a186c350303e1108534ab713 +Clang.v16.0.6+4.powerpc64le-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/sha512/7b96d52d66fe1bf4d967022067d5336b016947c120df7c892c03fa5b1b95a5fdbe24f220c13afc76ee25142c15aaa70000c11dab2b7e5db1445b0c73a7d3eaf5 +Clang.v16.0.6+4.powerpc64le-linux-gnu-cxx11-llvm_version+16.tar.gz/md5/1add4ae2faa3bf120b29a7d80b173b2b +Clang.v16.0.6+4.powerpc64le-linux-gnu-cxx11-llvm_version+16.tar.gz/sha512/7996347224e4b973a1174e253d022784b5a6e7fd7813d7ed47a0dccc3728bbe0432d94a89ced9e406e2ff5860d4b1646a40ec889f5dc1aec264f13608bb05661 +Clang.v16.0.6+4.x86_64-apple-darwin-llvm_version+16.asserts.tar.gz/md5/fd02d184d64e04010a5a31c1a9dd9d43 +Clang.v16.0.6+4.x86_64-apple-darwin-llvm_version+16.asserts.tar.gz/sha512/56d4be2fa7f0d2f1a6d17014ac4a8a6b302a542464c733c4637b1a3bbfc2bcf42755783445ba0f272d9bbbfb9f755eedb46d3ade8252136d9f3c4f328aac78d8 +Clang.v16.0.6+4.x86_64-apple-darwin-llvm_version+16.tar.gz/md5/8977be77d1e9c9fd7d4c509fd20e96d5 +Clang.v16.0.6+4.x86_64-apple-darwin-llvm_version+16.tar.gz/sha512/852290b61a1489f9251471d9863bdec6bbf179c325df6dd5acfc77937cd3cdecf0c988cc9eb02ce3ac5a5b7a44eb1c72ecb66f8cdd27b4f5b7bda237aa1ab1c2 +Clang.v16.0.6+4.x86_64-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/md5/e28a61211a0ff79356ce55126f934f6c +Clang.v16.0.6+4.x86_64-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/sha512/4a842bc9758d3ffba3143aabc4f4c91f5a73531565d7ed0a53191996b3e157c0cd0117bf5f9deb28a226f1bbd2a5c5bc880fb58d79673db2661d95baa73463fc +Clang.v16.0.6+4.x86_64-linux-gnu-cxx03-llvm_version+16.tar.gz/md5/31647f7142d211ce00910f806b203193 +Clang.v16.0.6+4.x86_64-linux-gnu-cxx03-llvm_version+16.tar.gz/sha512/15e10f67a5276aad4ac27fa6134b10b917b4e3dbeb19e615cf2691edc045029ece958a0208e347cab7c50fe0125e7b5cff97a0f73b5f05fc29dd9f4bbd439917 +Clang.v16.0.6+4.x86_64-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/md5/143114515a5460f569ab2b2bbb6f38a6 +Clang.v16.0.6+4.x86_64-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/sha512/4cbb239fae8d8677165a3ecf8b43a5583eb053bd6861fc16db0090574c3e588b07af9b195ab615d02836e28f069341d29540863578f3455c76a109aa81888672 +Clang.v16.0.6+4.x86_64-linux-gnu-cxx11-llvm_version+16.tar.gz/md5/bb7c9cedba80462c3414aafe3abe1960 +Clang.v16.0.6+4.x86_64-linux-gnu-cxx11-llvm_version+16.tar.gz/sha512/faaade14199c876784ecdb7a6a60a937591a6cc8ecef58a2cbcd52b7f94a9f282bb3a9ef51e1956081f1898bdfe6a63e3bbf1da677331888e164d25cb270690e +Clang.v16.0.6+4.x86_64-linux-musl-cxx03-llvm_version+16.asserts.tar.gz/md5/477eac2f9d86be87ac488141acda2e5b +Clang.v16.0.6+4.x86_64-linux-musl-cxx03-llvm_version+16.asserts.tar.gz/sha512/205a2bde47551dd09d5eb8bed193f1b64b18d423464c5bf7379553576d88fd33c23b6d43c4ad37587373fe41d7a3f1c3e0921efa7013dce1f314e88c6c3641a0 +Clang.v16.0.6+4.x86_64-linux-musl-cxx03-llvm_version+16.tar.gz/md5/e885ef6dc19efc4160eac81e99f9a220 +Clang.v16.0.6+4.x86_64-linux-musl-cxx03-llvm_version+16.tar.gz/sha512/30f6ab9e0c4b1f1ac8b1429f0e3b8d222b371c4c1198332d11268930cf425d3660cfda0fcddf0d2ba57afb74ebee1c39c0a3e92aaf1fb7564c33c053e58fa872 +Clang.v16.0.6+4.x86_64-linux-musl-cxx11-llvm_version+16.asserts.tar.gz/md5/d962d03f5a979124cde704add0e2a801 +Clang.v16.0.6+4.x86_64-linux-musl-cxx11-llvm_version+16.asserts.tar.gz/sha512/d677f1abff6c06411327ab78bd5276a6f880c3eb5e3ef32dcb02fbd64efe6751d24fb6bee0a45498a0e38f678a922ac95813da0ba025d5363f7a20c9dec44155 +Clang.v16.0.6+4.x86_64-linux-musl-cxx11-llvm_version+16.tar.gz/md5/4ea0a8e61ee8058982a1a4df1b30c001 +Clang.v16.0.6+4.x86_64-linux-musl-cxx11-llvm_version+16.tar.gz/sha512/ce95b6798901a84116542bf8566f520dccd5aacb4af8f8c7542abec62dc3c1d7136e6aa8922c5c2f2f61103730e7629e507acbff670b6f59ee62aa1f9a3d6e7f +Clang.v16.0.6+4.x86_64-unknown-freebsd-llvm_version+16.asserts.tar.gz/md5/03304c40a1e4727b9db5052e59ae5884 +Clang.v16.0.6+4.x86_64-unknown-freebsd-llvm_version+16.asserts.tar.gz/sha512/70467c23ab0a25c490803f78de10ae10b0afafcb76b77fd3ec325c4fecfcf03463c2b0a1842a7657f57c668811f4854776328e9af28227587f8268bb79363440 +Clang.v16.0.6+4.x86_64-unknown-freebsd-llvm_version+16.tar.gz/md5/461da00bc30e1dad361dce9ef79e0e42 +Clang.v16.0.6+4.x86_64-unknown-freebsd-llvm_version+16.tar.gz/sha512/51bc60c08fb0df8a565c88563642ef4a9ed4f8bb956a18f12ff570b673f69a26a2c8e8775c785a753727586336f276c42ce25385e7a813e227a93d2b532378e1 +Clang.v16.0.6+4.x86_64-w64-mingw32-cxx03-llvm_version+16.asserts.tar.gz/md5/3e6d5b7230676a2c149524cba960706c +Clang.v16.0.6+4.x86_64-w64-mingw32-cxx03-llvm_version+16.asserts.tar.gz/sha512/906d17378d957138de5f54bf0c8e8bda505a299d79c8b34b3db5113d2f9f732535ec464abbd198504f2a73c905f9209491ba30616e99258162c5045eb8e722d1 +Clang.v16.0.6+4.x86_64-w64-mingw32-cxx03-llvm_version+16.tar.gz/md5/a2c0cc701b90f580700a6d60186db8f0 +Clang.v16.0.6+4.x86_64-w64-mingw32-cxx03-llvm_version+16.tar.gz/sha512/e85533953837ed4caa6c429ce4228fa85e8b667c0212211755ea9be0029149d6e08f19b1d02cf5e69134250f1aa8918630b1623ad1ae2cd0b0e8535ee769c9cb +Clang.v16.0.6+4.x86_64-w64-mingw32-cxx11-llvm_version+16.asserts.tar.gz/md5/5de809c792f82069cd68693e90ae8521 +Clang.v16.0.6+4.x86_64-w64-mingw32-cxx11-llvm_version+16.asserts.tar.gz/sha512/b62502667136444890b85e589e3dd19153d71d23f9e989baa0cf83af09022b7ea042e6da5eff9094e50f0ba8200cbfc7a20ca76b89c251d0b887a0a970b28e65 +Clang.v16.0.6+4.x86_64-w64-mingw32-cxx11-llvm_version+16.tar.gz/md5/4b4c3064a5e613e2ac2d558a29d34a42 +Clang.v16.0.6+4.x86_64-w64-mingw32-cxx11-llvm_version+16.tar.gz/sha512/93739f9b85ae4d345c7522ca8ce8edb1c699d16756bec7fbbe44d5d3429a5c9ed2bd587c4ccf57df8a1758b8e52be009448db86e1de372fea02e3ab2a0221837 diff --git a/deps/checksums/compilersupportlibraries b/deps/checksums/compilersupportlibraries index 0908c34e13a58..48843f21c0feb 100644 --- a/deps/checksums/compilersupportlibraries +++ b/deps/checksums/compilersupportlibraries @@ -1,92 +1,92 @@ -CompilerSupportLibraries.v1.0.5+1.aarch64-apple-darwin-libgfortran5.tar.gz/md5/20ebaad57850393b6ac9fa924e511fe4 -CompilerSupportLibraries.v1.0.5+1.aarch64-apple-darwin-libgfortran5.tar.gz/sha512/020de4d8b0ff6bedbadaa305ff8445e6849f12053762ea4aa68412d1ec763dbd86f479587a2fbb862487f1feb04d976c38099ddf3887817a3d32b3f029cf85b1 -CompilerSupportLibraries.v1.0.5+1.aarch64-linux-gnu-libgfortran3.tar.gz/md5/3908fa1a2f739b330e787468c9bfb5c8 -CompilerSupportLibraries.v1.0.5+1.aarch64-linux-gnu-libgfortran3.tar.gz/sha512/1741e3403ac7aa99e7cfd9a01222c4153ed300f47cc1b347e1af1a6cd07a82caaa54b9cfbebae8751440420551621cc6524504413446d104f9493dff2c081853 -CompilerSupportLibraries.v1.0.5+1.aarch64-linux-gnu-libgfortran4.tar.gz/md5/2444dbb7637b32cf543675cc12330878 -CompilerSupportLibraries.v1.0.5+1.aarch64-linux-gnu-libgfortran4.tar.gz/sha512/8537f0b243df8544350c884021b21c585fd302e8dd462a30a6ee84c7a36a049133262e5d1bc362f972066b8e8d6a091c32c3b746bab1feb9fccf2e7cca65756c -CompilerSupportLibraries.v1.0.5+1.aarch64-linux-gnu-libgfortran5.tar.gz/md5/d79c1434594c0c5e7d6be798bf52c99e -CompilerSupportLibraries.v1.0.5+1.aarch64-linux-gnu-libgfortran5.tar.gz/sha512/7e71accc401a45b51b298702fb4c79a2fc856c7b28f0935f6ad3a0db5381c55fe5432daff371842930d718024b7c6c1d80e2bd09d397145203673bebbe3496ae -CompilerSupportLibraries.v1.0.5+1.aarch64-linux-musl-libgfortran3.tar.gz/md5/f212059053d99558a9b0bf54b20180e1 -CompilerSupportLibraries.v1.0.5+1.aarch64-linux-musl-libgfortran3.tar.gz/sha512/5c104b1282cec8a944e5d008f44a4d60f4394fd5d797fec7d1f487d13e7328cd9c88ec4916dabf18596d87160756bda914e4f8c5a356b5577f9349d0d9e976d6 -CompilerSupportLibraries.v1.0.5+1.aarch64-linux-musl-libgfortran4.tar.gz/md5/3e3b3795ee93ef317223050e803a9875 -CompilerSupportLibraries.v1.0.5+1.aarch64-linux-musl-libgfortran4.tar.gz/sha512/85d3c955e15f66bfe8bfec2f28c9160bc03d4d531ea4ffe6bc6b51e0d69ccea3ab67a16ca752dabc870861c407381c4519d75c6be3832e8dccd6122ec8c6ed75 -CompilerSupportLibraries.v1.0.5+1.aarch64-linux-musl-libgfortran5.tar.gz/md5/cf2d1315f6a348af2e6c065e2a286e7a -CompilerSupportLibraries.v1.0.5+1.aarch64-linux-musl-libgfortran5.tar.gz/sha512/58420377bc77aa7678034ee5f708eb6be7db359faef2c2638869765453633da9bf455512bd88e95b38ae0428ecc4053561517b176b2371129bdaef9d8d5dadfd -CompilerSupportLibraries.v1.0.5+1.armv6l-linux-gnueabihf-libgfortran3.tar.gz/md5/f5c09ed7e0eeb8d345d328f950582f26 -CompilerSupportLibraries.v1.0.5+1.armv6l-linux-gnueabihf-libgfortran3.tar.gz/sha512/9c657f55c8fcdeb404be168a3a63a5e84304730fe34f25673d92cdae4b0a1fcc6a877ee1433f060e1be854c7811d66632e32510a2ed591d88330f1340b9c20de -CompilerSupportLibraries.v1.0.5+1.armv6l-linux-gnueabihf-libgfortran4.tar.gz/md5/c685518aca4721cd8621d510e2039683 -CompilerSupportLibraries.v1.0.5+1.armv6l-linux-gnueabihf-libgfortran4.tar.gz/sha512/b760468c6377dcd2b8dd50200daaabe604006afc070984d78152b2becd0680b59036c9a6e91dea490121bd85b58d285bfc1e1cf696d29af236528400101de36c -CompilerSupportLibraries.v1.0.5+1.armv6l-linux-gnueabihf-libgfortran5.tar.gz/md5/8faf5c8ad62ab10f71dd2ec9683053e2 -CompilerSupportLibraries.v1.0.5+1.armv6l-linux-gnueabihf-libgfortran5.tar.gz/sha512/921239f241a5c89710cf07272d7f6c3f10201a7533068ed1e9643f9fb2f439e1bb765a4966d913829866ee0ce4f1589d30d06e4b5c1361e3c016a9473f087177 -CompilerSupportLibraries.v1.0.5+1.armv6l-linux-musleabihf-libgfortran3.tar.gz/md5/b38fcb70691ac2621379d298eef8c79e -CompilerSupportLibraries.v1.0.5+1.armv6l-linux-musleabihf-libgfortran3.tar.gz/sha512/06c7f64257ce721f5941f6e50a0d2717cdc9394fc532ded19ce3eaacd5e92a416969534227562e4fee04d2b6340c650d8bc9779e14519b90038bc41e8d1f5ce3 -CompilerSupportLibraries.v1.0.5+1.armv6l-linux-musleabihf-libgfortran4.tar.gz/md5/cdfab2c7bc41765caf4441c3caeed761 -CompilerSupportLibraries.v1.0.5+1.armv6l-linux-musleabihf-libgfortran4.tar.gz/sha512/7109d4a7b32c00309c42685f54a86fc2cc63c0c00f65584ad296b6e44ad3320eed1aaf49684a8831841cdffa5555d72f89272fb722a780596e27ef020528026b -CompilerSupportLibraries.v1.0.5+1.armv6l-linux-musleabihf-libgfortran5.tar.gz/md5/441980ebd23d72772cbe603f1c275336 -CompilerSupportLibraries.v1.0.5+1.armv6l-linux-musleabihf-libgfortran5.tar.gz/sha512/e273d9f1af259a3080df8f173e1808a1ade976a943aba97216bf59a96178e7c052e7a048b0ceee53ab486ed577a2ecb92579857be2f7b29e76322ee1f13c9d76 -CompilerSupportLibraries.v1.0.5+1.armv7l-linux-gnueabihf-libgfortran3.tar.gz/md5/f5c09ed7e0eeb8d345d328f950582f26 -CompilerSupportLibraries.v1.0.5+1.armv7l-linux-gnueabihf-libgfortran3.tar.gz/sha512/9c657f55c8fcdeb404be168a3a63a5e84304730fe34f25673d92cdae4b0a1fcc6a877ee1433f060e1be854c7811d66632e32510a2ed591d88330f1340b9c20de -CompilerSupportLibraries.v1.0.5+1.armv7l-linux-gnueabihf-libgfortran4.tar.gz/md5/c685518aca4721cd8621d510e2039683 -CompilerSupportLibraries.v1.0.5+1.armv7l-linux-gnueabihf-libgfortran4.tar.gz/sha512/b760468c6377dcd2b8dd50200daaabe604006afc070984d78152b2becd0680b59036c9a6e91dea490121bd85b58d285bfc1e1cf696d29af236528400101de36c -CompilerSupportLibraries.v1.0.5+1.armv7l-linux-gnueabihf-libgfortran5.tar.gz/md5/8faf5c8ad62ab10f71dd2ec9683053e2 -CompilerSupportLibraries.v1.0.5+1.armv7l-linux-gnueabihf-libgfortran5.tar.gz/sha512/921239f241a5c89710cf07272d7f6c3f10201a7533068ed1e9643f9fb2f439e1bb765a4966d913829866ee0ce4f1589d30d06e4b5c1361e3c016a9473f087177 -CompilerSupportLibraries.v1.0.5+1.armv7l-linux-musleabihf-libgfortran3.tar.gz/md5/b38fcb70691ac2621379d298eef8c79e -CompilerSupportLibraries.v1.0.5+1.armv7l-linux-musleabihf-libgfortran3.tar.gz/sha512/06c7f64257ce721f5941f6e50a0d2717cdc9394fc532ded19ce3eaacd5e92a416969534227562e4fee04d2b6340c650d8bc9779e14519b90038bc41e8d1f5ce3 -CompilerSupportLibraries.v1.0.5+1.armv7l-linux-musleabihf-libgfortran4.tar.gz/md5/cdfab2c7bc41765caf4441c3caeed761 -CompilerSupportLibraries.v1.0.5+1.armv7l-linux-musleabihf-libgfortran4.tar.gz/sha512/7109d4a7b32c00309c42685f54a86fc2cc63c0c00f65584ad296b6e44ad3320eed1aaf49684a8831841cdffa5555d72f89272fb722a780596e27ef020528026b -CompilerSupportLibraries.v1.0.5+1.armv7l-linux-musleabihf-libgfortran5.tar.gz/md5/441980ebd23d72772cbe603f1c275336 -CompilerSupportLibraries.v1.0.5+1.armv7l-linux-musleabihf-libgfortran5.tar.gz/sha512/e273d9f1af259a3080df8f173e1808a1ade976a943aba97216bf59a96178e7c052e7a048b0ceee53ab486ed577a2ecb92579857be2f7b29e76322ee1f13c9d76 -CompilerSupportLibraries.v1.0.5+1.i686-linux-gnu-libgfortran3.tar.gz/md5/6decf8fd5afb50451771c761e63a8917 -CompilerSupportLibraries.v1.0.5+1.i686-linux-gnu-libgfortran3.tar.gz/sha512/4984724bcc847724b1bc005b6f760a18b68147f7d5402d0faf4e28fc0d14fa10975368a951f9caf2a8856500046dec8343043274557d58269e77492b929a9e4b -CompilerSupportLibraries.v1.0.5+1.i686-linux-gnu-libgfortran4.tar.gz/md5/39d1e8a3baa144c018d3eaf7f3806482 -CompilerSupportLibraries.v1.0.5+1.i686-linux-gnu-libgfortran4.tar.gz/sha512/fc4d429279c5a93b6c28b6e911b1e7cfd1c1cfe46f11f2e901b3832ce90d45f49d3d29f0ef18518a94af6cc8651f67c4ed81672680f9281ada390440b172a2af -CompilerSupportLibraries.v1.0.5+1.i686-linux-gnu-libgfortran5.tar.gz/md5/37dabd9cd224c9fed9633dedccb6c565 -CompilerSupportLibraries.v1.0.5+1.i686-linux-gnu-libgfortran5.tar.gz/sha512/b253149e72eef9486888fbaace66e9b6945f4477f6b818f64f3047331165b0e2bc17aa6e3fc8c88686a72e478eb62c8f53883415d5419db448d8016fa3a1da5e -CompilerSupportLibraries.v1.0.5+1.i686-linux-musl-libgfortran3.tar.gz/md5/afdd32bfadd465848e6be458817a44ae -CompilerSupportLibraries.v1.0.5+1.i686-linux-musl-libgfortran3.tar.gz/sha512/eebd679c499143014514c7c9d1875dedbbab9e3af51526c4dd445a9e3dbade95d24522da8bbad0a50ab400755e47b018828b324c4ad7705e212ccd990e34439a -CompilerSupportLibraries.v1.0.5+1.i686-linux-musl-libgfortran4.tar.gz/md5/bc4a0f0b7cea328f7e8850583774496b -CompilerSupportLibraries.v1.0.5+1.i686-linux-musl-libgfortran4.tar.gz/sha512/82285b67946212b49cddf6259f2c60ff5469f8c5263ccefe44f1d93ace98ab68e2c152e1b54434b2f075fd8d192c06d5451bc8cca26d951ad15f3453102f02b5 -CompilerSupportLibraries.v1.0.5+1.i686-linux-musl-libgfortran5.tar.gz/md5/177f0232abce8d523882530ed7a93092 -CompilerSupportLibraries.v1.0.5+1.i686-linux-musl-libgfortran5.tar.gz/sha512/db80acf0f2434f28ee7680e1beb34f564940071815d1ad89fb5913cbd9ac24da528e826d0d54be6265a7340ebd661b6d308ed79d96b67fa5d8c98dc3f1bee8d6 -CompilerSupportLibraries.v1.0.5+1.i686-w64-mingw32-libgfortran3.tar.gz/md5/f5795dada5360eb8422f45150b13bae9 -CompilerSupportLibraries.v1.0.5+1.i686-w64-mingw32-libgfortran3.tar.gz/sha512/6acd1bf7c81631cef9b8b0576ccece08723c5ae2f49de2487d3aefd25f9a0ad49df09e3782735267997d40687b04b85c89e00f6889b026af599bf1bbe91803a1 -CompilerSupportLibraries.v1.0.5+1.i686-w64-mingw32-libgfortran4.tar.gz/md5/5e590f83161913f0145ba8d496b2504b -CompilerSupportLibraries.v1.0.5+1.i686-w64-mingw32-libgfortran4.tar.gz/sha512/4a3f36588afcdef26173764597054068e26f2376e6126a9a94c46b258b5d7a29951d47b5e1ba24df6c3d139bbc4decc5c501a266811692d7fadadc7bd7b6960d -CompilerSupportLibraries.v1.0.5+1.i686-w64-mingw32-libgfortran5.tar.gz/md5/27da4a7c890fe1427c33fe214cc5feaf -CompilerSupportLibraries.v1.0.5+1.i686-w64-mingw32-libgfortran5.tar.gz/sha512/310ad00f053f9f3ec715ce2e8d20446f397728dff5acc787ea9c9332346607a3d42b678099c424e6d6e5294acddf2aa26051de657b48d34abfd04486951bf241 -CompilerSupportLibraries.v1.0.5+1.powerpc64le-linux-gnu-libgfortran3.tar.gz/md5/4e5e4b23dc87450738da33926a07511d -CompilerSupportLibraries.v1.0.5+1.powerpc64le-linux-gnu-libgfortran3.tar.gz/sha512/fc09879d94b750e75775d8b64a41ab9924d675fb53c5700467604412928fe7f5cb21911da0f64898d2463fa77ffbaf4c96c397b9060f4746eec152747930cddc -CompilerSupportLibraries.v1.0.5+1.powerpc64le-linux-gnu-libgfortran4.tar.gz/md5/9a92138ed69aa317a932a615c6e62d69 -CompilerSupportLibraries.v1.0.5+1.powerpc64le-linux-gnu-libgfortran4.tar.gz/sha512/0b7785379936a2a209b074177b1424dd7e00b29b5165f564e799b0aa4e06a582e9d616525d97274ba2507cb88192028f1ac485d3f99bdc7ee53fc63c1a7e85de -CompilerSupportLibraries.v1.0.5+1.powerpc64le-linux-gnu-libgfortran5.tar.gz/md5/8ffee3d6de5197c7a1f354d72c8238fa -CompilerSupportLibraries.v1.0.5+1.powerpc64le-linux-gnu-libgfortran5.tar.gz/sha512/deadc4d7224c84f9b82dc956b69e815c44ae036802838365d870ab9f58c8bcf8ce0645f2f387c8ff344ac2108fc8e7e1ee907fa55e93c91aa5d9fd921bf3fdcb -CompilerSupportLibraries.v1.0.5+1.x86_64-apple-darwin-libgfortran3.tar.gz/md5/87449e72e3f33dbb69b7053cdc2649d4 -CompilerSupportLibraries.v1.0.5+1.x86_64-apple-darwin-libgfortran3.tar.gz/sha512/5ce02ad10c6f4686a476eb2a5de2988cd8b482f5e693db2880c84ad1c82f468ef03fe01b9d0feefe5d4ee741d1d16643d36b144e6261ed32311b3b6f312fac2f -CompilerSupportLibraries.v1.0.5+1.x86_64-apple-darwin-libgfortran4.tar.gz/md5/0407cde92cfa42fa89ac83217ca0ec16 -CompilerSupportLibraries.v1.0.5+1.x86_64-apple-darwin-libgfortran4.tar.gz/sha512/032c831f1166a336551138939ac40eb2c68a048ce786c0c1403b879a20c1b706caac16d22560b2c7f2b3d6373986c347188675674116005ca251336ee048d09f -CompilerSupportLibraries.v1.0.5+1.x86_64-apple-darwin-libgfortran5.tar.gz/md5/23418763b808371ee94772a90d501f4d -CompilerSupportLibraries.v1.0.5+1.x86_64-apple-darwin-libgfortran5.tar.gz/sha512/7867b843551457b11bda7821dd384c1c1cf23b80a308b2058a693de7b7da099f0b37eb0a6de2b84c04b625a68c60eea55138e200d5d6ec6f6af09bd7ce406a96 -CompilerSupportLibraries.v1.0.5+1.x86_64-linux-gnu-libgfortran3.tar.gz/md5/e3d33ae03c18affea74699bdc1fabb68 -CompilerSupportLibraries.v1.0.5+1.x86_64-linux-gnu-libgfortran3.tar.gz/sha512/42013f4921de5a69ad857195ce5c19ad1bca3c920d79699e5501f1f4534ab132fabd422362b2b5056f5d182215d6c069db5df460bafa700903faf962cc00f77b -CompilerSupportLibraries.v1.0.5+1.x86_64-linux-gnu-libgfortran4.tar.gz/md5/d40c1e8c0393213c6057c53a12f44175 -CompilerSupportLibraries.v1.0.5+1.x86_64-linux-gnu-libgfortran4.tar.gz/sha512/fe7baa4de7490065ab7b953cc12f41462a24bcb49d0a4a64b23249e98e7569b19bb1cb455af2f76090e34066a7d3cdd7a48cae6515ce6c7a5c8486b0cacc5106 -CompilerSupportLibraries.v1.0.5+1.x86_64-linux-gnu-libgfortran5.tar.gz/md5/48541b90f715c4c86ee4da0570275947 -CompilerSupportLibraries.v1.0.5+1.x86_64-linux-gnu-libgfortran5.tar.gz/sha512/7f2683fb98e80f12629f4ed3bea9fd59d32b7e7a9ed1699e782d8e238ff0915ecc61bf00adaf4597cfe41caf82cdca0f9be250f595f5f0bea6d8f77dba99eaf4 -CompilerSupportLibraries.v1.0.5+1.x86_64-linux-musl-libgfortran3.tar.gz/md5/4547059eb905995667be48bf85d49911 -CompilerSupportLibraries.v1.0.5+1.x86_64-linux-musl-libgfortran3.tar.gz/sha512/7400fdabc924434ab4a4949248c3603887ac06ffd2f205ae33e14495d86cd4f816bbd1999eeafa0257f518df1e7f7c522f596e847a71dbfbfccff4859f50acc7 -CompilerSupportLibraries.v1.0.5+1.x86_64-linux-musl-libgfortran4.tar.gz/md5/46267543cad6584d7b7b9fcc8f18f21d -CompilerSupportLibraries.v1.0.5+1.x86_64-linux-musl-libgfortran4.tar.gz/sha512/0353d7d724be48d4185d3c181692970b7996f53f6a01723072aa5c94b53a8c5055faeed30df51659c252a46f4b941dec0cb24569323e3c85c166f14c5b7c8e9e -CompilerSupportLibraries.v1.0.5+1.x86_64-linux-musl-libgfortran5.tar.gz/md5/14dba2897a6e9d370fa9091c045375fc -CompilerSupportLibraries.v1.0.5+1.x86_64-linux-musl-libgfortran5.tar.gz/sha512/10b79f9c059839f5b57fa8d2a381a034c4067262c4088bd354d14ea56bec097878069383aa9cfadaa09d73bd20fc348fb61662d863a8d62cb25d7af6b8e29858 -CompilerSupportLibraries.v1.0.5+1.x86_64-unknown-freebsd-libgfortran3.tar.gz/md5/1f069e9c832fa1e9c7c8d51e3e841f5c -CompilerSupportLibraries.v1.0.5+1.x86_64-unknown-freebsd-libgfortran3.tar.gz/sha512/877ed9953bc167ade224fc503a2b639c7c333d420804ccf0d3b1637e29bdaf8c608a8f7accb3ec7983d6881c4b00729a1327a121c741022aff1e627cdffb52ce -CompilerSupportLibraries.v1.0.5+1.x86_64-unknown-freebsd-libgfortran4.tar.gz/md5/3e542990ca4192dcecf2e8b8b17e8580 -CompilerSupportLibraries.v1.0.5+1.x86_64-unknown-freebsd-libgfortran4.tar.gz/sha512/0b9cf0f431a5de28bc11af31d965beaf6774d4e83cb3877fdca630d0327312eb966485d6a99d62b0212f505a6714c23fc7ac1ed17ec619baff13941f6ce7519c -CompilerSupportLibraries.v1.0.5+1.x86_64-unknown-freebsd-libgfortran5.tar.gz/md5/b09302632fda815a1248884a82a6f95a -CompilerSupportLibraries.v1.0.5+1.x86_64-unknown-freebsd-libgfortran5.tar.gz/sha512/7a64ae14e40d285bbcd27a0f54ba6ad0a108ee4f4fed7c99d3a876a70578445e0c7108fa945f3f5a0b202cf95e533b96eedaf7641ded6e9869af77db98075709 -CompilerSupportLibraries.v1.0.5+1.x86_64-w64-mingw32-libgfortran3.tar.gz/md5/0c2fc6fae4ebe293a7f0dc1e91f6531a -CompilerSupportLibraries.v1.0.5+1.x86_64-w64-mingw32-libgfortran3.tar.gz/sha512/fdb0ad061cacad0557fde3ec216fd3666284f24ad6a86f4a4b6f946dccb112c9704f52edba86f3b17d84c824affbcfef740720348ef227380cf6017811bda80b -CompilerSupportLibraries.v1.0.5+1.x86_64-w64-mingw32-libgfortran4.tar.gz/md5/005e608dbef2b5cdb7624702ccc426be -CompilerSupportLibraries.v1.0.5+1.x86_64-w64-mingw32-libgfortran4.tar.gz/sha512/8bb2bcd0a6b1901e8a9be20f505bead5c78ecafbe5a8271cd13385553e5744e0c7bff62976ac9e7d74b8f3bd467603d4c0f5658e6b120bb23066c15e0a644ed4 -CompilerSupportLibraries.v1.0.5+1.x86_64-w64-mingw32-libgfortran5.tar.gz/md5/d6c2c7ad72bff7f7e5c43678d716a57a -CompilerSupportLibraries.v1.0.5+1.x86_64-w64-mingw32-libgfortran5.tar.gz/sha512/36f5eba1b0be440797467cb7104652b74709913d2bad1b08ee2dc70f450fb8eab81b28f2b0bc8dfc238b3c46982c69aac831b4fad5bcee4e9dd114852fcb4a0b +CompilerSupportLibraries.v1.1.1+0.aarch64-apple-darwin-libgfortran5.tar.gz/md5/20ebaad57850393b6ac9fa924e511fe4 +CompilerSupportLibraries.v1.1.1+0.aarch64-apple-darwin-libgfortran5.tar.gz/sha512/020de4d8b0ff6bedbadaa305ff8445e6849f12053762ea4aa68412d1ec763dbd86f479587a2fbb862487f1feb04d976c38099ddf3887817a3d32b3f029cf85b1 +CompilerSupportLibraries.v1.1.1+0.aarch64-linux-gnu-libgfortran3.tar.gz/md5/d641904255ee412c45b089d92c53262b +CompilerSupportLibraries.v1.1.1+0.aarch64-linux-gnu-libgfortran3.tar.gz/sha512/ace0383fe9bd64faeed1fb05a11bbec932bd56b8460d06d2b7c3e1b5f4f6e9a9b3345937088684e5cd1ca9a85ef1a5ff56a97a1f60449cd6e35247de1e123d81 +CompilerSupportLibraries.v1.1.1+0.aarch64-linux-gnu-libgfortran4.tar.gz/md5/2a71f320d8b9242ad26aabed74cbf404 +CompilerSupportLibraries.v1.1.1+0.aarch64-linux-gnu-libgfortran4.tar.gz/sha512/03e2a4482baaca2d6ce5cc207224d03bd7851486ebe8072c7317f5fcdd641395d945552d9462ab44a9f2e4b0ffaa3874a76f314d67bc0f75393a1151ab518611 +CompilerSupportLibraries.v1.1.1+0.aarch64-linux-gnu-libgfortran5.tar.gz/md5/1beec15ad689a5f572040ca2a7b6a880 +CompilerSupportLibraries.v1.1.1+0.aarch64-linux-gnu-libgfortran5.tar.gz/sha512/27bbe212a8d43e841cf8f3e9964b72bc220fea03cf5e65721b02d2f3aa5193acdce41e512578ed6be935b413cd0d2224a6bcd2e9624931f39092ba3cfc5cbcc0 +CompilerSupportLibraries.v1.1.1+0.aarch64-linux-musl-libgfortran3.tar.gz/md5/9e949c2efe48a7b2a62bff7e1ffdede0 +CompilerSupportLibraries.v1.1.1+0.aarch64-linux-musl-libgfortran3.tar.gz/sha512/2947acb250f8ff4936da5ed02ddbfa492fc38bc87baa588a36bb892ba68b6636a912cda976f8fff00cc7a710c3bfb185826b4cd4a726750ef5f161d5f1aa21a2 +CompilerSupportLibraries.v1.1.1+0.aarch64-linux-musl-libgfortran4.tar.gz/md5/7202764b1a89a748b07460d9c40a9279 +CompilerSupportLibraries.v1.1.1+0.aarch64-linux-musl-libgfortran4.tar.gz/sha512/63236225a9becdd166c4395ea5081c64f57bc51af89c2edb5abeb419d6eb8224a380a633afd861bb84a12435fd19c8554cbe5ffadf8324ff2c7f17021ed53e69 +CompilerSupportLibraries.v1.1.1+0.aarch64-linux-musl-libgfortran5.tar.gz/md5/f66c30d3cec8057ae47f05df022ead51 +CompilerSupportLibraries.v1.1.1+0.aarch64-linux-musl-libgfortran5.tar.gz/sha512/5329d9469bb0f47560e52b15eb21ab70e0e2da0275bdb2f8e6ed4feb132bc9989a6b44984329455104546c95d05a05f8fb4f1cf232856219ba005100f4b16dc3 +CompilerSupportLibraries.v1.1.1+0.armv6l-linux-gnueabihf-libgfortran3.tar.gz/md5/05ff63780f5b7c8c6c590c3626f32ac0 +CompilerSupportLibraries.v1.1.1+0.armv6l-linux-gnueabihf-libgfortran3.tar.gz/sha512/8d3c4149531f3782f5efbb6a6fbbb7080ba005298ba962b5bc5f66250ea9fde91b34836ed909c16f306d21d2e358f985360962e9362a8e807ccd4254da3bb19b +CompilerSupportLibraries.v1.1.1+0.armv6l-linux-gnueabihf-libgfortran4.tar.gz/md5/3ca2b6e8101d831e546c1b6ed2ca9a42 +CompilerSupportLibraries.v1.1.1+0.armv6l-linux-gnueabihf-libgfortran4.tar.gz/sha512/21a0b9c5acde96c0a91303f4f395e55f272d5585ad18f0365105188d129a3ca94ad66d4dd99b471abdf41a7a7262a3b258fd04b887110ad15255b284cd1612b0 +CompilerSupportLibraries.v1.1.1+0.armv6l-linux-gnueabihf-libgfortran5.tar.gz/md5/d4d560b8ecce0ff2cb4dbc88cb25942a +CompilerSupportLibraries.v1.1.1+0.armv6l-linux-gnueabihf-libgfortran5.tar.gz/sha512/d405f61525af1b2fe85107a70ed67b8a1eb767923487fa71539e0f49d6e70358c8a24f4ef1c224256cf677af99b54a2f8243f1e207350fcb14d426a7a6bb3915 +CompilerSupportLibraries.v1.1.1+0.armv6l-linux-musleabihf-libgfortran3.tar.gz/md5/8c6eddaa156fd0afee28ac5a154bc3f7 +CompilerSupportLibraries.v1.1.1+0.armv6l-linux-musleabihf-libgfortran3.tar.gz/sha512/b9fc86bb706ad98d61b63eb4cc8bfce6b2c67b58ba2cebecea7574f44790cce044bb1b4db1d20050b59538fa43b51cb352d752c77333a0f0621fde47c63a3596 +CompilerSupportLibraries.v1.1.1+0.armv6l-linux-musleabihf-libgfortran4.tar.gz/md5/0a54c16fea86c6dadb39eff65c465528 +CompilerSupportLibraries.v1.1.1+0.armv6l-linux-musleabihf-libgfortran4.tar.gz/sha512/c635c636384d3af5b4b078be7398fbc665a185eae69dd223279affb4836fb5c575d6ab296ae940ccbe73777bdb5e355f4f28a2fa27606ac143ff424641c60c65 +CompilerSupportLibraries.v1.1.1+0.armv6l-linux-musleabihf-libgfortran5.tar.gz/md5/892dfd91703f0f77d170a5371a1c25d4 +CompilerSupportLibraries.v1.1.1+0.armv6l-linux-musleabihf-libgfortran5.tar.gz/sha512/8ac59d00192c0e847168e61b3e93957f3909aab59ba8d05e47686a9f8b7226496f89b932151c42198ec966ccd47721cdf547a247ea4e5c61b22bfccce2ec591c +CompilerSupportLibraries.v1.1.1+0.armv7l-linux-gnueabihf-libgfortran3.tar.gz/md5/05ff63780f5b7c8c6c590c3626f32ac0 +CompilerSupportLibraries.v1.1.1+0.armv7l-linux-gnueabihf-libgfortran3.tar.gz/sha512/8d3c4149531f3782f5efbb6a6fbbb7080ba005298ba962b5bc5f66250ea9fde91b34836ed909c16f306d21d2e358f985360962e9362a8e807ccd4254da3bb19b +CompilerSupportLibraries.v1.1.1+0.armv7l-linux-gnueabihf-libgfortran4.tar.gz/md5/3ca2b6e8101d831e546c1b6ed2ca9a42 +CompilerSupportLibraries.v1.1.1+0.armv7l-linux-gnueabihf-libgfortran4.tar.gz/sha512/21a0b9c5acde96c0a91303f4f395e55f272d5585ad18f0365105188d129a3ca94ad66d4dd99b471abdf41a7a7262a3b258fd04b887110ad15255b284cd1612b0 +CompilerSupportLibraries.v1.1.1+0.armv7l-linux-gnueabihf-libgfortran5.tar.gz/md5/d4d560b8ecce0ff2cb4dbc88cb25942a +CompilerSupportLibraries.v1.1.1+0.armv7l-linux-gnueabihf-libgfortran5.tar.gz/sha512/d405f61525af1b2fe85107a70ed67b8a1eb767923487fa71539e0f49d6e70358c8a24f4ef1c224256cf677af99b54a2f8243f1e207350fcb14d426a7a6bb3915 +CompilerSupportLibraries.v1.1.1+0.armv7l-linux-musleabihf-libgfortran3.tar.gz/md5/8c6eddaa156fd0afee28ac5a154bc3f7 +CompilerSupportLibraries.v1.1.1+0.armv7l-linux-musleabihf-libgfortran3.tar.gz/sha512/b9fc86bb706ad98d61b63eb4cc8bfce6b2c67b58ba2cebecea7574f44790cce044bb1b4db1d20050b59538fa43b51cb352d752c77333a0f0621fde47c63a3596 +CompilerSupportLibraries.v1.1.1+0.armv7l-linux-musleabihf-libgfortran4.tar.gz/md5/0a54c16fea86c6dadb39eff65c465528 +CompilerSupportLibraries.v1.1.1+0.armv7l-linux-musleabihf-libgfortran4.tar.gz/sha512/c635c636384d3af5b4b078be7398fbc665a185eae69dd223279affb4836fb5c575d6ab296ae940ccbe73777bdb5e355f4f28a2fa27606ac143ff424641c60c65 +CompilerSupportLibraries.v1.1.1+0.armv7l-linux-musleabihf-libgfortran5.tar.gz/md5/892dfd91703f0f77d170a5371a1c25d4 +CompilerSupportLibraries.v1.1.1+0.armv7l-linux-musleabihf-libgfortran5.tar.gz/sha512/8ac59d00192c0e847168e61b3e93957f3909aab59ba8d05e47686a9f8b7226496f89b932151c42198ec966ccd47721cdf547a247ea4e5c61b22bfccce2ec591c +CompilerSupportLibraries.v1.1.1+0.i686-linux-gnu-libgfortran3.tar.gz/md5/3094705222b6b61fd6a10422a73e1149 +CompilerSupportLibraries.v1.1.1+0.i686-linux-gnu-libgfortran3.tar.gz/sha512/27f874cde357ffa45aaa10f2e620ec0f8ab4e5a8bf4607fc023a2ec42040bcc9a724f959237c340d67451f8621402fa05133c1420086b87135f40326c30b97af +CompilerSupportLibraries.v1.1.1+0.i686-linux-gnu-libgfortran4.tar.gz/md5/ba0acaff60648efa3915348a8a353df8 +CompilerSupportLibraries.v1.1.1+0.i686-linux-gnu-libgfortran4.tar.gz/sha512/0b6aaf75363cbe6133ca3aed351ab58ef1e441f61375f5baf702d8043813c459d48e8af17630f1a07dc22772ec9b02076af33726ed94e6314ae37d5a139d6dcc +CompilerSupportLibraries.v1.1.1+0.i686-linux-gnu-libgfortran5.tar.gz/md5/95f1d57cfc43677e40bfc121bce79274 +CompilerSupportLibraries.v1.1.1+0.i686-linux-gnu-libgfortran5.tar.gz/sha512/edacd9960e9de1236c91752e103cddfc018d697e87fabb3cceadf36153b4e97842ef284bd1532290a5620007234882b4c4cd4f36525b61763d97b2f608358262 +CompilerSupportLibraries.v1.1.1+0.i686-linux-musl-libgfortran3.tar.gz/md5/f37fe1818e1634476c44afae478611c8 +CompilerSupportLibraries.v1.1.1+0.i686-linux-musl-libgfortran3.tar.gz/sha512/6e4e3eb5ac9570bfdf5280f59167eb6c4a74f3aa152afb4c5d180b9a6cdbdca557e7dd13f0b5b76943b45a65e848fe77c5b3bbc6ddb0fd846d03fbc9fbedf7ce +CompilerSupportLibraries.v1.1.1+0.i686-linux-musl-libgfortran4.tar.gz/md5/b4ffd52179aa0006c56f279b87cb7556 +CompilerSupportLibraries.v1.1.1+0.i686-linux-musl-libgfortran4.tar.gz/sha512/a047ac7db204c31802f646351af51c55fe06498e851b2df58d7f93f75d9c0067f8736f247f108991ec01ac7f86f3026ecf58b5f2f3a76d7eab00130754e7f704 +CompilerSupportLibraries.v1.1.1+0.i686-linux-musl-libgfortran5.tar.gz/md5/2d38fc835f236f89f457fdf859ccb903 +CompilerSupportLibraries.v1.1.1+0.i686-linux-musl-libgfortran5.tar.gz/sha512/51fbe41efbce33b1cf3728df6fa59fd0e85a13308b3e868fe9f70f4d67857615f83542ba69be824a73e89959503dd7a11335d1c495704bd7d6cad6656d0c5d57 +CompilerSupportLibraries.v1.1.1+0.i686-w64-mingw32-libgfortran3.tar.gz/md5/9650002f6729c0964d33afcab334d77d +CompilerSupportLibraries.v1.1.1+0.i686-w64-mingw32-libgfortran3.tar.gz/sha512/0b7907811a13d09b7b33203c7e46888308c7d6fcf5d69790babafc39f640541551f784264247f159a552f15df1ddd061c421a93b983d838d3bd7f85ba6427f70 +CompilerSupportLibraries.v1.1.1+0.i686-w64-mingw32-libgfortran4.tar.gz/md5/47e9fb99906b9647e26e4126a913074e +CompilerSupportLibraries.v1.1.1+0.i686-w64-mingw32-libgfortran4.tar.gz/sha512/d7285691fbe1318e48e061d678e54890762cc16996652a34b190924cc1462d24ab0b08729945eb25f4bef60e60d50f3e78db57d4cda0302b8ba579db8a1311e1 +CompilerSupportLibraries.v1.1.1+0.i686-w64-mingw32-libgfortran5.tar.gz/md5/b588b2710f2b83d2c70c6104e585a3bd +CompilerSupportLibraries.v1.1.1+0.i686-w64-mingw32-libgfortran5.tar.gz/sha512/b62a63b0c8750f85fc265db88456307b794e912352a68997c7cce06444391307c03edbe5b901833f53c5bd55f5a1e61a586538b08487cc139a2d71fccdce1d31 +CompilerSupportLibraries.v1.1.1+0.powerpc64le-linux-gnu-libgfortran3.tar.gz/md5/7cce4f3dc057ebebaa677bf6f0d51e9e +CompilerSupportLibraries.v1.1.1+0.powerpc64le-linux-gnu-libgfortran3.tar.gz/sha512/a0dd93905f0ede4da5e2fbacf2579154db8ac8e9963c77fb62284489686f2aa372925b3341742d86430a839267421af55f6e1e413473d17f13a1a199e6a904a0 +CompilerSupportLibraries.v1.1.1+0.powerpc64le-linux-gnu-libgfortran4.tar.gz/md5/06ee6aaeca78b3e9005f53f1fa32731f +CompilerSupportLibraries.v1.1.1+0.powerpc64le-linux-gnu-libgfortran4.tar.gz/sha512/ff0e33ce9f93b3a867cf409b95e763efbc8f4dde65ed19107eb14d29460d084f253e03ebd6375f1da996182b3d96e1fda4abff06507258da9a89ece36663db84 +CompilerSupportLibraries.v1.1.1+0.powerpc64le-linux-gnu-libgfortran5.tar.gz/md5/483251d28076ee959dff131d13d7e53b +CompilerSupportLibraries.v1.1.1+0.powerpc64le-linux-gnu-libgfortran5.tar.gz/sha512/a7c9053a8c1b784cb6459762f26e0c2106a9758cbe2aefe8975a14aaaf61b8a08e51c465e733e44d01537beb59d467c57e536ebd8b27b7b68f46945174c469c7 +CompilerSupportLibraries.v1.1.1+0.x86_64-apple-darwin-libgfortran3.tar.gz/md5/a147bf3a6d6550c177b8a784b9b02e21 +CompilerSupportLibraries.v1.1.1+0.x86_64-apple-darwin-libgfortran3.tar.gz/sha512/c6f7a13f0195eae8f7ad980a4b24de9b155be69c4437522723411f9866a4aee3c5b350ee2f0c95f41f19aba43acaca78309881157e8498df0664c902d0c05a5d +CompilerSupportLibraries.v1.1.1+0.x86_64-apple-darwin-libgfortran4.tar.gz/md5/3f19c9d0e723a8d5591357ac3a9452a0 +CompilerSupportLibraries.v1.1.1+0.x86_64-apple-darwin-libgfortran4.tar.gz/sha512/5752bac310d80ed2dc1fc3d6580300d185787b9b933e31c8e0f572099abd0727d9483da8f9af858f706e96a183d2b10702c44381a080438cbb17d6459321ccfb +CompilerSupportLibraries.v1.1.1+0.x86_64-apple-darwin-libgfortran5.tar.gz/md5/ad0f0e2fe3e7d147a0a27271a2aba0fc +CompilerSupportLibraries.v1.1.1+0.x86_64-apple-darwin-libgfortran5.tar.gz/sha512/f42231adea3d0b6133c3b5bc5fbf765bc6a7ba8ef0f407fa1b8def36dd8a71d20ef39fb6e57b43208489c2795a96562cdbf15f3d20b3f3a09edb29b99d19a33a +CompilerSupportLibraries.v1.1.1+0.x86_64-linux-gnu-libgfortran3.tar.gz/md5/4c78d56dbbbff682c0a78d11fb9d1e70 +CompilerSupportLibraries.v1.1.1+0.x86_64-linux-gnu-libgfortran3.tar.gz/sha512/0e9d6dcc4b8fddaaa94a26a46e915d33fb474f8a8ee14edd4d1c7e774846c44c5c5d852649a4f70409c99ac0e1d458077b7f0eb7dc0b0326ee8b625644d7074d +CompilerSupportLibraries.v1.1.1+0.x86_64-linux-gnu-libgfortran4.tar.gz/md5/039d37f813b183c75feebadd21011eb6 +CompilerSupportLibraries.v1.1.1+0.x86_64-linux-gnu-libgfortran4.tar.gz/sha512/05e7291de1fd2520247402f0db9d348fdd7a02d8dd9133ac65701f88d237110a3cc6c6e2c5717364ab786b6e6063038ec10c9605e77bc4dbe1064a0e77617f5d +CompilerSupportLibraries.v1.1.1+0.x86_64-linux-gnu-libgfortran5.tar.gz/md5/a985f13a85eb14d1b6339ba4983dc372 +CompilerSupportLibraries.v1.1.1+0.x86_64-linux-gnu-libgfortran5.tar.gz/sha512/27468ccd5642e6e11bd5972684518a0fb883bf4835ac18f5279c3fce97b1779131c7d9e39d8de26a15c293c832946334e964919f51d7679cd0569ce82b938579 +CompilerSupportLibraries.v1.1.1+0.x86_64-linux-musl-libgfortran3.tar.gz/md5/9d86ce2fe481ea97a1fd098bd47d524c +CompilerSupportLibraries.v1.1.1+0.x86_64-linux-musl-libgfortran3.tar.gz/sha512/a865a4127bacaedd81b6c81279f6a44bc3497ab29a0401f66da1abfc0738ea459be9f158d06969c161a65925739665084bec5f8650a8cd1e8f0d08f1f44d729f +CompilerSupportLibraries.v1.1.1+0.x86_64-linux-musl-libgfortran4.tar.gz/md5/86d9db869a7af6c96dea39f5d9d90505 +CompilerSupportLibraries.v1.1.1+0.x86_64-linux-musl-libgfortran4.tar.gz/sha512/01e0c69b04138989200ded92eddae6ff1873d3a440d17273d08bee40d53b2929e35bfd14be051074fe78671cac34ac2dd7360c1571790ee52f94a5921de42a65 +CompilerSupportLibraries.v1.1.1+0.x86_64-linux-musl-libgfortran5.tar.gz/md5/e72d28df4bcb60ab2f3389046e7c83a8 +CompilerSupportLibraries.v1.1.1+0.x86_64-linux-musl-libgfortran5.tar.gz/sha512/cac193a26328ddeff5f7bcc3d7207101c574f9bdb1bff5c2b925315c5c2404a2fdb6591d1968f30931373fbfcae9bda784c72e65580ad3acc398448cd193f65d +CompilerSupportLibraries.v1.1.1+0.x86_64-unknown-freebsd-libgfortran3.tar.gz/md5/35642304a9a2f435cf5214b2715198fe +CompilerSupportLibraries.v1.1.1+0.x86_64-unknown-freebsd-libgfortran3.tar.gz/sha512/a67f41ba31c99a064f504f508711537f9e90089ca5352bfc2698c3fcd3e499ca716f07ffeac4fb1b88c2c934f7f380f262af8c863d3b16ac7e805d5c805ab358 +CompilerSupportLibraries.v1.1.1+0.x86_64-unknown-freebsd-libgfortran4.tar.gz/md5/01df0fbb265e5ff1a480a7a5e23b0835 +CompilerSupportLibraries.v1.1.1+0.x86_64-unknown-freebsd-libgfortran4.tar.gz/sha512/57a79f2b8e846c1514dcb18420f26ae2889962040f410b746836cab4395749155fa9cd9d00d4c25954c0ffa72f9f3823b1b50688a20ddf675301f64e0d4b5c7e +CompilerSupportLibraries.v1.1.1+0.x86_64-unknown-freebsd-libgfortran5.tar.gz/md5/1f1f6380ce8815cc9cedcea0b40860e7 +CompilerSupportLibraries.v1.1.1+0.x86_64-unknown-freebsd-libgfortran5.tar.gz/sha512/a88ea8af8c8df792861812bfdf7f1bcaae31582ab78ce78b47a0dc6fd57b93441c0471f529ce23877131ac9701c6eed72ce89241746e18271f3686fbd718138c +CompilerSupportLibraries.v1.1.1+0.x86_64-w64-mingw32-libgfortran3.tar.gz/md5/38fc8c445a1a610db40a7609155e22d6 +CompilerSupportLibraries.v1.1.1+0.x86_64-w64-mingw32-libgfortran3.tar.gz/sha512/085652c7ca583c3623611ca9262b70765c9936c9feb5f9034b2c6b6d6677a7a1d7d201b83d82d0d268f3190bd1a62eab0124e8fae3625407dee7f1df89d4106c +CompilerSupportLibraries.v1.1.1+0.x86_64-w64-mingw32-libgfortran4.tar.gz/md5/f3f89eb3c2e441fde6e6b9c1c1a61183 +CompilerSupportLibraries.v1.1.1+0.x86_64-w64-mingw32-libgfortran4.tar.gz/sha512/c53f79e20ad043ab099873f38ece98c6bed22950610ba88b9c178a4bd943039cc426473828d509deb8c65c93309da1de87bdf36fb3954b8f8047277c418fe2e0 +CompilerSupportLibraries.v1.1.1+0.x86_64-w64-mingw32-libgfortran5.tar.gz/md5/024f7133425db23e215dc55589bb9171 +CompilerSupportLibraries.v1.1.1+0.x86_64-w64-mingw32-libgfortran5.tar.gz/sha512/819945496ea48dd44d8c0f12a11a358b7d1ebf198d60fbad576d74ddee68cdea98070cdd11ca96567d0c772ec007c03cbc83ff5c7d2ad737cbd486fe0c9afcd5 diff --git a/deps/checksums/curl b/deps/checksums/curl index a6eeccca3833c..e6f7989db33d7 100644 --- a/deps/checksums/curl +++ b/deps/checksums/curl @@ -1,36 +1,36 @@ LibCURL-a65b64f6eabc932f63c2c0a4a5fb5d75f3e688d0.tar.gz/md5/e8c53aa3fb963c80921787d5d565eb2c LibCURL-a65b64f6eabc932f63c2c0a4a5fb5d75f3e688d0.tar.gz/sha512/8e442ea834299df9c02acb87226c121395ad8e550025ac5ee1103df09c6ff43817e9e48dd1bcbc92c80331ef3ddff531962430269115179acbec2bab2de5b011 -LibCURL.v8.4.0+0.aarch64-apple-darwin.tar.gz/md5/7e1b8b96f4f38cd775c1be5bfd4d9b14 -LibCURL.v8.4.0+0.aarch64-apple-darwin.tar.gz/sha512/598c8418731770387f9d1b489eb0794978c4b98c9098f2c9b024fe758e6550ff82202fa4c911a6029ac39a9d395f1dccb9b5539dac788c29a85e79c958ab3bf9 -LibCURL.v8.4.0+0.aarch64-linux-gnu.tar.gz/md5/c8ef2231800a5ad488e2952e14a48710 -LibCURL.v8.4.0+0.aarch64-linux-gnu.tar.gz/sha512/1cc786ce4836e6ae904685bb44e47354eabf66dd82229d84cbeaa0d5b549900b77b68f878d915f04049c4f4118e5849262a6706fa9fac845b8a976e02140dea2 -LibCURL.v8.4.0+0.aarch64-linux-musl.tar.gz/md5/7fdc14704e467d7d9d71b54d48f690f4 -LibCURL.v8.4.0+0.aarch64-linux-musl.tar.gz/sha512/1af938d244e141c6ad77844c115afbff0ab75145e4e1422cf1444226ec0183a7449040c4a86c3dfb445820377278567427f8df77823a5ae0bede705a9b01335e -LibCURL.v8.4.0+0.armv6l-linux-gnueabihf.tar.gz/md5/77534d50dbb631146c85b6f2b92c7f84 -LibCURL.v8.4.0+0.armv6l-linux-gnueabihf.tar.gz/sha512/b7a4294ae8f7d24b5dcc10df52d76dd35ca1b4d0dc955307968f6f1a6a2edef194d134bcf6891a960d63e8763b1bc2738786db84393183210730d4d2d486dcdb -LibCURL.v8.4.0+0.armv6l-linux-musleabihf.tar.gz/md5/35e4dee0f70564d3d16d2dd8ef7c2c5d -LibCURL.v8.4.0+0.armv6l-linux-musleabihf.tar.gz/sha512/e9292a6dc52be228919a9af2b1e73a6968af843d508ffb216ae956a651822ddc1bcbb9fce495da05a11ffb9211903462f7504aa5da78f19f4db5c63c80fc9baf -LibCURL.v8.4.0+0.armv7l-linux-gnueabihf.tar.gz/md5/dbb847d7f7162b4a2ea5395eca8d7c30 -LibCURL.v8.4.0+0.armv7l-linux-gnueabihf.tar.gz/sha512/3a2c2779392eb19fe9202397688466fc05813a8e35ea6bf85d628e802759c97fae346939902e897313914ce2b38dcfabe0775be6a4a1b1d4264a327687d6a2f3 -LibCURL.v8.4.0+0.armv7l-linux-musleabihf.tar.gz/md5/f9e9557080e09fda47c92f14d5c89de4 -LibCURL.v8.4.0+0.armv7l-linux-musleabihf.tar.gz/sha512/603821f647d17daa1d731a63c7fff35b756b052ee53b6c86d9f35582356d57914336f8b9bb44567f10b437b2c0c677177959ae691e0244bab1262818eb61aa1e -LibCURL.v8.4.0+0.i686-linux-gnu.tar.gz/md5/eabf3f3392cf8717782c2cb1c1b71478 -LibCURL.v8.4.0+0.i686-linux-gnu.tar.gz/sha512/9584f28cf8f72a6e15afb96a2c27d82b74a12042311c07aecb61732fd6525227f082d448b81950a67c6cc880b39c0921cf039e97bf0bdd4329e2196de42ec81e -LibCURL.v8.4.0+0.i686-linux-musl.tar.gz/md5/dceef0a3aa971e3b7e3db44b5de159d1 -LibCURL.v8.4.0+0.i686-linux-musl.tar.gz/sha512/483dfe8a3b4589e59794b3b4efba1e60baf9fb45efad6c0b1b9626ad6e492fd803fda3f99545303a32749010df5b9bb89faf608d57eb4fee4c3f387d0b197a5e -LibCURL.v8.4.0+0.i686-w64-mingw32.tar.gz/md5/2a0a87e126ebaf7bcaff746e30f6905f -LibCURL.v8.4.0+0.i686-w64-mingw32.tar.gz/sha512/5a2867e8ac27a82e30bb88ea4c3f52faddfbd0b199f25dbef3a77a47be16a4b9299883fc84d50ce965aa2d01d473310b4cbc28c60dad3602f2ee6b56345ea6a5 -LibCURL.v8.4.0+0.powerpc64le-linux-gnu.tar.gz/md5/1f532601778a4dcf9932681e0e47833e -LibCURL.v8.4.0+0.powerpc64le-linux-gnu.tar.gz/sha512/6ddf56c44124e682eda9cedacb8893ada1e07b6a2cb10fd301afc04b3d1c981b25129b2495efb4d5e37784ee2bb5e47da13be9c2f367ff397a8ce7122136f6e2 -LibCURL.v8.4.0+0.x86_64-apple-darwin.tar.gz/md5/d7c9ab15d9739df8fb4329c0bb2546f6 -LibCURL.v8.4.0+0.x86_64-apple-darwin.tar.gz/sha512/5d5f17534053504dbc3c0b1251f329358bac60c14b734f6b82b4321f96f944b1733254cfd7489504c41d3d2cda5ae991e6cb4f9fa864990a6660cb4464f14530 -LibCURL.v8.4.0+0.x86_64-linux-gnu.tar.gz/md5/f8697c76204df4ac5a04608b6a480f3b -LibCURL.v8.4.0+0.x86_64-linux-gnu.tar.gz/sha512/ed583b6abc52f6b5a8ee0c93a4e9b877d2830eb131117ac17da044d2fadb218bc293ec6c625a526583aea01f41994a3f9bb5ed5b4263838bab8c251c672b8160 -LibCURL.v8.4.0+0.x86_64-linux-musl.tar.gz/md5/fcb9be4148376414f94d3ddb54f42d12 -LibCURL.v8.4.0+0.x86_64-linux-musl.tar.gz/sha512/8e53219d68fdb4c412de8a19f53572dbd74dc35bba3c6f3c5aab622f05aa76a28e386201d9dd1c78d37d0ea636bb43ad98d1e19599480ba307cc45098600818a -LibCURL.v8.4.0+0.x86_64-unknown-freebsd.tar.gz/md5/aa4b18c0b8be4be432e24c9cc88670da -LibCURL.v8.4.0+0.x86_64-unknown-freebsd.tar.gz/sha512/69548673aec3d199a0514ae0e90d6f8700ace47579631522a93ea351151af258127f3bd1fc82f110f22ac6b7c3eab884665773d99842dfc56fa94040a69f1043 -LibCURL.v8.4.0+0.x86_64-w64-mingw32.tar.gz/md5/b477d32139cef0e810e88ceed68b322c -LibCURL.v8.4.0+0.x86_64-w64-mingw32.tar.gz/sha512/3035a1e9af9eda70018ef56a5a620d5b07b46f63739e0f2e028518c6144577d51a9f9a9e76ed1bf28ee4a5811ea4f502ddeab46694432e441e0151056d5c7e6d -curl-8.4.0.tar.bz2/md5/1a61fde1fe5c7db5c29c1196435188a5 -curl-8.4.0.tar.bz2/sha512/27a27c5427acce3501833ac37125daaa03c90165692252115329c820a5a6396cdc79c11d12fe962af37a329549c2051addce3c91e8a8bc3ce3a48cb09d860086 +LibCURL.v8.6.0+0.aarch64-apple-darwin.tar.gz/md5/83854e8cdd078ec1fc5f92da2816e379 +LibCURL.v8.6.0+0.aarch64-apple-darwin.tar.gz/sha512/f3b3cc5804d9a7986ed9ea7c3186caa8dba0f4d6bbcb9b5d2070b4e6412234f2ed7908446dbe217323510c6d3b042540e18ec7839093c2c3c66f3195937a6a3b +LibCURL.v8.6.0+0.aarch64-linux-gnu.tar.gz/md5/880014fface52bddaa1a0240e0668dde +LibCURL.v8.6.0+0.aarch64-linux-gnu.tar.gz/sha512/a29b923e14425ad729484648ce15577e717a97acf6138e0ec3d35a8000aeef17f27ce01d1fdc1642c6eda72d8d8b46642d79844ef9a50f30a0148e29452565c1 +LibCURL.v8.6.0+0.aarch64-linux-musl.tar.gz/md5/b84fcb98f1305803d941f7a5accbfdb1 +LibCURL.v8.6.0+0.aarch64-linux-musl.tar.gz/sha512/0880dc91109aedd9b108f2e28a25bf7091ac976a6f94e65000f647802c57a01e8111d421b91a91244c3cfb56990155af2c47b3499997be2af8ab93d344b8331d +LibCURL.v8.6.0+0.armv6l-linux-gnueabihf.tar.gz/md5/24249f42db0bc99c2dde4cf61d61f11f +LibCURL.v8.6.0+0.armv6l-linux-gnueabihf.tar.gz/sha512/350443c86f7b6733fb6a28f8a2fe7a6c0e91462b9e4078fed3475059ec7e12fef5014e22d0d0babe44f172ace7258292de577a0ab90f90c65d825d74940c9c47 +LibCURL.v8.6.0+0.armv6l-linux-musleabihf.tar.gz/md5/375c01cef98204c4f63ac218b08c4c7b +LibCURL.v8.6.0+0.armv6l-linux-musleabihf.tar.gz/sha512/ed0981d458c6ddc9f380b90f1ec25cbaa6be910f0dab5d5485e4d1e9a33f8a918d210722a5e6685b4d3b917e0800194856f826164ee2e90b8a886ada7498a52b +LibCURL.v8.6.0+0.armv7l-linux-gnueabihf.tar.gz/md5/4c81aa99065cf797d6e09ce172dd2fa7 +LibCURL.v8.6.0+0.armv7l-linux-gnueabihf.tar.gz/sha512/08bbb1bc80411a5fd65699a0d721fc70a9bba1005194f2937accc2e98f7f803bac4a704c88aa1fc1d72e54e7c327a113963f8a4c52ebb1e5921778d1dd549143 +LibCURL.v8.6.0+0.armv7l-linux-musleabihf.tar.gz/md5/6865b2f2d93754b5206d618048c32b57 +LibCURL.v8.6.0+0.armv7l-linux-musleabihf.tar.gz/sha512/c1a5891f4c487d5f7cf91db7cd2d85394d9686cda01c89cddaf7afba09782aa9f00713592d72ed8a0dd20439884dd75c1e001a9ecb16dd8ce5f08f79c194c7c8 +LibCURL.v8.6.0+0.i686-linux-gnu.tar.gz/md5/3f35cc6a2dc7e9dba5e3b4aeaf130160 +LibCURL.v8.6.0+0.i686-linux-gnu.tar.gz/sha512/b34c5ba2fee272e6ca848c42335ffa0c4d0c06337608504a3a2cfeb111e228da3f82d91c0c4387c76fc347babbf50b368992b5b8d5fda1a60ed5c0ce5d9242db +LibCURL.v8.6.0+0.i686-linux-musl.tar.gz/md5/0072b83eaf91d9da4a8d25ef65fd8ca8 +LibCURL.v8.6.0+0.i686-linux-musl.tar.gz/sha512/029552e3dac29857726988352a01a3b57859bfe5e327e7b759bd9968ed5af5498fd27ab490810d2d3ef05b1003c1a950fd092d1dbce7732a911f7cb6e5714303 +LibCURL.v8.6.0+0.i686-w64-mingw32.tar.gz/md5/d58ef948bc9a04a8c934a88b7ab5599d +LibCURL.v8.6.0+0.i686-w64-mingw32.tar.gz/sha512/1e1742ea39f2fe1f13b0aff5907f96401276e3fc469a8f09f2bc31fffc72367a92856973db66eb9b05d20fd708764ad9429e385913f6236ce8067ec4e11dbb33 +LibCURL.v8.6.0+0.powerpc64le-linux-gnu.tar.gz/md5/60ec16b6dfd3e30eb0655cf177b026c7 +LibCURL.v8.6.0+0.powerpc64le-linux-gnu.tar.gz/sha512/f591897972c8b01edf64701885f636fc1d5c04cce8fc63577d06108e14e5480bad74306d6ee31515911bd8ba3db10d1f2c733a6149aceae32aa4b77e263087c3 +LibCURL.v8.6.0+0.x86_64-apple-darwin.tar.gz/md5/c6bc0d9cd0a9f9c35ed2aac058ae332f +LibCURL.v8.6.0+0.x86_64-apple-darwin.tar.gz/sha512/038f55bfb06dce877540ea7d50f5a0b8fdc070539c505774139a7c23df276a5fc75b5cecabecbc2826417e091028382d79298a51ed73c3d776249b4ff35f9f26 +LibCURL.v8.6.0+0.x86_64-linux-gnu.tar.gz/md5/18bf9d909dd5eebc0554d23bf4a4ee0f +LibCURL.v8.6.0+0.x86_64-linux-gnu.tar.gz/sha512/35e60faa1ee072003fdd5cd510295bc310aa99375aee6ef94eee3ee2d5e0b7844145866a74927c588c14131939c1d9865d6f5128ac4f6b93606a68042a94f39f +LibCURL.v8.6.0+0.x86_64-linux-musl.tar.gz/md5/213190e1d79c9c291ff460e1648a61d3 +LibCURL.v8.6.0+0.x86_64-linux-musl.tar.gz/sha512/4ea063982520400c02dcdf44ed3f018dec19607ad20762231316eb745cdb1cd054b18677fee1b5c5fb0bd55eb845121a2113704c5301be1d76edfc8a4a09d93f +LibCURL.v8.6.0+0.x86_64-unknown-freebsd.tar.gz/md5/30dda5aaeb7977eb3563c603af08cd6c +LibCURL.v8.6.0+0.x86_64-unknown-freebsd.tar.gz/sha512/edf603a6c013d3f6e01fc4fd6f12caf93ff99df9baf14bc73b610638a5b5ff90ec3118b112d9a39221294f5f419f3bf12232c16eaf91b07a68d92342a5c56912 +LibCURL.v8.6.0+0.x86_64-w64-mingw32.tar.gz/md5/9a2c980db329393f5274d42f87c2aec6 +LibCURL.v8.6.0+0.x86_64-w64-mingw32.tar.gz/sha512/6b926a87a3470796eb111e448c459a8ff1267533513f14d58f6e08cbebfb3e838c114827fcf39298bcefe8d76b8578bb4d3903c848bfafb0590022e6a49b2a00 +curl-8.6.0.tar.bz2/md5/4418e0d94f29d352afafdab445e37a3d +curl-8.6.0.tar.bz2/sha512/726fe7e21f8a2a925ab4ab6fe4e4ad099105f00656bfdc702beec12e1549ba2cb63d908d1d19f43b90feeb1d950ae5a6ac9a72a27447f5acf1907df396d1823c diff --git a/deps/checksums/dsfmt b/deps/checksums/dsfmt index 63c7e26f0eb43..0666e51efa994 100644 --- a/deps/checksums/dsfmt +++ b/deps/checksums/dsfmt @@ -1,34 +1,34 @@ -dSFMT.v2.2.4+4.aarch64-apple-darwin.tar.gz/md5/43b52709b7794c92931286174854c886 -dSFMT.v2.2.4+4.aarch64-apple-darwin.tar.gz/sha512/018b67a06cdf42dda2a906025e8a12e026af9b39fe8281890dc90d66a422c3af2a8430d42677f79d123fd0ab0e8d5c37db2e0a00ef03731d35cbb65f9e59b108 -dSFMT.v2.2.4+4.aarch64-linux-gnu.tar.gz/md5/260e14855dbc7773a2ca906d58cc57f2 -dSFMT.v2.2.4+4.aarch64-linux-gnu.tar.gz/sha512/820ca4c6afde931e855b74015150f4ffbb513276c3fa7dbcc1ec8d34c02d4989fb7424a6e4f81f93d054811b5f54f8633d955b05acdb088387ee90f1c3b00915 -dSFMT.v2.2.4+4.aarch64-linux-musl.tar.gz/md5/7ddccbad6b5c9de4be187fe76637a0d8 -dSFMT.v2.2.4+4.aarch64-linux-musl.tar.gz/sha512/e3c225da00927096e3a6cd4abc681fba8f469cb74828e7054d4f5684d71dcb8e75c9a81f14fa10bfbb78f62f9567a31a92edcca8d797e5810a2a44a3fc17bc84 -dSFMT.v2.2.4+4.armv6l-linux-gnueabihf.tar.gz/md5/a70329e0a6c57009c6b6950fd34089f6 -dSFMT.v2.2.4+4.armv6l-linux-gnueabihf.tar.gz/sha512/4418c42165660adc050e872ef834f920c89ed6a0d2b816821672b1e862e947aad7efd023289da9bf05bb2eb9ec4b9d2561c403e2d5384d5314a4ba016b1f9cfc -dSFMT.v2.2.4+4.armv6l-linux-musleabihf.tar.gz/md5/6ffc798b8a0c847fa5cb93640bd66ab3 -dSFMT.v2.2.4+4.armv6l-linux-musleabihf.tar.gz/sha512/94e5ae07d0b1420abd7290519bce6f77deae634bbb4df31e3f02416bf509e555a9b1c9d19dd77ca76a308c2b86d5c9d4718b9ef83c13167b88a8181d8ca7e73a -dSFMT.v2.2.4+4.armv7l-linux-gnueabihf.tar.gz/md5/660d95aa08580ca1716a89c4d8b1eb24 -dSFMT.v2.2.4+4.armv7l-linux-gnueabihf.tar.gz/sha512/bc757a9f805047be5375f92c10a3f3eab69345a4ec5cc997f763e66be36144a74d414ff926df8e17b9d5a2394189269c3188c55e0b7c75a72495394d65510cef -dSFMT.v2.2.4+4.armv7l-linux-musleabihf.tar.gz/md5/78c487049092fe61949d506637c713bb -dSFMT.v2.2.4+4.armv7l-linux-musleabihf.tar.gz/sha512/03ddada4478f05eab7d2971b2deaf2cba91f084d7ce66fc8219bcb3cf5c308ea13959fed95568ca80f4ce11794e197092984919265716de8f2558e2cb30d94ce -dSFMT.v2.2.4+4.i686-linux-gnu.tar.gz/md5/b0f535336cca76f1dcdacca29c6f8410 -dSFMT.v2.2.4+4.i686-linux-gnu.tar.gz/sha512/cc03a246b32875037a41a45c1004834abc7c67f90bf17e1b41cc604ee9893147b1ca3978a2e103b94c94ac617380570473de1f66bff15de8e4ee05c5a3c21059 -dSFMT.v2.2.4+4.i686-linux-musl.tar.gz/md5/a61405f72c9a3bba5718f078c68e61a5 -dSFMT.v2.2.4+4.i686-linux-musl.tar.gz/sha512/726f130bbbfd0dece4185b89a25a73f3b5b950ebfb7f86aea6e9cbcf9ae932e591d20b854de0b4985103dbf8b4b7cb3560661c5070af971cd2c1f3ec3e1ea7d2 -dSFMT.v2.2.4+4.i686-w64-mingw32.tar.gz/md5/93670f43a98f7c6045427dc9ddd89a4a -dSFMT.v2.2.4+4.i686-w64-mingw32.tar.gz/sha512/b76c2be073312ffec8c778b83d3e37b5d0c5dba770ffcc95a6ebfba516a948beb08419428192fcd5dda83357a64e90c4e3a40144688f128133400284a7363b8e -dSFMT.v2.2.4+4.powerpc64le-linux-gnu.tar.gz/md5/fd8c73961ef7c82201e6d86e8bf4324c -dSFMT.v2.2.4+4.powerpc64le-linux-gnu.tar.gz/sha512/1bd0ebd019cfc6f25f7ba007547c5ee297854655b93c55e90d8ead420875de5a087e38956693d5e901ff2abf667c72aa66fb34f587b82adf4b91b3d5d666b5c7 -dSFMT.v2.2.4+4.x86_64-apple-darwin.tar.gz/md5/b57ec1491ffdd40c72860b9f1869160c -dSFMT.v2.2.4+4.x86_64-apple-darwin.tar.gz/sha512/c3a192dbcd3e768712d12d3ac851f46bfa1517eca16c9a187025553076c8fb886b925e4e3f5f20531180f72b73e7eaa5281f54d5b7d4e6a4d53f4542c4bb33b6 -dSFMT.v2.2.4+4.x86_64-linux-gnu.tar.gz/md5/fa671f4ca14b171d53c8866d03f9162a -dSFMT.v2.2.4+4.x86_64-linux-gnu.tar.gz/sha512/2e242a1448da0508ea88cc1a106f1e74f8d7e7562cd82b80d86abf9a8b454653ad7612e25c30ce00c23757e8a5b7b5736253b00a52f9473af6c5d4df768138f2 -dSFMT.v2.2.4+4.x86_64-linux-musl.tar.gz/md5/c648294163882ec539ab646542c74880 -dSFMT.v2.2.4+4.x86_64-linux-musl.tar.gz/sha512/9e96a47d660854b6517364f0db40a2f4e0e3b814499a0349f7cf550b1c8d04589fca5eb4a75bf34f36d1b5d1b2277b3e9a961c887092abedd08f438e025329e7 -dSFMT.v2.2.4+4.x86_64-unknown-freebsd.tar.gz/md5/b4497d34d72ce134ce110b6185a82393 -dSFMT.v2.2.4+4.x86_64-unknown-freebsd.tar.gz/sha512/23d0fb273edbb5a08920a3683398e11d6f4df137dabcfc5f395a9175ddf14ab8999eb961ae8f4b76715a5a2dd2b77757f752abce35c1f752b800201e93aae874 -dSFMT.v2.2.4+4.x86_64-w64-mingw32.tar.gz/md5/d380963292bc54d27d39a3f94adbd5ac -dSFMT.v2.2.4+4.x86_64-w64-mingw32.tar.gz/sha512/ef2f99b17b1a36e61fb4d149d8a8fccc9e804b3b727f2426fca917c265c2d7ada4e3abaa5383e25136dec8de262c1d11970a01d7cfb513a55f1d86a23534e864 -dsfmt-2.2.4.tar.gz/md5/ed30e63552d62df48d709dde4f755660 -dsfmt-2.2.4.tar.gz/sha512/fe84e986cbf198172340adfac0436b08f087643eca3f1ceccacde146cbfd8c41e3eb0dfbb062f7ca5f462db13c386abd7c269bc0cbefc9a0ecf97a8a8870a2e4 +dSFMT.v2.2.5+0.aarch64-apple-darwin.tar.gz/md5/36284767f523bb297633d7da17a7db5a +dSFMT.v2.2.5+0.aarch64-apple-darwin.tar.gz/sha512/e6434c154db4c7187f227a550b159a8db8cfffc514323ca31112744a80a007ba5c95f2274cee30c0aa8caf1b20fb643cb814651a622b8e4bb2e5652878e504d2 +dSFMT.v2.2.5+0.aarch64-linux-gnu.tar.gz/md5/260e14855dbc7773a2ca906d58cc57f2 +dSFMT.v2.2.5+0.aarch64-linux-gnu.tar.gz/sha512/820ca4c6afde931e855b74015150f4ffbb513276c3fa7dbcc1ec8d34c02d4989fb7424a6e4f81f93d054811b5f54f8633d955b05acdb088387ee90f1c3b00915 +dSFMT.v2.2.5+0.aarch64-linux-musl.tar.gz/md5/7ddccbad6b5c9de4be187fe76637a0d8 +dSFMT.v2.2.5+0.aarch64-linux-musl.tar.gz/sha512/e3c225da00927096e3a6cd4abc681fba8f469cb74828e7054d4f5684d71dcb8e75c9a81f14fa10bfbb78f62f9567a31a92edcca8d797e5810a2a44a3fc17bc84 +dSFMT.v2.2.5+0.armv6l-linux-gnueabihf.tar.gz/md5/a70329e0a6c57009c6b6950fd34089f6 +dSFMT.v2.2.5+0.armv6l-linux-gnueabihf.tar.gz/sha512/4418c42165660adc050e872ef834f920c89ed6a0d2b816821672b1e862e947aad7efd023289da9bf05bb2eb9ec4b9d2561c403e2d5384d5314a4ba016b1f9cfc +dSFMT.v2.2.5+0.armv6l-linux-musleabihf.tar.gz/md5/6ffc798b8a0c847fa5cb93640bd66ab3 +dSFMT.v2.2.5+0.armv6l-linux-musleabihf.tar.gz/sha512/94e5ae07d0b1420abd7290519bce6f77deae634bbb4df31e3f02416bf509e555a9b1c9d19dd77ca76a308c2b86d5c9d4718b9ef83c13167b88a8181d8ca7e73a +dSFMT.v2.2.5+0.armv7l-linux-gnueabihf.tar.gz/md5/660d95aa08580ca1716a89c4d8b1eb24 +dSFMT.v2.2.5+0.armv7l-linux-gnueabihf.tar.gz/sha512/bc757a9f805047be5375f92c10a3f3eab69345a4ec5cc997f763e66be36144a74d414ff926df8e17b9d5a2394189269c3188c55e0b7c75a72495394d65510cef +dSFMT.v2.2.5+0.armv7l-linux-musleabihf.tar.gz/md5/78c487049092fe61949d506637c713bb +dSFMT.v2.2.5+0.armv7l-linux-musleabihf.tar.gz/sha512/03ddada4478f05eab7d2971b2deaf2cba91f084d7ce66fc8219bcb3cf5c308ea13959fed95568ca80f4ce11794e197092984919265716de8f2558e2cb30d94ce +dSFMT.v2.2.5+0.i686-linux-gnu.tar.gz/md5/11463fd3981a8c143d7aed691d18d4e0 +dSFMT.v2.2.5+0.i686-linux-gnu.tar.gz/sha512/db946a4fbd8a3163b8b1c25e02bfc4a841da7d2532892a99037bd48ac98e1840691e8cc0127d9457a82667a0131e4826cb4e9d0a13f127afc62da4eb68af5a3e +dSFMT.v2.2.5+0.i686-linux-musl.tar.gz/md5/a61405f72c9a3bba5718f078c68e61a5 +dSFMT.v2.2.5+0.i686-linux-musl.tar.gz/sha512/726f130bbbfd0dece4185b89a25a73f3b5b950ebfb7f86aea6e9cbcf9ae932e591d20b854de0b4985103dbf8b4b7cb3560661c5070af971cd2c1f3ec3e1ea7d2 +dSFMT.v2.2.5+0.i686-w64-mingw32.tar.gz/md5/3bc27ef8f26c7a26f096cf1d558d408d +dSFMT.v2.2.5+0.i686-w64-mingw32.tar.gz/sha512/ea3608d3ae3874ea57a1a08f69abe2a1638bc340db71c6fe3c4fd5637d8c54943bf16b099a46817387c1ed4cb5f3cd1c0ff19ae8a4ed85dd555555821af06374 +dSFMT.v2.2.5+0.powerpc64le-linux-gnu.tar.gz/md5/fd8c73961ef7c82201e6d86e8bf4324c +dSFMT.v2.2.5+0.powerpc64le-linux-gnu.tar.gz/sha512/1bd0ebd019cfc6f25f7ba007547c5ee297854655b93c55e90d8ead420875de5a087e38956693d5e901ff2abf667c72aa66fb34f587b82adf4b91b3d5d666b5c7 +dSFMT.v2.2.5+0.x86_64-apple-darwin.tar.gz/md5/6be9f2d3cd8d45a3fc1c3feeebcbbf00 +dSFMT.v2.2.5+0.x86_64-apple-darwin.tar.gz/sha512/5d17c2c0eedad6739b41b8a613e9e452df484136ecd11aed1f6b9f426ae1deaef9faf721810080ebc1701a88a3e7ae91b1992893598c33b342c3f876661f2f8e +dSFMT.v2.2.5+0.x86_64-linux-gnu.tar.gz/md5/fa671f4ca14b171d53c8866d03f9162a +dSFMT.v2.2.5+0.x86_64-linux-gnu.tar.gz/sha512/2e242a1448da0508ea88cc1a106f1e74f8d7e7562cd82b80d86abf9a8b454653ad7612e25c30ce00c23757e8a5b7b5736253b00a52f9473af6c5d4df768138f2 +dSFMT.v2.2.5+0.x86_64-linux-musl.tar.gz/md5/c648294163882ec539ab646542c74880 +dSFMT.v2.2.5+0.x86_64-linux-musl.tar.gz/sha512/9e96a47d660854b6517364f0db40a2f4e0e3b814499a0349f7cf550b1c8d04589fca5eb4a75bf34f36d1b5d1b2277b3e9a961c887092abedd08f438e025329e7 +dSFMT.v2.2.5+0.x86_64-unknown-freebsd.tar.gz/md5/5b53e6c5b78102f563742b4b3d888ec6 +dSFMT.v2.2.5+0.x86_64-unknown-freebsd.tar.gz/sha512/5db5902c7ec2624add768b9e2866f9aac224a31bcb4114d450c45717e2b244521b7c511c059527d557a71639cff98e190a38cd3e28db5be0b1faf0a1762cb1a5 +dSFMT.v2.2.5+0.x86_64-w64-mingw32.tar.gz/md5/386adb3b7593c222dc7a1060a1356b21 +dSFMT.v2.2.5+0.x86_64-w64-mingw32.tar.gz/sha512/fe2ab5021126807b37042e89a22ef9a869c6a0a028680df445773b2affd11c2b02148be07d53504ea3842bb38bb62fe039529688266c1cba3545a892bd4dc185 +dsfmt-2.2.5.tar.gz/md5/d22e476b52cdee7d5b90d2f289570073 +dsfmt-2.2.5.tar.gz/sha512/951e8669350f750b8915a819e704eae0a9b9c9518b3e3b9a1905f9ca0d25cc4c2486cb479e258a4a114e9c26ceb73a6c4e9f1cc02ed19173aeb8f20189754f6b diff --git a/deps/checksums/gmp b/deps/checksums/gmp index c9f6deac6e19b..0c7dd415e6f16 100644 --- a/deps/checksums/gmp +++ b/deps/checksums/gmp @@ -1,60 +1,60 @@ -GMP.v6.2.1+6.aarch64-apple-darwin.tar.gz/md5/8123f7925ae9aa60b6998313b21a9db9 -GMP.v6.2.1+6.aarch64-apple-darwin.tar.gz/sha512/5c7927ecfd47409dd4116cd4209768294ba229b51472ed220da498823dc1e7f9100292ec4b3a990491acd27f16ce3a3dce7a7c6e20dcd515982a9c8e364d91bc -GMP.v6.2.1+6.aarch64-linux-gnu-cxx03.tar.gz/md5/0d0d2ee67cff251941e3474341280b34 -GMP.v6.2.1+6.aarch64-linux-gnu-cxx03.tar.gz/sha512/69fb2f1476e0bb73f89ad2f73b58ec4da1b99e099124666e6da93b7705fde23913daa59f2ad479f99fcb4f0df152603bb0ba4875420b583f01fded0fec280a15 -GMP.v6.2.1+6.aarch64-linux-gnu-cxx11.tar.gz/md5/86ba1313c8ab4ca1ae8313cbf96e1e7d -GMP.v6.2.1+6.aarch64-linux-gnu-cxx11.tar.gz/sha512/05c306c01d1b0e9e4dc7ce937075eeaede4e5e0791826a8892fae2eb73cdb7f22c4873cf31cea3cfe3db996ac77387346f4f8a851ce52c29883146678f3851fd -GMP.v6.2.1+6.aarch64-linux-musl-cxx03.tar.gz/md5/2fbbb9adee7db794f5888442b7b7688c -GMP.v6.2.1+6.aarch64-linux-musl-cxx03.tar.gz/sha512/d8a1719e529374d00ba6372013d0c7ddc9f44f9f6ee0f966b4ed16d731ce74c26b6e6a807403b3396bed67dd3e775e18c1e70c247a371d622a6e7013eb6b8905 -GMP.v6.2.1+6.aarch64-linux-musl-cxx11.tar.gz/md5/b6a8c494d4c90decb6eacbca3ce3f22a -GMP.v6.2.1+6.aarch64-linux-musl-cxx11.tar.gz/sha512/6798406e20cc4d58647c266a2b1b8d0670e62f19bf4bff991c39eef13cf92c043f00717e7289bcc00007d7e248e943b37ba2eef89c9e68c42e30f0e2be9dd589 -GMP.v6.2.1+6.armv6l-linux-gnueabihf-cxx03.tar.gz/md5/a6866ee9784e9359e32dc18f417b2be7 -GMP.v6.2.1+6.armv6l-linux-gnueabihf-cxx03.tar.gz/sha512/548953ccc8444886316d4dfd7081783e397ec180e88a1d17a464e4b1d0a27f51ee7f6a1936ddab499db192d3cdfdc87d572731c5ab2f87d528609dabfccad2d3 -GMP.v6.2.1+6.armv6l-linux-gnueabihf-cxx11.tar.gz/md5/6b78c826a4aedc8107c1bbfccbe5c097 -GMP.v6.2.1+6.armv6l-linux-gnueabihf-cxx11.tar.gz/sha512/e8c075c29e4d8a916f087faeb2db50168e1a5546fcb02fc841477cf82a39188c3b9e7703b5354d4842880b5ac7215a32d022abe08aacc5e23238b63c6b994af4 -GMP.v6.2.1+6.armv6l-linux-musleabihf-cxx03.tar.gz/md5/57e1a6c71b3c5b4047bf08bfc4d4f22d -GMP.v6.2.1+6.armv6l-linux-musleabihf-cxx03.tar.gz/sha512/0f72c675ab3005ea183393bc4e5b4a157c13042367fd1bb3b03b3f7742e09604bddffb89f1478dc0dab4d992939519578549d05f9885b89319b0b51678b8a619 -GMP.v6.2.1+6.armv6l-linux-musleabihf-cxx11.tar.gz/md5/65a13f49cbdaa9d3a8e20d0b84bbc701 -GMP.v6.2.1+6.armv6l-linux-musleabihf-cxx11.tar.gz/sha512/0487b18d1c9c59d990e6c4ec435b8dff91ae02d5d56c665b12aaaea105f7d2ab5beae9dfcbb133c990f70774b0d32e55df7f2e91e2d0a85c391a4090dcadf080 -GMP.v6.2.1+6.armv7l-linux-gnueabihf-cxx03.tar.gz/md5/30e20c183153f8ce60e564b35e4b54bd -GMP.v6.2.1+6.armv7l-linux-gnueabihf-cxx03.tar.gz/sha512/41bdabc2610b46b215043e98eaddb2e2ad0695ae15f3088c9beef24a97864dce4088ae68993de928d952baaf123f279d74705664fffbf96be9b7436f1ba7692b -GMP.v6.2.1+6.armv7l-linux-gnueabihf-cxx11.tar.gz/md5/5f2cba31677e6681666c0b6ebd33c3ad -GMP.v6.2.1+6.armv7l-linux-gnueabihf-cxx11.tar.gz/sha512/a89399bf84bebf4b8432e48aae6dce5547bb6f1c048364697c577541c4f1a555b976370634624e9cf039fcbcb70e449a2f55563f0a4f48e60ee4653a185cf7dd -GMP.v6.2.1+6.armv7l-linux-musleabihf-cxx03.tar.gz/md5/4a682d832109d7ab5743832f73ca33d2 -GMP.v6.2.1+6.armv7l-linux-musleabihf-cxx03.tar.gz/sha512/d5062bd8eee926eb1177e70e5d9e8d6ed7a00a17c25d2b165b974c01aa79d45ca97e219b26ded752b5f323546192d595b838b474c61bdd87e641549db9e9ef5d -GMP.v6.2.1+6.armv7l-linux-musleabihf-cxx11.tar.gz/md5/caa51529cb1b6dc8db765e202e1b7737 -GMP.v6.2.1+6.armv7l-linux-musleabihf-cxx11.tar.gz/sha512/d11ae870e68ca8d28bbcdf799a04769c3df2fbd169f6f2b16d88a556c40866b39636820ac3497e869086e638ba31dc1c87ec780add2d1aafe5e4ca178641678e -GMP.v6.2.1+6.i686-linux-gnu-cxx03.tar.gz/md5/dfcb024b9cfba37f80da5b7cc0c5b1ad -GMP.v6.2.1+6.i686-linux-gnu-cxx03.tar.gz/sha512/10eb086228b4250ecce11ad5bbec15e2bfff2429530cfd700602ead7f108163bc48fc83d9714443cbf5a93e7dd5f9046bdc15ef324486475f6b4be1cf34bad4b -GMP.v6.2.1+6.i686-linux-gnu-cxx11.tar.gz/md5/e889c1d65c9ca710c859129ae99ef322 -GMP.v6.2.1+6.i686-linux-gnu-cxx11.tar.gz/sha512/4d97ebdd6a12d39907ccc9bad00266e286c949b3f99a306c1c4a4380a292694d944f275c351d9ddf465d020c8197b3b19dfccb5080249c75e3f5ffb9aa77a1c4 -GMP.v6.2.1+6.i686-linux-musl-cxx03.tar.gz/md5/d57b3948e7a120bafeae67c28fe40869 -GMP.v6.2.1+6.i686-linux-musl-cxx03.tar.gz/sha512/88165c809a73007d2b5e750d23c619fbb088f6de200aae1dee34b5e3783949150d91b94774cd1881d2a621d092c0e7e7332707ed4737ff8426686dfce7e0313a -GMP.v6.2.1+6.i686-linux-musl-cxx11.tar.gz/md5/e3c53fc468a9f48f9d06fdf51eafae62 -GMP.v6.2.1+6.i686-linux-musl-cxx11.tar.gz/sha512/3c6a99acd84c226d7a48177c8e18624a677ea2a3df15fb2d54002eb5a6d55144b6f51f82ff491373366f32e92252fd14747503166621c2d2359029bdb1b20741 -GMP.v6.2.1+6.i686-w64-mingw32-cxx03.tar.gz/md5/64b9bed188f9a300200659efdb9facef -GMP.v6.2.1+6.i686-w64-mingw32-cxx03.tar.gz/sha512/f7ed47cc29be31f99e612abd1db0d806ece84c117677cd639e04e2f6b08bbbfa4056ed9504bb073ec5f722de6955db668934f3d3ca05ddde0f22b096afcea2e3 -GMP.v6.2.1+6.i686-w64-mingw32-cxx11.tar.gz/md5/a8f38cefb46dc9c3faddfd597d0e1a4c -GMP.v6.2.1+6.i686-w64-mingw32-cxx11.tar.gz/sha512/f02c3458c05869fab493d9be5ea98390baf6eed136fe2916cd6214c4f24a6f22d0716d59f352454fd4c799df71a8fd90e3a169644e1c6ffe89f3620f2a52f158 -GMP.v6.2.1+6.powerpc64le-linux-gnu-cxx03.tar.gz/md5/7f8da2b7e16ef4cb593fea4bdb2e43eb -GMP.v6.2.1+6.powerpc64le-linux-gnu-cxx03.tar.gz/sha512/d0105fe7dfcc1daf7024d2f58b53240bab473c3ae44a904833d009beeb8e41f5487430f68e79bd79fc5c74b55f1111eb7479fedc84bcb45fe4dff3d8c3ac3e4f -GMP.v6.2.1+6.powerpc64le-linux-gnu-cxx11.tar.gz/md5/31fb7b6e37c650f0b8c3a2d475cb2b5b -GMP.v6.2.1+6.powerpc64le-linux-gnu-cxx11.tar.gz/sha512/d03f3f1303996008ff267682de5b9d6e3be78ca1b0d6aa7cadbf4a612b331fe70460b689125f4ededa1c6078092ad3dafaad32c68a98d31713764a7a7461cf98 -GMP.v6.2.1+6.x86_64-apple-darwin.tar.gz/md5/9276d90b4f850f167f673f731c7d3781 -GMP.v6.2.1+6.x86_64-apple-darwin.tar.gz/sha512/f914452a49988b0694915483547c2f878c0ba71be2079fd1228b3e583cb08e92d8c958a052f29025054ded74cacb699893a5a6ef27749c851e83607ad3f1fe8f -GMP.v6.2.1+6.x86_64-linux-gnu-cxx03.tar.gz/md5/cded149fcef93ab1ba89c51d7cc58b73 -GMP.v6.2.1+6.x86_64-linux-gnu-cxx03.tar.gz/sha512/8f97582d6323df6f86e3079b9a2534425bd4e64bb4cec337c21059605d50c1220fd006e55bdb34e8aa7195cd79ef518f1541c1b1a92187ed928f7939b3128dd6 -GMP.v6.2.1+6.x86_64-linux-gnu-cxx11.tar.gz/md5/0529bb60dcf584222cd91e9e11510f24 -GMP.v6.2.1+6.x86_64-linux-gnu-cxx11.tar.gz/sha512/0532821e81a4e51363570f87ec59c37dea24cab59a94e43127837ce4b388d1951853d50e52d4c9f30b4a21cfe222e368207239ce8ac0f1ee1e9375f51fb10127 -GMP.v6.2.1+6.x86_64-linux-musl-cxx03.tar.gz/md5/2d332d096da5515581ee92128aff88ab -GMP.v6.2.1+6.x86_64-linux-musl-cxx03.tar.gz/sha512/b17f7b762bd4d61fa4c4be8124275c2b337383da167bdeaca34e44d71f20716b182b46bc5a6714a798a0951d73b335ab9c87f451cf4c5456edbe76cf3ad36ba4 -GMP.v6.2.1+6.x86_64-linux-musl-cxx11.tar.gz/md5/a9dae953f9d59589162a3ea149c46d1e -GMP.v6.2.1+6.x86_64-linux-musl-cxx11.tar.gz/sha512/31e568aba38a29ec6713dda9eb1c7d7b50c2a736e8883ae8ff2eaf16840b15c93e6dc53025e7750d3ac3e4ffc7d2c91787bda5b799ecfdeea3d928657176b1b3 -GMP.v6.2.1+6.x86_64-unknown-freebsd.tar.gz/md5/6f42d7486fa85ce1bf0cac409d1dd5ae -GMP.v6.2.1+6.x86_64-unknown-freebsd.tar.gz/sha512/5111751619388e51d1b3c0e32548a6de0aa02b7967994a4b4b78cdc9e0e852dae9d78bf48a503a6fb67e3b08343ddcf5a9f0b7a64a803c4d5067d69e4cb2edee -GMP.v6.2.1+6.x86_64-w64-mingw32-cxx03.tar.gz/md5/39cca70db2d23bc73a47870a0ee5156c -GMP.v6.2.1+6.x86_64-w64-mingw32-cxx03.tar.gz/sha512/a2877a6641e4cccd39e7ef093dd9ba7501c6e312f160b2924880d129195aadb74badfbf198fd6ee11035a6a7c99d64c0965c44526104a43569ca0d97fa565b5a -GMP.v6.2.1+6.x86_64-w64-mingw32-cxx11.tar.gz/md5/e2e03ed150558405ca1993ca14488662 -GMP.v6.2.1+6.x86_64-w64-mingw32-cxx11.tar.gz/sha512/50995f6382ed2a4c425097e7abf762b847872734c104847f6a042090be132c68e864d34bb24baf64832d3636810cb631464767949eb2df2fedaa7ccd9824f78b -gmp-6.2.1.tar.bz2/md5/28971fc21cf028042d4897f02fd355ea -gmp-6.2.1.tar.bz2/sha512/8904334a3bcc5c896ececabc75cda9dec642e401fb5397c4992c4fabea5e962c9ce8bd44e8e4233c34e55c8010cc28db0545f5f750cbdbb5f00af538dc763be9 +GMP.v6.3.0+0.aarch64-apple-darwin.tar.gz/md5/70a730ecf64eefb5a13f4524e29a6388 +GMP.v6.3.0+0.aarch64-apple-darwin.tar.gz/sha512/51791b4ae0ede1db4c6e7759072d125ca56f6a3a3e43fd5970981a3b2d651f28fe0abefce4b3ad0589d3a46c143054d20fee801bbd423bd2a4c12ba97314c39c +GMP.v6.3.0+0.aarch64-linux-gnu-cxx03.tar.gz/md5/e2b0bf1317259972cdc4f0e6fc3c2bc8 +GMP.v6.3.0+0.aarch64-linux-gnu-cxx03.tar.gz/sha512/8de1dd5d6971c76693c67222725c9eb0a1d276a55a28cd49d94115123100bfe45144652421d4cde468dce67a5630736f4174c9491c8a6e2543aadcb44f1f2d12 +GMP.v6.3.0+0.aarch64-linux-gnu-cxx11.tar.gz/md5/2017b6215ed99c3aed8b04abe75cb3e9 +GMP.v6.3.0+0.aarch64-linux-gnu-cxx11.tar.gz/sha512/78b22106f96348f0d9222279fdf8d1e3f5bd400f771fb0c54dd4045985ee05b896e3097f788739eefab9a9ab09a885aad65c4adb31ae5ba59b7ab22ca10bb574 +GMP.v6.3.0+0.aarch64-linux-musl-cxx03.tar.gz/md5/6477f35f92203db871f56f047b99a1fe +GMP.v6.3.0+0.aarch64-linux-musl-cxx03.tar.gz/sha512/66a6d18979c1ee9a5d06323a717d0a5dd73efc196087349408e739d7aa0444e8ee1af4bd634f85dfd4cfa4c97c24dda4ba472b490f50409581aff967c81b0750 +GMP.v6.3.0+0.aarch64-linux-musl-cxx11.tar.gz/md5/4648558f1e42b8e679f5be494a910402 +GMP.v6.3.0+0.aarch64-linux-musl-cxx11.tar.gz/sha512/9b7ff68a412bccd423b3cffefbc6350db6db8f3f7657713767187c2c2ea3b09d835e1c80d34ab4407f79fccbec82594e024787def27b9ad2ee7ea01ef1607b53 +GMP.v6.3.0+0.armv6l-linux-gnueabihf-cxx03.tar.gz/md5/6cabb238d148b3e2e76e8527e65893cd +GMP.v6.3.0+0.armv6l-linux-gnueabihf-cxx03.tar.gz/sha512/07b5673b4680781b7d42399213ecd491ede8883bbf1825689ad6678986a76581f6c4e53f17353f63bec8db8df5ed3fbddc228694eecc54ae7fc949f106bb8f14 +GMP.v6.3.0+0.armv6l-linux-gnueabihf-cxx11.tar.gz/md5/0257216ad4e96b404d456f07fcc30b09 +GMP.v6.3.0+0.armv6l-linux-gnueabihf-cxx11.tar.gz/sha512/ae8bbbbe3992f78186fe7535e450330e94e6630540eefbdfb51bb5014afd90feac0b1583e3fd2bbf226e61523647b3ec6324188bd6267c353a2a98594566c02b +GMP.v6.3.0+0.armv6l-linux-musleabihf-cxx03.tar.gz/md5/48b949c062ea27dc0dbcc07ea5387821 +GMP.v6.3.0+0.armv6l-linux-musleabihf-cxx03.tar.gz/sha512/03699c20b5c50dbd44f45a0f5f115c6b10b4e8de68d747bceba605c3090469c819b82ad7e57fe7702c1700c25aae6ab9394a22ded319bc58c80e9d20692b610e +GMP.v6.3.0+0.armv6l-linux-musleabihf-cxx11.tar.gz/md5/847ba3116072a523e1ff4ce83e5a18a8 +GMP.v6.3.0+0.armv6l-linux-musleabihf-cxx11.tar.gz/sha512/402548acd57f4112bf2435803f35ea93fd8d07f3df0e2f053b0bec6b08aa3dff4052990a724e2547ce35a29ee376b17d34b7e7e2ab45ecb4981ffc99c56f1a9f +GMP.v6.3.0+0.armv7l-linux-gnueabihf-cxx03.tar.gz/md5/5cc75b66059c3b8b5fbf9b8fcb781b10 +GMP.v6.3.0+0.armv7l-linux-gnueabihf-cxx03.tar.gz/sha512/1ef583d014c825e1d4e6d5f7e2d84c3ba183ba9490410f5d424760e275b7032e98f8377d87ed349d4969c6ef8f9b961a1e8df6f40efb406d41983446a9510303 +GMP.v6.3.0+0.armv7l-linux-gnueabihf-cxx11.tar.gz/md5/c0295c143bcb6b53d6184e2852ce35c5 +GMP.v6.3.0+0.armv7l-linux-gnueabihf-cxx11.tar.gz/sha512/3c74edb123a6f4147b416e5f7f25903bc859ac5f58f141bd463d3dff8cc2928fedf176f20869a1018a2731c1d7170444b3b3405c8f89c3fc22dc2edf9c036c24 +GMP.v6.3.0+0.armv7l-linux-musleabihf-cxx03.tar.gz/md5/a67696b02a7f67405dd84252c908e071 +GMP.v6.3.0+0.armv7l-linux-musleabihf-cxx03.tar.gz/sha512/73ba1809cfc68199401974f73e7a37b1fe00d4c0cf3e58ed85d161a8fbac4390aeb28591c3108fc503ef8fb5b131d027cb76dcf5d7731698997c2f377d929dce +GMP.v6.3.0+0.armv7l-linux-musleabihf-cxx11.tar.gz/md5/484f00cd5b0beec20f63cd6734d02611 +GMP.v6.3.0+0.armv7l-linux-musleabihf-cxx11.tar.gz/sha512/46fc56f945647f5c8577ad45f540a034f747604e5a89230d9d419b10d5f0571c7580e18e1138ea920efc08b25798c0c7110e15359de17dce3b6db7f07b8ceb3a +GMP.v6.3.0+0.i686-linux-gnu-cxx03.tar.gz/md5/d36d84638e2e5f927d15f07c55919f5f +GMP.v6.3.0+0.i686-linux-gnu-cxx03.tar.gz/sha512/61c62084ab90d25f7168281c7fb672f5bcafdf909afbf66847cfaa1077dd5474b2c27464eb76cac45f5e319aca0c4f7367fc238b83d2dde46ba90a7c1f396dfb +GMP.v6.3.0+0.i686-linux-gnu-cxx11.tar.gz/md5/d87627470bdcac981f7b004c27ac9a89 +GMP.v6.3.0+0.i686-linux-gnu-cxx11.tar.gz/sha512/2a34028687f75422b43f5365b0a8c9530b29473d41bfec4fb9822f074f813b8c6c1fc9efbfbb17a7e4d3d66f2549b5589b3fdbd08711a365330deb72be4958d0 +GMP.v6.3.0+0.i686-linux-musl-cxx03.tar.gz/md5/a2f2fc663bcacfc3e7d6aff29a52de23 +GMP.v6.3.0+0.i686-linux-musl-cxx03.tar.gz/sha512/a30a5d0ee78e747f074b3a5f0a26b9ba99b7553b3c83411a3cb9298814e605509194e9f0d8934caaa1cb7b78eef521805bbc86a297aebd06473ba80a20ffc443 +GMP.v6.3.0+0.i686-linux-musl-cxx11.tar.gz/md5/246b24935442815ff75a13b3dcf24756 +GMP.v6.3.0+0.i686-linux-musl-cxx11.tar.gz/sha512/ca351c4b93adf3f3e40f93c7b0cd61b33ec10049d39e8d33975f46d509efcded67600e6b19d8018a29ee893027d7a28edef0b19c1d70451d072a7a0989e9317d +GMP.v6.3.0+0.i686-w64-mingw32-cxx03.tar.gz/md5/c3b321ae48db0cb8dac4e09e2722e56c +GMP.v6.3.0+0.i686-w64-mingw32-cxx03.tar.gz/sha512/6a6feeb8baf6d499409a9010295b474a8c6de461fa0e34562d53e58190b66c50e278fae7560495cd85ea6f5b41f9e8c6e950ff4f451d26d0757e1d1696e8bca5 +GMP.v6.3.0+0.i686-w64-mingw32-cxx11.tar.gz/md5/3f633b0ff74c2a44350855fc6ce310b8 +GMP.v6.3.0+0.i686-w64-mingw32-cxx11.tar.gz/sha512/eecb17dec70fe84d90f47e1958672d273c865da9607ba3056c9c923a6ff9a3cab5b30414389d8f0c7f5ae5d87c05999964ed0900c80ae5afb525eaec00f401e2 +GMP.v6.3.0+0.powerpc64le-linux-gnu-cxx03.tar.gz/md5/8b5f113ad7fd4a312229cfe8c2d1abca +GMP.v6.3.0+0.powerpc64le-linux-gnu-cxx03.tar.gz/sha512/36525ffc0ac5c363810c47945c34c81daabf88cf1f9c60d236447249d06332d3f5a130b431ab2d1c0148eb5413a4fa66bdd50671f2e7fcb77858d9fcdf83a94c +GMP.v6.3.0+0.powerpc64le-linux-gnu-cxx11.tar.gz/md5/7f1237e9668136b00dd719a5cad3b6aa +GMP.v6.3.0+0.powerpc64le-linux-gnu-cxx11.tar.gz/sha512/46a6efe23173a12299da371121847d16d7950ffe5c87d1221b54c5e95dafbf723c4a327b1c2e832d4742a91254aa40fd5d8152d6d0801769b2efd4f83a042afd +GMP.v6.3.0+0.x86_64-apple-darwin.tar.gz/md5/cd2d1b309aea2c781a9c28470fd2f0eb +GMP.v6.3.0+0.x86_64-apple-darwin.tar.gz/sha512/d7f94d80f1ba170c9553601d1af323bef7bbb98575b80b58b3d7b37d69d81cdee0e132fb4fa20393a0e8719984c785d0c7e5c8ae2c29c62ffbd82b00375993d4 +GMP.v6.3.0+0.x86_64-linux-gnu-cxx03.tar.gz/md5/5be8efef65dafe52e5726ef24238ae36 +GMP.v6.3.0+0.x86_64-linux-gnu-cxx03.tar.gz/sha512/f4c303fe915c89fecdb5a333a30412e0cfb04e07b4f1bc2f726179243dbc61d60ae5b0773a6bd5da8a10cb8764e448bc88035a639ea88d2e06f04e55074d8551 +GMP.v6.3.0+0.x86_64-linux-gnu-cxx11.tar.gz/md5/66f9a3858d07591227f2bc057c3c988b +GMP.v6.3.0+0.x86_64-linux-gnu-cxx11.tar.gz/sha512/5611b9bfd24efac0a189bbd85533e1cd2bee7f833f5ae0a06343f2c1d92925e0d0f0758b99c43520293348ad61f98e1b470829514c35d208697988d8b469fc41 +GMP.v6.3.0+0.x86_64-linux-musl-cxx03.tar.gz/md5/edaa83f6432ff7e75e106d8bfd03d509 +GMP.v6.3.0+0.x86_64-linux-musl-cxx03.tar.gz/sha512/1587e7b91e387da9c23559826c161fa4d447250bd7b6565f0b9fedc36e7502dc2b59caa8157abcb7e7862d24d696470289bd650511b07e8711ecf5a462330b6d +GMP.v6.3.0+0.x86_64-linux-musl-cxx11.tar.gz/md5/e668c4f0c1246aa1510c36f246b1b483 +GMP.v6.3.0+0.x86_64-linux-musl-cxx11.tar.gz/sha512/cf4bd47a5ddb067a57e852855fbd637a93f3652c3327af256f74e9e265c9e0de7c5be78b3e7bcbf08a03916876ecdc05cc294149e2c3d472a30fedc2e6dded47 +GMP.v6.3.0+0.x86_64-unknown-freebsd.tar.gz/md5/4cbf56d2884aa357291321b182d07cb8 +GMP.v6.3.0+0.x86_64-unknown-freebsd.tar.gz/sha512/0c723b8e0f5fabf9e43945d3fb355c3d7b036662a8d6542629aaff27164f12d13b2a19f5c4964f165466705b231884b7f7193d7a01a0e9d3644da1d79af79631 +GMP.v6.3.0+0.x86_64-w64-mingw32-cxx03.tar.gz/md5/02e8f5d66c15731117cf805e0a4c4976 +GMP.v6.3.0+0.x86_64-w64-mingw32-cxx03.tar.gz/sha512/1f94805fe9f34f4e77c54e92625615d91ade617468483409037d0693c3bf106187916d9d21e92681673faae158b376133c0ede643f31bfc9f73ac29c9fd13bcc +GMP.v6.3.0+0.x86_64-w64-mingw32-cxx11.tar.gz/md5/10752137fccc73175872db07749d6f49 +GMP.v6.3.0+0.x86_64-w64-mingw32-cxx11.tar.gz/sha512/3a5d7e8125f3b538a2e59e9c6919db36c974575e6b1950451cb60307da68dc092c4ce21b8f49c40871aadf3bd07681b43eea9c7bf37ba383da9a0e80c30b176e +gmp-6.3.0.tar.bz2/md5/c1cd6ef33085e9cb818b9b08371f9000 +gmp-6.3.0.tar.bz2/sha512/3b684c9bcb9ede2b7e54d0ba4c9764bfa17c20d4f3000017c553b6f1e135b536949580ff37341680c25dc236cfe0ba1db8cfdfe619ce013656189ef0871b89f8 diff --git a/deps/checksums/libgit2 b/deps/checksums/libgit2 index 63d67671b12a4..629a5f0601fcd 100644 --- a/deps/checksums/libgit2 +++ b/deps/checksums/libgit2 @@ -1,34 +1,34 @@ -LibGit2.v1.7.1+0.aarch64-apple-darwin.tar.gz/md5/80102fd8cd633a4875a1257bd61d4e17 -LibGit2.v1.7.1+0.aarch64-apple-darwin.tar.gz/sha512/3cc3679923c36e0020e692e79112a8fa71b53c1b83c9bea8d6defda124722a67c2859089d36fddef7be4547539575483db32de8137b43f7fc97843e579a02696 -LibGit2.v1.7.1+0.aarch64-linux-gnu.tar.gz/md5/74be95a3f7886a9804964f024df5311f -LibGit2.v1.7.1+0.aarch64-linux-gnu.tar.gz/sha512/3ad8a3c9ced9be2ab5fefe651f445a26900beae743127dcd1f887d01a7672d5d6c523641ba7d402620f3c44a1cc9557e43e11ad1692726c8cfabecca59a030e9 -LibGit2.v1.7.1+0.aarch64-linux-musl.tar.gz/md5/e63f4351250b4f4ac60d66b0bed2ddf5 -LibGit2.v1.7.1+0.aarch64-linux-musl.tar.gz/sha512/8f2dd17fe55b7cf7cf60504e5b630b22ce27e4e89d75f7e93dba3b112f662470612987e09abd82c2e3df48fc3c0fe1dbf98c690d972edb50c10a5571741cd9e8 -LibGit2.v1.7.1+0.armv6l-linux-gnueabihf.tar.gz/md5/f06611068a36fa575ec8eb219c068723 -LibGit2.v1.7.1+0.armv6l-linux-gnueabihf.tar.gz/sha512/04c554b9617300cea7750d18590e1a5629e70274ef1e1e8fdabbb4347e46fd8a68e82ba21714d7cd3809c3b6de2e254baca35ff60a4be87485643c460b10ac73 -LibGit2.v1.7.1+0.armv6l-linux-musleabihf.tar.gz/md5/7135ca6e52bf63855c5b6aa45d59ad80 -LibGit2.v1.7.1+0.armv6l-linux-musleabihf.tar.gz/sha512/e542180d2d8a0896ec586edac03b91d48d2ece3d22220d09b6e717b1b95a38bc1de2ae0faeed39dd3e99150684441bfb0504b55b3e18e543e00561f91147d405 -LibGit2.v1.7.1+0.armv7l-linux-gnueabihf.tar.gz/md5/7ffc92c821ec99bd76865ece43f5face -LibGit2.v1.7.1+0.armv7l-linux-gnueabihf.tar.gz/sha512/c945a0895be07479fd94c3c127060e58866bc9b9c93e80dc923ecdda6689e43d566896ecf4bfc7d85ca710b9ee51e6d85dec423edc48a3a2066db0fbb118d073 -LibGit2.v1.7.1+0.armv7l-linux-musleabihf.tar.gz/md5/3d00a6223612c23ac6b3c1f44bff8119 -LibGit2.v1.7.1+0.armv7l-linux-musleabihf.tar.gz/sha512/c3ef7783f140b28ad2d10e1c16f5be683d3332a4f9db5d26fdf3f2ac2b750aa0ceaec928740a9bdf7f4d9e83f666aa6e5fdd9c019630bf46f6797000943e1510 -LibGit2.v1.7.1+0.i686-linux-gnu.tar.gz/md5/6ea4e6777f5a8630f9fa98fb6a4a4ac7 -LibGit2.v1.7.1+0.i686-linux-gnu.tar.gz/sha512/d62a46b54dfc491a88fa35d06f3ed9c76ce676473b33acd5382e72ce07e6a313505755476c4732b7a22cd774ddcdf4ea5e8a5b62b93eb48b67363911655ac177 -LibGit2.v1.7.1+0.i686-linux-musl.tar.gz/md5/9f74dc4e93886f011412a4f61dfb487f -LibGit2.v1.7.1+0.i686-linux-musl.tar.gz/sha512/1370cab2ef917aa759dd4986f247a6b4878f12c4b17399fa75c9a2878f86c136e6f2f998a396df0757bf36ac09d5d194e4b7688705d115f09c176f4a5ab22347 -LibGit2.v1.7.1+0.i686-w64-mingw32.tar.gz/md5/ce866e600b2ad8c0fd54ff8c57dc015c -LibGit2.v1.7.1+0.i686-w64-mingw32.tar.gz/sha512/c7848b39f3515452e13fb156ee645f9a8d3917374ba874b10437b417b3c8e9a108e014b3baf30c7ced5fd0034d4f37de7e7d76fb105358d8e953dca30c873dc6 -LibGit2.v1.7.1+0.powerpc64le-linux-gnu.tar.gz/md5/b7c2f120e33f499860cb1e096923e7fe -LibGit2.v1.7.1+0.powerpc64le-linux-gnu.tar.gz/sha512/3df8e54d2086fbedb55b5dc31a2010f2ecd277089293473607e780340882bda5b2f9a2cc1c53c88bd7fcca0791cc2530645ceda17de3f37bb1ff98a19ccb85cf -LibGit2.v1.7.1+0.x86_64-apple-darwin.tar.gz/md5/b5334bd7e44c2c28705bb816fe03b9b7 -LibGit2.v1.7.1+0.x86_64-apple-darwin.tar.gz/sha512/d91cfde393499687cc699d55184c58ee5f543108902bf1f08fde2270dec0f38e0d70cbc7af04ffe46952afad12ce008e745f4aae9084f23df58982c14b48117c -LibGit2.v1.7.1+0.x86_64-linux-gnu.tar.gz/md5/9e2e2fe324a40bb0a5364d218c5ce45e -LibGit2.v1.7.1+0.x86_64-linux-gnu.tar.gz/sha512/da7e28c20c09c5c0731fd5cdff6fa6c319b2c4757d5c4228fc287238cd649f98c689814480119f21cbb938a29f52c895021b44c74eccc2f93ae51766555d9b6a -LibGit2.v1.7.1+0.x86_64-linux-musl.tar.gz/md5/7147480b9520116eb63ee3c30fa60a21 -LibGit2.v1.7.1+0.x86_64-linux-musl.tar.gz/sha512/f3dfb2a416cb786f229fe9eb3ef653a30ba5ebf3b978475f0a10fa79fa68b7bce9b6d99aed19f8dfb5599d988e3c6d4ede9ef1a6ccdbb3c2ea61f76b97d7fb29 -LibGit2.v1.7.1+0.x86_64-unknown-freebsd.tar.gz/md5/39e1a6d463e52ca0b2a1a8e6c3c4a286 -LibGit2.v1.7.1+0.x86_64-unknown-freebsd.tar.gz/sha512/3978ba9923cc8a188aca36d7320d46b2788de27142d11920976c47ad43574ad7056539812cebab62550e656b263c2d277754c341bd83d013de608a91e6a0aad3 -LibGit2.v1.7.1+0.x86_64-w64-mingw32.tar.gz/md5/7d92c546023f460741a8187999b76bbe -LibGit2.v1.7.1+0.x86_64-w64-mingw32.tar.gz/sha512/da00d54f969ce3b70cc95dda281ddfafee72073164c31d7999053ed704a59401d64894ad702306d6e19eb1a60e5e98e5960c9c7e9a0e1645a0f3048422e62eb9 -libgit2-e6325351ceee58cf56f58bdce61b38907805544f.tar.gz/md5/08777cc257825f218ceac1a24abafdc9 -libgit2-e6325351ceee58cf56f58bdce61b38907805544f.tar.gz/sha512/ebeaf3bb12ce7d58cd6d36e0123168de3af8f083f707dc20df9781537e38188a176667ac51daf8d9006d54f2beed13fbfff6c26fbb48e3228988578ef8fbc9b7 +LibGit2.v1.8.0+0.aarch64-apple-darwin.tar.gz/md5/c19f3a4f6567b7504f607fc6f328312f +LibGit2.v1.8.0+0.aarch64-apple-darwin.tar.gz/sha512/0a776ab3eb4593abe0f2198a7371cbcf653ac5cf71ab7af9d5520c2bbbbbc981cf07ba3afa70f1ef6ea56f81e2d4b33b1be1482f9e215e61178b3dd1149ecb80 +LibGit2.v1.8.0+0.aarch64-linux-gnu.tar.gz/md5/8137d530bea16d41a614983068c1909d +LibGit2.v1.8.0+0.aarch64-linux-gnu.tar.gz/sha512/bdcb6249acd8df887a8af7c084d409132694a39f5e9f90bd70bba0f3eba2bad3eab6958cce9f060b2a4392d99352ccda8be92000f24ed4498c85ba55e0cbf13f +LibGit2.v1.8.0+0.aarch64-linux-musl.tar.gz/md5/4b9508ea58d4b1bd99f8471bd7c9a839 +LibGit2.v1.8.0+0.aarch64-linux-musl.tar.gz/sha512/e0996627a3d3ab9b3b1d103bbdd3e1179ede5479816f6b1be54471f120f76fe0495d3c7587c382985173c0614b634903b58c67ac3badbead82b4d797cc5915d7 +LibGit2.v1.8.0+0.armv6l-linux-gnueabihf.tar.gz/md5/02d6fae1745562cf724190929383688e +LibGit2.v1.8.0+0.armv6l-linux-gnueabihf.tar.gz/sha512/11c14c5f395252143218a495c7dd2817c8f18f73320200a521f5ccd5f0c3c87403dee2c3b9e8166022fde1a67e83cbb83e6f222aac38b41efa43a6c0254548a9 +LibGit2.v1.8.0+0.armv6l-linux-musleabihf.tar.gz/md5/afa7b90751565b865f443b5a0a870d8b +LibGit2.v1.8.0+0.armv6l-linux-musleabihf.tar.gz/sha512/3594c223883a7da3bc0c78ae656fb15e47cc9dd196cf08f0abc0a1fb5f799842261e125440c07e92ba82896ad7427814bb43b63ba64d9b72ba38e9149132c26b +LibGit2.v1.8.0+0.armv7l-linux-gnueabihf.tar.gz/md5/ead27583a1cc5748c84d58a07fa6fc7e +LibGit2.v1.8.0+0.armv7l-linux-gnueabihf.tar.gz/sha512/c81c3582fda7b299aaed72de07e9d0bd06c0c231aed73bb980c12c89b2b7593b2fb7990421bc2e45173f3d107ab50660842289675efa31f24ff752f0ebc63875 +LibGit2.v1.8.0+0.armv7l-linux-musleabihf.tar.gz/md5/71e38f112e8629682cc383c510f1f13f +LibGit2.v1.8.0+0.armv7l-linux-musleabihf.tar.gz/sha512/0b4b2677bdfc4f8e2a5ef3b9adf8fa2c0c1e76bd240b2173c268835b59cda29cbffc4241319fe36dcd63d1507ecf0e0e843f48fca80e3fbe4d3df53601ad7dec +LibGit2.v1.8.0+0.i686-linux-gnu.tar.gz/md5/b54cdd02201c1481746ab81db6f39aac +LibGit2.v1.8.0+0.i686-linux-gnu.tar.gz/sha512/0496a607b0d6841fc0c87477274b61eb95afa24d48d2624f8aaf230028a24d0248336902b01a326010fdbc45d8d73eecedb14c82313e1b1a94b3b6a4296e2607 +LibGit2.v1.8.0+0.i686-linux-musl.tar.gz/md5/b8946f9c1b83a7c22b2a8ca6da05b23b +LibGit2.v1.8.0+0.i686-linux-musl.tar.gz/sha512/931ff27da4e35749a5c020af72b750dce0b1d672cd98c04ad5a340f4f33ddb61c4df6711c36c6601063262fb733d45a3bb24eb1141e9d2fd2e0ab7b9bfbf54c8 +LibGit2.v1.8.0+0.i686-w64-mingw32.tar.gz/md5/cb9db0b590efe0c60f6e2f9558de4e5b +LibGit2.v1.8.0+0.i686-w64-mingw32.tar.gz/sha512/b9f637ec10cd751dfb2156e154a68809af400cdecbf0d25910792937c63ea56e60b703c7b78e3038ab34b1100bb20df79dce6346f40f1d24e5188fefe9517ccc +LibGit2.v1.8.0+0.powerpc64le-linux-gnu.tar.gz/md5/4ac1fa6b1ca43d3f6e97d590d1d27127 +LibGit2.v1.8.0+0.powerpc64le-linux-gnu.tar.gz/sha512/5cc3b9b9d85068bb3c6711c63ccb2672be765888a114147f70cae0eebf96f5bde00a40f163202c20e18a4cf4af2488fd1c304060daa3dd35748b30ab5a1fdb1d +LibGit2.v1.8.0+0.x86_64-apple-darwin.tar.gz/md5/26c2d1dcf68bc7b9919dfd24eb2fabc7 +LibGit2.v1.8.0+0.x86_64-apple-darwin.tar.gz/sha512/9c3ba94c438682f321cb2130f71028587a4a21b960f94f8c3f633dbe007210ff1b7b5e0b0bc4972e818458843a47a9e8d50d88f1bd3fb03a8fe129fa66332a38 +LibGit2.v1.8.0+0.x86_64-linux-gnu.tar.gz/md5/8d48ad388cca20d6338095cf8a36c3c9 +LibGit2.v1.8.0+0.x86_64-linux-gnu.tar.gz/sha512/9ddf4dc7420a9129ff8c34eb07ee94d9213c1a1c22700521199032a773353ab2413fd70d002b273a06dd951558128cd5577b7e917de6575d379911831d737bed +LibGit2.v1.8.0+0.x86_64-linux-musl.tar.gz/md5/5d9fd50bdf38ec99311e9627f254b95d +LibGit2.v1.8.0+0.x86_64-linux-musl.tar.gz/sha512/eae36cc70cb414e15924c22c9b03a98949b89fd8ca429fbe23fa215253266ed4cd11530adb2feccc9a19299ebf2048f1f5929c1faffba463b593b6c3e1808bee +LibGit2.v1.8.0+0.x86_64-unknown-freebsd.tar.gz/md5/af9e9d4bbe0df291f07f41416809a6f2 +LibGit2.v1.8.0+0.x86_64-unknown-freebsd.tar.gz/sha512/859786f13ba1fb5cd448dd5f22ebdf3381f5290d4875d65840fb31567ccd012f283a1849a82f2b2f58c3d73eda4c748d3da07d84a99665e0f50aeb39c37a4fb2 +LibGit2.v1.8.0+0.x86_64-w64-mingw32.tar.gz/md5/b51e3e238d776d52e396dd749f895e4f +LibGit2.v1.8.0+0.x86_64-w64-mingw32.tar.gz/sha512/2581d4b1d6fd4d0f15b406f050bd8a2a41e13dc2a1699a9b956e56426778beb994e6552988bf50ddad682349209a8694eace9450dab0570434cdfbed9c9f0b24 +libgit2-d74d491481831ddcd23575d376e56d2197e95910.tar.gz/md5/2420def04a290dd7ad0e67f93789f106 +libgit2-d74d491481831ddcd23575d376e56d2197e95910.tar.gz/sha512/496c6640d2453c58b66bc53c6e4e9e84f48d89eb8f240aefc1eea7e2d19b6c601614764590f2d2ca9e51e7e1de8fcdd5bf67f27f32bab628eae40d26ed382048 diff --git a/deps/checksums/libuv b/deps/checksums/libuv index 6ad61210bc0cc..41a9a5bdf9722 100644 --- a/deps/checksums/libuv +++ b/deps/checksums/libuv @@ -1,34 +1,34 @@ -LibUV.v2.0.1+14.aarch64-apple-darwin.tar.gz/md5/1a58ce9dc88984c3b5f7df97af6cbf83 -LibUV.v2.0.1+14.aarch64-apple-darwin.tar.gz/sha512/2bfd482ac759ac88d885371854affa8e358a10fea6c7756e0d1b366bc82ecbea56bdf24ca634525fb2a6fc2b3a5c77b07a4c6dec2923d8bffe2bc962bd3e7f84 -LibUV.v2.0.1+14.aarch64-linux-gnu.tar.gz/md5/7f270dd1e3046c8db432e350dd5cf114 -LibUV.v2.0.1+14.aarch64-linux-gnu.tar.gz/sha512/c0debcf17b54ba9f1588d4b267d610751f739d8ff96936c9d5fb6d8742039f8736c63fa70037322705569e221d73fb83c03b6ba9fb4454442fffd3a9f1a1a2da -LibUV.v2.0.1+14.aarch64-linux-musl.tar.gz/md5/07f56c32d5a2c12e6c351cf9f705631c -LibUV.v2.0.1+14.aarch64-linux-musl.tar.gz/sha512/8037d7aa0cb06850f055fd19cebdcfcf3146dde0d12768a9669bf05dcab91fdf3708798203258cb3f452158bdec7faae41e6afbb0e60b21403e683db3e23a1c9 -LibUV.v2.0.1+14.armv6l-linux-gnueabihf.tar.gz/md5/5558a7f68c7c375f40bc64da59fef0ad -LibUV.v2.0.1+14.armv6l-linux-gnueabihf.tar.gz/sha512/92ed6601cb5aa9a3ea2478a1485849543c9e847c8e85542e72f372a2d37c4c8b90f5ecb1bee1e462db31e1e8dba460f584b3cca9c833989c2b9ee404e355654e -LibUV.v2.0.1+14.armv6l-linux-musleabihf.tar.gz/md5/de6bfb7f0c0468b79e8895f166fb6340 -LibUV.v2.0.1+14.armv6l-linux-musleabihf.tar.gz/sha512/7948d007171bf57b827b489f3627ac74df447f4d696e8226e54e95ef0c8eed5a5ddbf758fbad841bc367f78cd61e6a5899eb478003dca3a79cb494b38cab830b -LibUV.v2.0.1+14.armv7l-linux-gnueabihf.tar.gz/md5/5be35de1d881f80981647c369b9b4ec8 -LibUV.v2.0.1+14.armv7l-linux-gnueabihf.tar.gz/sha512/458e5058ea4e794e0dc790da4c98569676056bac336df69762e8ccfec8f2955dcc55e8d090daa1b191c0ffa41392a04530c9bc28aa27cf411c1df2f1ba14bb97 -LibUV.v2.0.1+14.armv7l-linux-musleabihf.tar.gz/md5/8d034490da1ec2ef3dd3c69336177654 -LibUV.v2.0.1+14.armv7l-linux-musleabihf.tar.gz/sha512/7f595a8ab8b664d229cf6144e9ed1b5936ba8aaa70b92611ddb85bbe9046bb1b94d8417355a5abf058fb00023d4d56be0b2ddfd5dba896cd7b64e84e32dbfc5a -LibUV.v2.0.1+14.i686-linux-gnu.tar.gz/md5/ccb9aba78456c99b8473e8ddd328f90e -LibUV.v2.0.1+14.i686-linux-gnu.tar.gz/sha512/d382d90137db308933257a75e51d90988d6d07663b3b2915478547127d32f73ae6cdb4575d5ee20758f8850c7e85908fe4710c053cb361826621f22bc5b6502d -LibUV.v2.0.1+14.i686-linux-musl.tar.gz/md5/5ade48f16aa26bb68dc046d285c73043 -LibUV.v2.0.1+14.i686-linux-musl.tar.gz/sha512/f5728a5dc567268e59aa2697deb793ae427e11dcb6796c577e3da3ac24225ece5d4a6c4f903d4a7b184d3c3a3c8c1586c34b97e4a75de0a4e23ace720020fa8c -LibUV.v2.0.1+14.i686-w64-mingw32.tar.gz/md5/541210fef837c2ef7cffa508d282f9bb -LibUV.v2.0.1+14.i686-w64-mingw32.tar.gz/sha512/4541a02c59b66f97099b5264dce0cad90fcdf9a4d7ccd8e950cc1f3a530616a0fb2aa43db21b5b1f52819efef22cd0b68595d419e2e5b05924e344b0333f8bf8 -LibUV.v2.0.1+14.powerpc64le-linux-gnu.tar.gz/md5/26656d4eaae8739099c55054bad54f57 -LibUV.v2.0.1+14.powerpc64le-linux-gnu.tar.gz/sha512/f85f8cfd91e7b1b02b073931ef9a3bb05620641d18ada039744a92b8c40e5a3de8d7c5efa7189b88baf1eb11fbcf9e6d16031b86e40f99f1b7cfebb0f5c5adf1 -LibUV.v2.0.1+14.x86_64-apple-darwin.tar.gz/md5/c7da6b91394a20c43acdf6f680cb62e2 -LibUV.v2.0.1+14.x86_64-apple-darwin.tar.gz/sha512/238d22bd299ae3b0dfd24a5b38d6d0d07b751fb301487a2d1d2f5313ae3596f33492388ea9fbff549293787505fc527e174ebcd4068f1bda43b40bc19e016d89 -LibUV.v2.0.1+14.x86_64-linux-gnu.tar.gz/md5/8c8913068263257cce5042b725918e0e -LibUV.v2.0.1+14.x86_64-linux-gnu.tar.gz/sha512/a848381012d5a20a0c881f5835e479cfff811928ce508cc57041d69668782f2135c14c7e5388e7dbf693ae57aa1825d911f6f450b9e909cce45487b03a581a23 -LibUV.v2.0.1+14.x86_64-linux-musl.tar.gz/md5/16747c066b6d7fe56850c77f66ea7478 -LibUV.v2.0.1+14.x86_64-linux-musl.tar.gz/sha512/833a02f9191edf3b56f1e02f5671f22de6cb27ec3c9f770530ec95d8da7ba0b9c05bcdf6b094224ea8e43ba70918e1599f3237bd98900763daef80c327d3d2de -LibUV.v2.0.1+14.x86_64-unknown-freebsd.tar.gz/md5/e828eb79728e75766a72d7b304c9f989 -LibUV.v2.0.1+14.x86_64-unknown-freebsd.tar.gz/sha512/37df5b966f70b3d1e0eae603d4a6b00c84dffdfc3632ca581669a99a0cd894a81aff4361de3beed53ec032273f62cf397cf52085c6c387d0bbb2c57b59ae84fe -LibUV.v2.0.1+14.x86_64-w64-mingw32.tar.gz/md5/bded9d94435a70fd0dfff3f0fc605736 -LibUV.v2.0.1+14.x86_64-w64-mingw32.tar.gz/sha512/48793a386f6231d12f01b4718d87aaab409f0b807b03a3577e2401f7493caef36a5072fdc33f3cd3ce9733ba50ab344cb2e2fa6a21ba5adb56d6cca642afad0c -libuv-2723e256e952be0b015b3c0086f717c3d365d97e.tar.gz/md5/d2284d7f6fa75d6a35673d22e1be058b -libuv-2723e256e952be0b015b3c0086f717c3d365d97e.tar.gz/sha512/68d6ab740945b9ce3475118ce3d186fb67d7e8125784cc0c827df23d63f50c40c0261ef37365d8c11ab9462a8dd4e2e6b19e91e3c84b64d8fb84fd3894afc4ac +LibUV.v2.0.1+16.aarch64-apple-darwin.tar.gz/md5/132266a501144f34eb9b8d5199db43c0 +LibUV.v2.0.1+16.aarch64-apple-darwin.tar.gz/sha512/e466ba8a2fe916f0e2dccb1d1075a6a20fcc5d5068d2375c940353a63522332fa8f665461adbb47ad4d30dabaea011b8e72a603601da29a071d98c7d7d130f46 +LibUV.v2.0.1+16.aarch64-linux-gnu.tar.gz/md5/1ae3018d9ab8bb293dbf6277c2c209cc +LibUV.v2.0.1+16.aarch64-linux-gnu.tar.gz/sha512/6e56876cdf0fdad1aade6435edf980b286438ee9fa695fa4e262b47f7ada6ff69535c59d216daee3eb1d061a90c2c16fd70d21438776c54addda93cf275ef1be +LibUV.v2.0.1+16.aarch64-linux-musl.tar.gz/md5/08243e727c7e957f5972a200b5d89113 +LibUV.v2.0.1+16.aarch64-linux-musl.tar.gz/sha512/4a684f248704b16b882d66ed7af60e2217a0b98f476bfdd1cb545d3e2adb17f6a410bf09e270c1e2623e550b36639c9282a562ab415850dfea98736ec03fd000 +LibUV.v2.0.1+16.armv6l-linux-gnueabihf.tar.gz/md5/c4dfccf5a899782715cbb0ca0197938c +LibUV.v2.0.1+16.armv6l-linux-gnueabihf.tar.gz/sha512/ecdcd655865a532187e4e98cb21ca68e62303813cad585de83382aa226d965213f24fe7a684e1189fad11b0e5f2f4b318c122f557a6117f61bb2948b51e16a76 +LibUV.v2.0.1+16.armv6l-linux-musleabihf.tar.gz/md5/5382dae963f3003aefdb119377a45e82 +LibUV.v2.0.1+16.armv6l-linux-musleabihf.tar.gz/sha512/f901c2965e8f9ca52900180c32cdb70d8adc13f12f076c1b109d57b749cac1ecaac3c72e22531e6fcb79c8f2c7cf952ff563779d3764b015b73db079f2b171cb +LibUV.v2.0.1+16.armv7l-linux-gnueabihf.tar.gz/md5/9c4cd82249c03ebeac670e2c7c8c1078 +LibUV.v2.0.1+16.armv7l-linux-gnueabihf.tar.gz/sha512/ee4b7f866e3f63df303d00d48d36680c490570979bb7174c12cfcf9efaf48ea7ae90aa05b41da8ab686de93c910c5a761f31da22845ad48fd980e9c16437cbfb +LibUV.v2.0.1+16.armv7l-linux-musleabihf.tar.gz/md5/5255d7e320ef37eb63d0e85c4b86d20d +LibUV.v2.0.1+16.armv7l-linux-musleabihf.tar.gz/sha512/5bcd3d22b1e2398879e654bb550fd093891775c64cb48bd179c4f9ff8dcbff23eda91a66ea14852ef5945d5c114732957075e3b3fded4cbd3cca559fead842db +LibUV.v2.0.1+16.i686-linux-gnu.tar.gz/md5/7f0fc52beb13dad773c6ab54deee7a62 +LibUV.v2.0.1+16.i686-linux-gnu.tar.gz/sha512/cb1736eab4fa1be89018b3c77c3551a99d0fa761ad2f1947587c215d87d963d43198ce87574b6eb9d1fb8a93abf1ae89e74fb8a3f3fb9c4fd08a49e04b4335f4 +LibUV.v2.0.1+16.i686-linux-musl.tar.gz/md5/ed22ccd7eaa09ed9c71afc0c6affa423 +LibUV.v2.0.1+16.i686-linux-musl.tar.gz/sha512/7f3ff061c3d7d0c3c0c0be3e4052aeed39f35e1ba0b92f3ee3d9f266f26d064acc153c08054a22d090167f00fef3c27ec54e836de35f348e4849baab301f7fa4 +LibUV.v2.0.1+16.i686-w64-mingw32.tar.gz/md5/7f1fe93df0b741ca30c4fb64ff9ac9bd +LibUV.v2.0.1+16.i686-w64-mingw32.tar.gz/sha512/9d71722c538d8232d8510fa2a43e7a52271b078401dfa838de9eedcfc34a2483aa3b1c221b17c41353b54554fe76d86b4973c5261b288228a91f0cc92820ad93 +LibUV.v2.0.1+16.powerpc64le-linux-gnu.tar.gz/md5/b796de6c75f18f318823e3e1cdd316c8 +LibUV.v2.0.1+16.powerpc64le-linux-gnu.tar.gz/sha512/f8dbb98cb49edfa06a0b48fbe1e658ca5a9bca13fe33d21872a012deaa1052a495faf74f90c0dfa48378b9f4f51f1045e01e563aec427d8c89d50e4eef0e4938 +LibUV.v2.0.1+16.x86_64-apple-darwin.tar.gz/md5/f2d55b315fa1f77b632a461530bb6b3b +LibUV.v2.0.1+16.x86_64-apple-darwin.tar.gz/sha512/eb40a193c3bca5e822a417879e854877b353a2a04b03a721ef4125360f1189a3685d2751e2f975360a2ad4c37e6043485a54b5349b3da423b8aae73d4a095d04 +LibUV.v2.0.1+16.x86_64-linux-gnu.tar.gz/md5/a573ded4f78f8677ef73594be9629638 +LibUV.v2.0.1+16.x86_64-linux-gnu.tar.gz/sha512/c5809635be3ab5dc53c37a028e58695d89ea91eee850af22a0e8db10ea021640f1e618a553848332ee6df66eecd08d34605e335aad46ece82365a3525b69c42f +LibUV.v2.0.1+16.x86_64-linux-musl.tar.gz/md5/5bdad561b5db7d19f198ef090ae3ec84 +LibUV.v2.0.1+16.x86_64-linux-musl.tar.gz/sha512/6662c8226f22f79f8c40857a5a531841f013031dd2e9536568498bfd536f133976ff71d0cc5f56f1e0c0b7f2403a35c2ccef9117d9e0d7819771bd492194f20d +LibUV.v2.0.1+16.x86_64-unknown-freebsd.tar.gz/md5/f4ad9e445e4b14e2b59b2b77c9ed72ad +LibUV.v2.0.1+16.x86_64-unknown-freebsd.tar.gz/sha512/a78deac6d8321f274a229961620da4d069ff2accf7d1ed9edfb01c21ad47eb33d364ba2f310ff4a93b2732dcd16f6d481843dbcb273770d731fd528f9c7a9ddc +LibUV.v2.0.1+16.x86_64-w64-mingw32.tar.gz/md5/72caa067cf24e304955405dcb4de195a +LibUV.v2.0.1+16.x86_64-w64-mingw32.tar.gz/sha512/de80ca98d199d3c5626ebc771325806ce3aae5927220201c2351207c10ff67791d2865f76e41519df88f0be3da534342965e7ba0d055d807c4b2b6c78bd2427d +libuv-ca3a5a431a1c37859b6508e6b2a288092337029a.tar.gz/md5/d1fbca8bcc5819037b8b81ae4f61c357 +libuv-ca3a5a431a1c37859b6508e6b2a288092337029a.tar.gz/sha512/e735861923c0fc597b53eb2efb56b26acec29e3fcae7e76d349fc08f8b9d340df9ac60a1cd245e46a434aa357ed8e377734c1c97bf08bd044c9ba0c02b082a6a diff --git a/deps/checksums/libwhich b/deps/checksums/libwhich index d4a0119625663..cd34ac7cc0b8b 100644 --- a/deps/checksums/libwhich +++ b/deps/checksums/libwhich @@ -1,2 +1,2 @@ -libwhich-81e9723c0273d78493dc8c8ed570f68d9ce7e89e.tar.gz/md5/22fd8368c7b40209dada50e3205c1294 -libwhich-81e9723c0273d78493dc8c8ed570f68d9ce7e89e.tar.gz/sha512/6fb77b715d70d9bc95a8546c3bf97bd3677c7ea344b88bb5bc3bbfac9dceabe8a8cde7a0f64dec884cde802e4a3000e30837d3f824b5a9242348c4fe061526a3 +libwhich-99a0ea12689e41164456dba03e93bc40924de880.tar.gz/md5/213f0ad813de677d25787cae05901a9a +libwhich-99a0ea12689e41164456dba03e93bc40924de880.tar.gz/sha512/7c42c3b6c480763b85f8c5eb927e776b48cb8a2be1e1c143e799628ee9265adea6a56b33c17583c8e6fc040a3889a4010ac674918bc6947899983a4942353526 diff --git a/deps/checksums/lld b/deps/checksums/lld index 590e2f3cf878e..c703c2c2d041f 100644 --- a/deps/checksums/lld +++ b/deps/checksums/lld @@ -1,108 +1,108 @@ -LLD.v15.0.7+8.aarch64-apple-darwin-llvm_version+15.asserts.tar.gz/md5/0edc0983135da9e37b18fa3fe6d56237 -LLD.v15.0.7+8.aarch64-apple-darwin-llvm_version+15.asserts.tar.gz/sha512/2adbb4eb76e72be28951c96140070b6d16c5144f689631d51b56365549a5d38535c1dbb5e351a6bdac4648ba52da02297591874193b1c16e7078060c99d23f04 -LLD.v15.0.7+8.aarch64-apple-darwin-llvm_version+15.tar.gz/md5/59b06fca083f1a5e9bf9517ae4f6a4d6 -LLD.v15.0.7+8.aarch64-apple-darwin-llvm_version+15.tar.gz/sha512/7f1dc641df9288dfcd887239b86e7fe2871220b9d7f877b24b3197ab73d2176c4533decbea427b09e8f70ddc6c7570d31f5682eaed7215193e95f323769276a8 -LLD.v15.0.7+8.aarch64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/c97e607a661b9ff571eba4238ec649dd -LLD.v15.0.7+8.aarch64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/7c7add8a0fac379b580a19a02966adca4932bd4573ba0111262544c0d935fc121c5aadaeadc97f9564331202b08c7366ceb170bb2b318db3425c157772d283ea -LLD.v15.0.7+8.aarch64-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/d55ebbd25b97a4e4628fad1e04782056 -LLD.v15.0.7+8.aarch64-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/681729b4d10d8f66b0cdb89ca4500ee8a417561cc886608d06af0809d946bdf7cf5c6bda2b6d5d577bae3a15dc347568a3d7d7428568f86ca61327041026fbd2 -LLD.v15.0.7+8.aarch64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/78b06e5a351e6eab372ae29d393ffdcf -LLD.v15.0.7+8.aarch64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/37a8b5fa3491ec8ae74da88e81a0c229d38166acbb46ff3f5a819034c40fa59ca2ebf4c0ed58e615baf7bf7da789ba86114738252501cfbd842be95cc2104dd4 -LLD.v15.0.7+8.aarch64-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/7ba5b76c83d746a3c62354bf753db697 -LLD.v15.0.7+8.aarch64-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/1fa403c8923487e2d6a8e8c1d86c2ea955ed32bcde2328cb1167a315cdcf704af896505e9c44b750ffca9e3ae66e805f60831136eb79fe1c6d58eaf81a78b1a4 -LLD.v15.0.7+8.aarch64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/md5/f052208026a0fd5120ea838843b244ac -LLD.v15.0.7+8.aarch64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/sha512/fd9ff2d5836300bcf76e4aeefb1e57860b3203fab0c32e668dce3e636dc362876d0fba1f2c23bf55a342ac17294c73e839a8eaf065d64d4397582dc212b8b9f4 -LLD.v15.0.7+8.aarch64-linux-musl-cxx03-llvm_version+15.tar.gz/md5/4d1077835df0f592a168c140ffe6299e -LLD.v15.0.7+8.aarch64-linux-musl-cxx03-llvm_version+15.tar.gz/sha512/8dfd44113b817f607bc38ac1b4ffb192be340c826b9bc8f9d41e92e0f0333d8fc4227f93aaed16a4b9e94a5ec8b79628f2d3a73fb644684a595921f36ccfbeb8 -LLD.v15.0.7+8.aarch64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/md5/0f31939f4ff00c572eb392b6e70aab38 -LLD.v15.0.7+8.aarch64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/sha512/581441087ad4869cfdba13808b2d6adaf929ea1b38ce96c357f276d77c3e63439f8edbb822c8f41770cb61fc08837d7eed2466d187683bc44f2cb3c553e2e60e -LLD.v15.0.7+8.aarch64-linux-musl-cxx11-llvm_version+15.tar.gz/md5/ca767173044b5a19a86c6a890dda3b05 -LLD.v15.0.7+8.aarch64-linux-musl-cxx11-llvm_version+15.tar.gz/sha512/0577785079039b534fd736ea7a51d9b5176693d81e0bcda4fccd760d7c1218042999b6a38b973a903c0ef68e57dfb3b86e9e2f9e307dbaf603997a853f34eed3 -LLD.v15.0.7+8.armv6l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/89bb950f17a5b792a6e60ef98450a6b4 -LLD.v15.0.7+8.armv6l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/54bb68159743cd14ac0fce7f218a66ff6bf29e626df8dbdbd6e8581699d9b1d357a3c10d86c6822bde7299c14728bc55480f91cefd041d1de61cc179ed347b9a -LLD.v15.0.7+8.armv6l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/md5/735e4dda5f8cc06934f6bda59eab21d6 -LLD.v15.0.7+8.armv6l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/sha512/a9b91beed959804b9e121fee786f28808a7670fc5d2728688cca1c7e0fe56e82e47d95712e38fdfc42e02030896843c4b3df9928eb34c2aca9ac02262427c76c -LLD.v15.0.7+8.armv6l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/30a95179bef252aaca41984daa54c680 -LLD.v15.0.7+8.armv6l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/0302db3c04396a30d1f6ab8d8d585bbe3a9e70342f068747ddb875b024c173bb9bb34518da7e76a10d3a325dfd741118f36f67fb83251bdb8a9901c4799ad79f -LLD.v15.0.7+8.armv6l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/md5/4386c746c5d9b1408dbe7df04bc6a08d -LLD.v15.0.7+8.armv6l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/sha512/d71c6ebf5d3eb42368ab336cf8520afcd05470308ea117fe95797171e5c573948412ce777f62cbd45ee99ffa59cc769c276a60393a22fecffbeaf8b77b50ea35 -LLD.v15.0.7+8.armv6l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/49287977de61b100979355e458c8970c -LLD.v15.0.7+8.armv6l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/85ed3b2c7d2478a307a393a2003e694fc3097cc6812143abb3cbdd73a7d36bcb6f06a7d341ea639b9849f714c2d8f418a8b96035ed1c19a3957b42d005c0427a -LLD.v15.0.7+8.armv6l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/md5/80a97341c9537b8a58c7df23f86d5cf4 -LLD.v15.0.7+8.armv6l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/sha512/5774b246ae820de4230a1f4f65bd683145dad5cbc4d326fd75649e06e773c74c2cffd48108a79ee0cc93175786450b6d50f7ac532e6f68961c18fe6119ef94f5 -LLD.v15.0.7+8.armv6l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/6f84d6858aecdfd95726a37c9b6a0e0f -LLD.v15.0.7+8.armv6l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/2cdac9a810c777ec6d85093926292c75e4287f83b7224246f6fa248e3874a2078c46377cd5ccb0f36a5e25b139691f1111d705079e89ea4215c9bc8659414094 -LLD.v15.0.7+8.armv6l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/md5/d40f0956cc36aa7846630755a672a91c -LLD.v15.0.7+8.armv6l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/sha512/01368311a0ecfbe3f23514115f0bce7ce816c878815d937f3fa067b9daab07da0c02f520a96ad793212e5056bfb6294dd0129dae75f274dfeb48191e504c5322 -LLD.v15.0.7+8.armv7l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/689120b8091b9da8cc9528c96f5c5df2 -LLD.v15.0.7+8.armv7l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/ab78810af7d77116a4973b5825d5090133218cf08d5d77be14f83e028821e83493a112adf71094cc208f74cf4deabda63d7fff98866cc0304793aec9b27b7222 -LLD.v15.0.7+8.armv7l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/md5/5627ccf1677c48b7ef8ac9e5faac1d20 -LLD.v15.0.7+8.armv7l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/sha512/454d2636cd72974c79c2d907e56e3c69c30c3fff78b199591c9ebe4f14d04c40c4bd7331f8dc2c957c37e214da8d28ef3a47ed8d3dd4ca9d480d52bab3429b39 -LLD.v15.0.7+8.armv7l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/8f50e5f684c41845308c123f8e45a0d5 -LLD.v15.0.7+8.armv7l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/21baf8a00fa65473ff6cf7ef2974ef88cd5b0eadd06ff85598de10d09425074297bcff3472ef001047a5440065a2de2fc6b1eefe3a32c7c1b3e3261165dc063c -LLD.v15.0.7+8.armv7l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/md5/c2e0a5f58e38a9acf2c3914177ceb827 -LLD.v15.0.7+8.armv7l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/sha512/2a1653d171a2ff08bde55c53973e62955fe9d9629388ae014a645d3199d8f4bcf0fb923d06812ccd62e224032b261c8ebed56ebebed750acbc87671203d7aee5 -LLD.v15.0.7+8.armv7l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/fa3959aa413a2b707d8831edd2bd7867 -LLD.v15.0.7+8.armv7l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/8b74fef916a72c2f4933c21d3344410c7e03e64265a44dd62cf2ef2ac0feeafeb2b443eafa5dad3d3d0028be96b9424ff67b16391f1b3a2185826de68921adab -LLD.v15.0.7+8.armv7l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/md5/b0751bf7eba4f7f7a28dc22993eac9cc -LLD.v15.0.7+8.armv7l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/sha512/7510f7349b06365e9cd260229e7b8c84da26bac072c5fe9a4e59484d82a0753d4ecf1066ffe41343f881a682590dc9ee4ef4a49cd83dba45c21b8d76dfb80f67 -LLD.v15.0.7+8.armv7l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/5abfe9e960bab4c8a44f41aaccaf936b -LLD.v15.0.7+8.armv7l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/efda0e0a35e2774af2f2df53f89d61f146a5730086d40865d448b009c833934b23ea4b296c3dc3f2039527b72ef40493fdee6f7c630484f64cec2d1aebf4a4c1 -LLD.v15.0.7+8.armv7l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/md5/bfe87378e965050b1b20e993c8b13a53 -LLD.v15.0.7+8.armv7l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/sha512/ef2fd5e81f349673417bffd68c4122a87c09caed3f6f8f0235bc70b75deca7363cad68276aa708fb9ad8f7edd249d49f78d9f5fe7b226b62e8604c7bd3d4b9cc -LLD.v15.0.7+8.i686-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/4ee16f57d7dc060007250e17ffd55817 -LLD.v15.0.7+8.i686-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/27fd3a21bac676feb2c2c2363c027cf12988c70d889174e52c6bc1fcb4a93241f4bae85d5750ceba5fa971611700a9d15e3e02803cc14382cf6a1ab2918b719c -LLD.v15.0.7+8.i686-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/06699da5617371442b0539203152405d -LLD.v15.0.7+8.i686-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/83ba6300d5669b52c1913440598a2577106ea73e0b83549a5b3b0f081a94b6b8ca9fc05687d2be4b60c2d6a524bafd43b839082f0eee58b4685758061b229fde -LLD.v15.0.7+8.i686-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/a051688aa3a6383b4be4faa4f4aee985 -LLD.v15.0.7+8.i686-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/2059c6ac6579c4720e7167cd547b679a9c1a27a2c68174ed543be935ee23122234b3f2a4555de0abab3a982aba73d1751db336f3e28005ce8e4659d61f9269aa -LLD.v15.0.7+8.i686-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/600baa66310cf348ef3b4351ada014f4 -LLD.v15.0.7+8.i686-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/52b4718993d8abdca8ab701e86022367655d7927dabb8f3a8e41e43dbc90a9af78caf8abd37907a79b0f05017b6f0ef72314a187dab5bdac8ef7996e74c96e2d -LLD.v15.0.7+8.i686-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/md5/e1e12162e5c63141dd95fca83cf1be64 -LLD.v15.0.7+8.i686-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/sha512/ce21d1cfa4af89fee1cb13587e15d355f179d38a4a9b4ffb9f357d46fe9b7fed4a5cad801ddf75d66b71d77170427609a51144450f83c8dd59af5c02fb0a9017 -LLD.v15.0.7+8.i686-w64-mingw32-cxx03-llvm_version+15.tar.gz/md5/932639f712fb881f621e5bb369921132 -LLD.v15.0.7+8.i686-w64-mingw32-cxx03-llvm_version+15.tar.gz/sha512/86426de75c4fd1e1d56e1bee08bb6447be0c7417f8492639158f436caa5794862dd3b2981f26f1828852f35806dd2177fbdaacbee67ea849e8de597948b276b9 -LLD.v15.0.7+8.i686-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/md5/2ff9f42465eb6e845ed6b7567b9d14d8 -LLD.v15.0.7+8.i686-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/sha512/a19b985b28a642b9a40ef25d02d4e94608b7e5c9925db5202e9d34ca8258e0b02d758de5bbeed63f958602f7fb59b70c4f32434a59008af3f0dd1b757a20e2bf -LLD.v15.0.7+8.i686-w64-mingw32-cxx11-llvm_version+15.tar.gz/md5/6de812afa415af04eb9d806adbd4b2ed -LLD.v15.0.7+8.i686-w64-mingw32-cxx11-llvm_version+15.tar.gz/sha512/c6799fb148f8b019d0b05fa707d58ac7f449347290fc3f47458cb84e0bef704fd179e6d29d2efdeb0c35e4ee799284f5850b1ec5c04ccfad45b38e058e449554 -LLD.v15.0.7+8.powerpc64le-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/331d844c447f564171345009764321a1 -LLD.v15.0.7+8.powerpc64le-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/978349a74fc5498408a5318c87ec6d25c01268b9d21fb85e6bb601243ad0d33be8501b181d1f9ab7663433a740912f5bcb7160caf1011b1a2c84fdd51e0fce78 -LLD.v15.0.7+8.powerpc64le-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/8595a49c49e851973fffae7c4062911d -LLD.v15.0.7+8.powerpc64le-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/f707e514843a206b53f380c7bd8d4d8203cc62219344c1234416462dc1cb3d3f8a7452ddfd0f07178d43dfb193b4402a018cc465dc76b43b687fd20fa1ea5222 -LLD.v15.0.7+8.powerpc64le-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/5b4463e81c156dabe3d182c42eb647e1 -LLD.v15.0.7+8.powerpc64le-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/995db577d4a78d62cfcfca3f1fafb333ff26548b41d8aa8d763e4705dcdfe8005e2f68873faba4040599a6d15821a523261d0451d75fdf6e1c5224e8e777a71e -LLD.v15.0.7+8.powerpc64le-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/d2f9f08cc952c0639f7ef1073c8630d6 -LLD.v15.0.7+8.powerpc64le-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/b1cab7b813fe0f7c26c55261e8561295cbdf1e812db3844b87605fb527d09855f2bef4a40ddb0a7cd354c7cbb626293d4d4012f33acc242f9af4abe1dbbbeeb7 -LLD.v15.0.7+8.x86_64-apple-darwin-llvm_version+15.asserts.tar.gz/md5/e82e3b67a073cfa6b019bf5604eabf2a -LLD.v15.0.7+8.x86_64-apple-darwin-llvm_version+15.asserts.tar.gz/sha512/9bb18adf78afa9dfa0054e6511f5750a9e2fa9138aeb1bd83f7a51d37d031e2f3c151463ea8f682dc7130cb98fafae0b84c60d3befe27f9d0d3dc3334ef82420 -LLD.v15.0.7+8.x86_64-apple-darwin-llvm_version+15.tar.gz/md5/56da3cbe81ddff089ccf6b6392a9396c -LLD.v15.0.7+8.x86_64-apple-darwin-llvm_version+15.tar.gz/sha512/2af483a1761022dcad414fa7cec7fb5c6fd54be28185e49539f4824cb0b6acdc1cfa5c78de31268dbdc444201936c5a6d2e04f39ef6f0b9fb184985ba4e3daa2 -LLD.v15.0.7+8.x86_64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/15cbf5eaf89c7b834ee19629387515a5 -LLD.v15.0.7+8.x86_64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/75ce7c398bdfd57af2c09dfc946b024d5a72e90575ed92f28e015e620ca89e421dfc9a391f4a78277c3e06c38dd696d572c5601a2b1866e521dbc2fc5a60da56 -LLD.v15.0.7+8.x86_64-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/b895da29b6082cdff6f0324179352fdf -LLD.v15.0.7+8.x86_64-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/e89a97dfd6c345158e3e12cdf97d33c22f849e5438401cf5a3670c0d1cf0252ca03e4c52475a42c3e6c2b2d689c2f53fc5cb7c925a23167ac51fa1a5e01e3d7f -LLD.v15.0.7+8.x86_64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/7edda2d8c2eaadec2d262ded2456934a -LLD.v15.0.7+8.x86_64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/0b1d60840d638c0b0269b901a3f5198e18e244da338aef2fb49b474b3601d44a2b4dec13e258909985e363ef8a8749838b01dd195e05a266ca36e6d9f274ef17 -LLD.v15.0.7+8.x86_64-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/e26138e3491a053ea9a998dd00ad728b -LLD.v15.0.7+8.x86_64-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/1215861fa52b1ee21196bbce0e99912b25f887f5734e0c2628ac78c1af5fdf57c4d7cf099cddcd7031a26c60cf141aeea66a0147428008cb485c207e90801835 -LLD.v15.0.7+8.x86_64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/md5/a1e786ac775517b8b483bbe3f6571d37 -LLD.v15.0.7+8.x86_64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/sha512/3937f156fc2fb8eecb13444c71f380753c16b08f29124228808c91ea4258ee2195219c4a9b601d4468cc24bd584403c16175518a620bd94a7dadff868b3771d7 -LLD.v15.0.7+8.x86_64-linux-musl-cxx03-llvm_version+15.tar.gz/md5/976d840de14ef6ee2c0a538197fe8f10 -LLD.v15.0.7+8.x86_64-linux-musl-cxx03-llvm_version+15.tar.gz/sha512/7f58f975dc3d69f502537aca79509bbc3c4f5da2ff8ddb1c7e27180a6bb2123713eb42da61cfabd7a48a31fc464fd74554b34935dfdb3ec095d14ff443f514f3 -LLD.v15.0.7+8.x86_64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/md5/ab0295ba327cfa6b9a252b0e7a4b50a5 -LLD.v15.0.7+8.x86_64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/sha512/7c750916d4157ba0a37cd1277a0f8faf32123dfc626ea76f848a7c567fd889a7801f8402a307c190ab34fc21b156f2a23967abc9972fc103e5847a200ffc7305 -LLD.v15.0.7+8.x86_64-linux-musl-cxx11-llvm_version+15.tar.gz/md5/6827f38ed653f33953ff7ae510a517d5 -LLD.v15.0.7+8.x86_64-linux-musl-cxx11-llvm_version+15.tar.gz/sha512/f01c655f6433ec6808b62872b8fb4c5a2d8e187643c11f0b4f5c06e2302e462353b516f431c1e26ee60b579c0f8c8c6385f018db3011c619745a39f9ef263436 -LLD.v15.0.7+8.x86_64-unknown-freebsd-llvm_version+15.asserts.tar.gz/md5/d444e6188476c8b7bb6026aac6ea0a1f -LLD.v15.0.7+8.x86_64-unknown-freebsd-llvm_version+15.asserts.tar.gz/sha512/69510929896bcfdc4377b180ed206617f96e1fa13fe9f8ccb18d78958c3f9f951a2ff8ada9d1217a02407819caed5308d91bf8fd72434ba347f548c9ba000a5e -LLD.v15.0.7+8.x86_64-unknown-freebsd-llvm_version+15.tar.gz/md5/728e79f334e5151fe5595711763baca7 -LLD.v15.0.7+8.x86_64-unknown-freebsd-llvm_version+15.tar.gz/sha512/2501a5cae4e61302e44f06fb7d38d15a776d750c6e99c30f89e1464e836a29c735d40972d48d19f1b38aac3f487cfb532d71ce9db2df8bfddd03d1fea5f3750a -LLD.v15.0.7+8.x86_64-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/md5/2b64bfd4bb5f6be4783144c53eaba27f -LLD.v15.0.7+8.x86_64-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/sha512/237d512962f0a3f8b98352a645141e788c1722adc761d63a4f0754dc79871912a2302a66bfdfbe80683ff3c6783dee0b4cf31beb50c5f76b6d97c978197b5a3d -LLD.v15.0.7+8.x86_64-w64-mingw32-cxx03-llvm_version+15.tar.gz/md5/c23d2716a9ab651f64908da8fdaf7780 -LLD.v15.0.7+8.x86_64-w64-mingw32-cxx03-llvm_version+15.tar.gz/sha512/78d6b3ebd845910dba5f147132378931f7e844a31dbc09a64222eca2c2f610b4e0134d9be184b9c80aef3aa2f4ac4d83de6bca7d1ec08fc775b066b67bc2a0f3 -LLD.v15.0.7+8.x86_64-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/md5/adb8cf2411501b6faaec1a9a4204bd7e -LLD.v15.0.7+8.x86_64-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/sha512/af9f3cd4f585b2c82dce53429b08d270c2be5114098bff8363a1777f82bfb15c40f59d4fdccb8ac06327d8114669a3beaa16ba8653027a911e654a9425c8963c -LLD.v15.0.7+8.x86_64-w64-mingw32-cxx11-llvm_version+15.tar.gz/md5/1839c9ccde51b3d0830a365ede764dd6 -LLD.v15.0.7+8.x86_64-w64-mingw32-cxx11-llvm_version+15.tar.gz/sha512/63b34855470c6838412e158334cbaad9214db8f4b7bdfcd6508c19fb891249adc7e89b0fc50ab52bcec17fb2603a68570a839700fd5ad9d1dd9b1b929bf08f12 +LLD.v16.0.6+4.aarch64-apple-darwin-llvm_version+16.asserts.tar.gz/md5/0e1fb589a6f0d95c4952c141fcc6edca +LLD.v16.0.6+4.aarch64-apple-darwin-llvm_version+16.asserts.tar.gz/sha512/f6f3bf38d9263c26f25191fbf8ac867167afdb700055f5fe34a06a1759fe2fb39a716760839d91e55a8149ff96a0821386aae8e5e0d4953e8a4cf9e7e627c4eb +LLD.v16.0.6+4.aarch64-apple-darwin-llvm_version+16.tar.gz/md5/4526e34074b99a330b0bd1cf2f1bbab7 +LLD.v16.0.6+4.aarch64-apple-darwin-llvm_version+16.tar.gz/sha512/24ce4daf534084bb20f123bfe6c85cd2f706f552262d831de34dd393d1466e8cd905812f9f1dbfeb394401508240a66a397c006cc7bb067d6a6baf73dee168ee +LLD.v16.0.6+4.aarch64-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/md5/ad127f66681d145045e47076d8d8e962 +LLD.v16.0.6+4.aarch64-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/sha512/b63aec7e18f3c92b9f098779fe04f2bde454f981b2d39ac688ed6bb5ed9f1d78a00f8f5607bcf57bfa2b7c7e85f02ed405802642c9567acbc64a3dc5a430f226 +LLD.v16.0.6+4.aarch64-linux-gnu-cxx03-llvm_version+16.tar.gz/md5/83945b3b814c496fbb0d7351877a0fb1 +LLD.v16.0.6+4.aarch64-linux-gnu-cxx03-llvm_version+16.tar.gz/sha512/3243f222dfc7dca9878606caf1593b5776c6066ffac25a195b9624b532fd5cdf5576faccb26db802c36f2d9bc3c84cfc62a83c77449d0056e190af1ccd0239bb +LLD.v16.0.6+4.aarch64-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/md5/aa335fce63e6cbcc7018c56d7ef16a78 +LLD.v16.0.6+4.aarch64-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/sha512/8d82a46d9f2e428a745687bff5b183bf91b1850c1e815aac56209c5a8817dbdc8d26fd0012917b5a14476ad8ec6ac450e01238da9024637b76be2899a1ca04ff +LLD.v16.0.6+4.aarch64-linux-gnu-cxx11-llvm_version+16.tar.gz/md5/56cc8663e0390eda08d05a491a172c71 +LLD.v16.0.6+4.aarch64-linux-gnu-cxx11-llvm_version+16.tar.gz/sha512/dc056292c94b2a0619eb49f94521912c5f3cfbb9cb00bbd7b701a8c6a172e50dcbee719bdb43622a0c8fb32404d742efb416dea6922a3799af9ca7dda0400fa6 +LLD.v16.0.6+4.aarch64-linux-musl-cxx03-llvm_version+16.asserts.tar.gz/md5/19f4bddcbb68108301580a16b4cef32f +LLD.v16.0.6+4.aarch64-linux-musl-cxx03-llvm_version+16.asserts.tar.gz/sha512/1694c0d7cc63680f7c04b22a5d842acf3f48568e682a1a7acf29529ffe9d6ca027c33f9ae5cf4cdb1b703d28f30a654bd1f4e0586eee49ddf64bfb40c8a9206c +LLD.v16.0.6+4.aarch64-linux-musl-cxx03-llvm_version+16.tar.gz/md5/eedfa092b0f4a2867cdb5bd4102a27d9 +LLD.v16.0.6+4.aarch64-linux-musl-cxx03-llvm_version+16.tar.gz/sha512/dd895f9c5d4c76186c7d2172f56e1052f2b8926466fd0deb725683ab4d98b85b695b7d571f076bc6eda679747946ffa380e1c23f497ed8cf1fdbde89132ec653 +LLD.v16.0.6+4.aarch64-linux-musl-cxx11-llvm_version+16.asserts.tar.gz/md5/d46f65ed03a071779bd5277040905721 +LLD.v16.0.6+4.aarch64-linux-musl-cxx11-llvm_version+16.asserts.tar.gz/sha512/1b3bf0d64ab0481b11f6776a249c252622b23f125eaef733dec4473ced554ba6b876632c0e4855d0f93a9e4eab06cbbfff8fe568661f569a08263543d8ccd930 +LLD.v16.0.6+4.aarch64-linux-musl-cxx11-llvm_version+16.tar.gz/md5/05ae31c675789b5b87bf800e971e8886 +LLD.v16.0.6+4.aarch64-linux-musl-cxx11-llvm_version+16.tar.gz/sha512/02342bb5a5c78f7c876bb75dd0c9b7140b4e41878203469a573c190baa2be82ce64101bffb93524b24444a69e15fa6eabda8f7f006e934595e52c97d4865faa9 +LLD.v16.0.6+4.armv6l-linux-gnueabihf-cxx03-llvm_version+16.asserts.tar.gz/md5/46ecafa64b02b6a72cbf33000728e023 +LLD.v16.0.6+4.armv6l-linux-gnueabihf-cxx03-llvm_version+16.asserts.tar.gz/sha512/72df77be62ede0737f2c9bbf58fe617ce8358ce3f08e3d3b9fbf8aa29c0ab4db4eb49e426fa43156d05f9f8c83b974594026029e67bf8a8fddf6ab33b1c2126b +LLD.v16.0.6+4.armv6l-linux-gnueabihf-cxx03-llvm_version+16.tar.gz/md5/ea36fa4169f9862db75bb1ff19b45645 +LLD.v16.0.6+4.armv6l-linux-gnueabihf-cxx03-llvm_version+16.tar.gz/sha512/7795affc068e64a5c3b68e9d0a773d21acec68eb2a4fb28c791938c993d0f0f8e9b1ce8b16cab3f25a48c2d7b8097449831f2e6340af4b47c59f9c35636e23b6 +LLD.v16.0.6+4.armv6l-linux-gnueabihf-cxx11-llvm_version+16.asserts.tar.gz/md5/ba2e078e52583ff43f3b840e3a144726 +LLD.v16.0.6+4.armv6l-linux-gnueabihf-cxx11-llvm_version+16.asserts.tar.gz/sha512/0af8ea82206204ed515bb6c222d2b51a16b360858a1a4c9db7b3749cab79e08a9b0a91b54b389a19f0b40586d0f0ab3665fd2df415a90aa401728d592c9a5716 +LLD.v16.0.6+4.armv6l-linux-gnueabihf-cxx11-llvm_version+16.tar.gz/md5/4f6c8a05367f8e70f6247f667737bc79 +LLD.v16.0.6+4.armv6l-linux-gnueabihf-cxx11-llvm_version+16.tar.gz/sha512/f5ddaa2c14dd6fe85456701d60f279bbb30f2d9c63948ec3212a2fe15670be11ca8db815c914cdd069cdc469e3ad37d848220c7d5e1feff5ab6913a94be22d17 +LLD.v16.0.6+4.armv6l-linux-musleabihf-cxx03-llvm_version+16.asserts.tar.gz/md5/758f27b74dea4231ca5ad4bb1eb812f8 +LLD.v16.0.6+4.armv6l-linux-musleabihf-cxx03-llvm_version+16.asserts.tar.gz/sha512/6f378dc8ab2a4b0f59a8107ddf6ef10ececdb03d616b6ba1741dc9ad0b7d96a8d506b45b1f3129c6f58b9ad8b3f97d300cea246c6687bedbe2b2abaec5738e95 +LLD.v16.0.6+4.armv6l-linux-musleabihf-cxx03-llvm_version+16.tar.gz/md5/7491cd8edcbd4933e0e897b2378e4f96 +LLD.v16.0.6+4.armv6l-linux-musleabihf-cxx03-llvm_version+16.tar.gz/sha512/ebfda1800a564de252c1e0bfa8ba505589cb15a137a731df927449714379c64478d51d08f973f59d29274eac66a205dfcb96d809b4ac431e4618d68068ff02c7 +LLD.v16.0.6+4.armv6l-linux-musleabihf-cxx11-llvm_version+16.asserts.tar.gz/md5/23303f8b1baa11890f92ce44a1210706 +LLD.v16.0.6+4.armv6l-linux-musleabihf-cxx11-llvm_version+16.asserts.tar.gz/sha512/45f5f696f0b4cdd3491c8601d24576628ac9ddea992ed0ea969d38389f50b547835b2b74b7df0b3710b8b1048ee11665c9dc40f6ba71aca428dffc81ce716b11 +LLD.v16.0.6+4.armv6l-linux-musleabihf-cxx11-llvm_version+16.tar.gz/md5/d46db7e7e1c7aded195d6eedb280f21b +LLD.v16.0.6+4.armv6l-linux-musleabihf-cxx11-llvm_version+16.tar.gz/sha512/8cc4958e9cd55ce3180b17fe171446fe222ce38c76778e77a34998eeb3f3fea3de09fb2590c2f4c2a1015575c1f5e1a37356695ccba0041f8381a2e4389563a3 +LLD.v16.0.6+4.armv7l-linux-gnueabihf-cxx03-llvm_version+16.asserts.tar.gz/md5/6892dea9c18297dfe41710092abb1d01 +LLD.v16.0.6+4.armv7l-linux-gnueabihf-cxx03-llvm_version+16.asserts.tar.gz/sha512/c7841b05942dc5edef6002e02452651d74e85ae4f8b575505e9955949efb47ccb8447a9157b52c88686c4334842fecf415912e228c58a2632002a4d1e72af0b8 +LLD.v16.0.6+4.armv7l-linux-gnueabihf-cxx03-llvm_version+16.tar.gz/md5/35525045309d318400241205333f366f +LLD.v16.0.6+4.armv7l-linux-gnueabihf-cxx03-llvm_version+16.tar.gz/sha512/a7176919817fd94c0ea39c4efd53f494958441a53c5ca18e0932f77b66a5a5aa7b2b4801ff776deb45161b6e099b5b626a83eab5258e9648307c8010b2baac09 +LLD.v16.0.6+4.armv7l-linux-gnueabihf-cxx11-llvm_version+16.asserts.tar.gz/md5/087b8f9b88ddfd488e196d6e1b966e45 +LLD.v16.0.6+4.armv7l-linux-gnueabihf-cxx11-llvm_version+16.asserts.tar.gz/sha512/68cef6e1f29d18e92ae70cf9c6c4e21c96b4ab86993f58f7addff16281debd50051dad828bf18b2ebe297d03ea1efe16b8fec3daf9f8cc33dd6124e77db28134 +LLD.v16.0.6+4.armv7l-linux-gnueabihf-cxx11-llvm_version+16.tar.gz/md5/5594492322eba79eea3141e29a71fc28 +LLD.v16.0.6+4.armv7l-linux-gnueabihf-cxx11-llvm_version+16.tar.gz/sha512/3745179518285c1e30ec4c554c6ecb630943985796e62713d85de79ff0d4585d790bdcd1d2ba50ad12333671e7bf45dcea09f7cdf84bbb25440348203f4f512e +LLD.v16.0.6+4.armv7l-linux-musleabihf-cxx03-llvm_version+16.asserts.tar.gz/md5/c751963cf072766374881f79cc5de719 +LLD.v16.0.6+4.armv7l-linux-musleabihf-cxx03-llvm_version+16.asserts.tar.gz/sha512/22d351e65de75276969c1bd9f78f8873878ac21035311fc138bb399c250e45d3c689bda1d13b00f0a1c1a93afe90292be749a0184acfba5bf2a17a2f9aab0c60 +LLD.v16.0.6+4.armv7l-linux-musleabihf-cxx03-llvm_version+16.tar.gz/md5/03fd8f5ec6894645206d1e8e1f2939ce +LLD.v16.0.6+4.armv7l-linux-musleabihf-cxx03-llvm_version+16.tar.gz/sha512/025a12586a3370d1e604daecb5eaaf6b3300fa8c57d68fb09cc39e0fa5c353160be2c1493fcbbc7df28a6c345c46731d77422a282cef7b8a90d0df12739326fc +LLD.v16.0.6+4.armv7l-linux-musleabihf-cxx11-llvm_version+16.asserts.tar.gz/md5/5ff5a4efb63282ba6e08663070c030e3 +LLD.v16.0.6+4.armv7l-linux-musleabihf-cxx11-llvm_version+16.asserts.tar.gz/sha512/16bc7d275079d10ba197910bd194edd4d67ba29bb8d86b110cf71658b10abef83c9ff37d5ee366a015302966382705f79c77c810ef9e37e8ab25c3e5be09c78e +LLD.v16.0.6+4.armv7l-linux-musleabihf-cxx11-llvm_version+16.tar.gz/md5/e92652c998ce524305d1af766f12b273 +LLD.v16.0.6+4.armv7l-linux-musleabihf-cxx11-llvm_version+16.tar.gz/sha512/a451ee76668601477459a474265d390a777a7a985ef976fa430766369d378ec71c32fcdcc8f99066fb6fcec8c56fa0154126d9f2d03d4d19b393508f8e0641d1 +LLD.v16.0.6+4.i686-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/md5/ed523fd79f3f2b42d1f8e752346906bd +LLD.v16.0.6+4.i686-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/sha512/34053f32db29857315eee9dfcae9067bfd517c42e761ee3e56218a9439725955a673756eb593ab2aec3d0b1eabadf0e4011f4a72fd1e14938a6bed0cd6592cf3 +LLD.v16.0.6+4.i686-linux-gnu-cxx03-llvm_version+16.tar.gz/md5/6c6f042e199db6e7fcb6fc9d877e9d4e +LLD.v16.0.6+4.i686-linux-gnu-cxx03-llvm_version+16.tar.gz/sha512/b884c46cd26b9312a7e07e3684806a42263635dd63c3127be7ea8c0ad3c76c7de4fe089da73f58306d65689116c7e83de73fb06533b322ba5eacdcf02241140f +LLD.v16.0.6+4.i686-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/md5/d2516d66916580f58cd2d79bd3372faa +LLD.v16.0.6+4.i686-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/sha512/0204064425018cc2b08d0f8bceb5f73f7823f3494b9200fce64de600c653cd28f9b65634a45d50e9fa1ba5b19d2182d3f599425e69ee38dd08effe065e5db2fc +LLD.v16.0.6+4.i686-linux-gnu-cxx11-llvm_version+16.tar.gz/md5/5f27602a98ef1a642fd1cf49141b92ac +LLD.v16.0.6+4.i686-linux-gnu-cxx11-llvm_version+16.tar.gz/sha512/267a0c91b1c9c44fd4bbcad09a7db13c94288368f45763f11796e575eedddf5218a17405eb737647c552c2c62aaa5761ca069f0063bb7aaed9c2c3d82f13f1d0 +LLD.v16.0.6+4.i686-w64-mingw32-cxx03-llvm_version+16.asserts.tar.gz/md5/07104f4a51ade84b7e2d218cb2e10c5f +LLD.v16.0.6+4.i686-w64-mingw32-cxx03-llvm_version+16.asserts.tar.gz/sha512/a22de36fcedfdb03f0d73c8bb12a1d4541d76a4b1af5d15c6b6f943b691c26cdb86003581b98d57bdcebf746d901e15c2c2e404fd0569846c7e12107d0280622 +LLD.v16.0.6+4.i686-w64-mingw32-cxx03-llvm_version+16.tar.gz/md5/f0e7f72818dde68f5b0ca858ce1370ba +LLD.v16.0.6+4.i686-w64-mingw32-cxx03-llvm_version+16.tar.gz/sha512/a6d010fcefb5a20cdeb328b2bf259b7c380598b488c00c4cd11dc5b3040457829e31979da0e7529b5a613257af6b95e666bbfa5c0ddd6baf97dccb507ca823f3 +LLD.v16.0.6+4.i686-w64-mingw32-cxx11-llvm_version+16.asserts.tar.gz/md5/2d81caa1f5e04e1b99d1e64c204ecf3a +LLD.v16.0.6+4.i686-w64-mingw32-cxx11-llvm_version+16.asserts.tar.gz/sha512/bcd5d9442aeaab69390b36f5c3be112dcf753fe57c9bbc36cfec35a7e17151860c421bd5bb30846fb4d4eb690b5de1d85f589c4f1b951e50838fbb2a2216fc97 +LLD.v16.0.6+4.i686-w64-mingw32-cxx11-llvm_version+16.tar.gz/md5/eda482cc028138e1a3999a637abe8a25 +LLD.v16.0.6+4.i686-w64-mingw32-cxx11-llvm_version+16.tar.gz/sha512/19cbb8fdf828f3f38a91c8e33c4c29bf21901fb344020e03903591c13c22fe4093bf0c78a07288d1edbfe2e4a7044510125ca5007f6eb0e9da506bc72240501a +LLD.v16.0.6+4.powerpc64le-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/md5/cbbe928d4453ad9b2ef7852a79a77be3 +LLD.v16.0.6+4.powerpc64le-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/sha512/31e85d5d253ea24740ff0f1b784a4eb425fa8a39e5c816f7d62eaa8fa68b38cdf4d4dbcf25136bf4aeacd35600ebce52aca535251af64c41709b9993162aa97e +LLD.v16.0.6+4.powerpc64le-linux-gnu-cxx03-llvm_version+16.tar.gz/md5/9ff5f80c86c0ac582af93811d51237cc +LLD.v16.0.6+4.powerpc64le-linux-gnu-cxx03-llvm_version+16.tar.gz/sha512/ad97304983ff2b5307ca9dc41d642bc5fb20be47f93629b92d977fa6f9b989f7234360c9b8cae07f8bdc800fc3b3f9b9970f15bc2b552dbb62072a12e7e716d3 +LLD.v16.0.6+4.powerpc64le-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/md5/f3d9c3944080a14bbb728e672958f9b6 +LLD.v16.0.6+4.powerpc64le-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/sha512/c741c0f11212773eb8a98b43ecdd67a86ca58afba5c1a35055f96ee688f2d1b7d7ebdaa7532131497b3b943f22c4985eaa9330b7efbc7f663b230f65ab80e2ac +LLD.v16.0.6+4.powerpc64le-linux-gnu-cxx11-llvm_version+16.tar.gz/md5/99a5bd1365d968249130a0823e29cae9 +LLD.v16.0.6+4.powerpc64le-linux-gnu-cxx11-llvm_version+16.tar.gz/sha512/018f80627982006ae5ee21251929a57e9cd69fbb575ab47a831a889f8b87769357144385ddd575a7943c11221bd5c59e02fd7ba861c4bb40940531dae8735c8a +LLD.v16.0.6+4.x86_64-apple-darwin-llvm_version+16.asserts.tar.gz/md5/063171318971a85982d02b597c1ecbca +LLD.v16.0.6+4.x86_64-apple-darwin-llvm_version+16.asserts.tar.gz/sha512/4bdeddbe74994aeccb8a24fe0255271fbb453e1e06daf2f34389770fd9b5ed71c88e50833ffcb44a052b886c8aaf38dcc5b90ee590d85a8479fc574cfaf142c9 +LLD.v16.0.6+4.x86_64-apple-darwin-llvm_version+16.tar.gz/md5/5bb545b987ab4ef00b7d04fa70c493a9 +LLD.v16.0.6+4.x86_64-apple-darwin-llvm_version+16.tar.gz/sha512/5adeebca1e5923c0121117f5cc08eaef3c798212c22b929019ada92fe545401a27ebabad6680470a720cc9a9d2421cb1cb24c4e524ee4b6d9e6cc453f78f2bcc +LLD.v16.0.6+4.x86_64-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/md5/c9508e437190c2448a0561905910c92b +LLD.v16.0.6+4.x86_64-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/sha512/a07a4dd7ea2bf59cc3afeabc488fe1b8f729cd8eb6572421b94693c6efd8e7163113aa5d392767e4eec9083fe6e667240dfbe49ea7de9f1ac7051ab478af5435 +LLD.v16.0.6+4.x86_64-linux-gnu-cxx03-llvm_version+16.tar.gz/md5/ea8ab6c04ddefdaec0a1273db6f20700 +LLD.v16.0.6+4.x86_64-linux-gnu-cxx03-llvm_version+16.tar.gz/sha512/c2ec35d58a115796aecdc37f967539d70138af22ea738b7e28d7427cc556a4729bd57e8971189e3291d5c739b4f55f4607680125bb9dbb6a8289185a1464c126 +LLD.v16.0.6+4.x86_64-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/md5/6747ca50400f608985d8e571c876b2a5 +LLD.v16.0.6+4.x86_64-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/sha512/3df9f8a361290f7a78cc20588cccbb22dd2eda1d9709568eb468008a345594270fd7df6c5c7b22bb5bd6c8338fccb508b7ec1b7e1dd855092bac2b25a954c3c5 +LLD.v16.0.6+4.x86_64-linux-gnu-cxx11-llvm_version+16.tar.gz/md5/38171d910690cebc8e6caf53ccae16dc +LLD.v16.0.6+4.x86_64-linux-gnu-cxx11-llvm_version+16.tar.gz/sha512/d6073f4cdfa7b291ce7cff44d4978b0258c0a81f95e563f9db9fa946be64e7611cc164859aeed55929b174fd9536bbc213a41605c12cbafc1cff91c38c9cc26b +LLD.v16.0.6+4.x86_64-linux-musl-cxx03-llvm_version+16.asserts.tar.gz/md5/7f8f893ae41c0bb12c84f60d6c9e9dc6 +LLD.v16.0.6+4.x86_64-linux-musl-cxx03-llvm_version+16.asserts.tar.gz/sha512/0a80b6e351f4efe79796fec98adfaca0b206783b38333056d35be1d6077a09b6fb00ea0e33daf3bec9261b31b1c024d545e5047507567caa925c9abd52660ff6 +LLD.v16.0.6+4.x86_64-linux-musl-cxx03-llvm_version+16.tar.gz/md5/7a7db99f2d12c8b73eda81d06116bebb +LLD.v16.0.6+4.x86_64-linux-musl-cxx03-llvm_version+16.tar.gz/sha512/09e99cd7cb0200296c6efc313be2c36635ac490f5d4100c62e830dfb40d5064d38430435ea9df7e49dcb1490f4d31fbbaaf706f21eebc0e3c78765db7e376ded +LLD.v16.0.6+4.x86_64-linux-musl-cxx11-llvm_version+16.asserts.tar.gz/md5/674055d6ff38ba166f9721647841fcbf +LLD.v16.0.6+4.x86_64-linux-musl-cxx11-llvm_version+16.asserts.tar.gz/sha512/5ff89cceff288ca9df2ba35a8acc08e75204c200a2204c6d5f2ffadadd1440c18398ed70c4f6a0133cb58fcdd5d95e4f67f03ce4faa4661c9d9e4965b1c9b38e +LLD.v16.0.6+4.x86_64-linux-musl-cxx11-llvm_version+16.tar.gz/md5/a30fbc6a5fa69f879d04e47a94e0b07d +LLD.v16.0.6+4.x86_64-linux-musl-cxx11-llvm_version+16.tar.gz/sha512/394db6aed33be75b7783aa0c1f98b678f0f96cb16946dadd1c143cd4b0378707d37fd0b35a4a9687de91ea293635142e9c433befe9be7fc2899612d48b94ae8f +LLD.v16.0.6+4.x86_64-unknown-freebsd-llvm_version+16.asserts.tar.gz/md5/ff14d881b50746ff4be6642233091bc4 +LLD.v16.0.6+4.x86_64-unknown-freebsd-llvm_version+16.asserts.tar.gz/sha512/6918a29cabcf3d4fa598cfc8390e534be63c049f8c69157629a0e02488f09b461fcf7cab5e0678b38b4c5be6d80a45a70b434889a7d1740100edadd3a32dbcb9 +LLD.v16.0.6+4.x86_64-unknown-freebsd-llvm_version+16.tar.gz/md5/2ec3ef1bea40aa16de080605f082b616 +LLD.v16.0.6+4.x86_64-unknown-freebsd-llvm_version+16.tar.gz/sha512/1d69b07dd179b1e146c4df8879beadd26ac5ba2c1fe0052bd5c40f0788f274c9c2e4e53a53703d95298fa889d2470197411c867247284de48834e7440a0c7977 +LLD.v16.0.6+4.x86_64-w64-mingw32-cxx03-llvm_version+16.asserts.tar.gz/md5/21e359427a484e7876f4b9d9145b0273 +LLD.v16.0.6+4.x86_64-w64-mingw32-cxx03-llvm_version+16.asserts.tar.gz/sha512/d1da7c1740f1c7ac07247b554f7cc76a64aa03200b44e41d1a10d66458caaa466099eb82d8d33327ec77eb784a5be91454c17d20c2fbefe049a128a2bf0956f5 +LLD.v16.0.6+4.x86_64-w64-mingw32-cxx03-llvm_version+16.tar.gz/md5/f2975d69c12e91660a84df715f971130 +LLD.v16.0.6+4.x86_64-w64-mingw32-cxx03-llvm_version+16.tar.gz/sha512/8fe4bfd7c692c66d871f2f0c67a8450076d7639addd2874cf6068427462a8656da96fe7c91bc93e5f0cb016ee3908e1b70c5212311f65ca7f0dc64f1f921e2ef +LLD.v16.0.6+4.x86_64-w64-mingw32-cxx11-llvm_version+16.asserts.tar.gz/md5/1e9103f812fe163e2fc6c868da4fd9ba +LLD.v16.0.6+4.x86_64-w64-mingw32-cxx11-llvm_version+16.asserts.tar.gz/sha512/e75bbedd72cb5f0337dac014d78d1276992a6f1d6c8832d16bd58ef83b2cd8602d023b45e1235e96ccdd13300e4b67c28f0a17cc0b9ff1ccb89263105a9a66db +LLD.v16.0.6+4.x86_64-w64-mingw32-cxx11-llvm_version+16.tar.gz/md5/b664032e83cddee3d1157f8c670ebe5f +LLD.v16.0.6+4.x86_64-w64-mingw32-cxx11-llvm_version+16.tar.gz/sha512/d7c36b0aa74b5d36446bb0aab413e4e12f042532d15e55eb79a807efabb93e590538cfedf7e3a6abe33da3f2326d631c2a041da2e00dc4051934fed17aed0c4e diff --git a/deps/checksums/llvm b/deps/checksums/llvm index f9bc921b4a8ee..f54ddc7379b6e 100644 --- a/deps/checksums/llvm +++ b/deps/checksums/llvm @@ -1,111 +1,111 @@ -LLVM.v15.0.7+8.aarch64-apple-darwin-llvm_version+15.asserts.tar.gz/md5/8b23ac6a55ad8a8a911af1cd2be84f7c -LLVM.v15.0.7+8.aarch64-apple-darwin-llvm_version+15.asserts.tar.gz/sha512/60df009ecef6b7d435fc198dd477e9198e1f96d89b3c583b06af5b6b938b9f4d545f75f09d0daee93f94448f6fef36ca2e0b541e1179280e6d918b02c619291f -LLVM.v15.0.7+8.aarch64-apple-darwin-llvm_version+15.tar.gz/md5/649c61e6bb2ddc619d2f967d923a62d6 -LLVM.v15.0.7+8.aarch64-apple-darwin-llvm_version+15.tar.gz/sha512/864910013f659f473def2a5df78adf5c13f6f84f400d3b87c5660ef51a97c86520a73cc6072db2ed739f3e7e3e2d326f34634bc9ab432edbab59baaf2085f4bf -LLVM.v15.0.7+8.aarch64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/2bae4d31c56385e73f3cf667e5cb5582 -LLVM.v15.0.7+8.aarch64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/27f8479a639434329fc5e67326df2c22aacee61237cb1eb1bf9938a58b1dcf883ea0476a066dd284047388a42a3b676c37b2c800f97feab694edf091aa10a8b7 -LLVM.v15.0.7+8.aarch64-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/040485a871ce0036fb19f3e9ab9ed1f6 -LLVM.v15.0.7+8.aarch64-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/0472ab3fb75efe1b67ba8c34cd9b36377ef9206506f741ea9a6289d3dad4ba4d0560578661bf9e433b51c03c77a9de5ed23df37e160a9225bcb50c27e5431e76 -LLVM.v15.0.7+8.aarch64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/12f54c1888f00373ea1e8fcafffbc313 -LLVM.v15.0.7+8.aarch64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/fc246d39c1711cc041b014a77e8b0c9fbbdf86c92a1c027e35a152d18bf6cc72d8b059c65457851ce240a5c745ebd39934ed6ce6f553d408ec8ad4078135c746 -LLVM.v15.0.7+8.aarch64-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/7065ccae75d0e16876aec6d96de41409 -LLVM.v15.0.7+8.aarch64-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/1c0bdce88797baca3188be7927d7694861bc491b4ad7970e485332ecf36b28e121a57e3a4ffbe83f6919f00da66d04d7809c589b8f9bf4b9940b15cc5c1590e0 -LLVM.v15.0.7+8.aarch64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/md5/708f78fb147a1a5455b1f0e27e0a8946 -LLVM.v15.0.7+8.aarch64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/sha512/f7aa0dab1f2b0a7dc5324ef1ed9969e31334245938f2954425eaea2c034072d844a3c9eed8b522bf1794a22d822117d6beff6c23872c875dcbcdd7cc719a6d61 -LLVM.v15.0.7+8.aarch64-linux-musl-cxx03-llvm_version+15.tar.gz/md5/eaf4c42566b3ffbd6c3fa3fdab126d0a -LLVM.v15.0.7+8.aarch64-linux-musl-cxx03-llvm_version+15.tar.gz/sha512/7d60771a1795fe328c8f5fb534580d34016f6dcd85c268e94d6ed8af7e68fc74e3bb25ed8262bef4100b9341dea180a2961d1705e453d37dc5d6a01ff9b00ae8 -LLVM.v15.0.7+8.aarch64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/md5/f4ed6360b1e46be18392900cf5e6e413 -LLVM.v15.0.7+8.aarch64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/sha512/0dbc3f7954ef085bff00cf1a7d18bf338bd76f67ab98d972a486942ade7f11a6c9761a4605ae653402d867c543a065d6c238bd6a375852544e89eb9f9cc6c335 -LLVM.v15.0.7+8.aarch64-linux-musl-cxx11-llvm_version+15.tar.gz/md5/b8cfc0edd85f79fe0ee22b84b9271d62 -LLVM.v15.0.7+8.aarch64-linux-musl-cxx11-llvm_version+15.tar.gz/sha512/8f8b5111223d433bbbb7ec48df5436ee5a4c21c4cd57fab1b6a4121d72230251998035c9d62993d442af279e3d393afa2df1c161af2762420ec149ee16f01833 -LLVM.v15.0.7+8.armv6l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/5cff8a24ca2d1da58ea709d34c33decd -LLVM.v15.0.7+8.armv6l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/2c20fef573744dacafc6333687be408b474c9773c558fe08ff1702d1957aac3bc99dd9998866651bf9af7473214d1be236273725501cf9f5c38d1807f1605456 -LLVM.v15.0.7+8.armv6l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/md5/2721d242b457748e3f8301b085282c3c -LLVM.v15.0.7+8.armv6l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/sha512/8a73828ebd84e683f9a043007bba955ca85deec608d9602fe29ef18fc999b4959bc8e87c865bcd060adb8b4c15f8cb04113027b37c3b4e075cc8c7946831a52d -LLVM.v15.0.7+8.armv6l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/8c8171ed600e466b022a96956fb93e69 -LLVM.v15.0.7+8.armv6l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/48798715ab9866528d55b0c75a9aa6a87e03b771c3a29b0f3d88bc8d89845ee8f39ddeb6232a0337eb1491092322fb7617e5ebef1e55c0bc4c46f05ed115a0f8 -LLVM.v15.0.7+8.armv6l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/md5/8343d87440928bdc76f0209eb905fac6 -LLVM.v15.0.7+8.armv6l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/sha512/e20a0fb7e3cff62832ba840782b84c12ce8ac38cbcd0eff82dbc6a6ab5cd58c59cb760f5b85e4e60dec63aa274e8e43035c5e1fa5e77639d607971690487b3a4 -LLVM.v15.0.7+8.armv6l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/f6a415de133867e9045c5821ae2bb286 -LLVM.v15.0.7+8.armv6l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/acd4c6a31f998f506c747f3d8d4ddae393f6a58a282a928913d4eab850147e485e99fc953d4847d00555a1bde295255eed530e4dcf1dbd46e62d2c0a4df9c4c3 -LLVM.v15.0.7+8.armv6l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/md5/4164c3a46ea05eeda4c471b290b9c782 -LLVM.v15.0.7+8.armv6l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/sha512/c19df17b57f67d2dd281f1939462250aca5b6cfd99598b0dd45eb76ba46f4b02c4e3ccc8e09ca57843c12de822a94df88d5593a34b401050f148452986094ec2 -LLVM.v15.0.7+8.armv6l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/1d3bd6f4770fba34a9f1b14a3871665c -LLVM.v15.0.7+8.armv6l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/686a8be7a96e37b91d70bd06259a8a61b6e8d2fbb3b28d9252b987b7ed58ae476a4cb1b773b143d41dc6de23a7b969cedf35c9b8070052a7b9882503379d4f6f -LLVM.v15.0.7+8.armv6l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/md5/3783a3c99a4c8c27ae367e5bb717cf3a -LLVM.v15.0.7+8.armv6l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/sha512/34bc40f96d054487022879477eb826ce769b507c90de9c481107f582a1c995a9af9172d1d017eb30e6a9875f68877c26b76fd3ec48a4338c9e5608d970d012f5 -LLVM.v15.0.7+8.armv7l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/b6d3fd4b5460fb336ccf71ed4f27112c -LLVM.v15.0.7+8.armv7l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/9558ed5556c8c0ef5f3882f76bf4f4f5ba8bb642eecd3a6f9f7d4ad82c0d9a15038e8fda0e613eb614b747ef045e3bdb1cc3cad6a674627e9f54e56abac67daf -LLVM.v15.0.7+8.armv7l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/md5/37ef614a98b59a8100ead9a5a0b3a369 -LLVM.v15.0.7+8.armv7l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/sha512/02f99b72dd23aeaeb5326c555e26f2090c311c8b111e3b203235ac503faa3d8499afda2e3541d0744dbbd8b6451c50d377b09ff7dfe37d3934b931e31b643640 -LLVM.v15.0.7+8.armv7l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/f0b6cfc6d392e597e305a77985949bbe -LLVM.v15.0.7+8.armv7l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/9e2487984369a573e000b92899888ec845cb76dc20aaf200eaa1f52dbaedc8311c4161f3c9febce47b077be86aa8780c6d9c550f508c6b4949d76d236ecefcea -LLVM.v15.0.7+8.armv7l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/md5/a369c3db68953d38d71d2e342ebb72d4 -LLVM.v15.0.7+8.armv7l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/sha512/e0a4f18dd056578b690903dddc971aa91f91b194758a4ec69422f4834dd1c85224bdfd2b95d6f801cd4d23aa26c09a104452266ba8d61b5f1a429b57b2f597b4 -LLVM.v15.0.7+8.armv7l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/ab49be8073da48f3a353346fd6d9a833 -LLVM.v15.0.7+8.armv7l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/3f0a76b3940a918bf8206529a15d538db59e4c419cdeb4af0f338d343912d5708bf89ff89435a95b7ea42d3d36a83616a2f45bd21e1e8e4a067df5be22898ba8 -LLVM.v15.0.7+8.armv7l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/md5/493df42ef5cae32a099ca7ee2c2dfb1d -LLVM.v15.0.7+8.armv7l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/sha512/ac27639cd6273f6dd38bad0db9bba82f1dd299d9260c6877cc4b876d4f82fd518c758aeeea58b389e790eb7e212d0976fd1af1741e7854e8c287c4a381c5f0dd -LLVM.v15.0.7+8.armv7l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/529256ff2095f50c1c57356819122ed5 -LLVM.v15.0.7+8.armv7l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/3922fb3a3419ee319392c68124550824e6644e7a638d39ac2831b6595c1e1fdce6934eaadb74415dc81f6ab349714e5443f746a61c0d8927c78b51987dc95a32 -LLVM.v15.0.7+8.armv7l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/md5/82216b18dcc9148c3cdc00bdaed163f2 -LLVM.v15.0.7+8.armv7l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/sha512/c212134793544ba37247842c733b540055bb987b0854194d038f90dabbdf9ab5df6ca1e64c7e85d8071263d0b8f1d2feb99ac9d842b731551aa58ee2be2618ea -LLVM.v15.0.7+8.i686-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/465288c493241318c5947f756319d2ae -LLVM.v15.0.7+8.i686-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/4ef949ced9cf6c9f30c56388a8e53eb67c528e0635bb3c00d1dd204c8b8f74e72a6bc2104745dcfecf7c653ce1499d86c3a7acac98b565546b3826230710b92d -LLVM.v15.0.7+8.i686-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/0437433a85900470209cde06ee185e9f -LLVM.v15.0.7+8.i686-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/efa646ae330dcb5d4dc40a8e91a51248b55811d090111deddd415e22d8d7f26009dd4a124756dcdb9df4d83804f11576b786758692a5e82ceaefc88de3b9b00d -LLVM.v15.0.7+8.i686-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/895eedc69041096b1643f66976f2a51b -LLVM.v15.0.7+8.i686-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/dab5fcbd7e48e60c7d16031930e954a83a87e705ac72cba3f660bd569fa8b6a05d7ddf2551eb8e5f163d385c31ce197ca519e47e6ccf85982c6ae7072c6a301d -LLVM.v15.0.7+8.i686-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/083c0b15d8747cedfd617a9127792ca6 -LLVM.v15.0.7+8.i686-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/427d8caba92e5bc3f88b9f33ca951fdb01dd6ce94bd852af61cd94f3476c35e82f9c387989c5847af8ae65d5e9ddfb6efc4afdcb57320bac1115b3ef23e9563b -LLVM.v15.0.7+8.i686-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/md5/0e0a82a95205bc58a31ac24a7d479d84 -LLVM.v15.0.7+8.i686-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/sha512/4472eb1cf1386d6754ee9fe2aed514b656ce34c0fcf478e3dd0532e3010e2fbd82b905060382eb661e7daa48d1e44e4606b1bf3625f539bcadff3f13497ac5c1 -LLVM.v15.0.7+8.i686-w64-mingw32-cxx03-llvm_version+15.tar.gz/md5/91639f25324ac04119751f1c4b09c0ec -LLVM.v15.0.7+8.i686-w64-mingw32-cxx03-llvm_version+15.tar.gz/sha512/a07270a19a7d8ba5dfdf8ddfb089ea19101ef9386b3d418353c9fb7d2812b3b6bb68dd3e170fccc0f2ae7a7c7cb5a0bbead36580249726346a04f028ff4f94d6 -LLVM.v15.0.7+8.i686-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/md5/6eee5cd139347f0dc6c0100a1fe34e78 -LLVM.v15.0.7+8.i686-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/sha512/2e8e4a14e0822b5078ee1dd5580a136f2e75f9e58a065b83cb857f7f2e17a5f462c6ef8a41b383c3c9ddc54e31876709472a53787b5b285332802babe9a41744 -LLVM.v15.0.7+8.i686-w64-mingw32-cxx11-llvm_version+15.tar.gz/md5/b4df578f69f7ff1a43c73b92f8b2fc08 -LLVM.v15.0.7+8.i686-w64-mingw32-cxx11-llvm_version+15.tar.gz/sha512/c965c4b42b6b22650f329d1187a27523e2e1ea6032de11f1cdb6a54b79871aaa260716e84e38ea5c8692930e1aa6927f5f82617a096bcfa10e3666e37cda50ce -LLVM.v15.0.7+8.powerpc64le-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/ff278e2aa1a91a9bd6dc0d240ce23e3b -LLVM.v15.0.7+8.powerpc64le-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/5903a1cb350391f7e15ad81ed9c0690b43e267c5b061e011c61badbf324be544287869fbb4c2d68415c4621ecc37cc798762cbde34ccb64194547d63cb3a980b -LLVM.v15.0.7+8.powerpc64le-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/a02b1dea41d86fd28411798b535f7674 -LLVM.v15.0.7+8.powerpc64le-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/7b6c94d8e60985206a7e6faefaca1dfab04a1a1378e8b52cd981726f6b6558caba23b716d44824fdb9fb54c16ec478e8de4296db9b2ef0be67680784ed26831c -LLVM.v15.0.7+8.powerpc64le-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/1d1f3db1b9a23c5948a5b982b46e5de6 -LLVM.v15.0.7+8.powerpc64le-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/d741dcb76c3a143fe3b000999a627ba4a3abd36d738028e739e96c861be661cd1de12f5c7ee7e03826b1109aea11e80371d18865fb5332d1834f4dcf0ceca245 -LLVM.v15.0.7+8.powerpc64le-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/3ed7af8b8e34820a83a8459edeb3843d -LLVM.v15.0.7+8.powerpc64le-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/eaab7e410cb433f2b83e6b38671a6e7489556865cc7007ce1588c8fcb0421c64dda480be231f96b30ce6fa75c2d9e99fbdb2a6293fa108ed9bc0599d2ca2ca63 -LLVM.v15.0.7+8.x86_64-apple-darwin-llvm_version+15.asserts.tar.gz/md5/88003e4d20bce7d175d8e4db003e0eeb -LLVM.v15.0.7+8.x86_64-apple-darwin-llvm_version+15.asserts.tar.gz/sha512/aed46b6b3aacf7ef373f9812da24edaf71ff1a382299e5f15eaffec605bb308fb3732846b9dde6d3afdeaa010de61773ba0e725d41e3b160fab37d2d0cf389cd -LLVM.v15.0.7+8.x86_64-apple-darwin-llvm_version+15.tar.gz/md5/ef75e04e4946bb58091eb1a57b08ace2 -LLVM.v15.0.7+8.x86_64-apple-darwin-llvm_version+15.tar.gz/sha512/7e6ff0a8d511f19eb868d61fa50ff8c0014da4ea50ad960603f0851a6c09a693a66baea4563af6cdcf2f2fcd018bf2456a9e9479b9278c656ef64f0a08aa11ae -LLVM.v15.0.7+8.x86_64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/95a8b1fe9135120ee5fba2441ad16f2c -LLVM.v15.0.7+8.x86_64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/167dcff24d6144a4e211ee473b389d5fb4daa51d4ddbd98655456d3bee6c00554b91580a627ca5355e5c2d38a9a326f13c13454de64c0cc32a1f214a52b21400 -LLVM.v15.0.7+8.x86_64-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/ba2fd64dcb09926f754fdde94ec5cde1 -LLVM.v15.0.7+8.x86_64-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/fac4e6ab8875aa94cf6cf2065d718403b733954c18b0f5dee11148b70ecafabd92ff23155224ba5628c743fe963ee82a376ba9025f8ec8ca199a66412855ecd2 -LLVM.v15.0.7+8.x86_64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/f80738de98ca42cfc8c4ef141ec7cf0c -LLVM.v15.0.7+8.x86_64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/ddf7670f555db3916933ea202672c7ffb51d254422af801ae928fa3618b3b7db1f7513710576dabd52f219cb2791e19803b5a4f3470f7ea05f71b0b04b03aaf4 -LLVM.v15.0.7+8.x86_64-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/a502a8057b19bebe8aac1c42d9cda20f -LLVM.v15.0.7+8.x86_64-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/6e20328737c46fae6204cca68b16fa72e55c4b00fbaf8470b4847a869741fc0e2cef0da2ed9507fc4980678500dffbd42ccfe9f173321dee633bb0cf5adbe57e -LLVM.v15.0.7+8.x86_64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/md5/3b06471de330c6f4a53012711c7c9d72 -LLVM.v15.0.7+8.x86_64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/sha512/c19a9a2d8e4d3bca25a1568b0fdc01653b0b467dadc68967c4ab88512b4b78cab2e829fcfcd3d1e7139ad5bc3fab62dfdfd50be57f0d0099de383d820dafd306 -LLVM.v15.0.7+8.x86_64-linux-musl-cxx03-llvm_version+15.tar.gz/md5/06a632eb722009eb4bbd49689b49a8a8 -LLVM.v15.0.7+8.x86_64-linux-musl-cxx03-llvm_version+15.tar.gz/sha512/67969cd09ab313b395cdf9f614dffeeda1ab72b2fa9e68057bc8dae9bad36d39443569b91ad88bbab9a6309a8084e09d40dff76d5d4bd261a1553afd5c3dd6c8 -LLVM.v15.0.7+8.x86_64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/md5/bc000f09920a305fea5b32cf2d1bebd4 -LLVM.v15.0.7+8.x86_64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/sha512/580a3b12d7620d4c3148f988125d081b3fc76defb0e78702286806e08dcecf2b9c41ad8071334ff1e4e9ee4155d8949d07c57e8470109c01acecd2b37a07083b -LLVM.v15.0.7+8.x86_64-linux-musl-cxx11-llvm_version+15.tar.gz/md5/6cd4cf2922f69efe968637d240bcfd62 -LLVM.v15.0.7+8.x86_64-linux-musl-cxx11-llvm_version+15.tar.gz/sha512/7a23f48faf2c4e85247c684bc43239a21ea0732c980b4d46b50df27857f143cdfc081bca867a5692b086d32ff9031a230d496c0231d3ccb5fe3f48beb6ad480e -LLVM.v15.0.7+8.x86_64-unknown-freebsd-llvm_version+15.asserts.tar.gz/md5/e7c54d9144ba36f697cd9be10e8e59cd -LLVM.v15.0.7+8.x86_64-unknown-freebsd-llvm_version+15.asserts.tar.gz/sha512/bd96434dd2a79b335bd7169222bf5bd78fdc00eeb7ef2f364bb5ec71071d68846a0ba6b51d5b182522e6ff577d8e3d7ca3c014962cbc04ef5a04a7157887cd6d -LLVM.v15.0.7+8.x86_64-unknown-freebsd-llvm_version+15.tar.gz/md5/22005a7066939bef459f0fa7ad8508a4 -LLVM.v15.0.7+8.x86_64-unknown-freebsd-llvm_version+15.tar.gz/sha512/2742a12f8a9054d6aa70fa823d2cae8126fc7a591849259e92b87b2e6b90ad7d21ffd855d34e08999600a5e2b591492502788d2ebd0a2564325ecc1860607b50 -LLVM.v15.0.7+8.x86_64-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/md5/bdc275515045127ff9883802963b3baf -LLVM.v15.0.7+8.x86_64-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/sha512/0fbed47ab0a14116a4b1f2b866ae65b5c81cc08007150750964eee8ac09a2a90afb0ba35d5d6447a33c75dd68755990a85e8874588faf84e7c1174b0c4f61c18 -LLVM.v15.0.7+8.x86_64-w64-mingw32-cxx03-llvm_version+15.tar.gz/md5/657059eb6dc68b3fdc56ea167ebede2d -LLVM.v15.0.7+8.x86_64-w64-mingw32-cxx03-llvm_version+15.tar.gz/sha512/ebdd42af6d04bd98d9bb50dbdb36b9f5087d9d303d5671e96395ee6dad248402bceab5b4765f0fea7a8381ad3fcc17aae7379206903c8e4624a4dadd5eec3faf -LLVM.v15.0.7+8.x86_64-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/md5/e6e68533173f74000ee4c1c84c579a28 -LLVM.v15.0.7+8.x86_64-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/sha512/8599d6ae2f84272ee4e8b176ae517ab3d2d25b7617c0520ee70cc33fdce9030a76205d3d9abc3f21c83e33258c78d52a31e0bab50a85f2050c4f3660eee378e1 -LLVM.v15.0.7+8.x86_64-w64-mingw32-cxx11-llvm_version+15.tar.gz/md5/8add628662e5b234c20a7dc986749dbc -LLVM.v15.0.7+8.x86_64-w64-mingw32-cxx11-llvm_version+15.tar.gz/sha512/047ce72373190ac36654d6c931d00a0b766d0fc5bbc8e9dc787ab9c97e7691908c61d9edb13ee1608b38b10662dd8a584a019bde36c339c5164b1bb8e23837d8 +LLVM.v16.0.6+4.aarch64-apple-darwin-llvm_version+16.asserts.tar.gz/md5/a01c365e50cb49bbb9ad7c63320afaea +LLVM.v16.0.6+4.aarch64-apple-darwin-llvm_version+16.asserts.tar.gz/sha512/b53adde91d5e21c734a85056e779f26f85b6fe084fa89b679e3d211c1209d14d0b7db627f9ed9692edae31554d76d8e1e85cc34737c4b29273c2a8ee908347e2 +LLVM.v16.0.6+4.aarch64-apple-darwin-llvm_version+16.tar.gz/md5/536058393999e62a2829b5a2b1e85f66 +LLVM.v16.0.6+4.aarch64-apple-darwin-llvm_version+16.tar.gz/sha512/e9d1f6280da11ad3cd956cd4f9520f344a2657c65833ea2c88844cad7005ac7e5ba16f607623ff80d03b7b362752102366d8c5928abe827187f74669b80d56ad +LLVM.v16.0.6+4.aarch64-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/md5/e923316d8d68df6ed6fbd8deb7aad6e7 +LLVM.v16.0.6+4.aarch64-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/sha512/9fc91d10805660a46bf10084c04a9bc53bc540f390ea3d88cd4614a9328d0f3b386862190ffcebe09331dc0324649c052a50326c44be2477062d1573f2ad3b09 +LLVM.v16.0.6+4.aarch64-linux-gnu-cxx03-llvm_version+16.tar.gz/md5/5b600b39079942129b78474ddbc32fa1 +LLVM.v16.0.6+4.aarch64-linux-gnu-cxx03-llvm_version+16.tar.gz/sha512/ae46cd569e408b4365f8e953aa1f7cce1f735872d032c71f4a6cfaa53e4d67829bb5a6c8f2c99cebc0eb224b02bba4c24771faeb79c6b99e641d11f5c83d31c2 +LLVM.v16.0.6+4.aarch64-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/md5/6f0980a7d08783cacb5613752b04d41b +LLVM.v16.0.6+4.aarch64-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/sha512/d45e5be28d6c786582bf79b3fa65d67eec12b2f792a408b32be91d76452bbf95255b6e60019b6f1af409dee4bd95120ac1c6fcf3e139613a774e684f4ab6595a +LLVM.v16.0.6+4.aarch64-linux-gnu-cxx11-llvm_version+16.tar.gz/md5/e25c9371e8d59d69dcd8009a892bbe38 +LLVM.v16.0.6+4.aarch64-linux-gnu-cxx11-llvm_version+16.tar.gz/sha512/0c67ec6ad0e31b98f1e13883ee2436a4eee61ce5cf53da01df3d12b670e7fe9d6ae408d2dbf622604fe95fe0ae14bb0240c1882616ff1d6c2bbfe8045077a90e +LLVM.v16.0.6+4.aarch64-linux-musl-cxx03-llvm_version+16.asserts.tar.gz/md5/039f50bf67a682eaa9d69474f5980302 +LLVM.v16.0.6+4.aarch64-linux-musl-cxx03-llvm_version+16.asserts.tar.gz/sha512/03a77ca15883b7e7535f606566d782af8b5706b5d2eda5caf33a3484baaba1f657b65d3e5be9bc40050f0a61521977839da8ef4008f4593296fe318cb3772377 +LLVM.v16.0.6+4.aarch64-linux-musl-cxx03-llvm_version+16.tar.gz/md5/30b471f0630f67245effe1481ad1ba6d +LLVM.v16.0.6+4.aarch64-linux-musl-cxx03-llvm_version+16.tar.gz/sha512/ba7c039b13915ed7b374a75c81578ddd5a8d569cd1022fd39cd40f089c39463d97c3306668ef072becae38171aea44713e27c4d34f4893c671f12ed08824939d +LLVM.v16.0.6+4.aarch64-linux-musl-cxx11-llvm_version+16.asserts.tar.gz/md5/38110539776130f9fb2bdbdb1b6a4598 +LLVM.v16.0.6+4.aarch64-linux-musl-cxx11-llvm_version+16.asserts.tar.gz/sha512/d8dc28c542ff6a07083e60feb8d7b731cc1b3b0a55ca332200b62e8b0218c2db30af6a5870923db70ba4f47a35f245a6b82504ea1af410e9a43dde03f41981d8 +LLVM.v16.0.6+4.aarch64-linux-musl-cxx11-llvm_version+16.tar.gz/md5/7916ed51676598067620090a9744e110 +LLVM.v16.0.6+4.aarch64-linux-musl-cxx11-llvm_version+16.tar.gz/sha512/46d9be864409108b2c87fac4b87582637fc10d96382ba265a008cf6edcb046f10c5d4e0de4f8f824c2dcfb32fdbacf05dd13928ea9905e3e338f7aa4d5f5fc4a +LLVM.v16.0.6+4.armv6l-linux-gnueabihf-cxx03-llvm_version+16.asserts.tar.gz/md5/9c51e4d7f15a1a7662d8acb674cb809b +LLVM.v16.0.6+4.armv6l-linux-gnueabihf-cxx03-llvm_version+16.asserts.tar.gz/sha512/e72918bc0af4806cd68f199decaadcc892a70dd9dd4f1cc23f59d361b3e01c7f3542ae295ea954fc8aa7d0121cdde2d06d53616d3196a532b1b9f45f9665ab9e +LLVM.v16.0.6+4.armv6l-linux-gnueabihf-cxx03-llvm_version+16.tar.gz/md5/10ed73102caf2bbd27766c292b939f89 +LLVM.v16.0.6+4.armv6l-linux-gnueabihf-cxx03-llvm_version+16.tar.gz/sha512/9f08eefe7c0027d7c84955160cf6bd25527688598c6191f9822c12c6558f534567f0c61e4f1397e62202c02f4c8cb61eb0094c343cdcaaad5221d90f147be210 +LLVM.v16.0.6+4.armv6l-linux-gnueabihf-cxx11-llvm_version+16.asserts.tar.gz/md5/b4fc948b8532d6add7177b77b89feccb +LLVM.v16.0.6+4.armv6l-linux-gnueabihf-cxx11-llvm_version+16.asserts.tar.gz/sha512/6a7c022e4ad205112a1c4665e28286672ed0460925d1e55fca04af9afb6a6a5fcac4e641ae0c1040181d81b1762b2a2ce05cdc532725d5da0702a44668bb8893 +LLVM.v16.0.6+4.armv6l-linux-gnueabihf-cxx11-llvm_version+16.tar.gz/md5/7b795c94a4f3dd8d79c5c4a85ecdc8e7 +LLVM.v16.0.6+4.armv6l-linux-gnueabihf-cxx11-llvm_version+16.tar.gz/sha512/bd6cb0c2f2ee25823fd9ad9ef931d7d1518c5315db70c33ddb959fece0fbdd1eb030630a5c63dc9f9e50b1bcf1cae9c2f7d24585f9de100e6fc56c4a4b6f7a06 +LLVM.v16.0.6+4.armv6l-linux-musleabihf-cxx03-llvm_version+16.asserts.tar.gz/md5/9812ca2eb64521bfd052821824227598 +LLVM.v16.0.6+4.armv6l-linux-musleabihf-cxx03-llvm_version+16.asserts.tar.gz/sha512/f939353f3fc667fab78b1083365c51bb15eb218bada2e061900bcd16550db5ed6765569ac9990e91294f030c04787228fed14324e2f192ffec26c6dfe851c9a3 +LLVM.v16.0.6+4.armv6l-linux-musleabihf-cxx03-llvm_version+16.tar.gz/md5/cc4e38e0a6cb55e16935f70e2e9035a9 +LLVM.v16.0.6+4.armv6l-linux-musleabihf-cxx03-llvm_version+16.tar.gz/sha512/297d7268808ab95317d576301f81fa52db3d8f9e91e7a2fa554e87afc21bf195fb904a585c39b4e83c7ede6a2ab64efcdeb929cc109894c3caaa7de9cfd41ae1 +LLVM.v16.0.6+4.armv6l-linux-musleabihf-cxx11-llvm_version+16.asserts.tar.gz/md5/40459a6691760b6c5a986221bb1d3c7e +LLVM.v16.0.6+4.armv6l-linux-musleabihf-cxx11-llvm_version+16.asserts.tar.gz/sha512/4b3e4bfeb1e1ddddec786d50a018588d6eb6c2b97fb6e68e1c24026f51aab59229cb8a389be075da5abf3fa50ea7fa36b870ef17c374e97d8e85377abf4846ec +LLVM.v16.0.6+4.armv6l-linux-musleabihf-cxx11-llvm_version+16.tar.gz/md5/07958dcb7b13ee0fba1379d729fae641 +LLVM.v16.0.6+4.armv6l-linux-musleabihf-cxx11-llvm_version+16.tar.gz/sha512/358c454d66b48846888fd696c64ca0d154bf9df81c27859169e6b3274fa9aafbd49935309e976761a4d95c3f2b80481834ce9691d08a2a456c20be15f92319e7 +LLVM.v16.0.6+4.armv7l-linux-gnueabihf-cxx03-llvm_version+16.asserts.tar.gz/md5/53572cb82b955197819eabdc1164073a +LLVM.v16.0.6+4.armv7l-linux-gnueabihf-cxx03-llvm_version+16.asserts.tar.gz/sha512/cd7da353ddbb1a18305a3b4b20bdaa92f60bc3bac1fdab32878cf53e921b8782f4e87bbfb55a7c7076ce927c42f02d86c890f8b82498eb41d58e2702efb0e044 +LLVM.v16.0.6+4.armv7l-linux-gnueabihf-cxx03-llvm_version+16.tar.gz/md5/7420d73f14f4124c79e6477e72ecce25 +LLVM.v16.0.6+4.armv7l-linux-gnueabihf-cxx03-llvm_version+16.tar.gz/sha512/992f5ecb3c9b162946bc16e8ce680bc56e5ee5b928e05600b87620554b9f7a22998a03e7380171d659b25790b8408ff656649e9c0cb7a8f91625ca38b0b4cb2d +LLVM.v16.0.6+4.armv7l-linux-gnueabihf-cxx11-llvm_version+16.asserts.tar.gz/md5/c52e1f13d082a786e02b9a28a5192ad1 +LLVM.v16.0.6+4.armv7l-linux-gnueabihf-cxx11-llvm_version+16.asserts.tar.gz/sha512/fcb6e45b4627b6e62cdfaa9bb333863f99ac4b629082fffcad62f223711e1c36c72be497a6905b74f99f259213eabade3cd7d9b2d95ef6c77049380b46476a4d +LLVM.v16.0.6+4.armv7l-linux-gnueabihf-cxx11-llvm_version+16.tar.gz/md5/abe229cb1c155815bbf39735fa2395bc +LLVM.v16.0.6+4.armv7l-linux-gnueabihf-cxx11-llvm_version+16.tar.gz/sha512/41ce4c5dda1d85818387a8c0b8061290ecb630b243542b9d8f9f28d5c4364b010ec9dad682a5c62859165d61664b1f43b5343a79bb7afccb79de0746ba4eb0ba +LLVM.v16.0.6+4.armv7l-linux-musleabihf-cxx03-llvm_version+16.asserts.tar.gz/md5/7f78b28890669a0140ef864153edf179 +LLVM.v16.0.6+4.armv7l-linux-musleabihf-cxx03-llvm_version+16.asserts.tar.gz/sha512/f610aeeb183e6c85e0e50d0a2b483b16d11cce83a237e70ab1297914062d78288dffe14f0a63e03dc8680c5bfc096b9e66a2fa6620833648896535c221640202 +LLVM.v16.0.6+4.armv7l-linux-musleabihf-cxx03-llvm_version+16.tar.gz/md5/106d266049c26933d4398ddd9c7e3af9 +LLVM.v16.0.6+4.armv7l-linux-musleabihf-cxx03-llvm_version+16.tar.gz/sha512/fff0928621c86fc50283ed5e929f88ac6191822d2af8280a61a08628d099203002b9a43c69df96d85de36efc364c7dd317e0db48d9e25699440e2279fed4789d +LLVM.v16.0.6+4.armv7l-linux-musleabihf-cxx11-llvm_version+16.asserts.tar.gz/md5/47e42e23351ddc592ed7e4c0acec01bd +LLVM.v16.0.6+4.armv7l-linux-musleabihf-cxx11-llvm_version+16.asserts.tar.gz/sha512/90f9e8b3f54373ee738115e9105b0ac94f298c574d96f8698ca72cb917181d82146c9f7dcde4468022b36cb2767a194d643e0c044af0397922b13b28bf2f6177 +LLVM.v16.0.6+4.armv7l-linux-musleabihf-cxx11-llvm_version+16.tar.gz/md5/29923410db87d76d9a86362713378f3e +LLVM.v16.0.6+4.armv7l-linux-musleabihf-cxx11-llvm_version+16.tar.gz/sha512/c1cf57dc3a3da5b16d14b80fec560c2886fc619addff07d388469aebf04b625d700d89aea52f6f0d50767dc2a4e426b017743fd66ebc15185fecd65904267b8c +LLVM.v16.0.6+4.i686-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/md5/540a5a6a7b785271e5354abb505868c5 +LLVM.v16.0.6+4.i686-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/sha512/61807c0e2f0cd25a234db25d171aa078c09d70d683ee874de79ea647c52a5875b8802853051b0f47a12809ddfc88e868cfda6551b789ba1633093fdd325ebe6a +LLVM.v16.0.6+4.i686-linux-gnu-cxx03-llvm_version+16.tar.gz/md5/7195f5e0bb86694fe6c6708510806a11 +LLVM.v16.0.6+4.i686-linux-gnu-cxx03-llvm_version+16.tar.gz/sha512/ea058346b3a600ca5fd3496d0a7f8ce7634a97eeedacc5013e458088462d82762eb92f8d82e6355bfebe4befe397cf0970fb8ec0005f8a80a4e8396b24f7205d +LLVM.v16.0.6+4.i686-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/md5/c59ab62f38a937f322326472a6faf6d8 +LLVM.v16.0.6+4.i686-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/sha512/54e6b32c0c3599804d44cfebc2d772a00130c3f41403b9211d16bf36112b9de9070fb3f663d09d239c4392daf4238b939cca1267369277a74bb7d176e841d732 +LLVM.v16.0.6+4.i686-linux-gnu-cxx11-llvm_version+16.tar.gz/md5/b5e5b32c0573a376ed4eb76473f98cae +LLVM.v16.0.6+4.i686-linux-gnu-cxx11-llvm_version+16.tar.gz/sha512/9f2fb572354d13b6444bd7ce786de90e8b394ecc6b69bce537b9636f3e6906971da317c6ac5a1da4a9ddb4a879060fde75aba5c96059cef5338cafdea78b47b4 +LLVM.v16.0.6+4.i686-w64-mingw32-cxx03-llvm_version+16.asserts.tar.gz/md5/f4f58134ca48a8485d70ac135a7b8cbf +LLVM.v16.0.6+4.i686-w64-mingw32-cxx03-llvm_version+16.asserts.tar.gz/sha512/84d3ff5c4d62c56b0fcdce4d9f6c83a5b079d182ac3dd6521ed4df3fa7e8f370cf4c0a3ca284f25a034525141094ec422e3383951c35c8fcfec97339fe477fb6 +LLVM.v16.0.6+4.i686-w64-mingw32-cxx03-llvm_version+16.tar.gz/md5/f797f871fc6c676da2c8006f2b2f1863 +LLVM.v16.0.6+4.i686-w64-mingw32-cxx03-llvm_version+16.tar.gz/sha512/a7b74a8e4d3f0ec5a76b2fb96ad3131c4847eaa46209667cd1dcc6e60c05fed3a584e9f302a05169a04088674529d2fec4deda5a9174735dbc21f98a681375b1 +LLVM.v16.0.6+4.i686-w64-mingw32-cxx11-llvm_version+16.asserts.tar.gz/md5/445e297dcd1aa83d9f7d29cca4f5e3fc +LLVM.v16.0.6+4.i686-w64-mingw32-cxx11-llvm_version+16.asserts.tar.gz/sha512/b90c9c7daf05b9afe7d8725c97491efe7e401f055614061ed894f140eb24c2f02f542daa15a4506037eec09a777070fefdec2e6594f1eb34af3eb113954b9543 +LLVM.v16.0.6+4.i686-w64-mingw32-cxx11-llvm_version+16.tar.gz/md5/401ea6f6290a5fffdf4cf55a72b41655 +LLVM.v16.0.6+4.i686-w64-mingw32-cxx11-llvm_version+16.tar.gz/sha512/87349bc4dc337f86e192068b82acc374bce467af8b1ec696fa5b9a98f174647ca8a7aaa98e48bb7ed308d2dd8dff459c8af622f7f2f9761898779879a7069de2 +LLVM.v16.0.6+4.powerpc64le-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/md5/977e40f66ead0070ee1b10c16b4821c4 +LLVM.v16.0.6+4.powerpc64le-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/sha512/de1431693f04469f280b512874836a03ada21d0dda05070e8486043999380fb73bddfcd15d1ba98cbb173bce90b2b69589064c02551785a868dc34b45a9dd17a +LLVM.v16.0.6+4.powerpc64le-linux-gnu-cxx03-llvm_version+16.tar.gz/md5/e74514de403eed67e4fbb647426a0fc0 +LLVM.v16.0.6+4.powerpc64le-linux-gnu-cxx03-llvm_version+16.tar.gz/sha512/3994895cbb78320048ac330cfd29700ef71ee48081a1eea4c881bee7e307c95caf8fa74be02f8dbc39a5a73b47e7498ede7e42b0c432fb263984f5122a6922d8 +LLVM.v16.0.6+4.powerpc64le-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/md5/2fa81725e463088df57dbfe286319e77 +LLVM.v16.0.6+4.powerpc64le-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/sha512/c3d58b6537674ac8b4a3281df3204a7ca6071c8acff7aa54fcd506e898ce872dde81147e955a708fd2ac3ddad31c4d7809c7bb152574e19bbdb72dfed2a75221 +LLVM.v16.0.6+4.powerpc64le-linux-gnu-cxx11-llvm_version+16.tar.gz/md5/397ae293e48777f768c2e98f9e6c558a +LLVM.v16.0.6+4.powerpc64le-linux-gnu-cxx11-llvm_version+16.tar.gz/sha512/526536b102e0650b3fda0c8f59639a3d97b8fd72c75c4a066da0955637cbaa5f331d4c1e5e11af48cbaaa813a274f6dbc360f75da9de357a14762703290a1fbe +LLVM.v16.0.6+4.x86_64-apple-darwin-llvm_version+16.asserts.tar.gz/md5/3524c7e60e752ba00e982730e76fa40f +LLVM.v16.0.6+4.x86_64-apple-darwin-llvm_version+16.asserts.tar.gz/sha512/39d5dd8905e51ec8fddd4c29142e1923f6379eeec6e5ce9e52ecafcebbc1642bb06bc912259082ece485071c32f35d56ecd1c19af270794d54a1e0a52b28ac2b +LLVM.v16.0.6+4.x86_64-apple-darwin-llvm_version+16.tar.gz/md5/b7e92004bf50eb5634b0134a41acd80c +LLVM.v16.0.6+4.x86_64-apple-darwin-llvm_version+16.tar.gz/sha512/ecf7dc9f70b74879ad067de733fe482e058231b76a2c220d0eed055f4967b0a9ae5d3e572b03bc20916beafe29f40a404c0e03ae18398ee8f7f95801b085e536 +LLVM.v16.0.6+4.x86_64-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/md5/8356012f5f046bf0c931ddbb600e18cb +LLVM.v16.0.6+4.x86_64-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/sha512/e9324a4bb5342c65dc978425c2da2c61baf1999dff9d30163576699b5be347217bf8750ded3f5a80c166f65ef94c150ee58e7f708791a41f045c2d9d10ed9f67 +LLVM.v16.0.6+4.x86_64-linux-gnu-cxx03-llvm_version+16.tar.gz/md5/f28b04fbb4f1781d36d4bf0d667737f6 +LLVM.v16.0.6+4.x86_64-linux-gnu-cxx03-llvm_version+16.tar.gz/sha512/62a6cff9bd8a73a17f8fe851cf4ad69fe70b12a3688323f3d5e169b942b8d2b5a7a2b1a6583a23e0d0c98fc9631b77cf0a21078999d45086f4620f08c0906782 +LLVM.v16.0.6+4.x86_64-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/md5/ed5489b9b7d1c2fa7632c91630079c88 +LLVM.v16.0.6+4.x86_64-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/sha512/ac27506410deb7ff4046def2bf277315e11a7cbf503631d62a749bee50fd09c4cfdd21ee7621f2232ecdac20c7be8b87dfa2125832aff8bb5cd65b7f9e7ae875 +LLVM.v16.0.6+4.x86_64-linux-gnu-cxx11-llvm_version+16.tar.gz/md5/c99551c46f7ae080f3d881247d83ad2a +LLVM.v16.0.6+4.x86_64-linux-gnu-cxx11-llvm_version+16.tar.gz/sha512/35b8df0053c904ff31db74d5d2b65fda7585b30753a6a96bb0237fc72e51cc39503fa93a0bfefe453941b047f82fa5aa08429ae1431b1590ef7bb358326485e1 +LLVM.v16.0.6+4.x86_64-linux-musl-cxx03-llvm_version+16.asserts.tar.gz/md5/1cdb2a5b49fe2a096d094ebfb5f298e1 +LLVM.v16.0.6+4.x86_64-linux-musl-cxx03-llvm_version+16.asserts.tar.gz/sha512/8919a3d2ccf821ad313bdae173db7519d627a6006804cd695e9836fd25ac2b4db5a4c3ba4faa673b89b33ccbc0c23f2b37ef4759f167ceba9a5564275840029b +LLVM.v16.0.6+4.x86_64-linux-musl-cxx03-llvm_version+16.tar.gz/md5/74e55203c31cafee26add48f1016796e +LLVM.v16.0.6+4.x86_64-linux-musl-cxx03-llvm_version+16.tar.gz/sha512/2fd6a34c49fb4c62e8104b91d34c01fef8cb79ecb076bd0c6e325db46a77b79652a741b9e9a4fbff454e7810b3b4352d62eee098b0a155496bec79add7f9ed5c +LLVM.v16.0.6+4.x86_64-linux-musl-cxx11-llvm_version+16.asserts.tar.gz/md5/2ba719127a88be8d60251d0b324c4aed +LLVM.v16.0.6+4.x86_64-linux-musl-cxx11-llvm_version+16.asserts.tar.gz/sha512/f117558f61da9a2ded579ed0f44151a6480f001aac9c082e3fc6fcb0737d400f97a3e9aa66a311ad6cd7a0b9c53dc2c37be7a0f1d7bad7b742ace2d80f20fed5 +LLVM.v16.0.6+4.x86_64-linux-musl-cxx11-llvm_version+16.tar.gz/md5/4bd2592a0f027b04599b35fe79dcc2de +LLVM.v16.0.6+4.x86_64-linux-musl-cxx11-llvm_version+16.tar.gz/sha512/6240c9f909a222b7c6f51b388d1b49be5ecb51e9a5c17bbf3aab53782e5de23c3e60877208fb0117d46306b9d3a1527ce8debf3790e5e66255938465c11d7570 +LLVM.v16.0.6+4.x86_64-unknown-freebsd-llvm_version+16.asserts.tar.gz/md5/1f161fb77f9b880a8d825e60b83a288c +LLVM.v16.0.6+4.x86_64-unknown-freebsd-llvm_version+16.asserts.tar.gz/sha512/0913c1033dd51c79db2169ea3a40a0809eea49c04e59aac7502a86cb561d7a1a2d61a903b77f92d7fbcf1f26bd9e9e956e3c88124792e9a0d8400fd7cfb593e2 +LLVM.v16.0.6+4.x86_64-unknown-freebsd-llvm_version+16.tar.gz/md5/78e5ea646651847d21af1a710b709660 +LLVM.v16.0.6+4.x86_64-unknown-freebsd-llvm_version+16.tar.gz/sha512/c8789f8fc1b8695f1dc1e49fe8d8512b5a1b23554d651479c39dd64570e3fe141018b5d168fedc5a6f5141a2e71a65dc30f87376374f8f549981e326c87ab39f +LLVM.v16.0.6+4.x86_64-w64-mingw32-cxx03-llvm_version+16.asserts.tar.gz/md5/8bec2b0452f7c87fc98ed9ccc103759f +LLVM.v16.0.6+4.x86_64-w64-mingw32-cxx03-llvm_version+16.asserts.tar.gz/sha512/adb89f7904850c31c42f8326fc67fc0331d652d02546368e1db0242fc940074ccdf569b065d77d4803e1ad6cc39ed3353caddf4d0f8cbd92db7d9e95c5564bc4 +LLVM.v16.0.6+4.x86_64-w64-mingw32-cxx03-llvm_version+16.tar.gz/md5/2765a9d83b62b29719f3c5ee63370565 +LLVM.v16.0.6+4.x86_64-w64-mingw32-cxx03-llvm_version+16.tar.gz/sha512/4e67a737e953b9147dd143d5d7b6ab6a3bf5811510664be41dc9a1572f89d75d97c4c2b334fc4c60320a8552845444ba3f12230e9843edc63884264db7d6e3bb +LLVM.v16.0.6+4.x86_64-w64-mingw32-cxx11-llvm_version+16.asserts.tar.gz/md5/4e9a5587cbc5926224ae0d4a36c581ff +LLVM.v16.0.6+4.x86_64-w64-mingw32-cxx11-llvm_version+16.asserts.tar.gz/sha512/247c989e3843fc4da54f4c133a96d5bcfc111b84801f52b1b322b548d667657a932e94fb7e0778d1d0151d8c5cc1d0a21d3aec555254bb4d8c8bd37593554ed0 +LLVM.v16.0.6+4.x86_64-w64-mingw32-cxx11-llvm_version+16.tar.gz/md5/8a71fcf24c6758307f0491ccd29e99ae +LLVM.v16.0.6+4.x86_64-w64-mingw32-cxx11-llvm_version+16.tar.gz/sha512/cbe1131e1202e15ffead552a8176c1a86e03556c9d85c6fcf886d0f32bdf31898e3d588e2d6e5723e89267b98cf111abf1bf90b1c6ec6879b7ef8c87d52a7e6f LLVMLibUnwind.v12.0.1+0.aarch64-apple-darwin.tar.gz/md5/b95ad4844e649bf46db43683b55b9f4f LLVMLibUnwind.v12.0.1+0.aarch64-apple-darwin.tar.gz/sha512/15e0996aebe6db91fe58121001aa7ea4b23685ead3c26b5d89afae34b535e34b4e801a971f4854d8e1a1fbc805cece06272470622eef863e225358113a127913 LLVMLibUnwind.v12.0.1+0.aarch64-linux-gnu.tar.gz/md5/6d8783dc9b86c9884e0877f0d8ac4167 @@ -138,115 +138,115 @@ LLVMLibUnwind.v12.0.1+0.x86_64-unknown-freebsd.tar.gz/md5/54ac594b4c8e7f261034a8 LLVMLibUnwind.v12.0.1+0.x86_64-unknown-freebsd.tar.gz/sha512/a43756afd92081e6dd7244d162862fc318b41ca110a5e8be6e4ee2d8fdfd8fb0f79961ae55e48913e055779791bd1c0ecd34fd59281fb66b3c4f24a1f44128f0 LLVMLibUnwind.v12.0.1+0.x86_64-w64-mingw32.tar.gz/md5/83cf8fc2a085a73b8af4245a82b7d32f LLVMLibUnwind.v12.0.1+0.x86_64-w64-mingw32.tar.gz/sha512/297a5c7b33bd3f57878871eccb3b9879ea5549639523a1b9db356b710cafb232906a74d668315340d60ba0c5087d3400f14ab92c3704e32e062e6b546abf7df6 -libLLVM.v15.0.7+8.aarch64-apple-darwin-llvm_version+15.asserts.tar.gz/md5/6f46148833fa386b55dd92e946561c85 -libLLVM.v15.0.7+8.aarch64-apple-darwin-llvm_version+15.asserts.tar.gz/sha512/ea1912f9a75c07374bc9c22c7b8b4321a0700f4ea196d29169e14a648a30d164897215ef4c4c6478810e5c5cdc76b1f2937fec4e6b0bb3e4f579bae06a219d0e -libLLVM.v15.0.7+8.aarch64-apple-darwin-llvm_version+15.tar.gz/md5/0f2558df8ed485a93837e790bc646018 -libLLVM.v15.0.7+8.aarch64-apple-darwin-llvm_version+15.tar.gz/sha512/e7f962c4dd9675b6abec6129e7fa460ba3ba4bbe9eead4f9327a1db5fc9c64651d7424f9439ef396f0546f8e232bdc34eb625df0fa36bdfaed0e6b7af4dd0070 -libLLVM.v15.0.7+8.aarch64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/3317b67ebfb01c8d3dcb17c04d0f5ee5 -libLLVM.v15.0.7+8.aarch64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/383ec73210e00b02b3fe3f247bfe81a8c81668d3faf59c0ba9b0f032c41603807cb65808f28fc8a91dcf7cf72f3f5fa3bc68277bcb17cd35ec758ba3dd8ec857 -libLLVM.v15.0.7+8.aarch64-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/f49e5ae9b0eb96cddeb3d68bec320d7b -libLLVM.v15.0.7+8.aarch64-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/e2cf6ef242479bc781f6ea70d7123b087370bad917c8f398757ba32d9446ef3b8c3d3b813be2c1ad3a8945dce1464551b57d3bc40a2e063199dfe555ad697dc8 -libLLVM.v15.0.7+8.aarch64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/e8eb2e0bc1b0693fbdf01c9dbe0e0170 -libLLVM.v15.0.7+8.aarch64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/245a157bd6b49ece87075d6250315f419506262c43ad6c5c76340d60ca1eedb00dab77c6588b91bb5cd8033e7ed1d97c21d801f8359486c9c350ded3bfae7642 -libLLVM.v15.0.7+8.aarch64-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/60dea992b59646da2d719185f32f4a7e -libLLVM.v15.0.7+8.aarch64-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/7fcd64e51c87de8683923460d4596e406c5775d2ec6b7f4111bcb0689692dee09584622ecfae17ce91d1632f03a37cc9d43696190a5b92e916f4b8d4d0a50003 -libLLVM.v15.0.7+8.aarch64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/md5/86385feda64011d17952300a29a28a46 -libLLVM.v15.0.7+8.aarch64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/sha512/2cf9f4934932b39a4a0195eab39f28c718c7cb5b830a425bddeb2f0a643fe6a74c7754483324f57e876ca8b92b5cfa1aaca802a44dc9ebcde86d8875c0493952 -libLLVM.v15.0.7+8.aarch64-linux-musl-cxx03-llvm_version+15.tar.gz/md5/72cd1154871445247a6af96536ae1b51 -libLLVM.v15.0.7+8.aarch64-linux-musl-cxx03-llvm_version+15.tar.gz/sha512/b35740ff38e77c2b718f151e3ef55831bb4145d164c9c74bb78b7cce94693a6accb39cc747c67171cc43c72fff6cc72b3a19ba73e4dc8cf127ffe61f640ceac0 -libLLVM.v15.0.7+8.aarch64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/md5/8dd9a908e0d94695352762d87bb5cfd4 -libLLVM.v15.0.7+8.aarch64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/sha512/3d9fbede7af089548bca9348c5ad13f146386ee4e2391223b4f9d299f9a52f7062dc758ab098805e481f434a5057ed37cb031d48de7fc3eb3137699f5540587e -libLLVM.v15.0.7+8.aarch64-linux-musl-cxx11-llvm_version+15.tar.gz/md5/f55864a3ee48940270502c637412dd58 -libLLVM.v15.0.7+8.aarch64-linux-musl-cxx11-llvm_version+15.tar.gz/sha512/c52bca8cb48cee54ad86bb86696f489d6a6d471238e49affb9af70b0e88ec6a4af57391a72f88fbea31f7c597a066d57978b1d3ea63a29cfae559457c66f6e06 -libLLVM.v15.0.7+8.armv6l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/20d1d88a807f96b469032e5c932c0696 -libLLVM.v15.0.7+8.armv6l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/d6cdc9746c81c504faa968638ea9d7fd9ded72ad0abdcf6c5adb8bcd127126d35506c2b6b3bedb977736a76318edb5c899ba555ff1d05df21f797a8f9a4e5122 -libLLVM.v15.0.7+8.armv6l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/md5/b4d7d4ebce4c7bbe3ac11cca0017a67a -libLLVM.v15.0.7+8.armv6l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/sha512/bb388db416d805ef5fbdbf4d2210d4b2188d6eae2c864692cfb1da7ba89de772423667e4648889db7cf43d9127aa13f77981d0b99ef321ff3f09faf7fd4b8bb9 -libLLVM.v15.0.7+8.armv6l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/b9382496b307f7e72fb12d394eef4e39 -libLLVM.v15.0.7+8.armv6l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/122a5f8daf1240c5d7b4f1926292311a3d287c4461516ee3321d1657ff3c9ca5caff54a1298f0d2a2b4c19627a57d626a4fb4455921862d87fe5897d672bdfae -libLLVM.v15.0.7+8.armv6l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/md5/bd564398619c3338e509e2f9ef7d7ba0 -libLLVM.v15.0.7+8.armv6l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/sha512/a54812732e95604031ef1fb420057ed40b909e211a68d5a533be617556c7c41e4022dea35fc6b8c81d8cb1227e4086d46b345cfcb6022dad9efc456ed85d7825 -libLLVM.v15.0.7+8.armv6l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/39daf88d35f518325138f8562dec70dd -libLLVM.v15.0.7+8.armv6l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/2b2fd30a04cbdcffa57d12ea2785e3016456fbf1dfe99b74a1628ce213bdc28d65f8c6937175c700c0df4f5f8f1c257ef71f27d1d5cca2c5efe5598c893d5011 -libLLVM.v15.0.7+8.armv6l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/md5/af88ec70f786750081d2948b6b9fd0ba -libLLVM.v15.0.7+8.armv6l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/sha512/f2a6bf3d74ddd06115d4791bd7ed833fc9ff1c24c0a317d7756d181f7cc59d21da73e085abb7ab0b46a12b0cbe4656df0e9e509b057cdf7d3d0cae2304f6b39c -libLLVM.v15.0.7+8.armv6l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/5dfdae75a373ef890d7e6c004cb16b2d -libLLVM.v15.0.7+8.armv6l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/3993609a2199c78cc12f430f76c2c3b58b0d6f208271bc474e3869d9f505fadfa61e1c2c5774ca5190c7886cad68507fff9a0f6d11641106507fc4b5e3bc8a41 -libLLVM.v15.0.7+8.armv6l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/md5/e426a70337cf4e38ba22f64c9adbabd0 -libLLVM.v15.0.7+8.armv6l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/sha512/c5ac86c22d016adf70e77970ae976a5081a85ebe1943a46fcb93be53f85f82e4aaa724e41b450306d0cf50dc4eb64367059737b6c828eab554f6166774201310 -libLLVM.v15.0.7+8.armv7l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/523077c1c0fedd6d5e62536e933b2c8a -libLLVM.v15.0.7+8.armv7l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/246df1e0d1a038ea2cb77a9678ed0e13f568ca35bc2c751547b9e871163c0dd7e18ea321c88eb6c9b2ccce9ec4347c29c69e5cbc41dbbd43d23d7a658ca5fc15 -libLLVM.v15.0.7+8.armv7l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/md5/23aa72bc1511ff8f4c09a2bdf70085a7 -libLLVM.v15.0.7+8.armv7l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/sha512/4d1d46652e98e75925478a86bc3b2e613ce54d3e99cbd75fecc637dab70a72187c36a979e2124592cb764d867c39555c013497fc3438103aa320fb7e45247254 -libLLVM.v15.0.7+8.armv7l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/ba162cc558297ef830f94323e351aae0 -libLLVM.v15.0.7+8.armv7l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/f98f1c74f5fb3a1e8936692c0b9c57fb8da1ae8fcbe187e019d61ff6691cbcdb03e7f53b5254bd219856bc3dc4408cb29926744f6086a13c3a32e66c1a79f422 -libLLVM.v15.0.7+8.armv7l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/md5/9b364f0c1f2bf550d5908407120ab68a -libLLVM.v15.0.7+8.armv7l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/sha512/c7bb0a90cb24352926c6a0d3599490db75f84e907697f48d1ac099767f4d3480e94a6e19995bd16d2c26c7cac1309bb579f71208899ce83aa617ec06cea2847f -libLLVM.v15.0.7+8.armv7l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/06ff2d8850863f47c6d7391d9bdc4cd9 -libLLVM.v15.0.7+8.armv7l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/6d03c53673b072ba1936124b681219ca474f7d269dbeb3c0278ea88aeba5949a5af5388f3776f9bf5c1f05fef81922c9720d16fbb57737bd9b83925206f46cf1 -libLLVM.v15.0.7+8.armv7l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/md5/7d37c75e75b9fa3e5db8cc2422a65b90 -libLLVM.v15.0.7+8.armv7l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/sha512/ffdb947940a033a01d0568f25018c125a743f3c71ce46565965ddbad52bd88596092190070c827aa6f7e70e22338e5d5162dfcb008deb80734f59d35437b41ed -libLLVM.v15.0.7+8.armv7l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/c31df5def85016f499c1a7daedf006ef -libLLVM.v15.0.7+8.armv7l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/d1b7220c0037450ec6efc4a2119c4dc4b9c48a20281c60b923f33bd80a9d146a69270983fe15cd8623ccfbac6c4bc4f4df3b44ad36cbcf86da8086f932e4da4d -libLLVM.v15.0.7+8.armv7l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/md5/a178f55baca2221e71a0badc79b49bd2 -libLLVM.v15.0.7+8.armv7l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/sha512/d613e2744c59b845b8893eba4a4579750d9dd4bf4375837aadd963f051475dcbf074d254dc8eacec2f4b2f77a9c1cca68f88b7948df8065910b1b0752dd2004b -libLLVM.v15.0.7+8.i686-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/2c5cd46b8a66d959807f4b285f5a221c -libLLVM.v15.0.7+8.i686-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/43061164687d44c1aa92f11198e218af19e96b1fc6519a2f41a2626302b3089beda58d24040e3220424e718366c761c41a064479411c8c923eea1708b4bb4c77 -libLLVM.v15.0.7+8.i686-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/1a0850a7e8c88088a6c26a50cd746396 -libLLVM.v15.0.7+8.i686-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/57bb6a55f7b84488d72195e1420826e94f082541fdaa0f981da1abcd8b1fb4f0a12f1398d786abd51943c6920513914e9476a05c7477b2e003e30a068304f0ae -libLLVM.v15.0.7+8.i686-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/538b7f590bfe5145c39c06aed74c845f -libLLVM.v15.0.7+8.i686-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/98e59c9a2354f4881b61bc23ebc34c2f70e6394a54cbc6735a56f59688e248db85b8a674e1e4a2a60bb8802f23213767c17985c44eb530743132d3366b0ed2ce -libLLVM.v15.0.7+8.i686-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/854ce5a05f524762390216637048a2c5 -libLLVM.v15.0.7+8.i686-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/028714472e5e5ae01b667f1058dc704b051030c8c95ef4816aae1118aea6442f44d824e48c13cf4111f81ea151a7dd951aad40e4c05208730da4a4f9f4e52c3f -libLLVM.v15.0.7+8.i686-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/md5/dc6aabdb3fff4b33a9eb75ace7e6615e -libLLVM.v15.0.7+8.i686-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/sha512/457a1f6d28b287033f501247499c4a9871be7db7880b65f34ab615846c06759fcbb9b4643a6692f670d56bb2346311806475fc7bb82a699755ea6410dd999d37 -libLLVM.v15.0.7+8.i686-w64-mingw32-cxx03-llvm_version+15.tar.gz/md5/0578542bbde3db752171f9420ce30d74 -libLLVM.v15.0.7+8.i686-w64-mingw32-cxx03-llvm_version+15.tar.gz/sha512/93a8310e3b5bf85bcc7d1b20ee836805eb087a318bde258d5117babb877689e05d5a0d66e8c00946544b6e07a322c2af5bfd1499927cc325a98fb5b6aefdbed3 -libLLVM.v15.0.7+8.i686-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/md5/c2b7dec7d566c3f110f84a69e781866b -libLLVM.v15.0.7+8.i686-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/sha512/44652389f9aeea2a8ba4e49bf78fa7d11ef3579205f178af844567e8e999917d737e233fe74a31cb3bf9a49847e364824109634e6683b75096b3f45a9fb4150d -libLLVM.v15.0.7+8.i686-w64-mingw32-cxx11-llvm_version+15.tar.gz/md5/1c13dbb8577ad9e7020059a474560286 -libLLVM.v15.0.7+8.i686-w64-mingw32-cxx11-llvm_version+15.tar.gz/sha512/1d3e4a06f3d6ae3afa976713ad8937a51c4e9fd0b544c1e9f8c74ae8579dba29fd8e77eb8acec0873dec8ec58fa91bfa6738212a504769807a758caa1f203b2e -libLLVM.v15.0.7+8.powerpc64le-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/ba4c9eceaa281a177f1f8b878bde35e6 -libLLVM.v15.0.7+8.powerpc64le-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/c0f8fecd3f6e1b342a075630ef0fbcd76d964e9bac08ac5e8a48ab0686c0ab91d327c263e045056a66b93f44346a0624af1687ea36d7f3c41c94d23d33c0d2ef -libLLVM.v15.0.7+8.powerpc64le-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/396cd763186100f920279ea551db8d09 -libLLVM.v15.0.7+8.powerpc64le-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/c79d60d522b4c186b51b7ea50fa8916ec63f64941225d535f8cceb25e3732458291260186684d098776091f8ba235c7c890fc635e8f39ac3c603aeb7c84f0782 -libLLVM.v15.0.7+8.powerpc64le-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/8288ec83ada261b340994f5ea62c2efb -libLLVM.v15.0.7+8.powerpc64le-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/c612e99e655dec48f0c1b2b28d5aa999c3ba8e83f7796a1f2807074ceccdb08a8b6a5e3554eacfc3ba7a99aeeb8c6b28217cdc957937e344a94636920cf288e0 -libLLVM.v15.0.7+8.powerpc64le-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/039c44655a46ee04f7de2b184bba432a -libLLVM.v15.0.7+8.powerpc64le-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/a47b840e221933001b9366aa90b5862f25eced86bead68164b98ac3b095a1a948a9d89a6d2f9e7db2fabf40df4de211b5ff5d7d10fc3572c24470764d14a559e -libLLVM.v15.0.7+8.x86_64-apple-darwin-llvm_version+15.asserts.tar.gz/md5/036be0a300369212fe943134678ba985 -libLLVM.v15.0.7+8.x86_64-apple-darwin-llvm_version+15.asserts.tar.gz/sha512/7dc49931b9775372385a61cd9de441dae567df791c83adcdacd45602e8cb64848895b26e8c53476fe53d86d21a06a00c21623eba4ef71ca97a545bc4fc2c5b18 -libLLVM.v15.0.7+8.x86_64-apple-darwin-llvm_version+15.tar.gz/md5/747b5946945160209c2e5705a0ab0bcd -libLLVM.v15.0.7+8.x86_64-apple-darwin-llvm_version+15.tar.gz/sha512/da1b2b543c648e322a1d87d2294a0cf87f4ae4c1c5b5708a3de6bfd29ffd589f970fa8f01182017db1743bc53d6726f9118438efef07484c7388ff0b0918c99b -libLLVM.v15.0.7+8.x86_64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/b5eebac2361874017e0908dd2b655953 -libLLVM.v15.0.7+8.x86_64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/b3e1b05afb00e44d1b6b1ce9adc6934e2f2caf1d8bc8d112a7aee5526a39cf77d52b256b888bdc54ac848a98aa2714c6a8beca5603fff62e974595f2c2ce1b25 -libLLVM.v15.0.7+8.x86_64-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/16864fadbf7366031ec0f5aa69a295ac -libLLVM.v15.0.7+8.x86_64-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/2099f8f4167f7a96ad2e9145e2c4e26d201aececd8a34d21c1e21b3865e8c63192cfef6e012863a6920b55d7865143527aba17805412b122f1fa0e2ff304b515 -libLLVM.v15.0.7+8.x86_64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/3146935cca2cf625311fda3df6860aef -libLLVM.v15.0.7+8.x86_64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/745310983881e2839797b3da1f56e4adc115f34ac932215546ee1bbc869baecea1be077b546c9a68dd0fddb89551dea19ff181c691892e572f86ada885da3bfc -libLLVM.v15.0.7+8.x86_64-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/7c3ba44af8f1fecd3c370614fda602dd -libLLVM.v15.0.7+8.x86_64-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/ddf418ea2dbfbb61ce4c977e0b0ae29137e378458fab37b5fc18db8a7d196663d7012e28161efbaaa75824ad0bd0c16841625349d89c8e3f7738d973e760cd27 -libLLVM.v15.0.7+8.x86_64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/md5/b7985ca8dc4eb2b89239ce6e0083db89 -libLLVM.v15.0.7+8.x86_64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/sha512/babb426085a330434ffca6e55d1b237b780754845e4699621ef581b2cdbd803c0e0a0b3f91693f76848ba2cf1f177b6c342ebbd459d9926d6a4e0b7f4184de18 -libLLVM.v15.0.7+8.x86_64-linux-musl-cxx03-llvm_version+15.tar.gz/md5/cc912aceddbfdd5278f302dff233aacc -libLLVM.v15.0.7+8.x86_64-linux-musl-cxx03-llvm_version+15.tar.gz/sha512/fbdd72353b76cab6c82af9e19e7f2abc7b5e19c634abb66d25cda0276c79e4c75f14eeaa8de6f03197a3b69cab130f0693bc3b17f144bed476a2e14268f2a5cb -libLLVM.v15.0.7+8.x86_64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/md5/e9bc3f99cf16ad7e37b6f8692610c0c5 -libLLVM.v15.0.7+8.x86_64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/sha512/b7e0b1b1804d3641e8763eb35d08e1f9c9c2bdbcf9d129d1fae5835067d5b6ccda1acf0888b686c5e8a268b14fa455f52451e3cb6909446c9d053d3d1d261fb2 -libLLVM.v15.0.7+8.x86_64-linux-musl-cxx11-llvm_version+15.tar.gz/md5/ba454f84baa9f746de63e22714c740f2 -libLLVM.v15.0.7+8.x86_64-linux-musl-cxx11-llvm_version+15.tar.gz/sha512/2b61a1a6693c7899fd9af8630c9eb45b54b6c84519e50828d80a6f7eb88dfb3df6ee4d473f8e30ca11a0e1db2c096711164dc08cc61707277f7beb1902930f43 -libLLVM.v15.0.7+8.x86_64-unknown-freebsd-llvm_version+15.asserts.tar.gz/md5/1ebc12781d75fa94145bb6c7a2ba235e -libLLVM.v15.0.7+8.x86_64-unknown-freebsd-llvm_version+15.asserts.tar.gz/sha512/43898f026a1f43ef035999654555afe4410425f2b6f56341100115e2a66825843fe93777c10ba052d60562a64a1150095fca4181a1c09f0820baa83736611627 -libLLVM.v15.0.7+8.x86_64-unknown-freebsd-llvm_version+15.tar.gz/md5/7259f7a15909745b0176c7bec2d5bb8e -libLLVM.v15.0.7+8.x86_64-unknown-freebsd-llvm_version+15.tar.gz/sha512/fcf10dcc6bc0bfd4389859db1ab6a827fcc15368cbd42f1f50a2b7a3e752c4cd2676e65fcd69bf0eb42579293d303d770e06c6d10150ff17d7fdd6fc71d0155f -libLLVM.v15.0.7+8.x86_64-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/md5/9ebab83ab97a6cfef7d745e799ce39bd -libLLVM.v15.0.7+8.x86_64-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/sha512/8892e553f08507b07b62e4c7859f3499a47e3eed5fe773e61c9c2831150fd49a06fc25e164b7053873c97830755d478c5527d23598ce674eb2e13246a64d00a8 -libLLVM.v15.0.7+8.x86_64-w64-mingw32-cxx03-llvm_version+15.tar.gz/md5/11915b49d288c71d78bab5c114bbd5dc -libLLVM.v15.0.7+8.x86_64-w64-mingw32-cxx03-llvm_version+15.tar.gz/sha512/0b38cca2f9492dabbd49a411660fea39e70c53ba105db7f8eb6a616d108ab8dee4b015b79a2cb34e75b91af5941d5f9acb2630f233ef9edd4f99118302a68537 -libLLVM.v15.0.7+8.x86_64-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/md5/e2de9f36a69100387209f59f99bd54a8 -libLLVM.v15.0.7+8.x86_64-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/sha512/641aa82ab684656ae82f12b19bf3d090080d62d3c933525f23607a85da0ed1f1dbdf02c30120994e52a447abf86552fb2a3e0ab70f718c4baafc9bc8b01fb17f -libLLVM.v15.0.7+8.x86_64-w64-mingw32-cxx11-llvm_version+15.tar.gz/md5/596a32ac2f4a2e4aedcef09cea7541a6 -libLLVM.v15.0.7+8.x86_64-w64-mingw32-cxx11-llvm_version+15.tar.gz/sha512/2ad659ebede4fbd2b09d487bd4efdf050988da7267797c4b884387f35c5e02cc0dbf93f56e51316dbdca3e8ffd6a7d9120c2cf10387fd1fead4a05d81366d83e -llvm-julia-15.0.7-7.tar.gz/md5/20678ba77ec517e16df67b6683fe1fd6 -llvm-julia-15.0.7-7.tar.gz/sha512/dd18b1d1817d7bff23bbd9598eb691e2d65ef679666e7219e4137223e739b62d5f4ef11d8c42474438b954026e0c3f0c5e350d3df23d8b4b294765d8d7788802 +libLLVM.v16.0.6+4.aarch64-apple-darwin-llvm_version+16.asserts.tar.gz/md5/bcdb469da04c788fb81a723ed285d268 +libLLVM.v16.0.6+4.aarch64-apple-darwin-llvm_version+16.asserts.tar.gz/sha512/d44eec8843f4b22fb8319e1a6e138f96795d82000a8f90f8157455e6a08c4706d2a2704a70ba96c33226bed6fdd27ac52da6a78c5f2eefddd98f8911fcaa966f +libLLVM.v16.0.6+4.aarch64-apple-darwin-llvm_version+16.tar.gz/md5/237d2d1a2fc596e502502ecf35a23623 +libLLVM.v16.0.6+4.aarch64-apple-darwin-llvm_version+16.tar.gz/sha512/9619bc2c0cb38cf38a53624a0a6f2711154d409178d7d9e510babef007c7eda33a9ffa4e5ae29c56bd52c77a230c4fa74f1730150232ebf0491736ede5805c85 +libLLVM.v16.0.6+4.aarch64-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/md5/2963b428aa281238a11df94715ccc7ab +libLLVM.v16.0.6+4.aarch64-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/sha512/04f34b768de43175f7b453e5e5d4516b1f42f42fae04be511a01fa865e7a778b4044e0d0cacec4c8efffbcd7b462a1b61c3c7a796c7c8ee474085b40d59e9e9b +libLLVM.v16.0.6+4.aarch64-linux-gnu-cxx03-llvm_version+16.tar.gz/md5/bad975489e45f75190a930e2adffdc5b +libLLVM.v16.0.6+4.aarch64-linux-gnu-cxx03-llvm_version+16.tar.gz/sha512/48cb7d77d85d9ba6826105e3707914f773e1b54412b0165da407f9248ad1b9f262c477a11b0d2fd2efbc638e9fe20102b3a6ad4e51d8169ecde0a8ded863be26 +libLLVM.v16.0.6+4.aarch64-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/md5/e47e1d4be6d503f069dad542c04f2391 +libLLVM.v16.0.6+4.aarch64-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/sha512/d6e98a2effa12796f305a90e401ddff5f29df7203d017fa08c681ef0b91a807f5dcecc63ec8a690c25a664c6a02bb55a205e238eb6696dd421888ce771fd7ffe +libLLVM.v16.0.6+4.aarch64-linux-gnu-cxx11-llvm_version+16.tar.gz/md5/9289127a28c909e8e7d5f3c44a20b4ba +libLLVM.v16.0.6+4.aarch64-linux-gnu-cxx11-llvm_version+16.tar.gz/sha512/5c253a38b9b06f227123fc3d7d9f2f55ca7445169f6c6f0ed73dce1b369039da6edee91a69cff88d6dda1651989685570c7fb40e2e196b0cc73e40a3e68df2b8 +libLLVM.v16.0.6+4.aarch64-linux-musl-cxx03-llvm_version+16.asserts.tar.gz/md5/9219850cd1c29f480f7ac7545fcbaf50 +libLLVM.v16.0.6+4.aarch64-linux-musl-cxx03-llvm_version+16.asserts.tar.gz/sha512/42ac83c9ddda05b0ff83159bbd72190daf05d6573f715ea7d2e0c015365ee81471069e43b2a58be4ffe340139f4ef5902c543820920537d0918f15ad9510bead +libLLVM.v16.0.6+4.aarch64-linux-musl-cxx03-llvm_version+16.tar.gz/md5/1ec5846ddedf6c8a57bc3b5633df9626 +libLLVM.v16.0.6+4.aarch64-linux-musl-cxx03-llvm_version+16.tar.gz/sha512/a40b5b13f3ceb0a261e7fc28209334a6ee4fd7d9490535810b1471f7dee1a5ff6f80fa98c4adbe3f56a6fdc10f2b2b22d835b974ef8bec4909aaaff4a87f1bcb +libLLVM.v16.0.6+4.aarch64-linux-musl-cxx11-llvm_version+16.asserts.tar.gz/md5/1f65b8354b9235b442400560acd0e7c3 +libLLVM.v16.0.6+4.aarch64-linux-musl-cxx11-llvm_version+16.asserts.tar.gz/sha512/c2b82578425e2a378cef3a1028b1a55492d3ef8aa4e2509ef06ba30475b95d1896764a3745de4eb905711f57b9ac7fb451d5d5f44afc5c95c9a0526a08197f97 +libLLVM.v16.0.6+4.aarch64-linux-musl-cxx11-llvm_version+16.tar.gz/md5/57b2b92268a1bd13f042cb8cc4f652a7 +libLLVM.v16.0.6+4.aarch64-linux-musl-cxx11-llvm_version+16.tar.gz/sha512/187a925182cefa8eaf42b9b4df42e9b5999deb27d864887ef65ba97fb09b1f6fbac1b0c7675aef0df91dce5b51adf35493bb6edf1d5049a73d1b76eac26144f9 +libLLVM.v16.0.6+4.armv6l-linux-gnueabihf-cxx03-llvm_version+16.asserts.tar.gz/md5/04bd3427ebb23c98f580ab27b8b73809 +libLLVM.v16.0.6+4.armv6l-linux-gnueabihf-cxx03-llvm_version+16.asserts.tar.gz/sha512/fa2fc5bfcb2ddd555ef173e95bcde24320cd5e54af7dc3e24f2ba6828753cb52f2c59381191d5b14a76ce53634203c40d126d533afcd1b620b718a75e38a51a8 +libLLVM.v16.0.6+4.armv6l-linux-gnueabihf-cxx03-llvm_version+16.tar.gz/md5/c4e68d1829702b7cde26bae87817273c +libLLVM.v16.0.6+4.armv6l-linux-gnueabihf-cxx03-llvm_version+16.tar.gz/sha512/4f656cd0b95f7bbb17acb49ef125c46e4d3d9e3336db5dbe80322e5089fad53248172acbae068b35ea82204839a11309336c8296e672c2d0b4aa8778bcc003fa +libLLVM.v16.0.6+4.armv6l-linux-gnueabihf-cxx11-llvm_version+16.asserts.tar.gz/md5/9372fa9a23ad5efe86fa002834cd8b71 +libLLVM.v16.0.6+4.armv6l-linux-gnueabihf-cxx11-llvm_version+16.asserts.tar.gz/sha512/6eacf49a32264fc88d5a034a96c6621e0ff9e5ebcfece0c702718c4d2027adb02ecce4a11096755010052ee4b61f99216f0c3f4bc3e31ef643dd6eae028220fb +libLLVM.v16.0.6+4.armv6l-linux-gnueabihf-cxx11-llvm_version+16.tar.gz/md5/535eae6a95d4d6be3be3b69eb180b4d6 +libLLVM.v16.0.6+4.armv6l-linux-gnueabihf-cxx11-llvm_version+16.tar.gz/sha512/df9820469c84d9c354dac9ac8ebddee3df878602ce2abbced63e77dd0f67e7e12929003e4c62def63be5b8cb90337707535bbebcca5fe1c730bafd90523fdf3d +libLLVM.v16.0.6+4.armv6l-linux-musleabihf-cxx03-llvm_version+16.asserts.tar.gz/md5/b82947eae6dff1fb15445b7dfd98c0db +libLLVM.v16.0.6+4.armv6l-linux-musleabihf-cxx03-llvm_version+16.asserts.tar.gz/sha512/a314e0297f60e55952960889445f4a9347782d8416644cbbabffef2af08cb1c9e59940a6f59b09627aba8783287749489d94ac2614aef4bd7f58b572bce146ad +libLLVM.v16.0.6+4.armv6l-linux-musleabihf-cxx03-llvm_version+16.tar.gz/md5/78457fbe2cb70afc8db031808080c732 +libLLVM.v16.0.6+4.armv6l-linux-musleabihf-cxx03-llvm_version+16.tar.gz/sha512/6ffb4628c4f2ec4651d0605709a67a5d5b86f1fdbd27a47551adef9b95d402c6bf0cee916f06f8d07de82cddb7e3b9fd5a557f98197e0c5431296917f6294dce +libLLVM.v16.0.6+4.armv6l-linux-musleabihf-cxx11-llvm_version+16.asserts.tar.gz/md5/72725d8f28c4e22596e8181e632b3d87 +libLLVM.v16.0.6+4.armv6l-linux-musleabihf-cxx11-llvm_version+16.asserts.tar.gz/sha512/90dcfbefbc58529616f64d7b1add5d8cb4a2e01406f7897aec64003e6c73b822be50c63cb8c2c9805d73cb3e7cd729846a39b6813d4f11872489c88d298249db +libLLVM.v16.0.6+4.armv6l-linux-musleabihf-cxx11-llvm_version+16.tar.gz/md5/494f84c84942b12d0bf26e4d2e8e946f +libLLVM.v16.0.6+4.armv6l-linux-musleabihf-cxx11-llvm_version+16.tar.gz/sha512/b5e1f1e2be533e7e5cbfb2dcec3ed573e4d6a7ada20442ee29122a2ff4d79246f140b7381eb33d56c436f893a65626a949474fc7d1a33502e40b857aeb5c01b0 +libLLVM.v16.0.6+4.armv7l-linux-gnueabihf-cxx03-llvm_version+16.asserts.tar.gz/md5/6f7e0ad54d6bc43bd539a3026c2dc564 +libLLVM.v16.0.6+4.armv7l-linux-gnueabihf-cxx03-llvm_version+16.asserts.tar.gz/sha512/c9a4b83795605d0550c8ed065e94df2523a45ff451b6cc894300b642cccc2778b9134e77a609ba62aaa838761c5b1572bd90436dfc96dab02319808036358a59 +libLLVM.v16.0.6+4.armv7l-linux-gnueabihf-cxx03-llvm_version+16.tar.gz/md5/e2ace2d6f83a72208f4475c6b19bac4f +libLLVM.v16.0.6+4.armv7l-linux-gnueabihf-cxx03-llvm_version+16.tar.gz/sha512/83b537561732308f126efc2b62433b3a0410d6e33834e2593dfdd029c1f874287c4c4c005564f64ded7b7127f839522d24626077f260b0a79cd9be64f9773d32 +libLLVM.v16.0.6+4.armv7l-linux-gnueabihf-cxx11-llvm_version+16.asserts.tar.gz/md5/3f92c8b09401190a5b5d7699b3e6e5e8 +libLLVM.v16.0.6+4.armv7l-linux-gnueabihf-cxx11-llvm_version+16.asserts.tar.gz/sha512/460e99bf7735ea4dde2adaced43c66c9e91a515e13a516775fee0a02a75d1c1270ff7390d2632af683912ffbef4e142e9d55e8870c3307fbdb4261515ccd1447 +libLLVM.v16.0.6+4.armv7l-linux-gnueabihf-cxx11-llvm_version+16.tar.gz/md5/dfc28578d1a2eaa0fb95dee5763024e2 +libLLVM.v16.0.6+4.armv7l-linux-gnueabihf-cxx11-llvm_version+16.tar.gz/sha512/9188d85d365c65cdfc487ab354e7593193efa3be09b5dc95701cb6244ba276409aa52ae0dbd3907be1b6e156a7d0506c6fa2ba8b60035b774d7ca5451233b287 +libLLVM.v16.0.6+4.armv7l-linux-musleabihf-cxx03-llvm_version+16.asserts.tar.gz/md5/8a8f929967e0d26152f145d93a79a4fd +libLLVM.v16.0.6+4.armv7l-linux-musleabihf-cxx03-llvm_version+16.asserts.tar.gz/sha512/9d474211b4ec025a56fe2a77bb78e6865fe14235620accc5110a4170a672295419bab55c750d2f7fc0fa1cbecbfa959e07126b8355c95231658446697f489b58 +libLLVM.v16.0.6+4.armv7l-linux-musleabihf-cxx03-llvm_version+16.tar.gz/md5/a8f7ff725142e3de01b6d76ac20fad71 +libLLVM.v16.0.6+4.armv7l-linux-musleabihf-cxx03-llvm_version+16.tar.gz/sha512/f29e03f0d7cd5610972c5b1538fa5714484641ce1281871d9f960f026ae9107076cfb9c45ad6bc5cd65f85cac395d978222c95600f4c91556f9386a84ce3088a +libLLVM.v16.0.6+4.armv7l-linux-musleabihf-cxx11-llvm_version+16.asserts.tar.gz/md5/f641a49cd631ade07e66c46c30c66570 +libLLVM.v16.0.6+4.armv7l-linux-musleabihf-cxx11-llvm_version+16.asserts.tar.gz/sha512/2081495e58ea755f43b76cc4ebc1d09e726f26893e2642b036165f75d417184e2038f97ccbe537f431aead46f8151036cfd4afe96a32192395e48fada13c83aa +libLLVM.v16.0.6+4.armv7l-linux-musleabihf-cxx11-llvm_version+16.tar.gz/md5/51b06c324130d8e8935bbd8a0b5a5740 +libLLVM.v16.0.6+4.armv7l-linux-musleabihf-cxx11-llvm_version+16.tar.gz/sha512/4150ad22761ed19493338d6d155e8c5ecdddf979b212f0fc6cae5ee3722ab4b6b0d4b0493778c665b81f2d4468e43589745b14b270b32953dfe71631d59ea4f5 +libLLVM.v16.0.6+4.i686-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/md5/ed66dbfebe14efcdcc1b71ff3a491fff +libLLVM.v16.0.6+4.i686-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/sha512/550db082a181cc53e5336f13cdc34c469f9d5934caf2fde41935068fd7bddc74102b822181d7cfb38848e7409e38975b100bdc2a96c112df335fd91ca326a90b +libLLVM.v16.0.6+4.i686-linux-gnu-cxx03-llvm_version+16.tar.gz/md5/703841665078d4061bb8f6839c49546f +libLLVM.v16.0.6+4.i686-linux-gnu-cxx03-llvm_version+16.tar.gz/sha512/c48786852da4b367356fc87703ec2dc4efbe9bbd66a16a1edff2f60f009ecac9fd95c54fd5de2e0a56ff8c59bcbf24df2609080c245aa8da166806a55f9868ec +libLLVM.v16.0.6+4.i686-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/md5/8d8bc21967705c1310219f9f91699a3f +libLLVM.v16.0.6+4.i686-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/sha512/52c44373d708f80409a1a83271effef97dffcb4828bf6d7094119378bc625cf3a2f3f7af649a590dc827fd2859ec3d302287cd235b65cbac9d1273ff241aed8b +libLLVM.v16.0.6+4.i686-linux-gnu-cxx11-llvm_version+16.tar.gz/md5/59ca3888cdbf9ff4b55d7a1092bbaba3 +libLLVM.v16.0.6+4.i686-linux-gnu-cxx11-llvm_version+16.tar.gz/sha512/8b30cc6eecbb496bfdaf2b9c81d5f0a08ae1c02f2433e1bf9dfcbb936d438e12c2a0de99014f325d76dddfda9b833207f615fbcd1d9a25cfc393593c32478621 +libLLVM.v16.0.6+4.i686-w64-mingw32-cxx03-llvm_version+16.asserts.tar.gz/md5/9298b06192d36c20b56f76348122eec7 +libLLVM.v16.0.6+4.i686-w64-mingw32-cxx03-llvm_version+16.asserts.tar.gz/sha512/ed173f7e0e7663f2186a519cc53429ad326958773a2211dfaff15ac20326617bf85c1233019b5ee996071b1158353ceea307c6ef2f6f617b81771a69072d3c49 +libLLVM.v16.0.6+4.i686-w64-mingw32-cxx03-llvm_version+16.tar.gz/md5/b267df02f127fbe710a75ce1b24ec70b +libLLVM.v16.0.6+4.i686-w64-mingw32-cxx03-llvm_version+16.tar.gz/sha512/4d78a547ef718810b4e5f2ff81e1edce7492808218e3313587afbc25d59877a2529df3405a9000eb254f88c59822da5a00e764153143b9f7418b0f4adc46c5c9 +libLLVM.v16.0.6+4.i686-w64-mingw32-cxx11-llvm_version+16.asserts.tar.gz/md5/8719ff4479ca28614a530a97540be161 +libLLVM.v16.0.6+4.i686-w64-mingw32-cxx11-llvm_version+16.asserts.tar.gz/sha512/d913eb261dabe4074566cb4620d7559613ac539b5f4fa5bde8db5d93c0c729ed5c59d177972f283a449a24df0ec76548ac7db26f8e162d7f08619e9bbfdc11cc +libLLVM.v16.0.6+4.i686-w64-mingw32-cxx11-llvm_version+16.tar.gz/md5/f3c3cbbaa2fe0289815dd022608721c3 +libLLVM.v16.0.6+4.i686-w64-mingw32-cxx11-llvm_version+16.tar.gz/sha512/951adf71911a4b4ec8ad2342c6d4e27797874499e10b8467b56cda5d36496130e72db7e8cda58f0512e04d94c1c144bd66d076451f6048c4c399a9af162202ea +libLLVM.v16.0.6+4.powerpc64le-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/md5/f9ee9ffa905583656c3405f553667e06 +libLLVM.v16.0.6+4.powerpc64le-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/sha512/91a96e8b8eba7d6505d6bdb28af993c7f8724a61c0d7179228035cd631535e5307880884249947f481a82b4f6a37f91c3cc15025383c7ce7302c010eb1130f99 +libLLVM.v16.0.6+4.powerpc64le-linux-gnu-cxx03-llvm_version+16.tar.gz/md5/d80972a0609c688f0a1064cb995e84ff +libLLVM.v16.0.6+4.powerpc64le-linux-gnu-cxx03-llvm_version+16.tar.gz/sha512/136c5f35ad7c41080472bac15f1a433134468f6b512d4ab7d41408da1d27a3aa6b0e5a6480ec185e4a1eee84ab222231f4de21fae7f852a324dfc702a80afd25 +libLLVM.v16.0.6+4.powerpc64le-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/md5/cc16d38262b9f3e1057d50bc57739ea0 +libLLVM.v16.0.6+4.powerpc64le-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/sha512/c62bcbde36850de855fe7c0e010a2a403c8b923da1c1bbc8384410e356c112186d1dc874f664260a8e819b0bde8a457746a32a1fc988b103e1277cf00912000f +libLLVM.v16.0.6+4.powerpc64le-linux-gnu-cxx11-llvm_version+16.tar.gz/md5/70b7a3e70f0f6566e34dcfba524d3ae7 +libLLVM.v16.0.6+4.powerpc64le-linux-gnu-cxx11-llvm_version+16.tar.gz/sha512/ae211588739af26ce61f2503adfc9e9711ecb1db5b8ff9072086accbfa55de8ba2086fd09d766d2de15ab3fa6a6a2790be363f20f03b146bd81764a8c6651506 +libLLVM.v16.0.6+4.x86_64-apple-darwin-llvm_version+16.asserts.tar.gz/md5/f2e71e81105619177a8d007f6ad8e9a6 +libLLVM.v16.0.6+4.x86_64-apple-darwin-llvm_version+16.asserts.tar.gz/sha512/dd4f6ee07f69ebd2f49da99d3de3b4394d0520fae6e506fdf4a028e3602c147b0a725f02b0e4712e6568815a460876cfcc05aa453e10ca30f9378925ca12b1d7 +libLLVM.v16.0.6+4.x86_64-apple-darwin-llvm_version+16.tar.gz/md5/fd64aaf12cac1e97a2d6c6a8a73b6bdc +libLLVM.v16.0.6+4.x86_64-apple-darwin-llvm_version+16.tar.gz/sha512/8917408e6f023de7d1f05027d389d9aac7f77623506e9d621487f601e3bc53053b99510266d31840985a380082e7af578c29c400a6dbccc31165bb90bf55bc41 +libLLVM.v16.0.6+4.x86_64-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/md5/d05a0f7df67d362e0c48e6a81a7efa73 +libLLVM.v16.0.6+4.x86_64-linux-gnu-cxx03-llvm_version+16.asserts.tar.gz/sha512/8a8adf8b13b02af3ac4ab29e314f689089359b35ac088cf30e0ae8d4d3cc24ca7d63ab908ecc18b6617935a01909e4eae2cdc392b6c7ee69fa0f3cbadff41349 +libLLVM.v16.0.6+4.x86_64-linux-gnu-cxx03-llvm_version+16.tar.gz/md5/d84ad34377aceaafcfb2922141b06790 +libLLVM.v16.0.6+4.x86_64-linux-gnu-cxx03-llvm_version+16.tar.gz/sha512/785bf236810f8c9ff0a6477c978059c136e936150e97bf2c45ec23b23accc0aa2df28442cb456d64f31350d1c49811fa4997029fd9f1044890284be494d68500 +libLLVM.v16.0.6+4.x86_64-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/md5/275083a9fb3c2cf281f64f08c840249b +libLLVM.v16.0.6+4.x86_64-linux-gnu-cxx11-llvm_version+16.asserts.tar.gz/sha512/01598d993a34faa33948d5a56a91401e100018b154112431c4b124e6e5356afa89870699006a6a4358d1bee1bc8155a1c96efba1c62a6fbcbf796087e1334f41 +libLLVM.v16.0.6+4.x86_64-linux-gnu-cxx11-llvm_version+16.tar.gz/md5/a7e0e0734e86acf47998c94441922a68 +libLLVM.v16.0.6+4.x86_64-linux-gnu-cxx11-llvm_version+16.tar.gz/sha512/a96b8e9fa2f34711c7826c4a8615d326fba3b6857ee77b9bf6120dfb3f64638da54fe50c7d6b2fac5aed77755da8d2529ae45daee7f1182b04f8248a34d02f9e +libLLVM.v16.0.6+4.x86_64-linux-musl-cxx03-llvm_version+16.asserts.tar.gz/md5/359b3ec71148c1467f3c763e90b5a7ce +libLLVM.v16.0.6+4.x86_64-linux-musl-cxx03-llvm_version+16.asserts.tar.gz/sha512/673dca9861ec87cc349678fad4b29489cda2a2f8ed02eb87baa893055e9574a0457c69bcacb17a8b3899a380dd43c44cd9632fd6e60bfc26625ceaa8ddf56385 +libLLVM.v16.0.6+4.x86_64-linux-musl-cxx03-llvm_version+16.tar.gz/md5/118c299f09ffd7946ab66844e056c9c4 +libLLVM.v16.0.6+4.x86_64-linux-musl-cxx03-llvm_version+16.tar.gz/sha512/880df9edee1f67ad1c68b504f03dcac492809aa1252589634300441b469cb84e9697771fd0c516ce25da5223814303b0c1d2bea48342d2b42bdda028272e9cbe +libLLVM.v16.0.6+4.x86_64-linux-musl-cxx11-llvm_version+16.asserts.tar.gz/md5/ecb34de28c31cdcab980c45d2c90faca +libLLVM.v16.0.6+4.x86_64-linux-musl-cxx11-llvm_version+16.asserts.tar.gz/sha512/bfb6cbb1ef7a712cb36c7d26de346b3bd7d10f3bbc8fa412b23e7332d2eb432a1bbec338cf92d7899d29036915871ecf70148468ef06dfe0042f5ae547505b1f +libLLVM.v16.0.6+4.x86_64-linux-musl-cxx11-llvm_version+16.tar.gz/md5/eff0891d113de03516e229eb0d9d9c33 +libLLVM.v16.0.6+4.x86_64-linux-musl-cxx11-llvm_version+16.tar.gz/sha512/078df9260d75354eeab5c6213e3ae926aa4050bc5c3f8691fffa4ce83e32648fefaa78bc67ee3d76a42dc4bad432ffc132e0733d15fe5f85a60f344fd51be639 +libLLVM.v16.0.6+4.x86_64-unknown-freebsd-llvm_version+16.asserts.tar.gz/md5/b28f51c7f01699b1b7488d3c9b7afaa1 +libLLVM.v16.0.6+4.x86_64-unknown-freebsd-llvm_version+16.asserts.tar.gz/sha512/297895fcbfd2b46e62e9e685eb08aa7710177a753ee7d6f3211dccd790f2677c9c57f8738785c35368c43dcedabb374ec57c7dd54110e0f153b0b8cc4b40bd5c +libLLVM.v16.0.6+4.x86_64-unknown-freebsd-llvm_version+16.tar.gz/md5/3822dc96d157aa849a5c73bafe221b19 +libLLVM.v16.0.6+4.x86_64-unknown-freebsd-llvm_version+16.tar.gz/sha512/a74d9ca78fa7daab47a9a0191356803ce98c8f3d7180073a949fe491d4364f148ff3fdc35d4960dff8ff7f6ecb65b31a471b171c7cab32e89b8252827b059775 +libLLVM.v16.0.6+4.x86_64-w64-mingw32-cxx03-llvm_version+16.asserts.tar.gz/md5/ab93b1ece593bf2fd37902d7cfcb26b9 +libLLVM.v16.0.6+4.x86_64-w64-mingw32-cxx03-llvm_version+16.asserts.tar.gz/sha512/3ee486621ec970ba991162a7ac80bc75be46b528a277d256b385a15c96018077862d0708eaa36234530d47d2f83fa934dc7993d4aa32cee0b2885e1c45cb16ab +libLLVM.v16.0.6+4.x86_64-w64-mingw32-cxx03-llvm_version+16.tar.gz/md5/22a246271f82a70c9a72d8387fa340ea +libLLVM.v16.0.6+4.x86_64-w64-mingw32-cxx03-llvm_version+16.tar.gz/sha512/f61f51bb3b7c9d253dcb40ca6a478ac44a6c47bb1f81fc7f562ee682d16081b2307695fb4feb0f3ca495fb8349aafd2b659a4f82b395f7d82e02dc909354b780 +libLLVM.v16.0.6+4.x86_64-w64-mingw32-cxx11-llvm_version+16.asserts.tar.gz/md5/f642a67480e79f269e3bbfe7b3ceed33 +libLLVM.v16.0.6+4.x86_64-w64-mingw32-cxx11-llvm_version+16.asserts.tar.gz/sha512/45930e3ae3bf3922c8b3096474cbe22035432e80d2b0f4ccbd424c69e874a76acf4a724e055a5ef73ce40ae00ee6ede60b210b8847c30239a6ec242550a3e464 +libLLVM.v16.0.6+4.x86_64-w64-mingw32-cxx11-llvm_version+16.tar.gz/md5/344f2a9d2b85a6c527fd6fa024d6763e +libLLVM.v16.0.6+4.x86_64-w64-mingw32-cxx11-llvm_version+16.tar.gz/sha512/73909c58087568d8654d48ed5fc952289d8b3a4e26e956e9220cf254b8fb2c4b6630ebc9258502a6c3b67ed3cfe0cbe3a92ee3d346b228d842af03f0a42794c7 +llvm-julia-16.0.6-2.tar.gz/md5/f05607b71ac8d1e7c30430d2a9efa0a6 +llvm-julia-16.0.6-2.tar.gz/sha512/5f2f88b4673b13780fa819c78cb27fc5dab77c2976768ae4f7863b904c911e39fc18ee85d212e512a7c60081c74efd1fa2e7142b78002982533b7326ff808f24 llvmunwind-12.0.1.tar.xz/md5/4ec327cee517fdb1f6a20e83748e2c7b llvmunwind-12.0.1.tar.xz/sha512/847b6ba03010a43f4fdbfdc49bf16d18fd18474d01584712e651b11191814bf7c1cf53475021d9ee447ed78413202b4ed97973d7bdd851d3e49f8d06f55a7af4 diff --git a/deps/checksums/mbedtls b/deps/checksums/mbedtls index 11ee2786abb98..2db4d7fed384f 100644 --- a/deps/checksums/mbedtls +++ b/deps/checksums/mbedtls @@ -1,34 +1,34 @@ -MbedTLS.v2.28.2+1.aarch64-apple-darwin.tar.gz/md5/ef83fb4706100ee678cd8af3f7a5c762 -MbedTLS.v2.28.2+1.aarch64-apple-darwin.tar.gz/sha512/03dda8cc9afa3d79c3c733e45c77891e75d939dc2bcca5ba8eb7aa3bd01fb52011ea9323df9cf7294fe6dcf87eb86c1b1c4b2f3b8af6116929b3371698559fe4 -MbedTLS.v2.28.2+1.aarch64-linux-gnu.tar.gz/md5/ac46c3840d2d0cc7c573f31c2f3d0d61 -MbedTLS.v2.28.2+1.aarch64-linux-gnu.tar.gz/sha512/bb458f1dc9b8684a38f603136ee4ba1c51b47f5047c5a5cfe2c552be266e79dfcd8243b216b0831abf24390eeb6f4524bc7e43b2642eb2ad0227399222cd0d8a -MbedTLS.v2.28.2+1.aarch64-linux-musl.tar.gz/md5/d74732e0bbcd03666243605e60bb345a -MbedTLS.v2.28.2+1.aarch64-linux-musl.tar.gz/sha512/90b0699477b697b94c0ab1ba0607fb3e1cd40d66a80a51cb1e0f3b927de03ba201e7e280d453db672e6265db5b07d0145846e53ddbcb4b550afcabef1716470b -MbedTLS.v2.28.2+1.armv6l-linux-gnueabihf.tar.gz/md5/65ce7c51884b50dcb8343a945644b862 -MbedTLS.v2.28.2+1.armv6l-linux-gnueabihf.tar.gz/sha512/e9df753e9f3a08fd645b15422be7cc0ec3aeac3f8d5f76e0c4c5ec24c54e1b653db320ed0c6799411802a05801241a5363bb449a8765fda7856413c7e3297721 -MbedTLS.v2.28.2+1.armv6l-linux-musleabihf.tar.gz/md5/7b7fc8eafc95416d75e3f1bfb2640e09 -MbedTLS.v2.28.2+1.armv6l-linux-musleabihf.tar.gz/sha512/68362114808fb4f986dea673ef1c7f104caad8233bed1c7f6a365d5d69bb7f7c92b234d6b1bfa5b014e7096411841c115a5cfe9932ae9ce642293cab962f8d38 -MbedTLS.v2.28.2+1.armv7l-linux-gnueabihf.tar.gz/md5/4a477379b15fafbf0c05435f5ab370ac -MbedTLS.v2.28.2+1.armv7l-linux-gnueabihf.tar.gz/sha512/fd34b475bf94b411e3155f5a5166d1ad081fef3622d7b99f4915b592d4235f63a0b910e0559ba2a0c3d596df9ccc2d7ecb61984091debb20bd4b995942857132 -MbedTLS.v2.28.2+1.armv7l-linux-musleabihf.tar.gz/md5/fc6551ef5f189010a84230dd48f6bdfe -MbedTLS.v2.28.2+1.armv7l-linux-musleabihf.tar.gz/sha512/d3a7199f3e1ffb1c289c5f0a4384f3b5d1af6e868eb1081d66d6cbfc60e6415e68a7e22afb497f2e7c7900678a19bf1ba2a4c888efa1019c03bce376af62154c -MbedTLS.v2.28.2+1.i686-linux-gnu.tar.gz/md5/5f06aeeacb93e8419da5dcc6dbadff67 -MbedTLS.v2.28.2+1.i686-linux-gnu.tar.gz/sha512/48dd5de23dd1513dd496b7ae9c88bc5a4f206442c3916ffdd602232b6f5fdc621adf0a3a014821d70092e1c3c90d96e462bc0e7608a984b0ff428c4bdbe42ecf -MbedTLS.v2.28.2+1.i686-linux-musl.tar.gz/md5/435b864b02d1d2c96e5d8dc32b433ae1 -MbedTLS.v2.28.2+1.i686-linux-musl.tar.gz/sha512/52e3a79a70b3ff4617c93cafdeb702105c13b34687fc0fa31eebc91aa5cacea356d5b6a6bdbbfd81417d77debe256ea8f0f2a43c8d140154099bde097740dce7 -MbedTLS.v2.28.2+1.i686-w64-mingw32.tar.gz/md5/09c0450a373e30ddef1ae31e06b288d4 -MbedTLS.v2.28.2+1.i686-w64-mingw32.tar.gz/sha512/59a3529e7826a2f2266c1482d5dbdae2fb578416b3b6ee3b0c8507df21c1395dcd681be65ad953e8306971c549efad342ee4e0725391a88b202475f56aebc062 -MbedTLS.v2.28.2+1.powerpc64le-linux-gnu.tar.gz/md5/26c8f09aa65e5b70be528311519d4376 -MbedTLS.v2.28.2+1.powerpc64le-linux-gnu.tar.gz/sha512/2d47567388b8554ce7714f4ded013fcbffbf94726dbc6a1b7287dc17b27d1fa35baba55cf7dac17c555892a5f4c74119afdf552b42b0e8f80f26621adaa4dbca -MbedTLS.v2.28.2+1.x86_64-apple-darwin.tar.gz/md5/dfc263208b1a8d4c29b4ec3b6f10e5ce -MbedTLS.v2.28.2+1.x86_64-apple-darwin.tar.gz/sha512/3b2941c4b151206a56a9a795f0f30519676ea4bc0c93f66b419b15568edc91bb976954f584116accb7f9bd067580712e61b3c580a249332640e27e6346ca51ff -MbedTLS.v2.28.2+1.x86_64-linux-gnu.tar.gz/md5/94b908036eecbe59372722b41f0b1985 -MbedTLS.v2.28.2+1.x86_64-linux-gnu.tar.gz/sha512/c37a4c34eb450bd716c076c4105bd6022892731c470d64a854ac0fca6653dcf5a70b23982050e7d82cdfd67d02902d9efe4c94d2cf5e0d29d497c3c5ac03f8e8 -MbedTLS.v2.28.2+1.x86_64-linux-musl.tar.gz/md5/217866be499144eeb2e0944b0b60cc09 -MbedTLS.v2.28.2+1.x86_64-linux-musl.tar.gz/sha512/144180e1968da627c92173277a130283aea711157a04a2655786658234232e397985f63d5407166377fc5f38a7447c19797c51b66a9c4b1773601d9e7e01d0e0 -MbedTLS.v2.28.2+1.x86_64-unknown-freebsd.tar.gz/md5/5a1ec1b183f30cb7998550e5ce15c62d -MbedTLS.v2.28.2+1.x86_64-unknown-freebsd.tar.gz/sha512/3d07fc1a54a832515a1340eaa5de03707fc52fe8770a75ac80106942f5d23e1d52297c6068d28ab07f55fd2b3f1c683b1e25e82bf4c34b4f14af58287a5b662f -MbedTLS.v2.28.2+1.x86_64-w64-mingw32.tar.gz/md5/edb5477223f9a35054160585fdb07f7e -MbedTLS.v2.28.2+1.x86_64-w64-mingw32.tar.gz/sha512/617779d6944ea153c63e6d9ce66d9bb33520e1539324a449151594937e648ef7ccb30364d3e7aa3eed3b68b02366e5724e14787565db565f0686334ab4df3701 -mbedtls-2.28.2.tar.gz/md5/421c47c18ef46095e3ad38ffc0543e11 -mbedtls-2.28.2.tar.gz/sha512/93cdb44f764b200131b8dbefb9363e5fa38760eaf01473a512f93673cc55db3515830e16b813e03b39cb819323ad78cee4cb7f3fa85861ec5e72e0f89541c7fc +MbedTLS.v2.28.6+0.aarch64-apple-darwin.tar.gz/md5/c97705b08c6bf695fa7a11a42167df94 +MbedTLS.v2.28.6+0.aarch64-apple-darwin.tar.gz/sha512/91825c3a495045ca74ceb5a23e3d7e9387701e401911b147d905a49892b1a5a9f22662a4f16a7f4468c5a807f2980b66e3409ea1ff7e04c6fdac0b105472e200 +MbedTLS.v2.28.6+0.aarch64-linux-gnu.tar.gz/md5/8ebaaeefd75c805227229086c262d0e7 +MbedTLS.v2.28.6+0.aarch64-linux-gnu.tar.gz/sha512/89983c1f9f9d7b901619522afcd12c6bc1996757edeb9f3012954992f82f3b36ae50f49dcf7731623fca197946e4281eecffdc29a5819f04e7f6203afd4eb93a +MbedTLS.v2.28.6+0.aarch64-linux-musl.tar.gz/md5/b40b2ba247f4ff755e15daad13c5a255 +MbedTLS.v2.28.6+0.aarch64-linux-musl.tar.gz/sha512/4cb4f2213b631dda0caa8baafa8effc9c8592c72a6a5b826fce060cd81f8f77c188c9ddc76595b47078db3c35b3043d9bf0cb891d822a940df87982de56dec44 +MbedTLS.v2.28.6+0.armv6l-linux-gnueabihf.tar.gz/md5/c6dd1cb1aba1075c73c41719a03c5ab5 +MbedTLS.v2.28.6+0.armv6l-linux-gnueabihf.tar.gz/sha512/981a8925dd90418150625e9467cc791e4a9d5223e7df6ead113ec41a279a5dd7e8ebcecb5b87611ef451fc6483fd6eb5bf984cf528037ad742e68b4be94e5c07 +MbedTLS.v2.28.6+0.armv6l-linux-musleabihf.tar.gz/md5/c30ed777bd74d269656f7e9bc8163765 +MbedTLS.v2.28.6+0.armv6l-linux-musleabihf.tar.gz/sha512/f04014181082561195caa4d3b178480bb5cce7f459d76aca8cdaa2f615d105b24871656ce4cbf8d9ec33f0424de35a16f12d4964a1f0fab9a416e5d18a468c94 +MbedTLS.v2.28.6+0.armv7l-linux-gnueabihf.tar.gz/md5/256f8327773ea2d0d6b4649541c34e84 +MbedTLS.v2.28.6+0.armv7l-linux-gnueabihf.tar.gz/sha512/ab4c9e82752386a0fd642a709bc90b712d6aaff78309968f1fdbf1121a790a9c0227ddd8e79373359cea9c75b21e762f600abea42036609571ba999531b50852 +MbedTLS.v2.28.6+0.armv7l-linux-musleabihf.tar.gz/md5/249ada3e9a7ad4eba08270e575ae68ec +MbedTLS.v2.28.6+0.armv7l-linux-musleabihf.tar.gz/sha512/0682e65f4257c3d237ba8cfc643be4430341888ec4cd17c2dc3018350aa7ff176e834a69ebc9d240b383a7aed439b34e45c237310ad66043956700b782323793 +MbedTLS.v2.28.6+0.i686-linux-gnu.tar.gz/md5/d0a176d2843ac780884395c90971bf68 +MbedTLS.v2.28.6+0.i686-linux-gnu.tar.gz/sha512/c2f96f314d0e5d9bffe46dc7d0adceb038db81e8c9d9a3c0fb0a237849d0d568d249e2df6c275d27a74a9122d0a53b38e5d8521807669a9c82bd67befbea169c +MbedTLS.v2.28.6+0.i686-linux-musl.tar.gz/md5/9c7501c6b04df53f8d56cd59dd42ae4c +MbedTLS.v2.28.6+0.i686-linux-musl.tar.gz/sha512/6fd35f9c2e1c5822920bc1d9315dc68b10694ee5507cc512868615c3d35dc389fa67038b9ab79fa86ea7ff6bf5f6f1eed053fafcc519080559057dcaff813ec5 +MbedTLS.v2.28.6+0.i686-w64-mingw32.tar.gz/md5/1eef46b3c89a81973778817a8856673c +MbedTLS.v2.28.6+0.i686-w64-mingw32.tar.gz/sha512/f202595cf971825601d5e12263eef0dd101e9be971d15592a12187f1d170fafaab358f02db89458f495ddc8922f66fbd662123b0d6df527fffa514e9f410784a +MbedTLS.v2.28.6+0.powerpc64le-linux-gnu.tar.gz/md5/fec1779ff02d71d5e94b3f1455453fc0 +MbedTLS.v2.28.6+0.powerpc64le-linux-gnu.tar.gz/sha512/e97ae38c555f6b45e33c023c7e07c982d36501f6c2dc36121bb73f2fb08db3fa3ab7f4ab0d9ecb622d25bfe1816eab3a6190d2034a05a66b7425c36a637623e0 +MbedTLS.v2.28.6+0.x86_64-apple-darwin.tar.gz/md5/6d44a0c126affaedad544460da9415ab +MbedTLS.v2.28.6+0.x86_64-apple-darwin.tar.gz/sha512/bf074429f32f51d954bc0c242fb4455ec6ead0e8337a3e5ab9e5b0df47d8a195947a488169f743db63d70b245be80084cd0d78f2211b6cd4b9524010b2c893cc +MbedTLS.v2.28.6+0.x86_64-linux-gnu.tar.gz/md5/95641af7a92c8c83d82264dd2275692c +MbedTLS.v2.28.6+0.x86_64-linux-gnu.tar.gz/sha512/3606ecd5a566e643cc03959a3eac9a45cb4c644006ee5820b852dfc22d40b85d75f5c018c46776954d92001986ecb49238058ca3d99340f9a689875b690aa6e7 +MbedTLS.v2.28.6+0.x86_64-linux-musl.tar.gz/md5/aee58ac107ca0d9e1eb5d7de8146ec8d +MbedTLS.v2.28.6+0.x86_64-linux-musl.tar.gz/sha512/86219aa5ba3280da39e91beded7455160c1ebc274c3158b9f0703a2c034756a9a9e51e5354d22ce983fcd026157d81f471446e6ee2743cae2663384e3e796176 +MbedTLS.v2.28.6+0.x86_64-unknown-freebsd.tar.gz/md5/67857ac031b10fb6a0620b453477653b +MbedTLS.v2.28.6+0.x86_64-unknown-freebsd.tar.gz/sha512/118f3c662580c88d092610be08b60236939c7fd7feab4cd524c7c1e2e2e1b557bddbd603902b697142695889ea6c0a8087982020cd5e7267c9c7c82b49622460 +MbedTLS.v2.28.6+0.x86_64-w64-mingw32.tar.gz/md5/15ebd14ae435b64b2a0006ee7bc21bd4 +MbedTLS.v2.28.6+0.x86_64-w64-mingw32.tar.gz/sha512/7b327ecd405417a3be6ad4ba746656c9b25b70f09985e3e53b07416ab6f271f630eee638c98be938d5cb827c92b5bf656c02865685306389efba2275a1b2113f +mbedtls-2.28.6.tar.gz/md5/768932cee6c42f7f4751362091ac56d4 +mbedtls-2.28.6.tar.gz/sha512/a5c876489bf89908f34626c879f68e8f962d84b50756df17b6b75dfb93e08fe163ed3f32bf70e89bce9080d15257a4cbd2679b743bf8f2e2d7a04606c5811c05 diff --git a/deps/checksums/mpfr b/deps/checksums/mpfr index 1bb4eca6bf4ad..050e9cbd8d5a8 100644 --- a/deps/checksums/mpfr +++ b/deps/checksums/mpfr @@ -1,34 +1,34 @@ -MPFR.v4.2.0+1.aarch64-apple-darwin.tar.gz/md5/f9393a636497b19c846343b456b2dd7e -MPFR.v4.2.0+1.aarch64-apple-darwin.tar.gz/sha512/a77a0387e84f572ef5558977096e70da8eb7b3674a8198cc6ae35462971f76d684145ffae7c2ddca32e2bd1c8b2ccb33e4447eb8606d5d5cd5958298472b3ea9 -MPFR.v4.2.0+1.aarch64-linux-gnu.tar.gz/md5/ade253017d195de694780c32f9161dcf -MPFR.v4.2.0+1.aarch64-linux-gnu.tar.gz/sha512/1b68de5f8e557b7434c8c1bc016227b58683b56c0977b763422ea85a673bec446fcfee3a4f69e1d4689abb9bb6bf47f2a50fbb56ecac6a9d40096e66bd0f2080 -MPFR.v4.2.0+1.aarch64-linux-musl.tar.gz/md5/7dbd121c7192ccaf7191de5ab8d91afb -MPFR.v4.2.0+1.aarch64-linux-musl.tar.gz/sha512/8614e3cb28491b24a0ec5060b44abaf264b61c91ddd29d70105ff583bd3112cff1b9bd5ed45e39f186265333982d5eeb8bf35fedc3b51b2a009cc7a51046b50b -MPFR.v4.2.0+1.armv6l-linux-gnueabihf.tar.gz/md5/adb2b7fdf111c8b19df1516cfb278bb1 -MPFR.v4.2.0+1.armv6l-linux-gnueabihf.tar.gz/sha512/0c47aeffd05a194802f6c4e0e2779d56fb46007e6c3e145ee6992854a21a317a9d51512c59a0ce4ddcd314c387945225c6557d6c2ab6961ae4848875e8983de8 -MPFR.v4.2.0+1.armv6l-linux-musleabihf.tar.gz/md5/c30358bdeffcff65ba9be906cd35889b -MPFR.v4.2.0+1.armv6l-linux-musleabihf.tar.gz/sha512/2857ec27ae2d53a451d62dd241ce9b43f7ee182bee180ecd9ad92c907c66d0b0ab2d1ea3b20fe61cc176ae44ecbe6041305cc8a9343b396c9cb54dd77a1e2868 -MPFR.v4.2.0+1.armv7l-linux-gnueabihf.tar.gz/md5/a1e30436bade2150c9dc924177f0c321 -MPFR.v4.2.0+1.armv7l-linux-gnueabihf.tar.gz/sha512/d2f4662c494fefda66847e7a085edda3ce396383aafb4e17fc2e176191b0f530541726c261cac3467f13136e8ec728c8a7cf0e352f3e9ebf960d153cbfe766b8 -MPFR.v4.2.0+1.armv7l-linux-musleabihf.tar.gz/md5/857e3c82804e7c853d21603f18caa715 -MPFR.v4.2.0+1.armv7l-linux-musleabihf.tar.gz/sha512/86cf3e940fd66820b5269e9aa2a49c3fc3077857bec037a08e0d301b0bf3cc5c79ac331cc6370d852e20f4acf8f601c49d5dbe24e96652e4411b3f33a11e3f45 -MPFR.v4.2.0+1.i686-linux-gnu.tar.gz/md5/5a432be79a112e67e970980f4bde13a0 -MPFR.v4.2.0+1.i686-linux-gnu.tar.gz/sha512/94198b23ac94dcb9dca95938a46b9899c3ef329bafbb13b32076cd3415b89f11908632c7c07e90549c01bd9ed7fc9a002dae07a645f85b8509234c49be729621 -MPFR.v4.2.0+1.i686-linux-musl.tar.gz/md5/4ce71dc250c2469f844a02c6ee6571a1 -MPFR.v4.2.0+1.i686-linux-musl.tar.gz/sha512/134b67b23de75ab172594cd0fac55b5c265730bfea195978698e3e6fbc47d65617652bd72d90ba092ed1bac4c29d5b2c109df5d8dc60b5d8f91159fd58575b67 -MPFR.v4.2.0+1.i686-w64-mingw32.tar.gz/md5/be7239432e8a26c59e2d418d310bd6e3 -MPFR.v4.2.0+1.i686-w64-mingw32.tar.gz/sha512/3144d84d41996fc19bfc9ed4f36755838470e17dce79895b37d93e32ae1cb1da428f2136948f939b19548d7dd62830ae43c434f88efbe192ed3184bae2df5970 -MPFR.v4.2.0+1.powerpc64le-linux-gnu.tar.gz/md5/d818894054b38232ba02ee0e129f6fe0 -MPFR.v4.2.0+1.powerpc64le-linux-gnu.tar.gz/sha512/0e73ca926f3e06466d1899f0b3e9ae4abe15102804dce6716ce23154344a571773c40d276f0038a0ae4e626799867ee715428e1d961334a01ad3091745367e8e -MPFR.v4.2.0+1.x86_64-apple-darwin.tar.gz/md5/9652148df4e771be39713c4f43d3ff61 -MPFR.v4.2.0+1.x86_64-apple-darwin.tar.gz/sha512/91a0219fd1880dfa90d196fa403f4e1df0347ced58a4772492196b94476f346d80696885a4f3520424494bc09679cca0c0ccf2f6e9247d60b52ebdf564485e72 -MPFR.v4.2.0+1.x86_64-linux-gnu.tar.gz/md5/4de39327a792be708119ac7b43957628 -MPFR.v4.2.0+1.x86_64-linux-gnu.tar.gz/sha512/447b59d5589a8517061627668e8baed4366408cacc9d8e063528b9b795de6d27e4005844578310185f03f568f4948bc4a794624235875fb61b6187264b6f483b -MPFR.v4.2.0+1.x86_64-linux-musl.tar.gz/md5/f9b8c3c094b339341b19828cc5e1d47c -MPFR.v4.2.0+1.x86_64-linux-musl.tar.gz/sha512/c661e7c5bded3bdf11b2bd5e5ef4ad8e446934d9b82dfe26f0be1b83cea98d7e56e0903bfc1075f91c8d23401cc6b3b722f2d60f46d73cab884e81fe518aba27 -MPFR.v4.2.0+1.x86_64-unknown-freebsd.tar.gz/md5/e402dceae753abbdd8b11f3c8d96e0dd -MPFR.v4.2.0+1.x86_64-unknown-freebsd.tar.gz/sha512/235f001f3b0101a6bafaeb45fb49d2992549b6c2f42a4e7ba38e1fa8c59246fe7463598e7cfda5ead50c9805dda0b82a23b5ae2af4ec993bb771611163e58907 -MPFR.v4.2.0+1.x86_64-w64-mingw32.tar.gz/md5/c5bbd2217060491e2773bdd84b055e5c -MPFR.v4.2.0+1.x86_64-w64-mingw32.tar.gz/sha512/74b059b22990ab79f243284687f571f47447457ac2c1cb4c4548ea1f3d8ea01b7466281f48429cb39e2d11394fb86650bfada7acab639c6537a143a95bd6e7ca -mpfr-4.2.0.tar.bz2/md5/f8c66d737283fd35f9fe433fb419b05f -mpfr-4.2.0.tar.bz2/sha512/cb2a9314b94e34a4ea49ce2619802e9420c982e55258a4bc423f802740632646a3d420e7fcf373b19618385b8b2b412abfa127e8f473053863424cac233893c0 +MPFR.v4.2.1+0.aarch64-apple-darwin.tar.gz/md5/816f9ff59070f21f1df2f310e2606c06 +MPFR.v4.2.1+0.aarch64-apple-darwin.tar.gz/sha512/dad9adba7a8867d1ce26d77efb5c33b602b920a2cdbec84ea58a054cfab3ab7df54d2bda101de72b71604e7844993f1e216b002ba092e69277d0764040216c81 +MPFR.v4.2.1+0.aarch64-linux-gnu.tar.gz/md5/c1e3c9619af6454d8adae9bcbd911dba +MPFR.v4.2.1+0.aarch64-linux-gnu.tar.gz/sha512/5d916492aa73d11e022a7ca3f31940ceb8f8667bdf878ba29d6256736a380a2f6a11ac90cd8de3f1d3454a79165db240a1b971b9794fd21692ed64502ec34b9a +MPFR.v4.2.1+0.aarch64-linux-musl.tar.gz/md5/8ada267e2d23eb0c65ab2d2df02362d5 +MPFR.v4.2.1+0.aarch64-linux-musl.tar.gz/sha512/0c7f18e6d0f3e2052541e3279dfa9a74eb34067ac4fea0b17ab805cd73010cc83f8d7cb4eda8f4a904da398268d1c0d638c35521a9f339f8c7c3b5f159f27277 +MPFR.v4.2.1+0.armv6l-linux-gnueabihf.tar.gz/md5/42bdb78eee83f496d7da699ad9603913 +MPFR.v4.2.1+0.armv6l-linux-gnueabihf.tar.gz/sha512/edaa9ece1404a606d6b635406ad5e721c8d094ffa1c73ce19222afc2b4ea7b3b9e23e7c5589ae10fd9f4c4aefa265773bcfce6c510efbca57782115d43daeb13 +MPFR.v4.2.1+0.armv6l-linux-musleabihf.tar.gz/md5/2213207772b8a50de4768816fdc20e2f +MPFR.v4.2.1+0.armv6l-linux-musleabihf.tar.gz/sha512/d24debc38b8135ac5c10c4ea19de0c69126b6881940b4e182118e12cc2c7cf0aca2db065620f0cca636742da32eddec5bda3b4f449a035274f05120c977ed449 +MPFR.v4.2.1+0.armv7l-linux-gnueabihf.tar.gz/md5/a0d9fe20c9ff0027b6816ee0102b1f9a +MPFR.v4.2.1+0.armv7l-linux-gnueabihf.tar.gz/sha512/97ce02898dc0d29a616048fd7ecee3100a710f7a30a21f2276c01675749034a5241be88bd46dff3dbf9ea0adca98a4357bd16e43fa9520e7a02477494c2d072e +MPFR.v4.2.1+0.armv7l-linux-musleabihf.tar.gz/md5/7898b9047c914b290b5928af5df63030 +MPFR.v4.2.1+0.armv7l-linux-musleabihf.tar.gz/sha512/cbefa9588752c65751630832417c1c42e4819d49ff9a505f61c2567ef4271097e585542fa898efd61409a43e439d827bb79f693a0937d0a3a427b39535979588 +MPFR.v4.2.1+0.i686-linux-gnu.tar.gz/md5/15fa598e5c1c723ff6cd2ad3ea51e437 +MPFR.v4.2.1+0.i686-linux-gnu.tar.gz/sha512/2ec4cf0c88363bc9fb39522bbcd6a9c2311c38efb166f604aab118fed39712beea68367ff5c4cabb2b7b3f5a53469414b8552fd22a70a637cbbfc936f0c4851b +MPFR.v4.2.1+0.i686-linux-musl.tar.gz/md5/6dc6a00d3ea22e2c60374d49926598d6 +MPFR.v4.2.1+0.i686-linux-musl.tar.gz/sha512/4a90356091b53d7238dda59f6e9c5c420614f16460dc67310e581611ad46a2dd3324d6164cfecf1bcd660b8f2e473f0afe137aac954c608b11be3acbda648e14 +MPFR.v4.2.1+0.i686-w64-mingw32.tar.gz/md5/bda99a916573607716c61473153a1927 +MPFR.v4.2.1+0.i686-w64-mingw32.tar.gz/sha512/ed3f45ff5ac8f4588584dd80036d9f3623651c87276a9b624955c831009dc33f8804c2845bd187ba750515725c29d65ac5d70c71db1b953c618cd771d2b066d0 +MPFR.v4.2.1+0.powerpc64le-linux-gnu.tar.gz/md5/ac70f716bddd5323b4add663b473b52d +MPFR.v4.2.1+0.powerpc64le-linux-gnu.tar.gz/sha512/ebb0f5ea76c892b7a4e4636706e71f476aaea58bb88e1734a7966c44495fda8c81318e0e8629e208185f0fc8d0c73b6f3463034cd831dfb5fbbd493a0689bc06 +MPFR.v4.2.1+0.x86_64-apple-darwin.tar.gz/md5/ff13e865e3be717b0fffc16296cb2f56 +MPFR.v4.2.1+0.x86_64-apple-darwin.tar.gz/sha512/98479210910945714da0285a40803674242581894a731ba4709c70dc1341849e736a88aa4914df0ff536c15f8848c417e712ff6abeb25047d300f8b215fd131f +MPFR.v4.2.1+0.x86_64-linux-gnu.tar.gz/md5/48194b9f92ad01b168e8b9612f4c9559 +MPFR.v4.2.1+0.x86_64-linux-gnu.tar.gz/sha512/638eb40d23fd492972809cdc3326ad4c2c99d3eae1ca5f7c0da6e0e335bb596de2899da5b3e65153225654b2cd9a805298e7241a21395e07d0b333eb1f101b5d +MPFR.v4.2.1+0.x86_64-linux-musl.tar.gz/md5/0babbb823964ccebf63b42fd07f08936 +MPFR.v4.2.1+0.x86_64-linux-musl.tar.gz/sha512/880b685d9b456fa2bf78e707273783423f9ff00791b529eba00c5e1b94ff96f4ba01e680152a4d6b45b695e3c1169d07f793db42c5a4120861813d5458dfc828 +MPFR.v4.2.1+0.x86_64-unknown-freebsd.tar.gz/md5/f11d634e5a19177fe36b2b2f6f5727ca +MPFR.v4.2.1+0.x86_64-unknown-freebsd.tar.gz/sha512/291245c06edf31b2e39b6774359ebd4f95b924f19d2a7e8581822a5bf908426d00f0452c061a027da0d7d4bb2fa1bb7ef8ab6d8e49bc848d6d7450a8d5c8a9c4 +MPFR.v4.2.1+0.x86_64-w64-mingw32.tar.gz/md5/e6d1347d5da312f7301d578ce9d7c4d9 +MPFR.v4.2.1+0.x86_64-w64-mingw32.tar.gz/sha512/3ea4b944172be250677ef271f1e10c2b95861755f203795a50b8d0f76f72498897059271e44e038625c3b73cccbd0165685d60afa994180d42e912bffbe86729 +mpfr-4.2.1.tar.bz2/md5/7765afa036e4ce7fb0e02bce0fef894b +mpfr-4.2.1.tar.bz2/sha512/c81842532ecc663348deb7400d911ad71933d3b525a2f9e5adcd04265c9c0fdd1f22eca229f482703ac7f222ef209fc9e339dd1fa47d72ae57f7f70b2336a76f diff --git a/deps/checksums/nghttp2 b/deps/checksums/nghttp2 index 66ae3cbf34c0e..f8226d4f68b3d 100644 --- a/deps/checksums/nghttp2 +++ b/deps/checksums/nghttp2 @@ -1,34 +1,34 @@ -nghttp2-1.52.0.tar.bz2/md5/bde5874bd8e7e8be3512a621de27b9d5 -nghttp2-1.52.0.tar.bz2/sha512/019ec7a904d1baf8755ffcea0b38acf45ea9c6829d989a530ab35807338ba78d3328b86eebb3106b8372b7a8c51b466974d423e0cd786b6d6d020f0840c160bf -nghttp2.v1.52.0+1.aarch64-apple-darwin.tar.gz/md5/e3d9e07029e184cc55b7e0c4d2e27c7f -nghttp2.v1.52.0+1.aarch64-apple-darwin.tar.gz/sha512/cd098db984f751b00d2cc99d7f7eba0fa830ba178dd85a9dfa679a591e62d57364dcfd74e6a55ef513a0436a8e520b1a5474d4bfa9a8bdcd70e398482b7c9985 -nghttp2.v1.52.0+1.aarch64-linux-gnu.tar.gz/md5/73fe75f3cfa2bd3e804ea39a4eb884a9 -nghttp2.v1.52.0+1.aarch64-linux-gnu.tar.gz/sha512/71f4b2a23ba148b66432797b0db954dbd98fc900045d4572f488b43779aae125f71929e5bba6bbadd30c7998a133c5e5beb70888968bf3b01bb5fe9c9ea0e451 -nghttp2.v1.52.0+1.aarch64-linux-musl.tar.gz/md5/736a24a7eee567851a965558e31489fb -nghttp2.v1.52.0+1.aarch64-linux-musl.tar.gz/sha512/ab36182b04a590b092fae9e3a912a87467e8b01ad40a628a1d2e52910ee513ab327d5d2836df598d5aa8203f60a605d19d0b9636eb35d12a84a1c9d87124604b -nghttp2.v1.52.0+1.armv6l-linux-gnueabihf.tar.gz/md5/56fd32e8d77d4c9d9e2355565f4db19b -nghttp2.v1.52.0+1.armv6l-linux-gnueabihf.tar.gz/sha512/85718e0e5cee35d91a8684ea33d8f965bb30d62dbd6b74a574a2fbc4c1027b1ef23ef68f1dec3f037fa6c5739287329567df9591a69f8f23b23fab2516a0b644 -nghttp2.v1.52.0+1.armv6l-linux-musleabihf.tar.gz/md5/283273d3bf4d53b56d12ef6af2e72f20 -nghttp2.v1.52.0+1.armv6l-linux-musleabihf.tar.gz/sha512/5c1d92cbf5f2f4e1ceb4ee13634c0bceb6ca28abaf9d87cc673f264d274bb96aa095648295e9aa76f86eb0890a426f47c0b942e72610daf722ed8e86b5f0df69 -nghttp2.v1.52.0+1.armv7l-linux-gnueabihf.tar.gz/md5/d7ae84e5365759a42d0fe0360f679b61 -nghttp2.v1.52.0+1.armv7l-linux-gnueabihf.tar.gz/sha512/63212e3ad94d2bc54ca9ebd452d8de8e67aa53c03a3b3033d36da765303e714d8d5c24156ea4fb985acc72fe52e2977e8e8a658cdd9409bd41ecf401c08c1aee -nghttp2.v1.52.0+1.armv7l-linux-musleabihf.tar.gz/md5/a6ad0f25f43b7f1832faeaaadf683ed4 -nghttp2.v1.52.0+1.armv7l-linux-musleabihf.tar.gz/sha512/64b9075c0d819288345d53c5ce88b360d2ca4d24c3d2e81fb53c55f86054b1a3e95d7831b363a4100965cdbf479268a5993d66ef59089a219a97b4151d8fef60 -nghttp2.v1.52.0+1.i686-linux-gnu.tar.gz/md5/9781f6eeb4d24a291d6737e59e74edc1 -nghttp2.v1.52.0+1.i686-linux-gnu.tar.gz/sha512/2b542cb67e78993ef881694dc50c980b57db3761c5f4e11c381afb1b31d1fb8ab0a8b20e1279303a602c07912f21e8ef9d732366b76ab3f356a74b444a5dc78c -nghttp2.v1.52.0+1.i686-linux-musl.tar.gz/md5/08603b9364179ab4cbe0637b9b1b63b5 -nghttp2.v1.52.0+1.i686-linux-musl.tar.gz/sha512/0a5b79709482548c6a713843b670695b4b13d2b219b592d029719da0b4187fe884798fb44e2c511c300f02bab03f2b0b289d49d6256e3ce0b9602a66ea2382bd -nghttp2.v1.52.0+1.i686-w64-mingw32.tar.gz/md5/1abdf0cad466ed0ca0da137809999d8e -nghttp2.v1.52.0+1.i686-w64-mingw32.tar.gz/sha512/04680895ead989fda56b284d8963e7ca31680492c8f77f4c6bd7ca03b9a66ee7529b78cf35e07b2e106f43c9aa543dffd4081b034339803ba95021293d3df997 -nghttp2.v1.52.0+1.powerpc64le-linux-gnu.tar.gz/md5/ae411e40e24cb3f3b07fe8de211b58c6 -nghttp2.v1.52.0+1.powerpc64le-linux-gnu.tar.gz/sha512/7433502d76646e5761ea2707fa65ea5a412c513c70908a4d9ceb504f08121b1f39bcff984543370c221814785b7064f85dedc777a22df5e30a64a64e510e0978 -nghttp2.v1.52.0+1.x86_64-apple-darwin.tar.gz/md5/59f0de0affaa17898e837b5074de68fc -nghttp2.v1.52.0+1.x86_64-apple-darwin.tar.gz/sha512/e639c813373b17d95220640ec2a568e9731cfc32df826610357ec9ff8e9d7e7abe10291140eaeb9342ae69215798bf3f999db7647c23efb4f815b54f4da9cfe4 -nghttp2.v1.52.0+1.x86_64-linux-gnu.tar.gz/md5/6bc8501392d47b349c7463e984dc5909 -nghttp2.v1.52.0+1.x86_64-linux-gnu.tar.gz/sha512/522cc2a8464ee5770c01b83a6b4ecbbcce322efffbd738f7c907643fe85342e785bbc805028d41c2b7404d6241168d1ab37a9db15018623c265b53905bcf060f -nghttp2.v1.52.0+1.x86_64-linux-musl.tar.gz/md5/725a6adc23880b28303017597b974535 -nghttp2.v1.52.0+1.x86_64-linux-musl.tar.gz/sha512/ede5a34b7f71310e4c3cd99b9b61b2453db5dc8117675de12adb1e68c9283cdf821614f49f4d04bdd3b0f17d51a52972ec1e226d0dbdc5462b1a4a1fcc9f39e7 -nghttp2.v1.52.0+1.x86_64-unknown-freebsd.tar.gz/md5/02e68f367dd5f2ceac3a619da402cbb4 -nghttp2.v1.52.0+1.x86_64-unknown-freebsd.tar.gz/sha512/d0522c4f40471cdfc0768863f9b0e97b453b2e0c850417811d4f264fd167622493141beea66a8668b15dc6b9b4ae42a38017b9f31ed59c9205701188df3d84b9 -nghttp2.v1.52.0+1.x86_64-w64-mingw32.tar.gz/md5/e1c8ec6ec2d69b2ac64b114ebf09f8b4 -nghttp2.v1.52.0+1.x86_64-w64-mingw32.tar.gz/sha512/cb43cb138f14717501e852ed388a44d41012e2bb70b6887584b37b4e0f42827d74f17ea85ba4aa0bc09d623dedeef73eee80815c1db2b6858b31251feb0b5580 +nghttp2-1.60.0.tar.bz2/md5/ec20d9a6df7cc006894f72f81f9f2b42 +nghttp2-1.60.0.tar.bz2/sha512/95b76dd492dad490640469c4b806dd1a446f11143bc990220ff106fe4bfb76cdc4dfb112e0297c543b2d828f2870aa09ba820d88e3e9dedb29c8f3d3c9741af8 +nghttp2.v1.60.0+0.aarch64-apple-darwin.tar.gz/md5/dbf9f8161a124dc88ba44b54094b96e4 +nghttp2.v1.60.0+0.aarch64-apple-darwin.tar.gz/sha512/1997f473ea802afb09d7e13feb4eec9c11ad1d161cf83659ef6059a7c81639e00f8a3461c3538c81ea025e359b0927c3a362cef4a57e6544ad27588683142203 +nghttp2.v1.60.0+0.aarch64-linux-gnu.tar.gz/md5/d2e821a693d7d0720f0158b3e19ef7fa +nghttp2.v1.60.0+0.aarch64-linux-gnu.tar.gz/sha512/4165a1282d125b461d670d7d953c8a06b6508d1b97383a4126bc2fa9641454a9e0be749dbbaf772f2c2e6ea8cc3e64eb980cb0e09ac3d2fe5533eb3e6f7fa9e8 +nghttp2.v1.60.0+0.aarch64-linux-musl.tar.gz/md5/61ecc91336fcddb0f58af6af167e9a81 +nghttp2.v1.60.0+0.aarch64-linux-musl.tar.gz/sha512/802c7455e8f1ddfea74d3de3ceb937d1d10312f51594257cd406aedd67c181ada6ee5115bca00f8ee340a1471e2903bbe0159a0c08b80c556188647345e2c85b +nghttp2.v1.60.0+0.armv6l-linux-gnueabihf.tar.gz/md5/2998ae8d24d1bd540a29e0c6054bfcc8 +nghttp2.v1.60.0+0.armv6l-linux-gnueabihf.tar.gz/sha512/5b2235a0c8bded57adcbab11dbe97b85a7d6d8a083c155bd74b0ac5546aa861730e88b615f1cbfa1071fcc2eb252aae8508e926ad3d5a1ddf0374536c260217e +nghttp2.v1.60.0+0.armv6l-linux-musleabihf.tar.gz/md5/7ebec92e3b340e25b952ccc4e714aa2e +nghttp2.v1.60.0+0.armv6l-linux-musleabihf.tar.gz/sha512/eb0e5c584527182816203ce9bfc35688a969803104ffd17dd4ac3720c27a4fcde3b3b471bf66fda8ac83ec8a56aa82d6d40f492ce06cbf6af39fafc60f35574d +nghttp2.v1.60.0+0.armv7l-linux-gnueabihf.tar.gz/md5/8c124c0daf59c622aedc7b9f1423d522 +nghttp2.v1.60.0+0.armv7l-linux-gnueabihf.tar.gz/sha512/6e03246d1bfef7f184da68ac0eacc975dcb99172f2f352ce4ea5f5ae77536812163874d7ffc4fcb2df65dc51079880fdb83afc8988b73edb241cb641dc72f1fb +nghttp2.v1.60.0+0.armv7l-linux-musleabihf.tar.gz/md5/79968e1cb68c3b0518db528933251b0e +nghttp2.v1.60.0+0.armv7l-linux-musleabihf.tar.gz/sha512/f40790aa9a86fa2f44072c36a33416a7a8b4778881233989f8ed64ccb84f59ccdf3632b7a9d48d3e434e26cbd48c020e5d208da8fcb96e4e4ad41757e050213a +nghttp2.v1.60.0+0.i686-linux-gnu.tar.gz/md5/1580bf21084fa62ec26020f5c89430a1 +nghttp2.v1.60.0+0.i686-linux-gnu.tar.gz/sha512/cf83afe1bb796c57e220c0ba32a6990830df50cd91f82c781f2722d7b0ca5e5fbd8b708a0204be65bb8838c85b548f867c97e85941d124b81c67e01738f1db1a +nghttp2.v1.60.0+0.i686-linux-musl.tar.gz/md5/605eb6cd67b6fe3a1ba2d95413635831 +nghttp2.v1.60.0+0.i686-linux-musl.tar.gz/sha512/2c626b76898b4d782038661601fe34580c3cd560a519a46c4f6bc62d94ab987c7f2984350fc65933c00850cd2fe0b942fc64fcb23d2fb7db29bfed5866291b1a +nghttp2.v1.60.0+0.i686-w64-mingw32.tar.gz/md5/5b5694f36addbc503bc1e78e57159e5a +nghttp2.v1.60.0+0.i686-w64-mingw32.tar.gz/sha512/e70069b1dde2cf4dd041c4cc1c1ff40f67c20a8954b88d997fd7bf03d925b08148bc55293380dffce8c3b550a0e5768c94066e1a3b881ce4109ee94076c9a8b8 +nghttp2.v1.60.0+0.powerpc64le-linux-gnu.tar.gz/md5/1d073bba8e90c970bf1325a3d150d129 +nghttp2.v1.60.0+0.powerpc64le-linux-gnu.tar.gz/sha512/7e6f3895316d47a701944e8ee192b56f66aa05bf212c41164d25a0507f0e54c4c58c856e1c464fe3ec3eae78e0fe09ba8cf8b595c246faa3300a797750677180 +nghttp2.v1.60.0+0.x86_64-apple-darwin.tar.gz/md5/27d405bf53d4d438f74f91176d638741 +nghttp2.v1.60.0+0.x86_64-apple-darwin.tar.gz/sha512/59c4b4cca09e9a99e2e9ccc765068870824b907577c385313568ea29cd395caa3352bda230238888412b625e4a428a24c9ae0e59d122730cbd8025c6edbf0196 +nghttp2.v1.60.0+0.x86_64-linux-gnu.tar.gz/md5/ed1fe996e4c3e51d9ea8f724883dd3bc +nghttp2.v1.60.0+0.x86_64-linux-gnu.tar.gz/sha512/0b20db04ef7b2cc470b9abaab05d0e1e7ea3d674c1ed47c63e1cda00b98a6f10ce19ceb77ebd5ece28f6e4a2cf46227f5858f767ff0f04feed867c57941793ee +nghttp2.v1.60.0+0.x86_64-linux-musl.tar.gz/md5/cf3fcdb5720633700e4f9a9d8cd0cfc0 +nghttp2.v1.60.0+0.x86_64-linux-musl.tar.gz/sha512/d8f87b354de0f47be21b8e3aab6c2b05ee2af377e4bcc7df692fc4dd361ee5b731a190a0d9b4d4fdedf9c3a6a8a300f43338b38ac096da39ec13d4b79b544144 +nghttp2.v1.60.0+0.x86_64-unknown-freebsd.tar.gz/md5/b83aba7b3bd97ed7de770a597d6ec374 +nghttp2.v1.60.0+0.x86_64-unknown-freebsd.tar.gz/sha512/e40e47835bb0d5d548fbcfb28a64124323422bcdab411bcee7d4288cea765c6c82d7f4586980ee28b6ff310c6c7313aa4185ede192cd94839fbe708ab1ed14a7 +nghttp2.v1.60.0+0.x86_64-w64-mingw32.tar.gz/md5/37ba862c196b4d7c063cddc87722f7ff +nghttp2.v1.60.0+0.x86_64-w64-mingw32.tar.gz/sha512/ce0b70b4ad5cb30b83e672c3875fac7bcc8fc039506f05fef552a9d9cb53f053187dd02da4550dd7e5ef9aaaf8d587cee331eace0335f663d4190bacbc4ff9a2 diff --git a/deps/checksums/openblas b/deps/checksums/openblas index 9e11a478e7427..34eca34c4ec2c 100644 --- a/deps/checksums/openblas +++ b/deps/checksums/openblas @@ -1,94 +1,94 @@ -OpenBLAS.v0.3.24+0.aarch64-apple-darwin-libgfortran5.tar.gz/md5/9b52215f0729a2d96bc86a820c280433 -OpenBLAS.v0.3.24+0.aarch64-apple-darwin-libgfortran5.tar.gz/sha512/b772022a498075f1712e707ed394583af49a25db60bce9d59981827f3d82968e5ddbbd05394c791ce89595b4c2442330870c3a36739e5511a99f166c2805b3ae -OpenBLAS.v0.3.24+0.aarch64-linux-gnu-libgfortran3.tar.gz/md5/d89ee1bb7ebefe186c02354209d60e94 -OpenBLAS.v0.3.24+0.aarch64-linux-gnu-libgfortran3.tar.gz/sha512/ed8178b62bacf8aefe094adb822cfda4cb696161ca712b7b70c635b1f7868d84c2912880010465cb6ba00632e25b3bdcdb9f1bab9307ccb7bf1d7eb7a2a760c7 -OpenBLAS.v0.3.24+0.aarch64-linux-gnu-libgfortran4.tar.gz/md5/271a15b5d77c1501b13934702e0bea3d -OpenBLAS.v0.3.24+0.aarch64-linux-gnu-libgfortran4.tar.gz/sha512/f2dfb1478471870526c3b8a2b8173eb4705b2dcb7e2c8b4fd4acebe24f4bdc999262e998f832a6769a5def4577256b07f4aa76c058abf7e93ea5e814a74d014d -OpenBLAS.v0.3.24+0.aarch64-linux-gnu-libgfortran5.tar.gz/md5/7c47ee25fade4bf4d4fcbd9b8e5feea7 -OpenBLAS.v0.3.24+0.aarch64-linux-gnu-libgfortran5.tar.gz/sha512/b92e08f527c30d74470e1fa8275dfa6f8ce6bdae028394fc1440a55e4b1ddd15d8ab14bf36b87cf400ee22a69c13ce9b78c22889dccb5ffc4945c5293deea445 -OpenBLAS.v0.3.24+0.aarch64-linux-musl-libgfortran3.tar.gz/md5/2c00dd0f2d0e8e3938e02e21ad4fcbed -OpenBLAS.v0.3.24+0.aarch64-linux-musl-libgfortran3.tar.gz/sha512/e61635827d9495bbbe8726a3d7803c786b9304dbb32ba3d99ac5a359ae827b6e48e0db7ffa3fc2279d65e2cda3ca96abccdbcdd86b54e962cc8b7f86937e9594 -OpenBLAS.v0.3.24+0.aarch64-linux-musl-libgfortran4.tar.gz/md5/35297e1b32247edeb41907fca6f18a0a -OpenBLAS.v0.3.24+0.aarch64-linux-musl-libgfortran4.tar.gz/sha512/b8277b5f07b29940f5893f44af4fec3278c8cb7e52db7fb7f416ffe644d47f781d98f8d9fef89dea623762473e78abe61977eff1f4c3f1c9ff44dd833430f897 -OpenBLAS.v0.3.24+0.aarch64-linux-musl-libgfortran5.tar.gz/md5/ce187d3e01cfa633421567c2c5d18509 -OpenBLAS.v0.3.24+0.aarch64-linux-musl-libgfortran5.tar.gz/sha512/408106528aa80ea68808d99544ff4e6485d5912bf7e2586936c0e7758d94a53705388077140b8335714cd75772ef9d5465fa629fdac6939268ba11617076c42d -OpenBLAS.v0.3.24+0.armv6l-linux-gnueabihf-libgfortran3.tar.gz/md5/52a7f29bfe47e89f32b5c95f6ce65c29 -OpenBLAS.v0.3.24+0.armv6l-linux-gnueabihf-libgfortran3.tar.gz/sha512/4816b3e022613b6a7a2073dd292311b29d8e848887f4d0ed7bcf32140bce2e9f15e05d754c48c1575cbaf70fcae8cfec501725df15d6bb1c7ac1ac1c74b59489 -OpenBLAS.v0.3.24+0.armv6l-linux-gnueabihf-libgfortran4.tar.gz/md5/6f4aa76cc544ce52da6aa094ee5a1aeb -OpenBLAS.v0.3.24+0.armv6l-linux-gnueabihf-libgfortran4.tar.gz/sha512/c3428c36e19e9bd89dd0eb809f2617b7f90cbfaadffe9c58bf16c319e5e36d07102f48bafeb7a787bb6a18655ba3688a29ccd4f3724bc9354915d7e55fb64d44 -OpenBLAS.v0.3.24+0.armv6l-linux-gnueabihf-libgfortran5.tar.gz/md5/298a83c0550694b3b6c22f6dd460cc9d -OpenBLAS.v0.3.24+0.armv6l-linux-gnueabihf-libgfortran5.tar.gz/sha512/b8e5ac5304364c950a427384db13ed5c573e7b5e9d16bb7c354b7b5e46ca8e7471bc911a2cf9defbfc933fbfbff315949011b5596532b8863c551de6d543f434 -OpenBLAS.v0.3.24+0.armv6l-linux-musleabihf-libgfortran3.tar.gz/md5/2b584ae3aa496ff871cc735242e63cf4 -OpenBLAS.v0.3.24+0.armv6l-linux-musleabihf-libgfortran3.tar.gz/sha512/ba0911f7f6f9f9a5823fa0e98778e4a7d2a9bbd5d674d552fefd20458743b60baf10a49e1ee65a3b934636229296d64b1eece55826f136824f9cae91b6a208c4 -OpenBLAS.v0.3.24+0.armv6l-linux-musleabihf-libgfortran4.tar.gz/md5/2ff249795f3d8796b99536a4bf11670c -OpenBLAS.v0.3.24+0.armv6l-linux-musleabihf-libgfortran4.tar.gz/sha512/517c4a99933e439de7ede07376b6f40785406216489f1bd6f995e5d6e444c860df401807abba6dc04ff83dd701f32047fcd4d3fd01660601f27edbc545ea783e -OpenBLAS.v0.3.24+0.armv6l-linux-musleabihf-libgfortran5.tar.gz/md5/f3c3fab4d30655b8cd831511a307d4d3 -OpenBLAS.v0.3.24+0.armv6l-linux-musleabihf-libgfortran5.tar.gz/sha512/06d47db95ec374b5238857c1ad595cee882441fd462ae6a030f1a1c4465b617228feb34fd80a97d8ca965bfc87f8d4a3a94a3654b2c0615ef4506fa827e8c155 -OpenBLAS.v0.3.24+0.armv7l-linux-gnueabihf-libgfortran3.tar.gz/md5/52a7f29bfe47e89f32b5c95f6ce65c29 -OpenBLAS.v0.3.24+0.armv7l-linux-gnueabihf-libgfortran3.tar.gz/sha512/4816b3e022613b6a7a2073dd292311b29d8e848887f4d0ed7bcf32140bce2e9f15e05d754c48c1575cbaf70fcae8cfec501725df15d6bb1c7ac1ac1c74b59489 -OpenBLAS.v0.3.24+0.armv7l-linux-gnueabihf-libgfortran4.tar.gz/md5/6f4aa76cc544ce52da6aa094ee5a1aeb -OpenBLAS.v0.3.24+0.armv7l-linux-gnueabihf-libgfortran4.tar.gz/sha512/c3428c36e19e9bd89dd0eb809f2617b7f90cbfaadffe9c58bf16c319e5e36d07102f48bafeb7a787bb6a18655ba3688a29ccd4f3724bc9354915d7e55fb64d44 -OpenBLAS.v0.3.24+0.armv7l-linux-gnueabihf-libgfortran5.tar.gz/md5/298a83c0550694b3b6c22f6dd460cc9d -OpenBLAS.v0.3.24+0.armv7l-linux-gnueabihf-libgfortran5.tar.gz/sha512/b8e5ac5304364c950a427384db13ed5c573e7b5e9d16bb7c354b7b5e46ca8e7471bc911a2cf9defbfc933fbfbff315949011b5596532b8863c551de6d543f434 -OpenBLAS.v0.3.24+0.armv7l-linux-musleabihf-libgfortran3.tar.gz/md5/2b584ae3aa496ff871cc735242e63cf4 -OpenBLAS.v0.3.24+0.armv7l-linux-musleabihf-libgfortran3.tar.gz/sha512/ba0911f7f6f9f9a5823fa0e98778e4a7d2a9bbd5d674d552fefd20458743b60baf10a49e1ee65a3b934636229296d64b1eece55826f136824f9cae91b6a208c4 -OpenBLAS.v0.3.24+0.armv7l-linux-musleabihf-libgfortran4.tar.gz/md5/2ff249795f3d8796b99536a4bf11670c -OpenBLAS.v0.3.24+0.armv7l-linux-musleabihf-libgfortran4.tar.gz/sha512/517c4a99933e439de7ede07376b6f40785406216489f1bd6f995e5d6e444c860df401807abba6dc04ff83dd701f32047fcd4d3fd01660601f27edbc545ea783e -OpenBLAS.v0.3.24+0.armv7l-linux-musleabihf-libgfortran5.tar.gz/md5/f3c3fab4d30655b8cd831511a307d4d3 -OpenBLAS.v0.3.24+0.armv7l-linux-musleabihf-libgfortran5.tar.gz/sha512/06d47db95ec374b5238857c1ad595cee882441fd462ae6a030f1a1c4465b617228feb34fd80a97d8ca965bfc87f8d4a3a94a3654b2c0615ef4506fa827e8c155 -OpenBLAS.v0.3.24+0.i686-linux-gnu-libgfortran3.tar.gz/md5/316162aaa72c7b0938ea3f1e77b904a9 -OpenBLAS.v0.3.24+0.i686-linux-gnu-libgfortran3.tar.gz/sha512/b6ed223ab2ac32938d4283d7fd45a88565ef6d5b361ff28adfd7def4da8bc51895af26d3dff1104e472ae0681dac3af7ba4e75760e1c57e25b2a51599d8152c2 -OpenBLAS.v0.3.24+0.i686-linux-gnu-libgfortran4.tar.gz/md5/6dc4ceeb488810cd8e6e90b05743764f -OpenBLAS.v0.3.24+0.i686-linux-gnu-libgfortran4.tar.gz/sha512/c0a13c43667218fe1ffb4e19fc74ca5b25dbccb74fb57e193fc9343e8659731664e60c1c7a50c243a0825ba8747c97729f2a68bb36421ec6ba646c4f0c507219 -OpenBLAS.v0.3.24+0.i686-linux-gnu-libgfortran5.tar.gz/md5/124d38befd30a1fba95048612eb20911 -OpenBLAS.v0.3.24+0.i686-linux-gnu-libgfortran5.tar.gz/sha512/009d3217b990ec40729db4313526a4dcd977760fde4878c5fd420cce9649f367f348cc1d9f480057ff87b59545421935c7494f9fa7646206f8d114fede13051c -OpenBLAS.v0.3.24+0.i686-linux-musl-libgfortran3.tar.gz/md5/908d7ebace63754a7842e7d1dd338a43 -OpenBLAS.v0.3.24+0.i686-linux-musl-libgfortran3.tar.gz/sha512/8ab5364d7b3a445390b07dad5046d6e428fddf0eeb0b3b36f630b8a3e428fb8544d1a8fb493ba429cd3683dbe70e0a476333d322fe233cec356895b7a94c5670 -OpenBLAS.v0.3.24+0.i686-linux-musl-libgfortran4.tar.gz/md5/cf70732029bae0fc4f87713d75faad9c -OpenBLAS.v0.3.24+0.i686-linux-musl-libgfortran4.tar.gz/sha512/fea55b3aa2d6a8282345d710957401ad9d6350c57bf39d695173f3996c886a05d7f2672f3767397f6e0878c405117bd650923b7de5313fcd855b4846325b22e8 -OpenBLAS.v0.3.24+0.i686-linux-musl-libgfortran5.tar.gz/md5/cb4f6f8c7175c9d2159c213c37f71d86 -OpenBLAS.v0.3.24+0.i686-linux-musl-libgfortran5.tar.gz/sha512/0e92168e196d271105d8d267ed2329ab8b213375ca67539dd91a293576842ed13755f9e21536a48332d0c55e9323349f7c96817e5e80ced06986c8ad1e47a8fc -OpenBLAS.v0.3.24+0.i686-w64-mingw32-libgfortran3.tar.gz/md5/54b061bb2191fdbb39cb8103615d70bd -OpenBLAS.v0.3.24+0.i686-w64-mingw32-libgfortran3.tar.gz/sha512/b45fa1d1f5a8556f0a56f81e602457d98528d2b7b6dccbbad93cedcc3cecbe1a31d3f9543161ae801d0bdbb1413a9a70589ba6f221dc5d81fd3fc17e6e536f7f -OpenBLAS.v0.3.24+0.i686-w64-mingw32-libgfortran4.tar.gz/md5/8590055b4fc585317a2f36fbce1c2a50 -OpenBLAS.v0.3.24+0.i686-w64-mingw32-libgfortran4.tar.gz/sha512/47bc23c59e314606d45e252dedd7ec228c36bf25cf1cc19d64ca2b862407eede5410a6e7594881d8dc263aab0d140bfdde3082d57fa41ea038c286467d8354a3 -OpenBLAS.v0.3.24+0.i686-w64-mingw32-libgfortran5.tar.gz/md5/32120e6d84ff84bd859e080e4f124986 -OpenBLAS.v0.3.24+0.i686-w64-mingw32-libgfortran5.tar.gz/sha512/7ddada5781415099d6b1f198d27277d91a36f6850d957446ccfa7b8b2b38594b18de3ce5b1c7861c97e66897a7df1500531ac2c3ad12c597c7548def1db05fef -OpenBLAS.v0.3.24+0.powerpc64le-linux-gnu-libgfortran3.tar.gz/md5/d7dbb78769e588175612a939ce69c32b -OpenBLAS.v0.3.24+0.powerpc64le-linux-gnu-libgfortran3.tar.gz/sha512/a1894b94dcf26d5da9da8ad119b3ecde8e6bf6d3a9d97286c1e405a059ec999a15e69ac6f497b170b485d64d6b324b4f38eb59984ec6b20d5aa6b881be819c4d -OpenBLAS.v0.3.24+0.powerpc64le-linux-gnu-libgfortran4.tar.gz/md5/acda753318cb352a2581c3ebc49d6d42 -OpenBLAS.v0.3.24+0.powerpc64le-linux-gnu-libgfortran4.tar.gz/sha512/82bda1158b223fa899b7c9350b2abf0a7b693de75f928e48698ce9555998fc98d357b1b63ac8ca18d935636812141f0ceb58f51c2a1f48faec60cc8c0ca85126 -OpenBLAS.v0.3.24+0.powerpc64le-linux-gnu-libgfortran5.tar.gz/md5/abbd21e61746ec41b03c1efb5dfb884c -OpenBLAS.v0.3.24+0.powerpc64le-linux-gnu-libgfortran5.tar.gz/sha512/7b284db00a9ab39b222caa9d4d6f3bcff3cc86560e275b7bac68c8c06c0cbe1c39226f381fba9fa90f52b8273e0c5436897c9ae0507482eb62bdeacc39ef3d35 -OpenBLAS.v0.3.24+0.x86_64-apple-darwin-libgfortran3.tar.gz/md5/36258636e985f73ab926fd4ee0e4cccb -OpenBLAS.v0.3.24+0.x86_64-apple-darwin-libgfortran3.tar.gz/sha512/a53aa38afdb9ca13e14b3420f63c728ad83451a4c7bcda53a9944ff5488c6455367db6f8253983fbe174fd408513f2a48e7fec5d184900113165f2e82a812e7e -OpenBLAS.v0.3.24+0.x86_64-apple-darwin-libgfortran4.tar.gz/md5/780f66fc5707839a98c7bdad0432975b -OpenBLAS.v0.3.24+0.x86_64-apple-darwin-libgfortran4.tar.gz/sha512/52957cdab20b7042e844468eb6fab822937636346807469decfdb6cfb43cd6d8cc479e3e58f0259d593f120ad5ad330006a470c390bfdafbf4865ee04d3fc401 -OpenBLAS.v0.3.24+0.x86_64-apple-darwin-libgfortran5.tar.gz/md5/83e26fedfa1f84a0a13c92e10b9c12f7 -OpenBLAS.v0.3.24+0.x86_64-apple-darwin-libgfortran5.tar.gz/sha512/7a5eddc7486d46f2ef7b285d8d25ed17e0cf6a00c9c74784044b086b0063f0494bbdd28197a6f933f32345167e337fe62400f5d6b76d69f578aeb3a37ab56069 -OpenBLAS.v0.3.24+0.x86_64-linux-gnu-libgfortran3.tar.gz/md5/d4b83cbbd5a06eb84edd40e10cea9c78 -OpenBLAS.v0.3.24+0.x86_64-linux-gnu-libgfortran3.tar.gz/sha512/28b86fbaad0adae2efe39f086f573e19b1f4a1db16f48144cb1ed4212013b82a5a22dabb75c7bb8c2b5f465ea0bfbf413af7e2fac75884baef1a2cfd7dfb0ceb -OpenBLAS.v0.3.24+0.x86_64-linux-gnu-libgfortran4.tar.gz/md5/2be815ebba026781801af79447f85383 -OpenBLAS.v0.3.24+0.x86_64-linux-gnu-libgfortran4.tar.gz/sha512/2d86efa35a0b29e1b96d4063031e64c823440aca01739d2020856e825da22cf9044561f11ebf447674ced34c697c8b43207fe324f64c42c1c62f2edf869698e3 -OpenBLAS.v0.3.24+0.x86_64-linux-gnu-libgfortran5.tar.gz/md5/235767c8f001d56bb8c9e2b4694e94fe -OpenBLAS.v0.3.24+0.x86_64-linux-gnu-libgfortran5.tar.gz/sha512/f326fed57de6017cf1de0e1d2806af9bc0b2897377611472f051634dd862fb3d964131e02f04d7b741a64c8e72241399716510e70132f802d046f54b1d170a35 -OpenBLAS.v0.3.24+0.x86_64-linux-musl-libgfortran3.tar.gz/md5/b202b8750e1f69099875a83be96683a1 -OpenBLAS.v0.3.24+0.x86_64-linux-musl-libgfortran3.tar.gz/sha512/c83a2c0a977b3afbc6fbe408b07888750a89e8dd66e65e1563d609296214b5114cf0b97009e4dea65a2579377d14ff1ef281c20dc327154d8635c23d50261263 -OpenBLAS.v0.3.24+0.x86_64-linux-musl-libgfortran4.tar.gz/md5/63cf99543c8c7d741808775d0e2b133c -OpenBLAS.v0.3.24+0.x86_64-linux-musl-libgfortran4.tar.gz/sha512/5a850adffb7cc1026b81c1780e3ff97875b29e5f794a33e9d1b3feb41d45eb25c9867c4c9e1699d0d78884ed93b32fb90d9ae8b48000939af7ecbe0d53793ac5 -OpenBLAS.v0.3.24+0.x86_64-linux-musl-libgfortran5.tar.gz/md5/2c34974e3d5ae02dcdbb8c091dd39e2f -OpenBLAS.v0.3.24+0.x86_64-linux-musl-libgfortran5.tar.gz/sha512/ec27f0891b9d56fe0ab86811c7a3043a6bee6518952e6ec58d00313cb6de858c8eaa7918692876e3bc8a0e3aac5f5ec563f612af9eacca47ebbdd4bea1153a0e -OpenBLAS.v0.3.24+0.x86_64-unknown-freebsd-libgfortran3.tar.gz/md5/85cbc32584528f6e5f1573eee0815f41 -OpenBLAS.v0.3.24+0.x86_64-unknown-freebsd-libgfortran3.tar.gz/sha512/e7d48088e65b74a0e5c4a0714c97ce379ba4edc1f0f9cdf733a41da1a51fcdb2231589ac92d71a28605526c1408fb3eec7bb1f1d914e67cacd37425ce22c450e -OpenBLAS.v0.3.24+0.x86_64-unknown-freebsd-libgfortran4.tar.gz/md5/ad6c269fa00c998ea49311656afbf8cd -OpenBLAS.v0.3.24+0.x86_64-unknown-freebsd-libgfortran4.tar.gz/sha512/604067b4b827395cdb7802f88a850e023acfee7e642669567eda0d1ed9612bdf968af4f5e43a25e6b3a213fd118bf3a49cd7a02dc2ef019ee853e31bf4316664 -OpenBLAS.v0.3.24+0.x86_64-unknown-freebsd-libgfortran5.tar.gz/md5/e5f233a4f87cec0c18b7a47073d9ea4d -OpenBLAS.v0.3.24+0.x86_64-unknown-freebsd-libgfortran5.tar.gz/sha512/04fda9400f11948ec4f4c088096dd2971b4fbe2c513c755515bee757903f50c3246a5076f1fa095aae26092b0a76847ee1db4c972e235cd154af188600c387d8 -OpenBLAS.v0.3.24+0.x86_64-w64-mingw32-libgfortran3.tar.gz/md5/0134d022448a5cc11f0b92b37600f7ce -OpenBLAS.v0.3.24+0.x86_64-w64-mingw32-libgfortran3.tar.gz/sha512/8da81604e96bb917d6300242709056ddd9c07232a0e7a1862cb1972e8ec631af093e3ddad8538e352295de78c19387dcb9ad7eb163c40fe715a8129855fe1dfb -OpenBLAS.v0.3.24+0.x86_64-w64-mingw32-libgfortran4.tar.gz/md5/7f79751f5fb9713152410e665061872d -OpenBLAS.v0.3.24+0.x86_64-w64-mingw32-libgfortran4.tar.gz/sha512/50d836e6aac10e3bd181b87946917d77aacfc74bba3d973c12e94dcdac946bcc8d58fb079838784812c27c581c46fb5485bc6e0a192287fc11df46fb7674ec17 -OpenBLAS.v0.3.24+0.x86_64-w64-mingw32-libgfortran5.tar.gz/md5/8b13dbb97a45754c418994d522eee197 -OpenBLAS.v0.3.24+0.x86_64-w64-mingw32-libgfortran5.tar.gz/sha512/1883a4b88cede205def3c29d82773379035edc3b04b8738197137a00bcf95726ca689866f5fc1a25914ea09e3fa2775435f8b4fff95738f7fda3857bcd44653d -openblas-9f815cf1bf16b4e64d4aee681b33558fc090b62a.tar.gz/md5/d890367a6452aa209f396359fb770bc7 -openblas-9f815cf1bf16b4e64d4aee681b33558fc090b62a.tar.gz/sha512/0627960fc2582e08fd039c77aab1f4105f18edb1beba52176a791813bb3f1ebb04f52d174814e156826c45c6adf5f3c1d2242e81fdb520a7fff2170cb1277228 +OpenBLAS.v0.3.26+2.aarch64-apple-darwin-libgfortran5.tar.gz/md5/62b6e0f8591668113a4985d88f386a16 +OpenBLAS.v0.3.26+2.aarch64-apple-darwin-libgfortran5.tar.gz/sha512/fa5a15d853701818604c48bd152b599438eeeb1da67db552beb21b284027c313a9c7d8779b7236831a5f2631912baf881b0d2165aa77cb7ffec975a5c113a631 +OpenBLAS.v0.3.26+2.aarch64-linux-gnu-libgfortran3.tar.gz/md5/bc83bc702eb292c4491229cd6142fe06 +OpenBLAS.v0.3.26+2.aarch64-linux-gnu-libgfortran3.tar.gz/sha512/d4e872be87cc450e1547e51ba15540f5f0082a874788c6ba414cab839147e69f7f37b2c4080a79bdcd411efec829e3510b83df8a9c319a3b132a117614d205cc +OpenBLAS.v0.3.26+2.aarch64-linux-gnu-libgfortran4.tar.gz/md5/436ae51ef0b2f7a053ed8fb19fd55fd1 +OpenBLAS.v0.3.26+2.aarch64-linux-gnu-libgfortran4.tar.gz/sha512/686c9c04a11d2f8a4879876ae34c410ad9e5cb0fddecd44aea9d642e662af9766a07bf4846405685835968253f2cda004a7d7ca1a0a04b2799984489294ed4c5 +OpenBLAS.v0.3.26+2.aarch64-linux-gnu-libgfortran5.tar.gz/md5/fa57f3598b2c24ec3111c60f84bb6ed9 +OpenBLAS.v0.3.26+2.aarch64-linux-gnu-libgfortran5.tar.gz/sha512/d6111ab03a8940892e8023af548de52da7b8c3c76ad2cfcf98a78cadf11b95ef4c39830dedc5a3c6a20e8d95c64fb24a4ed655971ea83ba11f69ff7a3ba34292 +OpenBLAS.v0.3.26+2.aarch64-linux-musl-libgfortran3.tar.gz/md5/184444acaf2f975b4bf5168a32577863 +OpenBLAS.v0.3.26+2.aarch64-linux-musl-libgfortran3.tar.gz/sha512/419da1e017208596e89482ac42005ed2adebfc917419cf900fad3b45ec40f7da46acffc3e6461f46862e3c4642588cd629f6dde151fd02edd6642c8d98d77ea6 +OpenBLAS.v0.3.26+2.aarch64-linux-musl-libgfortran4.tar.gz/md5/2aef4330a9995541bae287a3c2583ea6 +OpenBLAS.v0.3.26+2.aarch64-linux-musl-libgfortran4.tar.gz/sha512/caf7ae4ec0c4fddfe522409d1a5c3be5a376620baa9ddce63f62e4856cea4b1ab2818ef54f3fb5ffbcc4ae73247459d0de2ac7b93eba4bcc1ca744f4fe69695e +OpenBLAS.v0.3.26+2.aarch64-linux-musl-libgfortran5.tar.gz/md5/308471abda2c5eba60976d227f6d1c1e +OpenBLAS.v0.3.26+2.aarch64-linux-musl-libgfortran5.tar.gz/sha512/225242f309d343994176664f590471ec2e56254ebec877d97291c1334517993510422954774749d23da4c52f32a41e06aaba072bf2e3cbb3b2a8dac6c01dba8c +OpenBLAS.v0.3.26+2.armv6l-linux-gnueabihf-libgfortran3.tar.gz/md5/69618373222d15ede34de1f913a93dac +OpenBLAS.v0.3.26+2.armv6l-linux-gnueabihf-libgfortran3.tar.gz/sha512/585fa3233d6c88c7ee5571da4dce7b83c53643937a0077d816c24c556b471926c808bcbd46c9ddddafbe24567f91039525b3a260505854e62e9b9b09acaede54 +OpenBLAS.v0.3.26+2.armv6l-linux-gnueabihf-libgfortran4.tar.gz/md5/874b639fcce8fb077d1a9cdecc916a6c +OpenBLAS.v0.3.26+2.armv6l-linux-gnueabihf-libgfortran4.tar.gz/sha512/0a065ccd197822b028da36e157aed19fbe440d7eedc94fe1955cd8980423d477bdb73db7e0f99a6ea683701a2c8fc649e3967a781694a7219d82915d1c22767c +OpenBLAS.v0.3.26+2.armv6l-linux-gnueabihf-libgfortran5.tar.gz/md5/03683c3d2310870905bc386c5fc89eb5 +OpenBLAS.v0.3.26+2.armv6l-linux-gnueabihf-libgfortran5.tar.gz/sha512/2e0f4ae61623cf094cb8396522dfb6e2a7cf7de23a4dd25fb3fb2f0a414fd0b8d6397ce1c74d3a88296fe4fe76d1a61e1df87b2814bf87852564c097aa1f45e2 +OpenBLAS.v0.3.26+2.armv6l-linux-musleabihf-libgfortran3.tar.gz/md5/9c9dbe9afb8fc5d1f2fae971e1dfce1d +OpenBLAS.v0.3.26+2.armv6l-linux-musleabihf-libgfortran3.tar.gz/sha512/e592f91a2c0a303a059a9694565555247b76ab20ecfcf9c9f7235e0b67b458d530b35e9a2263f3c37b7ecc884c02124fac134f6061788348475fe1d546e07f1e +OpenBLAS.v0.3.26+2.armv6l-linux-musleabihf-libgfortran4.tar.gz/md5/ede329cb3e37e7c9008fec5273e38ce2 +OpenBLAS.v0.3.26+2.armv6l-linux-musleabihf-libgfortran4.tar.gz/sha512/bbb081f217c977e77d77c2091918aeff99855dbe99f4396059954e057edf41c63e926769afc3a2360eb87cef5612a2c0de519852182dba51d9306be10c4508fe +OpenBLAS.v0.3.26+2.armv6l-linux-musleabihf-libgfortran5.tar.gz/md5/c884cc9c752857a6a020891040c1bbd4 +OpenBLAS.v0.3.26+2.armv6l-linux-musleabihf-libgfortran5.tar.gz/sha512/67e671c05964b2a1456b9bddfe300cf29f88f4712cbe0c11cae01955abb31e70d9ad9a31175d2e29e4c5dd02d22febcafac8abe47d78dc179aa1e40776c51883 +OpenBLAS.v0.3.26+2.armv7l-linux-gnueabihf-libgfortran3.tar.gz/md5/69618373222d15ede34de1f913a93dac +OpenBLAS.v0.3.26+2.armv7l-linux-gnueabihf-libgfortran3.tar.gz/sha512/585fa3233d6c88c7ee5571da4dce7b83c53643937a0077d816c24c556b471926c808bcbd46c9ddddafbe24567f91039525b3a260505854e62e9b9b09acaede54 +OpenBLAS.v0.3.26+2.armv7l-linux-gnueabihf-libgfortran4.tar.gz/md5/874b639fcce8fb077d1a9cdecc916a6c +OpenBLAS.v0.3.26+2.armv7l-linux-gnueabihf-libgfortran4.tar.gz/sha512/0a065ccd197822b028da36e157aed19fbe440d7eedc94fe1955cd8980423d477bdb73db7e0f99a6ea683701a2c8fc649e3967a781694a7219d82915d1c22767c +OpenBLAS.v0.3.26+2.armv7l-linux-gnueabihf-libgfortran5.tar.gz/md5/03683c3d2310870905bc386c5fc89eb5 +OpenBLAS.v0.3.26+2.armv7l-linux-gnueabihf-libgfortran5.tar.gz/sha512/2e0f4ae61623cf094cb8396522dfb6e2a7cf7de23a4dd25fb3fb2f0a414fd0b8d6397ce1c74d3a88296fe4fe76d1a61e1df87b2814bf87852564c097aa1f45e2 +OpenBLAS.v0.3.26+2.armv7l-linux-musleabihf-libgfortran3.tar.gz/md5/9c9dbe9afb8fc5d1f2fae971e1dfce1d +OpenBLAS.v0.3.26+2.armv7l-linux-musleabihf-libgfortran3.tar.gz/sha512/e592f91a2c0a303a059a9694565555247b76ab20ecfcf9c9f7235e0b67b458d530b35e9a2263f3c37b7ecc884c02124fac134f6061788348475fe1d546e07f1e +OpenBLAS.v0.3.26+2.armv7l-linux-musleabihf-libgfortran4.tar.gz/md5/ede329cb3e37e7c9008fec5273e38ce2 +OpenBLAS.v0.3.26+2.armv7l-linux-musleabihf-libgfortran4.tar.gz/sha512/bbb081f217c977e77d77c2091918aeff99855dbe99f4396059954e057edf41c63e926769afc3a2360eb87cef5612a2c0de519852182dba51d9306be10c4508fe +OpenBLAS.v0.3.26+2.armv7l-linux-musleabihf-libgfortran5.tar.gz/md5/c884cc9c752857a6a020891040c1bbd4 +OpenBLAS.v0.3.26+2.armv7l-linux-musleabihf-libgfortran5.tar.gz/sha512/67e671c05964b2a1456b9bddfe300cf29f88f4712cbe0c11cae01955abb31e70d9ad9a31175d2e29e4c5dd02d22febcafac8abe47d78dc179aa1e40776c51883 +OpenBLAS.v0.3.26+2.i686-linux-gnu-libgfortran3.tar.gz/md5/030e3714c2544175861a411fc2520dc2 +OpenBLAS.v0.3.26+2.i686-linux-gnu-libgfortran3.tar.gz/sha512/5ea2318d786b423770e77cd820fef4bd995da7f6aaea70f73f55eb9210508e89f61fb0c6a728421c6fd3859ab11774f3111979c53a6c48ddf7a528a04295c4ba +OpenBLAS.v0.3.26+2.i686-linux-gnu-libgfortran4.tar.gz/md5/ef935e9cf567efa381de917a0cf66fbc +OpenBLAS.v0.3.26+2.i686-linux-gnu-libgfortran4.tar.gz/sha512/81a463ca756d4d363fd32f735d32d7b6e8c6f325401e943a8210fcf258ecd25507beac17b9b24ecddef0eb8521a58fdfcf6633794724f86e6977b162e099c70e +OpenBLAS.v0.3.26+2.i686-linux-gnu-libgfortran5.tar.gz/md5/ce1a4b1dc5e3c001b5c12f9cd81cb3fb +OpenBLAS.v0.3.26+2.i686-linux-gnu-libgfortran5.tar.gz/sha512/8f82012c0e9f955be2ab87551742a334f865cbdec95f0a62103c5127d6176838653884a6790234c5548940cec63e73ce95095bfcfaf241dc445020ab449c6687 +OpenBLAS.v0.3.26+2.i686-linux-musl-libgfortran3.tar.gz/md5/acfe348c1c1f33376469dd9106603222 +OpenBLAS.v0.3.26+2.i686-linux-musl-libgfortran3.tar.gz/sha512/2048723ce88ef28b2b7fa42cc73eccae8b663466a496709931ccecf06b4f4a9455fdfa3b5bd05bba4d847d3c0489251ecc3a34e7e3fb49bcdb40aacd4b53c140 +OpenBLAS.v0.3.26+2.i686-linux-musl-libgfortran4.tar.gz/md5/d82eb058ea705286058230f68b43c9fe +OpenBLAS.v0.3.26+2.i686-linux-musl-libgfortran4.tar.gz/sha512/c97ca8dd352c3067ec437b1a8b2b9ee06bd92157f0806dca55fcc3107a94e784cdf8f6d5986aae93ccb608fb90ba2ab9bba4acf7562c1fd519804897ab158c10 +OpenBLAS.v0.3.26+2.i686-linux-musl-libgfortran5.tar.gz/md5/014aa93d275d56e6176ab50f4040ede2 +OpenBLAS.v0.3.26+2.i686-linux-musl-libgfortran5.tar.gz/sha512/cd3d1dd1aab373fe4e1135042dee3fc959371fcbebe70f6c60327df35e389442a2f47351757db84cdc1d181b659ce2d6a0892508de6af27b01324e3119d9dabb +OpenBLAS.v0.3.26+2.i686-w64-mingw32-libgfortran3.tar.gz/md5/0b3f11710883a3cac2551cac0d72aad5 +OpenBLAS.v0.3.26+2.i686-w64-mingw32-libgfortran3.tar.gz/sha512/7d86f74c585dff33ed9384dc1c77b600a6893ceca335340232536f6e2cec1bbc86e85763c932c4b81c4012f12d68d7f37634e2f685aff6a999b875eae596cd9c +OpenBLAS.v0.3.26+2.i686-w64-mingw32-libgfortran4.tar.gz/md5/64d7806e859c804161a59641db744f7f +OpenBLAS.v0.3.26+2.i686-w64-mingw32-libgfortran4.tar.gz/sha512/7ebefd3ee2abd284c82e15d2497fd08bd4eccb264af7aacc1378c75d4b1dffb11ab95eafd98029c513ee7c667749a4917e26e4d57c65ccb77c5ba52cd8770009 +OpenBLAS.v0.3.26+2.i686-w64-mingw32-libgfortran5.tar.gz/md5/2a91e979a37722549d95cbd607e822fa +OpenBLAS.v0.3.26+2.i686-w64-mingw32-libgfortran5.tar.gz/sha512/f08880e18e6a361458a495c8860382a00001fe5d2989852b034ff9623b4517c8b75cd3f03986f62d72222e3cf07f8f2eb123785577500c7cf0e79109b9eed960 +OpenBLAS.v0.3.26+2.powerpc64le-linux-gnu-libgfortran3.tar.gz/md5/a417db847b1d610e3f52a891f98ae2e0 +OpenBLAS.v0.3.26+2.powerpc64le-linux-gnu-libgfortran3.tar.gz/sha512/16da5454b0987ecae76b6fe547d28f699684aa5fbf05a0e579960b71b3ab75dc1f3602fc60182fea3556eda8d905a870f03dd976009ea1972e9693a584e584f3 +OpenBLAS.v0.3.26+2.powerpc64le-linux-gnu-libgfortran4.tar.gz/md5/c63388278f7e037c232d9e7b29d3c725 +OpenBLAS.v0.3.26+2.powerpc64le-linux-gnu-libgfortran4.tar.gz/sha512/bedbb30539530e16314c97b87df59caf38c75de7b68616cdca78d27e241dce06527d7265924902d557394a14a6348dc2b5e45c4a006158a6b9182382183bd0ff +OpenBLAS.v0.3.26+2.powerpc64le-linux-gnu-libgfortran5.tar.gz/md5/5785f320f43600a791b543fb0bd8b00b +OpenBLAS.v0.3.26+2.powerpc64le-linux-gnu-libgfortran5.tar.gz/sha512/37ae82c2f57ec7c6ce0cd3f93b33870602f44bffdbeb2cb9bceef4515f5851aba73214d4a1fd24606fe4fa9bf0bf0a5552e1aebd65027ae57f76f3379a04344e +OpenBLAS.v0.3.26+2.x86_64-apple-darwin-libgfortran3.tar.gz/md5/ebd368ef0d4fd4f39534bb52b506e109 +OpenBLAS.v0.3.26+2.x86_64-apple-darwin-libgfortran3.tar.gz/sha512/a81bf2244b160786530ae449be1e81981d17146ccf833ea5afcea84eb24644002d5824591c96b7e89fe9642911a865a292be062d325ff2307d850b4cfbd0e7d6 +OpenBLAS.v0.3.26+2.x86_64-apple-darwin-libgfortran4.tar.gz/md5/f453ba9786d0916c9fc8c46281588c5d +OpenBLAS.v0.3.26+2.x86_64-apple-darwin-libgfortran4.tar.gz/sha512/7dea0549e3fec7e18043b8b4f93491ddbb4e041325330a2e6e28d137c25431c12371b534bae5170b22c5077d285ee23fda1579bae54c894a46a885b4571daf84 +OpenBLAS.v0.3.26+2.x86_64-apple-darwin-libgfortran5.tar.gz/md5/3be8449cb3046322f21b1d17be41bd1f +OpenBLAS.v0.3.26+2.x86_64-apple-darwin-libgfortran5.tar.gz/sha512/89bb94d645879bd2daa8e83fc9b391b84f32b8bc1f633caacc5eda138c8034b726bc5c7ddb931040f24b77482743cd2038fed6d1d17698f287c09f176a6085c8 +OpenBLAS.v0.3.26+2.x86_64-linux-gnu-libgfortran3.tar.gz/md5/2410734a7fc47705361267023c001272 +OpenBLAS.v0.3.26+2.x86_64-linux-gnu-libgfortran3.tar.gz/sha512/e9b239a75ee7521145f30caa52ec4e8b42d99f5246df47c8e203497c577f161ec4f3e7df7d7fa8135b4f947f941314bbbb00fa56ed813bbbefd6f887af1a768b +OpenBLAS.v0.3.26+2.x86_64-linux-gnu-libgfortran4.tar.gz/md5/06212c9081e7d355bce7e7960e17371d +OpenBLAS.v0.3.26+2.x86_64-linux-gnu-libgfortran4.tar.gz/sha512/aeb9913167f6e860c4f2082dc15310fa4fb2a0edcf70a6aa31626c59e132d2af9265544f1b18161d994405649f2923abd4cb21521466768f1ed4fe0ef8243bcd +OpenBLAS.v0.3.26+2.x86_64-linux-gnu-libgfortran5.tar.gz/md5/e21eb44972f00ff7c054e3fade50602d +OpenBLAS.v0.3.26+2.x86_64-linux-gnu-libgfortran5.tar.gz/sha512/8d9c1bb8c300e2523fd7a42ef4d4bcfbaba94e29e69c630008a194a62e575648c9f89a42301d1cbd0e74613ba41e5282d69bd8e24376c64fedb1d2657dd1e4a4 +OpenBLAS.v0.3.26+2.x86_64-linux-musl-libgfortran3.tar.gz/md5/aa1501555419f9bc6c243e77d1a413c8 +OpenBLAS.v0.3.26+2.x86_64-linux-musl-libgfortran3.tar.gz/sha512/8a4c5166e76c14bf6c65953bdcb8a4b222dee61123cfdb243876ee5af2d5d04e3125149936ce71c25cd90b0af5208d733ed9a1944b3134634de6a2ddfd1796a8 +OpenBLAS.v0.3.26+2.x86_64-linux-musl-libgfortran4.tar.gz/md5/597ff5c5a516278129a3077daeb4ffd0 +OpenBLAS.v0.3.26+2.x86_64-linux-musl-libgfortran4.tar.gz/sha512/0b4b3e58d046e7a5f3eac36e967025983575df1979c5bdc73330481982e229f832735d1a7bce373009e666b9094ba5877d290bd06d9baf5f8a9efef0d3b0b4ac +OpenBLAS.v0.3.26+2.x86_64-linux-musl-libgfortran5.tar.gz/md5/410507ba0176c16180163556294d359e +OpenBLAS.v0.3.26+2.x86_64-linux-musl-libgfortran5.tar.gz/sha512/659167ca4b307c00afbfbc08a8bade4c144a0567c13354c81ef17dbd210f2a98b40289bb9f166a70cd447193c236561c7a6df2603ebcd0dec501529d4e976508 +OpenBLAS.v0.3.26+2.x86_64-unknown-freebsd-libgfortran3.tar.gz/md5/e12abdd6fa5a9211cdf3472ec9e2bf2c +OpenBLAS.v0.3.26+2.x86_64-unknown-freebsd-libgfortran3.tar.gz/sha512/cc68370a2e2c76303272e0d91a71ae1a9408233d7f39292682fc901b8474d8272ac42f233c2d0c3cdc07c481968bee2486831e8df48d9f34877b45e3af694b62 +OpenBLAS.v0.3.26+2.x86_64-unknown-freebsd-libgfortran4.tar.gz/md5/a02fcf5127ae14a00204c1b43b1c6807 +OpenBLAS.v0.3.26+2.x86_64-unknown-freebsd-libgfortran4.tar.gz/sha512/e502418902d1af01efa1c9d6db94659b1472db8af812cd2507fb324836e5bab3364e54bca9a2ee809114947b4210f5fe3ec640d7bb741114a66e110d2668ddab +OpenBLAS.v0.3.26+2.x86_64-unknown-freebsd-libgfortran5.tar.gz/md5/f4d49963b37686671d1d6f1f5b4b680e +OpenBLAS.v0.3.26+2.x86_64-unknown-freebsd-libgfortran5.tar.gz/sha512/0c395e956243b40bc29a820ac755740efd932f56eaba85a554041c223238300f0370ef2d051cb67357bf9a740950bbc78de1fba52522bd53a9d3a8f24799f975 +OpenBLAS.v0.3.26+2.x86_64-w64-mingw32-libgfortran3.tar.gz/md5/fecab0026d2b7b7e0261ccf1f9aeb282 +OpenBLAS.v0.3.26+2.x86_64-w64-mingw32-libgfortran3.tar.gz/sha512/a9032b79cbc18448b141bc61b404e156af2ccaccd3919f0d38d5f2963e9e89c93f4992ed58217f969a7f5201b4489e0bdb91761fe507ee7f07e65e712f9ed584 +OpenBLAS.v0.3.26+2.x86_64-w64-mingw32-libgfortran4.tar.gz/md5/28d440488310d3730db018612078bad7 +OpenBLAS.v0.3.26+2.x86_64-w64-mingw32-libgfortran4.tar.gz/sha512/a24e5e8f8da4734ce10b6d959b566df0ece3f3ba30737584ad3e7795d07b950e2bd57fb13d4a4f21eb92b29c38cf40fd846b5f771eb820a769e50f4074e36142 +OpenBLAS.v0.3.26+2.x86_64-w64-mingw32-libgfortran5.tar.gz/md5/7202113787553198c32b0b6964630b5a +OpenBLAS.v0.3.26+2.x86_64-w64-mingw32-libgfortran5.tar.gz/sha512/9553d90d0580bd6f1e6676be85029db615564bbada470ee8751619b5888e79ee23e1e699f1aba1e770a29c5ef1e4d13eb8c24cfa787ef22167c9100ed6af478e +openblas-6c77e5e314474773a7749357b153caba4ec3817d.tar.gz/md5/4971eeb7adadee085d7c991db416fe7a +openblas-6c77e5e314474773a7749357b153caba4ec3817d.tar.gz/sha512/7b85c9fb7be54407ba627d77897f40de4395d6d307230aa7df83cf8e0a41f545e4af4ae0576abb40cc9e0c385e1c6a488100dff292ea307439a89587c07ba66f diff --git a/deps/checksums/p7zip b/deps/checksums/p7zip index 3a3986977e3cf..272f1d768161f 100644 --- a/deps/checksums/p7zip +++ b/deps/checksums/p7zip @@ -1,34 +1,34 @@ -p7zip-17.04.tar.gz/md5/00acfd6be87848231722d2d53f89e4a5 -p7zip-17.04.tar.gz/sha512/ad176db5b657b1c39584f6792c47978d94f2f1ccb1cf5bdb0f52ab31a7356b3822f4a922152c4253f4aa7e79166ba052b6592530b7a38f548cd555fe9c008be3 -p7zip.v17.4.0+2.aarch64-apple-darwin.tar.gz/md5/b418adbae6512a13e04407c120ba78e0 -p7zip.v17.4.0+2.aarch64-apple-darwin.tar.gz/sha512/818f7afb0d3ffbff8079f5f4b8e9745a847148ac9cb5a261b6ca2f2f3a1dd722fa93f798645129bc9bc4a48f756bf2e55605791abb394a32635dfaef31f21e70 -p7zip.v17.4.0+2.aarch64-linux-gnu.tar.gz/md5/3f976d6514e6327a9aee4a3f21a25a64 -p7zip.v17.4.0+2.aarch64-linux-gnu.tar.gz/sha512/a4dd8be97c53b864e81aae40e248759f97249fbd6d8c5b91f0ac115a84126cbfc4825ffa3876f5e8b66652b014a78ba04e3ffc1ba1d9c96786b914b1279682c0 -p7zip.v17.4.0+2.aarch64-linux-musl.tar.gz/md5/b31699d7ea671c689fa9194913fbe7ee -p7zip.v17.4.0+2.aarch64-linux-musl.tar.gz/sha512/5c8d95df66055ab8027b047b23534743ac929befd37dc8a8e591deece22006209f94524f7951de580a5ded9530ead2ce7ec3370c482865554830b53d09f41bf1 -p7zip.v17.4.0+2.armv6l-linux-gnueabihf.tar.gz/md5/fbe2ebeeaa6e5b33dcb71662fb7040f1 -p7zip.v17.4.0+2.armv6l-linux-gnueabihf.tar.gz/sha512/6ca1d7eb1d3f6a7c4dc9860ac3d5a835abce92cddcda015a93086ecde44ed1b3d9f83a3c1e1eddc510af429ec269716dde6bc5fae4aa6bbbc3dcfc9a51326786 -p7zip.v17.4.0+2.armv6l-linux-musleabihf.tar.gz/md5/dae4b1e6060bf4431d3ead53e6b3e167 -p7zip.v17.4.0+2.armv6l-linux-musleabihf.tar.gz/sha512/856c2283c63728d8c542ce5a3d58e38c985f190774c407fc421dd30f05e0ae3467e2844cb7d535aa8a6b8fb24b21b29af75b736fbd9af67c24340609ad6b5841 -p7zip.v17.4.0+2.armv7l-linux-gnueabihf.tar.gz/md5/42b6b9b19158303c4399d651ee5b14cf -p7zip.v17.4.0+2.armv7l-linux-gnueabihf.tar.gz/sha512/4f8792639db8599af026f592496a8c594c0fd6a62dc949965add55b1b85a95d4edc2f99960cf5b21e7beeb8e1bca1d9c1a1a34600103df04dc20d0509410c486 -p7zip.v17.4.0+2.armv7l-linux-musleabihf.tar.gz/md5/e34f5585a50c0cfce29c79e9ece60cf4 -p7zip.v17.4.0+2.armv7l-linux-musleabihf.tar.gz/sha512/87f72568f5a877008d3a8a032a85f69c29d3af4293d7b42d70419bb4c9ca7e99dc13c6b22621ca83886d07f765118451ee9f2a3aee63979d8070910887bf7cdd -p7zip.v17.4.0+2.i686-linux-gnu.tar.gz/md5/d917247133b1c62663334b6a908e30e9 -p7zip.v17.4.0+2.i686-linux-gnu.tar.gz/sha512/4839bec129b7fbd68c61d35fd3b3af9863c757d9fec0220926d45f1f58174d88d0bbb4a472d259d1d77775b906e9c58ba707fc20e2a4a060ca9030722609182d -p7zip.v17.4.0+2.i686-linux-musl.tar.gz/md5/951614fc7597de8c12e0109cbd81bfa9 -p7zip.v17.4.0+2.i686-linux-musl.tar.gz/sha512/f0420ddd6df82d2b3e1ece9cc5cf537cb0803d291d274a495bb9a575bb253a4241cdae38a88e43ddafaab7f6911b310a30c1b874b0a0a9bc447f8c42c5a24652 -p7zip.v17.4.0+2.i686-w64-mingw32.tar.gz/md5/cc81daf0e40990c48db178cb53a95d08 -p7zip.v17.4.0+2.i686-w64-mingw32.tar.gz/sha512/ae5bcbcf32dad20db95319c3c2f874fdbb0cd41054d6c192f2ab106e0aece1b4b0b591055b37c2c909b07b303204a75dec5c4b3c224243c2041da811f99cd7e5 -p7zip.v17.4.0+2.powerpc64le-linux-gnu.tar.gz/md5/e97d74ac4dacfaa215c3119e055a2df0 -p7zip.v17.4.0+2.powerpc64le-linux-gnu.tar.gz/sha512/8b0596ebd84fa9947e8f15f63c426339980e08c81eb4c1474b4a66af6329f0a2fe1bd31eef964d147bf9cf0213e85bdc143fab1a4f1dbfa09da5ebd9e73a3d8d -p7zip.v17.4.0+2.x86_64-apple-darwin.tar.gz/md5/4d9a26dbfc0a02a812c8f7de20ea5440 -p7zip.v17.4.0+2.x86_64-apple-darwin.tar.gz/sha512/3cba51ba9742b616afec13a14e8e3bd3c73c835256af8f6a49d4abf32f5ddf3f86ac8ae08ffd9bc331caa8a711dd1b63f4cd082443a7863e3d512f6ca2152bcd -p7zip.v17.4.0+2.x86_64-linux-gnu.tar.gz/md5/37b7570712ecb8677059f4280a346201 -p7zip.v17.4.0+2.x86_64-linux-gnu.tar.gz/sha512/9445add6a475bdfc2924dc52c07917c2746b07a41a2dbfdab8ad4b4e5b87b0192c13f4da5da64e5d3544bbf9c79fda3c633664eecb372e8475031789770c41ee -p7zip.v17.4.0+2.x86_64-linux-musl.tar.gz/md5/04d6ae950d05c81c6b165721de2ba7e7 -p7zip.v17.4.0+2.x86_64-linux-musl.tar.gz/sha512/524d8ed80a1af903b572d5e32710b384702175cacc83ce2305d7f7a35d45aae7d08e2afc14a9e40c934ba4eb578787afa9bece4f820e96e4b624869cb2bcec26 -p7zip.v17.4.0+2.x86_64-unknown-freebsd.tar.gz/md5/e2a3361e91258e39db541c9dec5a73fe -p7zip.v17.4.0+2.x86_64-unknown-freebsd.tar.gz/sha512/ecc1db9a1823ebdac290548f6e001688b5d111caede4cbfab4e2ef492dbb31844690e9b69360ed9c6ebb2affded7f352d57c0e5cfe67be951876d1fc5e87d92d -p7zip.v17.4.0+2.x86_64-w64-mingw32.tar.gz/md5/2b5f77bb31526c469e0fd48399d0cf9a -p7zip.v17.4.0+2.x86_64-w64-mingw32.tar.gz/sha512/a3a17af4db98b82b71c8d4d09e5315dc4fa77b38cc19f0593654b63744bc7489383d40032e48c2141d6b55e330d1538c527819378a2575a245de436bc6daf532 +p7zip-17.05.tar.gz/md5/de921a08f37242a8eed8e4a758fbcb58 +p7zip-17.05.tar.gz/sha512/97a7cfd15287998eb049c320548477be496c4ddf6b45c833c42adca4ab88719b07a442ae2e71cf2dc3b30a0777a3acab0a1a30f01fd85bacffa3fa9bd22c3f7d +p7zip.v17.5.0+0.aarch64-apple-darwin.tar.gz/md5/2a254e251901b3d1ddfd7aff23a6e5eb +p7zip.v17.5.0+0.aarch64-apple-darwin.tar.gz/sha512/8efb9a2c9bcab388e523adba3dc0b876e8ae34e2440c3eee01fd780eb87c8619c7a7bbdc46d703ccefff6aa6ad64c4e4b45b723136ab1f6fd6de4f52e75ebbbf +p7zip.v17.5.0+0.aarch64-linux-gnu.tar.gz/md5/bb1f3773fd409dbb91a10f7d9d2e99b5 +p7zip.v17.5.0+0.aarch64-linux-gnu.tar.gz/sha512/e95ccc342be644570d218d25403b91a7db9ee983fbf8cce3deff453355d68d426f9301eaac865a98691025b596b8cd77ebebf6184c0eaf8b2f294bc6763b9a4b +p7zip.v17.5.0+0.aarch64-linux-musl.tar.gz/md5/3fac518a6a70412294d71ca510958cf2 +p7zip.v17.5.0+0.aarch64-linux-musl.tar.gz/sha512/fc127790739bf8a8b918b2e83753d86f5e79ee8706bde4cc79d74d9f7d846aae99a109da4b2b3cc92ccedc1eef4d52a555a65a95f588e173e0fecc11f2ca21e6 +p7zip.v17.5.0+0.armv6l-linux-gnueabihf.tar.gz/md5/355410848192de3b02d12fd663867f4b +p7zip.v17.5.0+0.armv6l-linux-gnueabihf.tar.gz/sha512/8f103b41e755d157d70dacca89a0ef4610bea109686b4005e8edd5f79ed2e6419c00c2625d0ab90e6e33fa389e670490d8de263c0bdae952cc34cbbf440e275f +p7zip.v17.5.0+0.armv6l-linux-musleabihf.tar.gz/md5/34363b227306fce34a728af54b71064f +p7zip.v17.5.0+0.armv6l-linux-musleabihf.tar.gz/sha512/8dd7b37ce6223c9fedcaa999eb806eb6dec8c4a3133d3c07e2456cb8543b8e4f5b881c1bff2d2e25f19b1312b18673e9013aeff87d6a274eec6c451b1ba0d6b9 +p7zip.v17.5.0+0.armv7l-linux-gnueabihf.tar.gz/md5/dbb1fc0cf3bea674442ff8cc932a94cd +p7zip.v17.5.0+0.armv7l-linux-gnueabihf.tar.gz/sha512/c4d71d905fa420391417786ed206a0c334475dd0df8baa1fc3f6560ce548db11805003d0d0b35bb622fe818c761f2b0abe0796d1cbfce2a922da69e697f056a2 +p7zip.v17.5.0+0.armv7l-linux-musleabihf.tar.gz/md5/d188b5dd453faedb616ba9c48fdeab6b +p7zip.v17.5.0+0.armv7l-linux-musleabihf.tar.gz/sha512/ea30a775370502ca9e271b87cbda528d0c51d63ce0df41883d4dbc1527a32f251d797f3692fcf9b883b5fbaaad80515b971a8f8fe09ba102978b19a0ecb58528 +p7zip.v17.5.0+0.i686-linux-gnu.tar.gz/md5/dc02bdde045a0b6b22cf14d6960e63ed +p7zip.v17.5.0+0.i686-linux-gnu.tar.gz/sha512/d2d0dd14a5fc1163fea2276e0925bfa8d075d5dba1d8018e4e3160977d3b09642b2e521d8e57d049abaf0e2ea391a846f0b0136b3c59e8b476c8c52ac5210447 +p7zip.v17.5.0+0.i686-linux-musl.tar.gz/md5/0b8658147938a8ec109ee2b3b0a0665f +p7zip.v17.5.0+0.i686-linux-musl.tar.gz/sha512/411b2950f5928c537b87ba0651c09c08e57afed765db9fee89eda8b12939ef0da94c8ba38c0a24ba46b4513a0e4cca798eb09f2b20a011099ed3cf14455dd19e +p7zip.v17.5.0+0.i686-w64-mingw32.tar.gz/md5/98bdd8767c77a35f71303ff490a3d363 +p7zip.v17.5.0+0.i686-w64-mingw32.tar.gz/sha512/14f08071af74297df8bfe1d9f7efa3c0212e62ace573848f17b729e4c36dc3861110f3c5cc9315364c318e5b040736443a24492e86d76161993653a309996eb3 +p7zip.v17.5.0+0.powerpc64le-linux-gnu.tar.gz/md5/b18c917b9852898a9b9d6d24bcc6863e +p7zip.v17.5.0+0.powerpc64le-linux-gnu.tar.gz/sha512/0148dc8a0bc9c95212d7f8e2f92ee24e968eb7290fe72c7ae02e286bf5c05dd6b1f10b32350a7ff37777ed5a8cc21f3303f464620f3394c7a4690ae98bf77299 +p7zip.v17.5.0+0.x86_64-apple-darwin.tar.gz/md5/da31752a2556644d39e48649bb0111de +p7zip.v17.5.0+0.x86_64-apple-darwin.tar.gz/sha512/0695ad111263d2fadfdf9a46ce7ee80def0bf60db7d1c2585ed2af6fc945fb169311a9f1ffc6f95fb43b0b03694d2d1be9136d3d78ba2ef2b19228987883a385 +p7zip.v17.5.0+0.x86_64-linux-gnu.tar.gz/md5/2fb55d86e4eaccb0488bd637d088b996 +p7zip.v17.5.0+0.x86_64-linux-gnu.tar.gz/sha512/38ac355157d59c09f308fc29964d0e9c1466c9633efd8d3c6ff3c738abce2af45ebc6b92a29f56d5e7baa4871f9f39b14ecfcbedd4e2f4ca7c0fe6627c6b13e7 +p7zip.v17.5.0+0.x86_64-linux-musl.tar.gz/md5/f0bd567a851d2dd9d306552ffafbca3a +p7zip.v17.5.0+0.x86_64-linux-musl.tar.gz/sha512/e60047a6e7e3496cb6658f87c8c88676f399cd9f3d0d7daa880b6be09cd5525f7f22776896f1375722b47555514ff8c018f02ce800ec3fd0ed922e16e8a6d657 +p7zip.v17.5.0+0.x86_64-unknown-freebsd.tar.gz/md5/d37bd26e39a3ec84f262636f70624341 +p7zip.v17.5.0+0.x86_64-unknown-freebsd.tar.gz/sha512/0604a880c19f9d72d5828edd75be641625c29f230b3a5e7d70ec3812c014c96b76ee7b45e0e80f49be63f109a48700e75d1e5be01b5ae7b46d42dafda9885e8c +p7zip.v17.5.0+0.x86_64-w64-mingw32.tar.gz/md5/f02c7b2481dee880b096340a8735350f +p7zip.v17.5.0+0.x86_64-w64-mingw32.tar.gz/sha512/08b717c1b072d1309f6af8973eb09b1a482abb7ae7d01fba79873d4310a7c11292e2e8779029f99cc60627ed0d064224bc87782e587c520f970b840b7b838052 diff --git a/deps/checksums/patchelf b/deps/checksums/patchelf index a7122c400749a..e2029b83f14fc 100644 --- a/deps/checksums/patchelf +++ b/deps/checksums/patchelf @@ -1,2 +1,2 @@ -patchelf-0.13.tar.bz2/md5/d387eee9325414be0b1a80c8fbd2745f -patchelf-0.13.tar.bz2/sha512/43c3f99fe922e2f34d860389165bcc2b0f3f3317e124eb8443017f71b1f223d96a7c815dc81f51b14958b7dc316f75c4ab367ccc287cd99c82abe890b09a478d +patchelf-0.18.0.tar.bz2/md5/9b091a689583fdc7c3206679586322d5 +patchelf-0.18.0.tar.bz2/sha512/bf26194ca3435b141dd330890fcc0c9d805d0ad6a537901dabe6707a13cd28e7e6217462f3ebb3cb4861302dd8632342ec988fc18246c35332a94f2b349d4f4f diff --git a/deps/checksums/pcre b/deps/checksums/pcre index 9b9717b61688b..744d16540d6c8 100644 --- a/deps/checksums/pcre +++ b/deps/checksums/pcre @@ -1,34 +1,34 @@ -PCRE2.v10.42.0+1.aarch64-apple-darwin.tar.gz/md5/667a570d341396c3213749ee1e5b5fda -PCRE2.v10.42.0+1.aarch64-apple-darwin.tar.gz/sha512/c1bb99e8928efded9b0ea3f294ceb41daea7254204ca30c0ff88686110ccd58138d8ea8b20b9a9d6d16a6d8d3f34e27e74e7b57d3c8fe6b051c9d8fa6f86431a -PCRE2.v10.42.0+1.aarch64-linux-gnu.tar.gz/md5/1a758f275ff3306fbad7698df7b9b7be -PCRE2.v10.42.0+1.aarch64-linux-gnu.tar.gz/sha512/d09508c0b255366d01f1b4d1ae6748a8e47f18c451498d30715f5f968784990949dab7540cd086396abd912f61b5f7c44c8c72a27efaba0a7fc08b71a167c057 -PCRE2.v10.42.0+1.aarch64-linux-musl.tar.gz/md5/e61147579fdc9b57a61b814bdf9c84bb -PCRE2.v10.42.0+1.aarch64-linux-musl.tar.gz/sha512/eecaf4c1937fc04210b910ac65318524c02d690e8c4894c38e74eaba36d26c87a1fd9e1cc36f4307a11ff3552a79f081fa8f05085435eb34872dc2fdecce2d18 -PCRE2.v10.42.0+1.armv6l-linux-gnueabihf.tar.gz/md5/b4c484a3b87923c0e2e4d9cc5f140eb7 -PCRE2.v10.42.0+1.armv6l-linux-gnueabihf.tar.gz/sha512/5931cf13d340971356a9b87f62c9efdb3656ba649e7b25f1722127a3fd70973d94c815a37b43cecab8eb0ed8d1ae02ef1a0c0a12051852c1b9242c3eaa01c496 -PCRE2.v10.42.0+1.armv6l-linux-musleabihf.tar.gz/md5/bc7b5bb1c5b0b99c121bad5a89299ca7 -PCRE2.v10.42.0+1.armv6l-linux-musleabihf.tar.gz/sha512/86b5ad4fa6f4b5bd1a76ad68ddff4b39916d0ed0acc03a3fee8eab5256aaed53abc0ff4ce9d9d9f8b9203c087211684da92fe6aa06ff5bc331ba1b3da2cba57e -PCRE2.v10.42.0+1.armv7l-linux-gnueabihf.tar.gz/md5/3541eb26fa5a4d13e2c7d063dbd900d8 -PCRE2.v10.42.0+1.armv7l-linux-gnueabihf.tar.gz/sha512/872181f931662edaf653351486c5e2a700e94cfa0966ca90eca893fdc75dd46eb40d9d45737c198aa4b9ad8ebab33fd78697ef35906985e4e1c9748ddf58d363 -PCRE2.v10.42.0+1.armv7l-linux-musleabihf.tar.gz/md5/fe059feb18fcc9312f1033362070fe34 -PCRE2.v10.42.0+1.armv7l-linux-musleabihf.tar.gz/sha512/5a96acf3908c964ccb4f296c449499388ed447d9a094c2760c979e02ef656fa710ede3926b9626e89fb5b0545c111e6eedff21e48416e923c17fc9ff129d0519 -PCRE2.v10.42.0+1.i686-linux-gnu.tar.gz/md5/d6c804ae6cc661d039ee3edd2f1dbcb6 -PCRE2.v10.42.0+1.i686-linux-gnu.tar.gz/sha512/256ca677b169854686ca34cf30af5a6709758b41b65f2c66d497c552858770a69a49834fd16daa2f7d481964b21a2e3ec68ff1b1fbd08f4e2257ec46b85c0063 -PCRE2.v10.42.0+1.i686-linux-musl.tar.gz/md5/092af10d8182cb4240cdd975efce4d7c -PCRE2.v10.42.0+1.i686-linux-musl.tar.gz/sha512/79a48f4fd50ffdf49c8d57581e01ace38c1b3d7edd86d44db44b8efd93074d16faf035131a0d60c6631b8bf22f0fd8296acedba45908da56e8096c296122f047 -PCRE2.v10.42.0+1.i686-w64-mingw32.tar.gz/md5/bafc5fc1621d0f4fb2b7b271e2f66db1 -PCRE2.v10.42.0+1.i686-w64-mingw32.tar.gz/sha512/a5ac2b788fb2e4baf129fb339f28da04e9a5c9b7dbba0a1f43da2a7193917d361d961ba48abd0aeec30d2352ebaa401d667c8eec81c5f40859ef8adf8487edca -PCRE2.v10.42.0+1.powerpc64le-linux-gnu.tar.gz/md5/0de1215b2a1e9c0efd131355e9fbf2c1 -PCRE2.v10.42.0+1.powerpc64le-linux-gnu.tar.gz/sha512/69dae12627685ae665db8c91264a79aba7c60ae97eccdc79ef889f2a5f69b465fa333aba298fc90bbb95710cfc324e3630bc427a97577855e8fb6c8fe227cfec -PCRE2.v10.42.0+1.x86_64-apple-darwin.tar.gz/md5/c5c52b399921c5ab81a5f598b350d2ca -PCRE2.v10.42.0+1.x86_64-apple-darwin.tar.gz/sha512/e6c8ba3aa3fbf54b37079301ab317104c6852812b23835f52ca40f31f0831678172d32e077fbaa712a8a2cb16d62bb97d475827004353e7807922a2d6e049b28 -PCRE2.v10.42.0+1.x86_64-linux-gnu.tar.gz/md5/b074dd1f85e24e723349e566350e2c78 -PCRE2.v10.42.0+1.x86_64-linux-gnu.tar.gz/sha512/236017e02c9f32b913b772dbf22897c8460e5791f196c86f8a073e329ad8925f6859afe48f3bf18ca057c265f08fedbde255360d8f859e2303c6569ab1b0e1bb -PCRE2.v10.42.0+1.x86_64-linux-musl.tar.gz/md5/9f32ca77e79843fc9c4b5fc8ed336d11 -PCRE2.v10.42.0+1.x86_64-linux-musl.tar.gz/sha512/334a31724e9d69c6517568d922717ce76d85cf87dbc863b7262b25ab43c79734b457833cd42674eb6a004864e5c74da3ae1d0a45794b4cd459eea24d9669fac5 -PCRE2.v10.42.0+1.x86_64-unknown-freebsd.tar.gz/md5/76cde3c509ed39ca67a18fe58e728821 -PCRE2.v10.42.0+1.x86_64-unknown-freebsd.tar.gz/sha512/219c82067a242554c523be5be2b5561cd955609eac1addc336004df64a2a12e815ea40ff94d3f610970f7d0215b410f098d4baaa2c722f5cf21dab175b288b7e -PCRE2.v10.42.0+1.x86_64-w64-mingw32.tar.gz/md5/b0771d5b0132b554776e7cee0e1374e6 -PCRE2.v10.42.0+1.x86_64-w64-mingw32.tar.gz/sha512/d4435ff703e51c88df7764a732d6b67b1ee4d3b09b915ac822af05a33347642691837818d4c389226ef1d70cd69dbac792ebe1e7de1d8900443fe162051916ae -pcre2-10.42.tar.bz2/md5/a8e9ab2935d428a4807461f183034abe -pcre2-10.42.tar.bz2/sha512/72fbde87fecec3aa4b47225dd919ea1d55e97f2cbcf02aba26e5a0d3b1ffb58c25a80a9ef069eb99f9cf4e41ba9604ad06a7ec159870e1e875d86820e12256d3 +PCRE2.v10.43.0+0.aarch64-apple-darwin.tar.gz/md5/f1bee27b8d9465c14eaf9362701fb795 +PCRE2.v10.43.0+0.aarch64-apple-darwin.tar.gz/sha512/33b8f6e3703f0a52cd2d57897c28e35fb3c63af459296a2fef4e414dc99239617833b2ab176068d6aab690122a34a9ab9b6042dfff54b5a30ad60429a809818d +PCRE2.v10.43.0+0.aarch64-linux-gnu.tar.gz/md5/c55a569260e302f315f4a1bd185346ab +PCRE2.v10.43.0+0.aarch64-linux-gnu.tar.gz/sha512/be4d2883e69d562898a157424b2baa146fe79545a8c10935cf25b54e498ca2c14fae026fa0d958d175895fe2cb695d0f96ef7f09fecbf54e1cee4a55b81a382b +PCRE2.v10.43.0+0.aarch64-linux-musl.tar.gz/md5/fb041ccace415ccc26263968c6435a47 +PCRE2.v10.43.0+0.aarch64-linux-musl.tar.gz/sha512/06672ebe18e0f6bfa1dd2d5c02e10d9fd67236a73fd38ee2e8f4496d98f297f7866760f0be3b9cebeca348a5d748a3719e416b84cec96a90c71eac55afbbd905 +PCRE2.v10.43.0+0.armv6l-linux-gnueabihf.tar.gz/md5/4f303a4cbf26abb7bf4ffb8bfe3d636d +PCRE2.v10.43.0+0.armv6l-linux-gnueabihf.tar.gz/sha512/dddb3b227ee48d8329f6c65c5d0fce9f460eccaec98594a05bf28d1d9af01397cf7ef86c96e88b0e96030a7f6d8406461f78dd5fa558db8fc8f7bfb3b522ed54 +PCRE2.v10.43.0+0.armv6l-linux-musleabihf.tar.gz/md5/eade1fff90404bf3584fd15b62be0cfa +PCRE2.v10.43.0+0.armv6l-linux-musleabihf.tar.gz/sha512/351f6fa11c39b90fcc4086bd00b1b1126ed92272595f0b745757ca4e7e360c84d244446a871029245c3bcf838b23f42d908f858e44fae7deb9002a36cb76753c +PCRE2.v10.43.0+0.armv7l-linux-gnueabihf.tar.gz/md5/daa0a34b2cf0b71a6f8e1f9456cd4b06 +PCRE2.v10.43.0+0.armv7l-linux-gnueabihf.tar.gz/sha512/ae72956ae7a9a5f315bfc816fdbb500937a170dfea306a28289ec9eac57d883cf2fa5a467ce9406eea80546b632a272c63bbb48b89ebe6d9f69d30366fd84180 +PCRE2.v10.43.0+0.armv7l-linux-musleabihf.tar.gz/md5/90bfb9e4efd7c92a2bb6a1a48fd88ecb +PCRE2.v10.43.0+0.armv7l-linux-musleabihf.tar.gz/sha512/147ac98d82fec4695de0c43c87d3d9242b9c024bc6df7ad7504d17ef6a12a029ed703c4deade0e2b24faf5283d66309f880d62f8c4834f27b2cc8889587d7abe +PCRE2.v10.43.0+0.i686-linux-gnu.tar.gz/md5/6fde649bf449c4122438fff32c0706ab +PCRE2.v10.43.0+0.i686-linux-gnu.tar.gz/sha512/edfaa15490497723c095eaa5df26194637b0606e9dce7b89b400024ef8ac42e21f010bb31c2cee5c735ce82fc8de0c42bf2b35b095a1e70a9a111d3bfba6da64 +PCRE2.v10.43.0+0.i686-linux-musl.tar.gz/md5/73aa8d13cc48338a5071e30b3a899109 +PCRE2.v10.43.0+0.i686-linux-musl.tar.gz/sha512/200e2d3ffd68f49b76c70a5be80cb0ae9703049214674485a2ab24abaaea7aefd6dec2042a14bd48cc52b04379f57322ec1e1788dc8c00896e1074921725d9cc +PCRE2.v10.43.0+0.i686-w64-mingw32.tar.gz/md5/4ddf0f31c97463e5216ed71afc4fb014 +PCRE2.v10.43.0+0.i686-w64-mingw32.tar.gz/sha512/75903d81668a66a5c4d830e31657391d507883943d86245998f224655406dcc6a95ba4f5fad20dcf608a98d6ccf49abe50107993448669b03c42a878d8466611 +PCRE2.v10.43.0+0.powerpc64le-linux-gnu.tar.gz/md5/64cb71080da1c97eba3a440ff53d298c +PCRE2.v10.43.0+0.powerpc64le-linux-gnu.tar.gz/sha512/16348b96a45c7a7d86775cb1d082b4d1c060e5a8acfb37554885d8da0db87430d8a40f834f008a90f4a7b1c07b8329df96836ba0430ecec506a143b7347bb101 +PCRE2.v10.43.0+0.x86_64-apple-darwin.tar.gz/md5/31bbb2485f5e06c3616fb061ffb2f022 +PCRE2.v10.43.0+0.x86_64-apple-darwin.tar.gz/sha512/3284ee63ed1e5631267efacb354a1d90bd1b7db0bc81d7233c9580eee4a9af06093c1c4f240786c34299df89a36a17ed92598fc302074f5a200c56cc96081bf1 +PCRE2.v10.43.0+0.x86_64-linux-gnu.tar.gz/md5/2fb7e0e9bbc32dddf543f4d395b50d3f +PCRE2.v10.43.0+0.x86_64-linux-gnu.tar.gz/sha512/5a533a3a01f817689077377835dc88edf914459ed0df7323f8f4dba602a47fd6af700075feb1f448221366b1cf7e2d717c615a5c506eb4ca2db9c600fd290fb0 +PCRE2.v10.43.0+0.x86_64-linux-musl.tar.gz/md5/b432063c93aa477dd0883428191041f8 +PCRE2.v10.43.0+0.x86_64-linux-musl.tar.gz/sha512/36475e90e29d7324046fe1da669fb37f667245a680df23f3978394964e14eb9bda3fd56703ad62cd56e27a5af77d8b6b9612516457ae803cef0627bd919e4628 +PCRE2.v10.43.0+0.x86_64-unknown-freebsd.tar.gz/md5/6124870a991e70c2ed8a64d8f3258760 +PCRE2.v10.43.0+0.x86_64-unknown-freebsd.tar.gz/sha512/4645a2d05af149467f2e4ce5e48853b57c585d6a5950c70726d04bc71a5d82f50809af141ad98e99671e764ac74965651ecad1c49a849caa8fd077c7f4911c7c +PCRE2.v10.43.0+0.x86_64-w64-mingw32.tar.gz/md5/cc4e9f45471f538c1fefa657ab99b878 +PCRE2.v10.43.0+0.x86_64-w64-mingw32.tar.gz/sha512/eed45e621263cb307b6e8ab42e2c12cf9e1d61ad523760fd721a85765c359b74d580752ca7c3d222e0cba26a74e872a6d43dbf2dbf08e4733a3e709417e48651 +pcre2-10.43.tar.bz2/md5/c8e2043cbc4abb80e76dba323f7c409f +pcre2-10.43.tar.bz2/sha512/8ac1520c32e9e5672404aaf6104e23c9ee5c3c28ad28ff101435599d813cbb20e0491a3fd34e012b4411b3e0366a4c6dfa3f02d093acaa6ff0ab25478bb7ade9 diff --git a/deps/checksums/suitesparse b/deps/checksums/suitesparse index ad571d8be1f28..537ec7dd6d3fa 100644 --- a/deps/checksums/suitesparse +++ b/deps/checksums/suitesparse @@ -1,36 +1,34 @@ -SuiteSparse-7.2.0.tar.gz/md5/a751b1161f03eb6bd8bd7b9c9be74b67 -SuiteSparse-7.2.0.tar.gz/sha512/62fc796a77f2a8c95cd688a4fa0e39c19d7ccfafde7a6623d62ca6928cee68ac9863a0f721959a1d5a07e62888ab621a4b1cb4f63371f4ac10f4ffe513241340 -SuiteSparse-e8285dd13a6d5b5cf52d8124793fc4d622d07554.tar.gz/md5/46541001073d1c3c85e18d910f8308f3 -SuiteSparse-e8285dd13a6d5b5cf52d8124793fc4d622d07554.tar.gz/sha512/f7470a447b934ca9315e216a07b97e363f11bc93186f9aa057b20b2d05092c58ae4f1b733de362de4a0730861c00be4ca5588d0b3ba65f018c1798b9122b9672 -SuiteSparse.v7.2.0+1.aarch64-apple-darwin.tar.gz/md5/1a10261e5bed293a66849c7a50605a1c -SuiteSparse.v7.2.0+1.aarch64-apple-darwin.tar.gz/sha512/11ecce872aac1f30a3d4ce870920ebb03c7828d0fd740c3789d3f65c3f91ed3682372e9807b0593e2850ae9024450306451ee2e03866afee16b4169e4b5de1c6 -SuiteSparse.v7.2.0+1.aarch64-linux-gnu.tar.gz/md5/65e2e7ae54e94e00b306d17a1d08ed34 -SuiteSparse.v7.2.0+1.aarch64-linux-gnu.tar.gz/sha512/7714598448c6f98a7d931822f9ddb661a903342d4c8384648c1b7457511794ff95ad64266c9377a4a5856dcb1fb8864cb05eab1c7787fca58802473270313570 -SuiteSparse.v7.2.0+1.aarch64-linux-musl.tar.gz/md5/95eb68e02c04d075d6ecc974c3b44457 -SuiteSparse.v7.2.0+1.aarch64-linux-musl.tar.gz/sha512/1d7835106cd5baef701a3b670778a757d97ab9933f7da909e1e5521150f7e44bee30cf4dc7c1e9f57731366db0fca1b91d1cdfddbc53b7cc7457ca11534be6d7 -SuiteSparse.v7.2.0+1.armv6l-linux-gnueabihf.tar.gz/md5/5f627cc9c9d4d70e2f0d749e09926b1a -SuiteSparse.v7.2.0+1.armv6l-linux-gnueabihf.tar.gz/sha512/5ae4b79b418b45d954bfb578ac384afd6fff10a602d16d8b503997ba15251a7db0e12da66061ffd27c23b7459ff56b4a5d200c7c8aaa4a33c608a88752535c15 -SuiteSparse.v7.2.0+1.armv6l-linux-musleabihf.tar.gz/md5/61b5ee7e2b50665caf15e7c4f7353048 -SuiteSparse.v7.2.0+1.armv6l-linux-musleabihf.tar.gz/sha512/854c0165bceeb8202aeeaa16e6ba1f643e8cb9bf0561816cf2c44d1c7334ba7c376ee9e9085316439ca7e27dd4e37814f4916382096a5889c6bb656d22a7fb8d -SuiteSparse.v7.2.0+1.armv7l-linux-gnueabihf.tar.gz/md5/618b9687ce30e630a52f72a8f34cfb9f -SuiteSparse.v7.2.0+1.armv7l-linux-gnueabihf.tar.gz/sha512/add77a8842faf6515d94dc1d000784247d13f0211a9bb3f98edbc65b5f8994c0101940875fa050ca7a4605aaf33ec14daeaaf6b837673b3b4c600d4c5c0f4876 -SuiteSparse.v7.2.0+1.armv7l-linux-musleabihf.tar.gz/md5/b84d7f98b953772517689478d6bfc121 -SuiteSparse.v7.2.0+1.armv7l-linux-musleabihf.tar.gz/sha512/2c461eb23194bf61d3166abd8eb308dc643d865ff07466a88a580aa5a040f3c4fbfeadf7c78e1a3ce737fe0602909ff2b25be439741d36a780a260ccfdc6ed83 -SuiteSparse.v7.2.0+1.i686-linux-gnu.tar.gz/md5/3a38deddb5c1952584782378892d9579 -SuiteSparse.v7.2.0+1.i686-linux-gnu.tar.gz/sha512/5d6a9090c1c275c2bdcdc8d510c6372913109c1a775819354922c0d0afc2bc13a27950ed0e8cb8e05bc94d245220e322d93879054e5082814b7796b305981987 -SuiteSparse.v7.2.0+1.i686-linux-musl.tar.gz/md5/8076c50a73ab08ce0b9374956c2dbf29 -SuiteSparse.v7.2.0+1.i686-linux-musl.tar.gz/sha512/e54bcfe7eb9b266514a35a3c48676b7a792b59830e44bfcd5dfcf35be790f534cc31bd2e63ce4da1a22fcb3b0afb0ebebcc94f0e596806d6e832c3f68195cc5b -SuiteSparse.v7.2.0+1.i686-w64-mingw32.tar.gz/md5/45ac2448ac7a1a9c67114ea58e8ceacf -SuiteSparse.v7.2.0+1.i686-w64-mingw32.tar.gz/sha512/ed65a4071144c0096dfa768f26fd6216d4554c606a981658675d42f201865032972d9ce252dca5dc8a4a7ab0e33411c77bba9287e8013bdb0907ed6cb63d576f -SuiteSparse.v7.2.0+1.powerpc64le-linux-gnu.tar.gz/md5/64845ee8bb2f3f44a0837297e47e412d -SuiteSparse.v7.2.0+1.powerpc64le-linux-gnu.tar.gz/sha512/5f935e497db4ebbcdfb96603a7ee9c6c520d7f4df04f65952305ceff4271ab637079e9144b98044c5f159b4bed0963df8c95ed1578d2828f1a2356e6d34d7042 -SuiteSparse.v7.2.0+1.x86_64-apple-darwin.tar.gz/md5/fb8b00d4ca63004fe8ab8c159128e01f -SuiteSparse.v7.2.0+1.x86_64-apple-darwin.tar.gz/sha512/bcfb18c11be4b1147ff857e2ad0c881c9fc4ae16db8e88fb6e7e0448418c1fc4bff9ea8f1e6aa7202c277273c44c1afb3cc6c2bfcaa0735c7c09052f033248c7 -SuiteSparse.v7.2.0+1.x86_64-linux-gnu.tar.gz/md5/043594ee1cb90fd47b36acfa829fffb8 -SuiteSparse.v7.2.0+1.x86_64-linux-gnu.tar.gz/sha512/6a5bb3c85bb7e97b915f7dd40e8be1ed1bbbd5c756ef510deaecc8505b95bd42f3662f82e25c80055947060e0429e2ce427d4ff67b434acbe28d46b88279c65f -SuiteSparse.v7.2.0+1.x86_64-linux-musl.tar.gz/md5/67c6d3a7fd8635a43bd86b2b1e986978 -SuiteSparse.v7.2.0+1.x86_64-linux-musl.tar.gz/sha512/d46be3f60102fc69707c3e7cc3d693c7ecb4d4307c636afde61e5fab3c46fcf32564716a11d2cfe47b4e541422d5b6e13fbcc3e8749764527b4f4132e8ce17fc -SuiteSparse.v7.2.0+1.x86_64-unknown-freebsd.tar.gz/md5/124057f8455c9710fd1e6b4b4b469fb0 -SuiteSparse.v7.2.0+1.x86_64-unknown-freebsd.tar.gz/sha512/da0e56a8b1cf3967275cb64aea0b939d8982392f9ca1c3b268607e37c0b9bebbd456172c507c6dc2293989a0fe8df04ba1fea67442a4bb738cc8d894bea457a5 -SuiteSparse.v7.2.0+1.x86_64-w64-mingw32.tar.gz/md5/0f99c67d25c0fdd0f3c3e11f18925c43 -SuiteSparse.v7.2.0+1.x86_64-w64-mingw32.tar.gz/sha512/32fcd894cb4197970aa311f7bd12ccb91df7bbe27e389e793a2d3565c9c5c36c751f6dfa37e155cd2c2245be52f0a872dba032b78dc45c45d3fd7d7f2eeb773e +SuiteSparse-7.6.1.tar.gz/md5/566fa42263477c0b4e2f9abc2e01173e +SuiteSparse-7.6.1.tar.gz/sha512/cef8a8aa0624876dd8882eb3960dd22682eaae6c255e54d3c696d4b5760d04463a445560f956115eae06a04d9ae6b9227755ce0b375f8e55e979409b23fffd54 +SuiteSparse.v7.6.1+0.aarch64-apple-darwin.tar.gz/md5/ece087d641de1f41ba2deec6471c9b1c +SuiteSparse.v7.6.1+0.aarch64-apple-darwin.tar.gz/sha512/31402f1906f7af17deac749930a997aac0b90436cdd80b30ff2c5b8148feaf5077fff8480f33cd7aa4fac41f098535f256de8e26599b39d88676f505518e8e3a +SuiteSparse.v7.6.1+0.aarch64-linux-gnu.tar.gz/md5/94a615dac1459aa8f19e46e2954ae761 +SuiteSparse.v7.6.1+0.aarch64-linux-gnu.tar.gz/sha512/b02cc3b9c24af3e90ab9711b980c60faaa7fc7e01b7c076f3b2fb03189b87143ea91c29f8a816cf71370a6c42f58a794c7774da793157ba99dfbc0c3deb1d59f +SuiteSparse.v7.6.1+0.aarch64-linux-musl.tar.gz/md5/35e2ce86179b26d3129a2c63a81fb033 +SuiteSparse.v7.6.1+0.aarch64-linux-musl.tar.gz/sha512/5d512986178b8140eb99be1e319ec787396d8806be158181fe97d99b788f895b596dcc0f70457d46167c9391dbfeb484411ddbca40f13b943c6c29956382ca45 +SuiteSparse.v7.6.1+0.armv6l-linux-gnueabihf.tar.gz/md5/3616cbe1e8badbbb2c3671d092821d2c +SuiteSparse.v7.6.1+0.armv6l-linux-gnueabihf.tar.gz/sha512/aab2e86f2d25d305ae79595e02db4e04365e96d374915b390970f58272faa34eee0ce374ab3dc8fab7e6a89e1375e7ec1658bb5d485e3ab7ae3b7d3f03ae7461 +SuiteSparse.v7.6.1+0.armv6l-linux-musleabihf.tar.gz/md5/4db750ef1f63484a0e78e7e36bde5835 +SuiteSparse.v7.6.1+0.armv6l-linux-musleabihf.tar.gz/sha512/3ff7d45d98e5f99062d8ebead1ec26c72025d2623129da1999ea1e7efcae5cb4c5bc66bcb2e2fc7f7f31dc8b5824f4b8f6ea61147564056bb8daa45a5d53dee6 +SuiteSparse.v7.6.1+0.armv7l-linux-gnueabihf.tar.gz/md5/170612f9ebd79ea013bf13a884e7ef9f +SuiteSparse.v7.6.1+0.armv7l-linux-gnueabihf.tar.gz/sha512/d53e9984f64b2dcbe76ea1b7d24703dbead785b987782b73881869b07cf3513149b576d2c537684cda48ad7c4447048c885413a2861e878c2026cb83875aa5d7 +SuiteSparse.v7.6.1+0.armv7l-linux-musleabihf.tar.gz/md5/d9f67600dcd77960a858a1e243009392 +SuiteSparse.v7.6.1+0.armv7l-linux-musleabihf.tar.gz/sha512/1222c095f4b1368d22e8b0bf00753714afc60062f9550414f59f71d26cf29b414ed91bb6ed8d2aa5152ea875280f620db7d6e7aaa459088c9bdd95f3c5afa237 +SuiteSparse.v7.6.1+0.i686-linux-gnu.tar.gz/md5/f0c81cd95e8b06541541f676c020bb01 +SuiteSparse.v7.6.1+0.i686-linux-gnu.tar.gz/sha512/da0dff1b38ae2d20cac3af6d43a3f2b9dfb6c7ee0d120ff2500cffafe9b851a2ccfeb8857a831a306cc8ae61108cdd2090cb66abcd8071a3503e514176f4b848 +SuiteSparse.v7.6.1+0.i686-linux-musl.tar.gz/md5/d9584256d6c8fe5631727c6947233dab +SuiteSparse.v7.6.1+0.i686-linux-musl.tar.gz/sha512/a3e58f21dcae3e4eaa59cbc5846f4036be891753b1152423b5530ca2aba655c6283f982fe9d68e6304d6ef4629437e02f9850b142c8b7b62094a94125438cd80 +SuiteSparse.v7.6.1+0.i686-w64-mingw32.tar.gz/md5/92d639cf624c22db2f64f3cce94f1a9e +SuiteSparse.v7.6.1+0.i686-w64-mingw32.tar.gz/sha512/e26188190fe2fa95535df220f8cc6da0f54dbcbffb8c3d5fe9cf9dac9865825a483ba2e6ed7a603f49b94ca3b4973adbabbf2b9d96f4837b5e27a716d98ed9bf +SuiteSparse.v7.6.1+0.powerpc64le-linux-gnu.tar.gz/md5/8fb15955fb750dd38b18615855afd2f3 +SuiteSparse.v7.6.1+0.powerpc64le-linux-gnu.tar.gz/sha512/369e29a19f087982e74f083bc550b60e22705571ac29f0b92565c68c31068d32ee037c4af3114b1856558a4d35e338d575e2b8a382a14afbf8d37c5d8fa78117 +SuiteSparse.v7.6.1+0.x86_64-apple-darwin.tar.gz/md5/a52d9799e0c6a7fa704b4ef684f1151e +SuiteSparse.v7.6.1+0.x86_64-apple-darwin.tar.gz/sha512/89ce162803afdd2626a202eb6c83fb2ec79c606d46343ecaf4e15cb5b3c191a5303cc5571384433f8e70b3c548de2d274d660acaa144f9357fd890172fb3f08d +SuiteSparse.v7.6.1+0.x86_64-linux-gnu.tar.gz/md5/fb03e7f53715f17c07853151ccccc869 +SuiteSparse.v7.6.1+0.x86_64-linux-gnu.tar.gz/sha512/43490679d3c748850a9d052f92ef7ed7a002c18c014acc9c82e605576ae088c810a01bf05e7f0934dea9890a3cf6dcb45d76b1b2fc1249fc7f36d8b989a7f044 +SuiteSparse.v7.6.1+0.x86_64-linux-musl.tar.gz/md5/ee56d6cbf18b6e1da363d0ff3bdfd6a8 +SuiteSparse.v7.6.1+0.x86_64-linux-musl.tar.gz/sha512/31c33d29ad5cd153b37afed6560ba2903a77293ddb536f31effd217ec5953c1adc3217d1eb65a6bad78013e1da9fdb713c3d9537daae836cc560e648787a53ef +SuiteSparse.v7.6.1+0.x86_64-unknown-freebsd.tar.gz/md5/78d819e5712b2ac9d254fdeb9e72e733 +SuiteSparse.v7.6.1+0.x86_64-unknown-freebsd.tar.gz/sha512/5effdb7f1808d82ed73b1ad380e82016c46965146098c503c90fef3834b919d50b7419e6d9b856f462786d4314b6e34194612fbbd83e4b76d04e5f1a19b7f774 +SuiteSparse.v7.6.1+0.x86_64-w64-mingw32.tar.gz/md5/72876b211640159db2a0e149ca9e4bae +SuiteSparse.v7.6.1+0.x86_64-w64-mingw32.tar.gz/sha512/be80559f2fd19e1c0f8305e03dc7256202110e4e0c91757fc911e8c15e5e0706210d5323cd2c4fe759e35dc27d9469f40e5a49f0f562ca6fec03adad068962af diff --git a/deps/checksums/utf8proc b/deps/checksums/utf8proc index c1b2a6779e555..543c1805afbd8 100644 --- a/deps/checksums/utf8proc +++ b/deps/checksums/utf8proc @@ -1,2 +1,2 @@ -utf8proc-1cb28a66ca79a0845e99433fd1056257456cef8b.tar.gz/md5/aff37aadd1b02cad3259683e8a5f4543 -utf8proc-1cb28a66ca79a0845e99433fd1056257456cef8b.tar.gz/sha512/3ee433e5577e01f334aa4224275dfb7ee6ae7c785013df3eee6fc0488218d3bc895649811589edf57461c6520ad70437fbf6a376959a6a6f70bd920eb01c5001 +utf8proc-34db3f7954e9298e89f42641ac78e0450f80a70d.tar.gz/md5/e70e4fd2c914b4d4c0e3f0e2ca6c96d4 +utf8proc-34db3f7954e9298e89f42641ac78e0450f80a70d.tar.gz/sha512/0037f144e1150abd1b330d8a0c3a46c8352903acc9f4c8aad6bddd1370b19cc34551f8def58752cdff4eaace3efe54180bc11439a0e35c5ccad2fec4678c017e diff --git a/deps/checksums/zlib b/deps/checksums/zlib index 72fd884183e47..b6fc106747c67 100644 --- a/deps/checksums/zlib +++ b/deps/checksums/zlib @@ -1,34 +1,34 @@ -Zlib.v1.2.13+1.aarch64-apple-darwin.tar.gz/md5/bc44b2016065fb20cbd639b3cd5dbb88 -Zlib.v1.2.13+1.aarch64-apple-darwin.tar.gz/sha512/9cfecc16a29b0a13282846ed7d4c17c420b3f62379777d3fac61a8c9c4eeaf4214b826cd9f7479f480e951617b22c96e6ca2976a709345e16fbe7f81e9bdd83f -Zlib.v1.2.13+1.aarch64-linux-gnu.tar.gz/md5/a2d3265543017db03bc47b9d9778d99d -Zlib.v1.2.13+1.aarch64-linux-gnu.tar.gz/sha512/c8143445222e151d7f522a98ee8f2742571542f4e71d515e88086c9d7f27b952662ced93f40c795e0de42e3a07c0cb5e1d9d8e792347f3c068cb07ccc144a640 -Zlib.v1.2.13+1.aarch64-linux-musl.tar.gz/md5/c1f2a1c562f72c7aa4b228f57c2346d4 -Zlib.v1.2.13+1.aarch64-linux-musl.tar.gz/sha512/7ed89bc7696690c03617c7413f5456ff5a1caa0dd600880ae67132f6c9190672ae451a06d23956a1969be00bf5c8f29bfa4f5bc4ab646b3b375c350f67c993e5 -Zlib.v1.2.13+1.armv6l-linux-gnueabihf.tar.gz/md5/7dff966f7bc5dd2902fa9ce20444235b -Zlib.v1.2.13+1.armv6l-linux-gnueabihf.tar.gz/sha512/49e7b4a7c84996b697cf944b11ce06ce6064983a6a911c4539587385afa1e0119e3b1dbf816703a2c132acc90f7f114ec10631647638b59b14954382c1a82014 -Zlib.v1.2.13+1.armv6l-linux-musleabihf.tar.gz/md5/6982f19d2446559c0fd369afe84ebe4a -Zlib.v1.2.13+1.armv6l-linux-musleabihf.tar.gz/sha512/8f69dfb7fb91cd6f7c934e1acddd83f77c2ebcc1732553f41ae1adcb7805a3304d16062133ce5094a8aea18ff5eca5f7a2df5724ae5a5cb9137caee732c1bf36 -Zlib.v1.2.13+1.armv7l-linux-gnueabihf.tar.gz/md5/30579a91f8f1c96752fe9a82bc053523 -Zlib.v1.2.13+1.armv7l-linux-gnueabihf.tar.gz/sha512/64f6a0e66ee13b086609e0d070c8742de20052e1ef43da201be0007e478c65b2f0a28a3c19ca5be6537b7c8bbeb6a4b2886c15a1e47bb2bd1cfe9d5e1590a620 -Zlib.v1.2.13+1.armv7l-linux-musleabihf.tar.gz/md5/b052ad151dbc3bad78762bc06164d667 -Zlib.v1.2.13+1.armv7l-linux-musleabihf.tar.gz/sha512/b5d2de09a4d65d898cf9ba0db34327c712f42a78cd1fd0f1d77fd8798910502049be63ccfed23de5fe3b499d9e0fe3d4cbb07c72765fd54db275e92f8f1e4dc4 -Zlib.v1.2.13+1.i686-linux-gnu.tar.gz/md5/3074702010889f586b43aa3dbbda4ceb -Zlib.v1.2.13+1.i686-linux-gnu.tar.gz/sha512/92aa87c5aa3831155305276c2f0da091b5be4e8a396772e1a28650c2837ceb116dd2207329732b653a97c011abd7dd6ac1fc9574ac64cb3049ccd36fa6700748 -Zlib.v1.2.13+1.i686-linux-musl.tar.gz/md5/eff02476825ea7a53ab26b346d58f96e -Zlib.v1.2.13+1.i686-linux-musl.tar.gz/sha512/14b72607d524948198e999e3919ee01046c049b3ec441bc581c77642cf37c3d28cc3c5500a3c073d62e9b8dc1efc9661b23bb925ed9c80b5e69abaddbcb59115 -Zlib.v1.2.13+1.i686-w64-mingw32.tar.gz/md5/138cb27334b8f6f9e818131ac394bf43 -Zlib.v1.2.13+1.i686-w64-mingw32.tar.gz/sha512/07fbf4a21f6cb5a6120be253e5769b8bbdf60658f9f3705222307fbe203d8710de59fd3dab7a35714ebe1a7385600d4e1b01eae0b1addca47f9d8d862173e667 -Zlib.v1.2.13+1.powerpc64le-linux-gnu.tar.gz/md5/bc69de101d9159b22b7a334e2700faa6 -Zlib.v1.2.13+1.powerpc64le-linux-gnu.tar.gz/sha512/174eb4f154594d268d970d23eb6144dd2f6be41ddcfb9bc756b2ff48f0781ad0ed6571e2ead64dab0967da91517a02cd8db2b0e33a0bde9400103b5204f78e85 -Zlib.v1.2.13+1.x86_64-apple-darwin.tar.gz/md5/60279d648bce4801cd0e311ea95a6481 -Zlib.v1.2.13+1.x86_64-apple-darwin.tar.gz/sha512/921fc557317f87012d76f5d2cb0a7bbed29cdfdb2274ed6d37577f8e99dda2afb2a8dd4725d720eb8fb0a93c0d3afe68dd54fdd3a6e7cb07c15607a8aed72f82 -Zlib.v1.2.13+1.x86_64-linux-gnu.tar.gz/md5/b192d547d56124262e2ae744f385efd6 -Zlib.v1.2.13+1.x86_64-linux-gnu.tar.gz/sha512/c6dca3c0a713ef2e2296bc9e9afa75e103a4cc4f00b5c905ebc5cff688904d6a454f83ab5ef3b6c66bdf425daa2fcd25825e50a3534c0ff109b13affbb686179 -Zlib.v1.2.13+1.x86_64-linux-musl.tar.gz/md5/f2a466b38b2ff1c895f630982147a950 -Zlib.v1.2.13+1.x86_64-linux-musl.tar.gz/sha512/191261d37fc501591005bf680d76bf518da261252456c4fef1c12bc572f9200a855fbd1b125bb8ad10d803eedbc53d4c9d7a2861e9a35d629fb40f87e5306f5f -Zlib.v1.2.13+1.x86_64-unknown-freebsd.tar.gz/md5/36e53efdafdb8b8e1fb18817ea40c9ab -Zlib.v1.2.13+1.x86_64-unknown-freebsd.tar.gz/sha512/3067eace2a46b45c071dd1d2c046ab21e3f4a34b87346905bf4c00ef4ea57f41c4c30e32cbd5d4b60a560fa45aeeba7b0ce95566c0889f06f00f7a25de771cb1 -Zlib.v1.2.13+1.x86_64-w64-mingw32.tar.gz/md5/4c14730c6e89a3b05dcf352007f9c1e5 -Zlib.v1.2.13+1.x86_64-w64-mingw32.tar.gz/sha512/b6fbfe93d0c4fc6ebb740dbe0aebaa31aa5ecf352589452f6baac3ee28514531a1d0de9795634f97774ebb492dd23dee9f7865c2b8ba3f70c7f03cdc5430e85a -zlib-04f42ceca40f73e2978b50e93806c2a18c1281fc.tar.gz/md5/60a49c89b9409dd91c1b039266f7bd0c -zlib-04f42ceca40f73e2978b50e93806c2a18c1281fc.tar.gz/sha512/83122539da9399ce5f51c2ecbc38a627405334a9a6d53a024341353c1263a1e3aef7498f30ee281a49b3022be70e992eae475691e33da7a9c6a59b83207bd688 +Zlib.v1.3.1+0.aarch64-apple-darwin.tar.gz/md5/50b48e14f0b3578e3f398d130749a25d +Zlib.v1.3.1+0.aarch64-apple-darwin.tar.gz/sha512/d970e183035b3615b410f7b0da2c7a1d516234744491d65ed1ebc3800b55732f20bf00fcbb0cf91289b8b4660915282873fb23788896713cf8dfae2984a8fd85 +Zlib.v1.3.1+0.aarch64-linux-gnu.tar.gz/md5/ee42c0bae86fc39968c8cd6a77a801bf +Zlib.v1.3.1+0.aarch64-linux-gnu.tar.gz/sha512/5d21cbeab03d44008c6cbad114d45c917ebee2fe98de6b19686f4f6ba1fc67eeedf968b94ed1c2d4efb89e93be9efa342bcc8a57cb8a505085d177abae14bc2d +Zlib.v1.3.1+0.aarch64-linux-musl.tar.gz/md5/9091d1288736b218f7b016791dc1a9c8 +Zlib.v1.3.1+0.aarch64-linux-musl.tar.gz/sha512/b49cbfe734beb2af9ef8e847542d006765345cbb08aee0854779e35e03c98df25c93539b046547c6b66029987c49499ddf6cb207824b1e376900bfceaa79691a +Zlib.v1.3.1+0.armv6l-linux-gnueabihf.tar.gz/md5/b686c85047b7dad2c2f08d1d16e7978a +Zlib.v1.3.1+0.armv6l-linux-gnueabihf.tar.gz/sha512/511fda619519dccedb264988e3b59a0e0fbf8f73d3ae290f238346209ebc0202a22f945257cea19afef64246574285e0322901a46bb48d7b48364c1e2eacd801 +Zlib.v1.3.1+0.armv6l-linux-musleabihf.tar.gz/md5/374be5cb926876f3f0492cfe0e193220 +Zlib.v1.3.1+0.armv6l-linux-musleabihf.tar.gz/sha512/4d3a2cc0c7c48146e63ed098da5a5acad75517197adc965550c123f7f8bcee0811a27be76fa37b6b0515eee4b5ba1c1a85c854e7b23bea36b5e21671805bedce +Zlib.v1.3.1+0.armv7l-linux-gnueabihf.tar.gz/md5/9febbc6a3d492e34c9ed53c95f3b799f +Zlib.v1.3.1+0.armv7l-linux-gnueabihf.tar.gz/sha512/4cee0e2cf572eb91028a09ef356e1aa6360949e046ceec03bd37574295ddcc4a7cefca9276f7565f152697d55b35f62af2ab107cdbf402b42846818629fea9c7 +Zlib.v1.3.1+0.armv7l-linux-musleabihf.tar.gz/md5/5d0d59a6cbbd1e63193ba6f7dbb755f9 +Zlib.v1.3.1+0.armv7l-linux-musleabihf.tar.gz/sha512/ee3f48b354168342ef63509b19a26aca3301fb3e5f4f6898afe2d3b44ee3380515efd6ced5d4e06e69736d851d19352deb9595bad82c051caccaee8c55e629d8 +Zlib.v1.3.1+0.i686-linux-gnu.tar.gz/md5/834350a64b2302a9caf0250a8f6068e5 +Zlib.v1.3.1+0.i686-linux-gnu.tar.gz/sha512/63dc158c4dfc42db97875893fcdd9784d9487af855bd576dbe04d1b967ad64510222df74a4cfb1b7e67386329d2a5686d7931b81720883fc1924f0d706a0a711 +Zlib.v1.3.1+0.i686-linux-musl.tar.gz/md5/e4f96efdeafa3d74c7c348059a8dc46a +Zlib.v1.3.1+0.i686-linux-musl.tar.gz/sha512/b47a571d94887ddcab8d7b50c6dce3afed3f56513a9d1859feaefebfad4a271d428b440df1d19ef3c2ed01ca4c8fd121ffc1572f5e252f27d0930f616cb47f18 +Zlib.v1.3.1+0.i686-w64-mingw32.tar.gz/md5/6bc27bd7dbbe17243dbbfaff225d3b23 +Zlib.v1.3.1+0.i686-w64-mingw32.tar.gz/sha512/5777661682831519875fffbb114c62596bf7bdb62011667c0f3dc5da9910e14de2285200a0a05187769b9c68c99b07024caafc16fef03c76e96e618f77149790 +Zlib.v1.3.1+0.powerpc64le-linux-gnu.tar.gz/md5/27dcad8557994cfd89d6fa7072bb843c +Zlib.v1.3.1+0.powerpc64le-linux-gnu.tar.gz/sha512/3b388dd286b273881d4344cff61c7da316c2bd2bab93072bf47ce4cb1cf9662158351b8febb0d5b1f8dfd9bc73cd32f7cae37fdd19b0ca91531bd3375df104bb +Zlib.v1.3.1+0.x86_64-apple-darwin.tar.gz/md5/9187319377191ae8b34162b375baa5db +Zlib.v1.3.1+0.x86_64-apple-darwin.tar.gz/sha512/895203434f161926978be52a223dd49a99454651a79c1c5e0529fa064f3f7ac2d7a069fed47a577b32523df22afadd6eb97d564dbd59c5d67ed90083add13c00 +Zlib.v1.3.1+0.x86_64-linux-gnu.tar.gz/md5/55d4d982d60cb643aa8688eb031b07ee +Zlib.v1.3.1+0.x86_64-linux-gnu.tar.gz/sha512/d8f94d22ffc37df027de23b2408c2000014c8b7b6c8539feca669ac1f2dbbe1679ca534c3be4d32c90fe38bbba27c795689226962fb067346b5ca213e64b9c4b +Zlib.v1.3.1+0.x86_64-linux-musl.tar.gz/md5/95d735bba178da4b8bee23903419919c +Zlib.v1.3.1+0.x86_64-linux-musl.tar.gz/sha512/370370f08133a720e3fbedcc434f102dc95225fda3ec8a399e782851bd4be57fb2b64a3ed62dc0559fb0c58d2e28db9b9e960efafd940982e4cb6652be0e81f1 +Zlib.v1.3.1+0.x86_64-unknown-freebsd.tar.gz/md5/df158f50fdb8ac1179fe6dad3bc62713 +Zlib.v1.3.1+0.x86_64-unknown-freebsd.tar.gz/sha512/f4ba4ccfeaf3fd2e172a2d5b3b1ae083ee9854022e71e062e29423e4179cb1fc49b2b99df49b3f5f231e2a0c5becc59b89644e9dcaf0fda9c97e83af7ea1c25d +Zlib.v1.3.1+0.x86_64-w64-mingw32.tar.gz/md5/9cc735c54ddf5d1ea0db60e05d6631ea +Zlib.v1.3.1+0.x86_64-w64-mingw32.tar.gz/sha512/8a2fd20944866cb7f717517ea0b80a134466e063f85bec87ffba56ca844f983f91060dfdc65f8faee1981d7329348c827b723aaad4fea36041e710b9e35c43de +zlib-51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf.tar.gz/md5/7ce1b2766499af7d948130113b649028 +zlib-51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf.tar.gz/sha512/79d032b8c93260ce6b9806f2289cdccce67e9d80865b5bb39ac46dadffc8ee009da51c551eead59c56249c7adfa164c1d5ebcf2b10a8645e0b11b5650176cb24 diff --git a/deps/clang.version b/deps/clang.version index 4c5f1ecc02062..a950bc32aca6b 100644 --- a/deps/clang.version +++ b/deps/clang.version @@ -1,4 +1,4 @@ ## jll artifact # Clang (paired with LLVM, only here as a JLL download) CLANG_JLL_NAME := Clang -CLANG_JLL_VER := 15.0.7+8 +CLANG_JLL_VER := 16.0.6+4 diff --git a/deps/csl.mk b/deps/csl.mk index 37956ba5c3505..fefe71a5e871c 100644 --- a/deps/csl.mk +++ b/deps/csl.mk @@ -1,13 +1,13 @@ # Interrogate the fortran compiler (which is always GCC based) on where it is keeping its libraries STD_LIB_PATH := $(shell LANG=C $(FC) -print-search-dirs 2>/dev/null | grep '^programs: =' | sed -e "s/^programs: =//") -STD_LIB_PATH += :$(shell LANG=C $(FC) -print-search-dirs 2>/dev/null | grep '^libraries: =' | sed -e "s/^libraries: =//") -ifneq (,$(findstring CYGWIN,$(BUILD_OS))) # the cygwin-mingw32 compiler lies about it search directory paths +STD_LIB_PATH += $(PATHSEP)$(shell LANG=C $(FC) -print-search-dirs 2>/dev/null | grep '^libraries: =' | sed -e "s/^libraries: =//") +ifeq ($(BUILD_OS),WINNT) # the mingw compiler lies about it search directory paths STD_LIB_PATH := $(shell echo '$(STD_LIB_PATH)' | sed -e "s!/lib/!/bin/!g") endif # Given a colon-separated list of paths in $(2), find the location of the library given in $(1) define pathsearch -$(firstword $(wildcard $(addsuffix /$(1),$(subst :, ,$(2))))) +$(firstword $(wildcard $(addsuffix /$(1),$(subst $(PATHSEP), ,$(2))))) endef # CSL bundles lots of system compiler libraries, and while it is quite bleeding-edge @@ -104,4 +104,22 @@ distclean-csl: clean-csl else $(eval $(call bb-install,csl,CSL,true)) +ifeq ($(OS),WINNT) +install-csl: + mkdir -p $(build_private_libdir)/ + cp -a $(build_libdir)/gcc/$(BB_TRIPLET)/13/libgcc_s.a $(build_private_libdir)/ + cp -a $(build_libdir)/gcc/$(BB_TRIPLET)/13/libgcc.a $(build_private_libdir)/ + cp -a $(build_libdir)/gcc/$(BB_TRIPLET)/13/libmsvcrt.a $(build_private_libdir)/ + cp -a $(build_libdir)/gcc/$(BB_TRIPLET)/13/libssp.dll.a $(build_private_libdir)/ + cp -a $(build_libdir)/gcc/$(BB_TRIPLET)/13/libssp.dll.a $(build_libdir)/ +endif +endif +ifeq ($(OS),WINNT) +uninstall-csl: uninstall-gcc-libraries +uninstall-gcc-libraries: + -rm -f $(build_private_libdir)/libgcc_s.a + -rm -f $(build_private_libdir)/libgcc.a + -rm -f $(build_private_libdir)/libmsvcrt.a + -rm -f $(build_private_libdir)/libssp.dll.a + -rm -f $(build_libdir)/libssp.dll.a endif diff --git a/deps/curl.mk b/deps/curl.mk index a063dfe07fba0..444334b581fed 100644 --- a/deps/curl.mk +++ b/deps/curl.mk @@ -14,7 +14,7 @@ $(BUILDDIR)/curl-$(CURL_VER)/build-configured: | $(build_prefix)/manifest/nghttp endif ifneq ($(USE_BINARYBUILDER_CURL),1) -CURL_LDFLAGS := $(RPATH_ESCAPED_ORIGIN) +CURL_LDFLAGS := $(RPATH_ESCAPED_ORIGIN) -Wl,-rpath,$(build_shlibdir) # On older Linuces (those that use OpenSSL < 1.1) we include `libpthread` explicitly. # It doesn't hurt to include it explicitly elsewhere, so we do so. diff --git a/deps/curl.version b/deps/curl.version index 288347a9935ed..5ecfcc2b642a1 100644 --- a/deps/curl.version +++ b/deps/curl.version @@ -3,4 +3,4 @@ CURL_JLL_NAME := LibCURL ## source build -CURL_VER := 8.4.0 +CURL_VER := 8.6.0 diff --git a/deps/dsfmt.version b/deps/dsfmt.version index bbb63417f46cd..d81db2d10ff09 100644 --- a/deps/dsfmt.version +++ b/deps/dsfmt.version @@ -1,5 +1,7 @@ +# -*- makefile -*- + ## jll artifact DSFMT_JLL_NAME := dSFMT ## source build -DSFMT_VER := 2.2.4 +DSFMT_VER := 2.2.5 diff --git a/deps/gmp.mk b/deps/gmp.mk index 491d649e9202f..23075c861cd35 100644 --- a/deps/gmp.mk +++ b/deps/gmp.mk @@ -35,34 +35,17 @@ $(SRCCACHE)/gmp-$(GMP_VER)/source-extracted: $(SRCCACHE)/gmp-$(GMP_VER).tar.bz2 checksum-gmp: $(SRCCACHE)/gmp-$(GMP_VER).tar.bz2 $(JLCHECKSUM) $< -# Apply fix to avoid using Apple ARM reserved register X18 -# Necessary for version 6.2.1, remove after next gmp release -$(SRCCACHE)/gmp-$(GMP_VER)/gmp-HG-changeset.patch-applied: $(SRCCACHE)/gmp-$(GMP_VER)/source-extracted - cd $(dir $@) && \ - patch -p1 -f < $(SRCDIR)/patches/gmp-HG-changeset.patch - echo 1 > $@ - -$(SRCCACHE)/gmp-$(GMP_VER)/gmp-exception.patch-applied: $(SRCCACHE)/gmp-$(GMP_VER)/gmp-HG-changeset.patch-applied +$(SRCCACHE)/gmp-$(GMP_VER)/gmp-exception.patch-applied: $(SRCCACHE)/gmp-$(GMP_VER)/source-extracted cd $(dir $@) && \ patch -p1 -f < $(SRCDIR)/patches/gmp-exception.patch echo 1 > $@ -$(SRCCACHE)/gmp-$(GMP_VER)/gmp_alloc_overflow_func.patch-applied: $(SRCCACHE)/gmp-$(GMP_VER)/gmp-exception.patch-applied - cd $(dir $@) && \ - patch -p1 -f < $(SRCDIR)/patches/gmp_alloc_overflow_func.patch - echo 1 > $@ - -$(SRCCACHE)/gmp-$(GMP_VER)/gmp-CVE-2021-43618.patch-applied: $(SRCCACHE)/gmp-$(GMP_VER)/gmp_alloc_overflow_func.patch-applied - cd $(dir $@) && \ - patch -p1 -f < $(SRCDIR)/patches/gmp-CVE-2021-43618.patch - echo 1 > $@ - -$(SRCCACHE)/gmp-$(GMP_VER)/gmp-more_alloc_overflow.patch-applied: $(SRCCACHE)/gmp-$(GMP_VER)/gmp-CVE-2021-43618.patch-applied +$(SRCCACHE)/gmp-$(GMP_VER)/gmp-alloc_overflow.patch-applied: $(SRCCACHE)/gmp-$(GMP_VER)/gmp-exception.patch-applied cd $(dir $@) && \ - patch -p1 -f < $(SRCDIR)/patches/gmp-more_alloc_overflow.patch + patch -p1 -f < $(SRCDIR)/patches/gmp-alloc_overflow.patch echo 1 > $@ -$(SRCCACHE)/gmp-$(GMP_VER)/source-patched: $(SRCCACHE)/gmp-$(GMP_VER)/gmp-more_alloc_overflow.patch-applied +$(SRCCACHE)/gmp-$(GMP_VER)/source-patched: $(SRCCACHE)/gmp-$(GMP_VER)/gmp-alloc_overflow.patch-applied echo 1 > $@ $(BUILDDIR)/gmp-$(GMP_VER)/build-configured: $(SRCCACHE)/gmp-$(GMP_VER)/source-patched diff --git a/deps/gmp.version b/deps/gmp.version index f77cac5906cea..3b6659faea7b7 100644 --- a/deps/gmp.version +++ b/deps/gmp.version @@ -1,5 +1,6 @@ +# -*- makefile -*- ## jll artifact GMP_JLL_NAME := GMP ## source build -GMP_VER := 6.2.1 +GMP_VER := 6.3.0 diff --git a/deps/ittapi.mk b/deps/ittapi.mk index 1a47c3ae89390..b62b981a34ddb 100644 --- a/deps/ittapi.mk +++ b/deps/ittapi.mk @@ -40,4 +40,5 @@ fastcheck-ittapi: #none check-ittapi: #none clean-ittapi: - -rm -f $(BUILDDIR)/$(ITTAPI_SRC_DIR)/build-compiled $(build_libdir)/libopenlibm.a + -rm -f $(BUILDDIR)/$(ITTAPI_SRC_DIR)/build-compiled + -rm -f $(build_libdir)/libittnotify.a $(build_libdir)/libjitprofiling.a diff --git a/deps/libgit2.mk b/deps/libgit2.mk index d68a7a80d6d5b..b65ac022885a3 100644 --- a/deps/libgit2.mk +++ b/deps/libgit2.mk @@ -33,8 +33,12 @@ LIBGIT2_OPTS += -DBUILD_TESTS=OFF -DDLLTOOL=`which $(CROSS_COMPILE)dlltool` LIBGIT2_OPTS += -DCMAKE_FIND_ROOT_PATH=/usr/$(XC_HOST) -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY endif endif +ifeq ($(OS),OpenBSD) +# iconv.h is third-party +LIBGIT2_OPTS += -DCMAKE_C_FLAGS="-I/usr/local/include" +endif -ifneq (,$(findstring $(OS),Linux FreeBSD)) +ifneq (,$(findstring $(OS),Linux FreeBSD OpenBSD)) LIBGIT2_OPTS += -DUSE_HTTPS="mbedTLS" -DUSE_SHA1="CollisionDetection" -DCMAKE_INSTALL_RPATH="\$$ORIGIN" endif diff --git a/deps/libgit2.version b/deps/libgit2.version index 9bd56f1bd0001..d51beb34c27f5 100644 --- a/deps/libgit2.version +++ b/deps/libgit2.version @@ -3,11 +3,12 @@ LIBGIT2_JLL_NAME := LibGit2 ## source build -LIBGIT2_BRANCH=v1.7.1 -LIBGIT2_SHA1=a2bde63741977ca0f4ef7db2f609df320be67a08 +LIBGIT2_BRANCH=v1.8.0 +LIBGIT2_SHA1=d74d491481831ddcd23575d376e56d2197e95910 ## Other deps # Specify the version of the Mozilla CA Certificate Store to obtain. # The versions of cacert.pem are identified by the date (YYYY-MM-DD) of their changes. # See https://curl.haxx.se/docs/caextract.html for more details. -MOZILLA_CACERT_VERSION := 2023-01-10 +# Keep in sync with `stdlib/MozillaCACerts_jll/Project.toml`. +MOZILLA_CACERT_VERSION := 2024-03-11 diff --git a/deps/libssh2.mk b/deps/libssh2.mk index 3f9738515e4a1..c293d8309d2bc 100644 --- a/deps/libssh2.mk +++ b/deps/libssh2.mk @@ -11,6 +11,10 @@ endif LIBSSH2_OPTS := $(CMAKE_COMMON) -DBUILD_SHARED_LIBS=ON -DBUILD_EXAMPLES=OFF \ -DCMAKE_BUILD_TYPE=Release +ifneq ($(fPIC),) +LIBSSH2_OPTS += -DCMAKE_C_FLAGS="-fPIC" +endif + ifeq ($(OS),WINNT) LIBSSH2_OPTS += -DCRYPTO_BACKEND=WinCNG -DENABLE_ZLIB_COMPRESSION=OFF ifeq ($(BUILD_OS),WINNT) @@ -20,7 +24,7 @@ else LIBSSH2_OPTS += -DCRYPTO_BACKEND=mbedTLS -DENABLE_ZLIB_COMPRESSION=OFF endif -ifneq (,$(findstring $(OS),Linux FreeBSD)) +ifneq (,$(findstring $(OS),Linux FreeBSD OpenBSD)) LIBSSH2_OPTS += -DCMAKE_INSTALL_RPATH="\$$ORIGIN" endif diff --git a/deps/libsuitesparse.mk b/deps/libsuitesparse.mk index 10161f08ecf5b..0207c5ef7c099 100644 --- a/deps/libsuitesparse.mk +++ b/deps/libsuitesparse.mk @@ -3,33 +3,45 @@ include $(SRCDIR)/libsuitesparse.version ifneq ($(USE_BINARYBUILDER_LIBSUITESPARSE), 1) -LIBSUITESPARSE_PROJECTS := AMD BTF CAMD CCOLAMD COLAMD CHOLMOD LDL KLU UMFPACK RBio SPQR -LIBSUITESPARSE_LIBS := $(addsuffix .*$(SHLIB_EXT)*,suitesparseconfig amd btf camd ccolamd colamd cholmod klu ldl umfpack rbio spqr) +LIBSUITESPARSE_PROJECTS := "suitesparse_config;amd;btf;camd;ccolamd;colamd;cholmod;klu;ldl;umfpack;rbio;spqr" +LIBSUITESPARSE_LIBS := $(addsuffix .*$(SHLIB_EXT)*,suitesparseconfig $(subst ;, ,$(LIBSUITESPARSE_PROJECTS))) + +ifeq ($(OS),WINNT) +BLAS_LIB_NAME_NO_EXT:=blastrampoline-5 +else +BLAS_LIB_NAME_NO_EXT:=blastrampoline +endif LIBSUITESPARSE_CMAKE_FLAGS := $(CMAKE_COMMON) \ -DCMAKE_BUILD_TYPE=Release \ - -DENABLE_CUDA=0 \ - -DNFORTRAN=1 \ - -DNOPENMP=1 \ - -DNPARTITION=0 \ - -DNSTATIC=1 \ + -DBUILD_STATIC_LIBS=OFF \ + -DBUILD_TESTING=OFF \ + -DSUITESPARSE_ENABLE_PROJECTS=$(LIBSUITESPARSE_PROJECTS) \ + -DSUITESPARSE_DEMOS=OFF \ + -DSUITESPARSE_USE_STRICT=ON \ + -DSUITESPARSE_USE_CUDA=OFF \ + -DSUITESPARSE_USE_FORTRAN=OFF \ + -DSUITESPARSE_USE_OPENMP=OFF \ + -DCHOLMOD_PARTITION=ON \ -DBLAS_FOUND=1 \ - -DBLAS_LIBRARIES="$(build_shlibdir)/libblastrampoline.$(SHLIB_EXT)" \ - -DBLAS_LINKER_FLAGS="blastrampoline" \ - -DBLAS_UNDERSCORE=ON \ - -DBLA_VENDOR="blastrampoline" \ - -DBLAS64_SUFFIX="_64" \ - -DALLOW_64BIT_BLAS=ON \ - -DLAPACK_FOUND=1 \ - -DLAPACK_LIBRARIES="$(build_shlibdir)/libblastrampoline.$(SHLIB_EXT)" \ - -DLAPACK_LINKER_FLAGS="blastrampoline" - -ifneq (,$(findstring $(OS),Linux FreeBSD)) + -DBLAS_LIBRARIES="$(build_shlibdir)/lib$(BLAS_LIB_NAME_NO_EXT).$(SHLIB_EXT)" \ + -DBLAS_LINKER_FLAGS="$(BLAS_LIB_NAME_NO_EXT)" \ + -DBLA_VENDOR="$(BLAS_LIB_NAME_NO_EXT)" \ + -DLAPACK_LIBRARIES="$(build_shlibdir)/lib$(BLAS_LIB_NAME_NO_EXT).$(SHLIB_EXT)" \ + -DLAPACK_LINKER_FLAGS="${BLAS_LIB_NAME_NO_EXT}" + +ifeq ($(BINARY),64) +LIBSUITESPARSE_CMAKE_FLAGS += -DBLAS64_SUFFIX="_64" -DSUITESPARSE_USE_64BIT_BLAS=YES +else +LIBSUITESPARSE_CMAKE_FLAGS += -DSUITESPARSE_USE_64BIT_BLAS=NO +endif + +ifneq (,$(findstring $(OS),Linux FreeBSD OpenBSD)) LIBSUITESPARSE_CMAKE_FLAGS += -DCMAKE_INSTALL_RPATH="\$$ORIGIN" endif $(SRCCACHE)/SuiteSparse-$(LIBSUITESPARSE_VER).tar.gz: | $(SRCCACHE) - $(JLDOWNLOAD) $@ https://github.com/Wimmerer/SuiteSparse/archive/v$(LIBSUITESPARSE_VER).tar.gz + $(JLDOWNLOAD) $@ https://github.com/DrTimothyAldenDavis/SuiteSparse/archive/v$(LIBSUITESPARSE_VER).tar.gz $(BUILDDIR)/SuiteSparse-$(LIBSUITESPARSE_VER)/source-extracted: $(SRCCACHE)/SuiteSparse-$(LIBSUITESPARSE_VER).tar.gz $(JLCHECKSUM) $< @@ -40,17 +52,15 @@ $(BUILDDIR)/SuiteSparse-$(LIBSUITESPARSE_VER)/source-extracted: $(SRCCACHE)/Suit checksum-libsuitesparse: $(SRCCACHE)/SuiteSparse-$(LIBSUITESPARSE_VER).tar.gz $(JLCHECKSUM) $< +$(BUILDDIR)/SuiteSparse-$(LIBSUITESPARSE_VER)/source-patched: $(BUILDDIR)/SuiteSparse-$(LIBSUITESPARSE_VER)/source-extracted + echo 1 > $@ + $(BUILDDIR)/SuiteSparse-$(LIBSUITESPARSE_VER)/build-compiled: | $(build_prefix)/manifest/blastrampoline -$(BUILDDIR)/SuiteSparse-$(LIBSUITESPARSE_VER)/build-compiled: $(BUILDDIR)/SuiteSparse-$(LIBSUITESPARSE_VER)/source-extracted - cd $(dir $<); \ - for PROJ in SuiteSparse_config $(LIBSUITESPARSE_PROJECTS); do \ - cd $${PROJ}/build || exit 1; \ - $(CMAKE) .. $(LIBSUITESPARSE_CMAKE_FLAGS) || exit 1; \ - make || exit 1; \ - make install || exit 1; \ - cd ../..; \ - done +$(BUILDDIR)/SuiteSparse-$(LIBSUITESPARSE_VER)/build-compiled: $(BUILDDIR)/SuiteSparse-$(LIBSUITESPARSE_VER)/source-patched + cd $(dir $<) && $(CMAKE) . $(LIBSUITESPARSE_CMAKE_FLAGS) + $(MAKE) -C $(dir $<) + $(MAKE) -C $(dir $<) install echo 1 > $@ ifeq ($(OS),WINNT) @@ -59,7 +69,7 @@ else LIBSUITESPARSE_SHLIB_ENV:=LD_LIBRARY_PATH="$(build_shlibdir)" endif $(BUILDDIR)/SuiteSparse-$(LIBSUITESPARSE_VER)/build-checked: $(BUILDDIR)/SuiteSparse-$(LIBSUITESPARSE_VER)/build-compiled - for PROJ in $(LIBSUITESPARSE_PROJECTS); do \ + for PROJ in $(shell echo $(subst ;, ,$(LIBSUITESPARSE_PROJECTS))); do \ $(LIBSUITESPARSE_SHLIB_ENV) $(MAKE) -C $(dir $<)$${PROJ} default $(LIBSUITESPARSE_MFLAGS) || exit 1; \ done echo 1 > $@ diff --git a/deps/libsuitesparse.version b/deps/libsuitesparse.version index 867e52304477c..98e27d9125700 100644 --- a/deps/libsuitesparse.version +++ b/deps/libsuitesparse.version @@ -1,7 +1,8 @@ +# -*- makefile -*- + ## jll artifact LIBSUITESPARSE_JLL_NAME := SuiteSparse ## source build -LIBSUITESPARSE_VER := 7.2.0 -LIBSUITESPARSE_BRANCH=guard-CXX_Standard -LIBSUITESPARSE_SHA1=1b4edf467637dbf33a26eee9a6c20afa40c7c5ea +LIBSUITESPARSE_VER := 7.6.1 +LIBSUITESPARSE_SHA1=d4dad6c1d0b5cb3e7c5d7d01ef55653713567662 diff --git a/deps/libuv.version b/deps/libuv.version index 01bf4fecc6dc6..bc8e2e57c9517 100644 --- a/deps/libuv.version +++ b/deps/libuv.version @@ -3,5 +3,5 @@ LIBUV_JLL_NAME := LibUV ## source build LIBUV_VER := 2 -LIBUV_BRANCH=julia-uv2-1.44.2 -LIBUV_SHA1=2723e256e952be0b015b3c0086f717c3d365d97e +LIBUV_BRANCH=julia-uv2-1.48.0 +LIBUV_SHA1=ca3a5a431a1c37859b6508e6b2a288092337029a diff --git a/deps/libwhich.version b/deps/libwhich.version index 0fa713024ef99..09ea0197d10c1 100644 --- a/deps/libwhich.version +++ b/deps/libwhich.version @@ -1,2 +1,2 @@ LIBWHICH_BRANCH=master -LIBWHICH_SHA1=81e9723c0273d78493dc8c8ed570f68d9ce7e89e +LIBWHICH_SHA1=99a0ea12689e41164456dba03e93bc40924de880 diff --git a/deps/lld.version b/deps/lld.version index 64e8aca55fe84..949577bdacae2 100644 --- a/deps/lld.version +++ b/deps/lld.version @@ -1,3 +1,3 @@ ## jll artifact LLD_JLL_NAME := LLD -LLD_JLL_VER := 15.0.7+8 +LLD_JLL_VER := 16.0.6+4 diff --git a/deps/llvm-tools.version b/deps/llvm-tools.version index da7a4ec18bdcc..8191c8742dcbb 100644 --- a/deps/llvm-tools.version +++ b/deps/llvm-tools.version @@ -1,5 +1,5 @@ ## jll artifact # LLVM_tools (downloads LLVM_jll to get things like `lit` and `opt`) LLVM_TOOLS_JLL_NAME := LLVM -LLVM_TOOLS_JLL_VER := 15.0.7+8 -LLVM_TOOLS_ASSERT_JLL_VER := 15.0.7+8 +LLVM_TOOLS_JLL_VER := 16.0.6+4 +LLVM_TOOLS_ASSERT_JLL_VER := 16.0.6+4 diff --git a/deps/llvm.mk b/deps/llvm.mk index a06db1fb0781b..3e3ea4e79c24e 100644 --- a/deps/llvm.mk +++ b/deps/llvm.mk @@ -133,7 +133,7 @@ endif # USE_PERF_JITEVENTS ifeq ($(BUILD_LLDB),1) ifeq ($(USECLANG),0) -LLVM_CXXFLAGS += -std=c++0x +LLVM_CXXFLAGS += -std=c++17 endif # USECLANG ifeq ($(LLDB_DISABLE_PYTHON),1) LLVM_CXXFLAGS += -DLLDB_DISABLE_PYTHON @@ -210,6 +210,9 @@ LLVM_CMAKE += -DCMAKE_EXE_LINKER_FLAGS="$(LLVM_LDFLAGS)" \ LLVM_CMAKE += -DLLVM_VERSION_SUFFIX:STRING="jl" LLVM_CMAKE += -DLLVM_SHLIB_SYMBOL_VERSION:STRING="JL_LLVM_$(LLVM_VER_SHORT)" +# Change the default bug report URL to Julia's issue tracker +LLVM_CMAKE += -DBUG_REPORT_URL="https://github.com/julialang/julia" + # Apply version-specific LLVM patches sequentially LLVM_PATCH_PREV := define LLVM_PATCH diff --git a/deps/llvm.version b/deps/llvm.version index 52a3a11e1c145..7e74cf2e4fd65 100644 --- a/deps/llvm.version +++ b/deps/llvm.version @@ -2,14 +2,14 @@ ## jll artifact LLVM_JLL_NAME := libLLVM -LLVM_ASSERT_JLL_VER := 15.0.7+8 +LLVM_ASSERT_JLL_VER := 16.0.6+4 ## source build # Version number of LLVM -LLVM_VER := 15.0.7 +LLVM_VER := 16.0.6 # Git branch name in `LLVM_GIT_URL` repository -LLVM_BRANCH=julia-15.0.7-7 +LLVM_BRANCH=julia-16.0.6-2 # Git ref in `LLVM_GIT_URL` repository -LLVM_SHA1=julia-15.0.7-7 +LLVM_SHA1=julia-16.0.6-2 ## Following options are used to automatically fetch patchset from Julia's fork. This is ## useful if you want to build an external LLVM while still applying Julia's patches. @@ -18,6 +18,6 @@ LLVM_APPLY_JULIA_PATCHES := 0 # GitHub repository to use for fetching the Julia patches to apply to LLVM source code. LLVM_JULIA_DIFF_GITHUB_REPO := https://github.com/llvm/llvm-project # Base GitHub ref for generating the diff. -LLVM_BASE_REF := llvm:llvmorg-15.0.7 +LLVM_BASE_REF := llvm:llvmorg-16.0.6 # Julia fork's GitHub ref for generating the diff. -LLVM_JULIA_REF := JuliaLang:julia-15.0.7-7 +LLVM_JULIA_REF := JuliaLang:julia-16.0.6-0 diff --git a/deps/mbedtls.mk b/deps/mbedtls.mk index b4147c2c2684e..39cf817d70658 100644 --- a/deps/mbedtls.mk +++ b/deps/mbedtls.mk @@ -13,7 +13,7 @@ ifeq ($(BUILD_OS),WINNT) MBEDTLS_OPTS += -G"MSYS Makefiles" endif -ifneq (,$(findstring $(OS),Linux FreeBSD)) +ifneq (,$(findstring $(OS),Linux FreeBSD OpenBSD)) MBEDTLS_OPTS += -DCMAKE_INSTALL_RPATH="\$$ORIGIN" endif diff --git a/deps/mbedtls.version b/deps/mbedtls.version index f262476af1684..ef88b8109f68c 100644 --- a/deps/mbedtls.version +++ b/deps/mbedtls.version @@ -1,5 +1,7 @@ +# -*- makefile -*- + ## jll artifact MBEDTLS_JLL_NAME := MbedTLS ## source build -MBEDTLS_VER := 2.28.2 +MBEDTLS_VER := 2.28.6 diff --git a/deps/mpfr.version b/deps/mpfr.version index e4f1c8a45aeb0..ec109e181ecdc 100644 --- a/deps/mpfr.version +++ b/deps/mpfr.version @@ -2,4 +2,4 @@ MPFR_JLL_NAME := MPFR ## source build -MPFR_VER := 4.2.0 +MPFR_VER := 4.2.1 diff --git a/deps/nghttp2.version b/deps/nghttp2.version index 200e08bf4bfd9..e9587297d0e32 100644 --- a/deps/nghttp2.version +++ b/deps/nghttp2.version @@ -3,4 +3,4 @@ NGHTTP2_JLL_NAME := nghttp2 ## source build -NGHTTP2_VER := 1.52.0 +NGHTTP2_VER := 1.60.0 diff --git a/deps/openblas.mk b/deps/openblas.mk index d890a5be6046a..d1e0c03aabb1c 100644 --- a/deps/openblas.mk +++ b/deps/openblas.mk @@ -95,12 +95,22 @@ $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/openblas-ofast-power.patch-applied: $(BUILDDIR)/ patch -p1 -f < $(SRCDIR)/patches/openblas-ofast-power.patch echo 1 > $@ -$(BUILDDIR)/$(OPENBLAS_SRC_DIR)/neoverse-generic-kernels.patch-applied: $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/openblas-ofast-power.patch-applied +$(BUILDDIR)/$(OPENBLAS_SRC_DIR)/openblas-avx512bf-kernels.patch-applied: $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/openblas-ofast-power.patch-applied cd $(BUILDDIR)/$(OPENBLAS_SRC_DIR) && \ - patch -p1 -f < $(SRCDIR)/patches/neoverse-generic-kernels.patch + patch -p1 -f < $(SRCDIR)/patches/openblas-avx512bf-kernels.patch echo 1 > $@ -$(BUILDDIR)/$(OPENBLAS_SRC_DIR)/build-configured: $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/neoverse-generic-kernels.patch-applied +$(BUILDDIR)/$(OPENBLAS_SRC_DIR)/openblas-gemv-multithreading.patch-applied: $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/openblas-avx512bf-kernels.patch-applied + cd $(BUILDDIR)/$(OPENBLAS_SRC_DIR) && \ + patch -p1 -f < $(SRCDIR)/patches/openblas-gemv-multithreading.patch + echo 1 > $@ + +$(BUILDDIR)/$(OPENBLAS_SRC_DIR)/openblas-darwin-sve.patch-applied: $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/openblas-gemv-multithreading.patch-applied + cd $(BUILDDIR)/$(OPENBLAS_SRC_DIR) && \ + patch -p1 -f < $(SRCDIR)/patches/openblas-darwin-sve.patch + echo 1 > $@ + +$(BUILDDIR)/$(OPENBLAS_SRC_DIR)/build-configured: $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/openblas-darwin-sve.patch-applied echo 1 > $@ $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/build-compiled: $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/build-configured diff --git a/deps/openblas.version b/deps/openblas.version index 9790ad1718e78..f62282b7ea8cc 100644 --- a/deps/openblas.version +++ b/deps/openblas.version @@ -3,9 +3,9 @@ OPENBLAS_JLL_NAME := OpenBLAS ## source build -OPENBLAS_VER := 0.3.24 -OPENBLAS_BRANCH=v0.3.24 -OPENBLAS_SHA1=9f815cf1bf16b4e64d4aee681b33558fc090b62a +OPENBLAS_VER := 0.3.26 +OPENBLAS_BRANCH=v0.3.26 +OPENBLAS_SHA1=6c77e5e314474773a7749357b153caba4ec3817d # LAPACK, source-only LAPACK_VER := 3.9.0 diff --git a/deps/p7zip.version b/deps/p7zip.version index d4a13155d9162..0fcde938eeb95 100644 --- a/deps/p7zip.version +++ b/deps/p7zip.version @@ -2,4 +2,4 @@ P7ZIP_JLL_NAME := p7zip ## source build -P7ZIP_VER := 17.04 +P7ZIP_VER := 17.05 diff --git a/deps/patchelf.mk b/deps/patchelf.mk index 9b4947f183117..c019892058d0e 100644 --- a/deps/patchelf.mk +++ b/deps/patchelf.mk @@ -20,7 +20,7 @@ $(BUILDDIR)/patchelf-$(PATCHELF_VER)/build-configured: XC_HOST:=$(BUILD_MACHINE) $(BUILDDIR)/patchelf-$(PATCHELF_VER)/build-configured: $(SRCCACHE)/patchelf-$(PATCHELF_VER)/source-extracted mkdir -p $(dir $@) cd $(dir $@) && \ - $(dir $<)/configure $(CONFIGURE_COMMON) LDFLAGS="$(CXXLDFLAGS)" CPPFLAGS="$(CPPFLAGS)" + $(dir $<)/configure $(CONFIGURE_COMMON) LDFLAGS="$(CXXLDFLAGS)" CPPFLAGS="$(CPPFLAGS)" MAKE=$(MAKE) echo 1 > $@ $(BUILDDIR)/patchelf-$(PATCHELF_VER)/build-compiled: $(BUILDDIR)/patchelf-$(PATCHELF_VER)/build-configured diff --git a/deps/patchelf.version b/deps/patchelf.version index bbeaa87d25136..9038338d45faf 100644 --- a/deps/patchelf.version +++ b/deps/patchelf.version @@ -1,3 +1,3 @@ ## source build # Patchelf (we don't ship this or even use a JLL, we just always build it) -PATCHELF_VER := 0.13 +PATCHELF_VER := 0.18.0 diff --git a/deps/patches/gmp-CVE-2021-43618.patch b/deps/patches/gmp-CVE-2021-43618.patch deleted file mode 100644 index a4e420e9219da..0000000000000 --- a/deps/patches/gmp-CVE-2021-43618.patch +++ /dev/null @@ -1,24 +0,0 @@ -# Origin: https://gmplib.org/repo/gmp-6.2/rev/561a9c25298e -# HG changeset patch -# User Marco Bodrato -# Date 1634836009 -7200 -# Node ID 561a9c25298e17bb01896801ff353546c6923dbd -# Parent e1fd9db13b475209a864577237ea4b9105b3e96e -mpz/inp_raw.c: Avoid bit size overflows - -diff -r e1fd9db13b47 -r 561a9c25298e mpz/inp_raw.c ---- a/mpz/inp_raw.c Tue Dec 22 23:49:51 2020 +0100 -+++ b/mpz/inp_raw.c Thu Oct 21 19:06:49 2021 +0200 -@@ -88,8 +88,11 @@ - - abs_csize = ABS (csize); - -+ if (UNLIKELY (abs_csize > ~(mp_bitcnt_t) 0 / 8)) -+ return 0; /* Bit size overflows */ -+ - /* round up to a multiple of limbs */ -- abs_xsize = BITS_TO_LIMBS (abs_csize*8); -+ abs_xsize = BITS_TO_LIMBS ((mp_bitcnt_t) abs_csize * 8); - - if (abs_xsize != 0) - { diff --git a/deps/patches/gmp-HG-changeset.patch b/deps/patches/gmp-HG-changeset.patch deleted file mode 100644 index 7437fb6f2f748..0000000000000 --- a/deps/patches/gmp-HG-changeset.patch +++ /dev/null @@ -1,520 +0,0 @@ - -# HG changeset patch -# User Torbjorn Granlund -# Date 1606685500 -3600 -# Node ID 5f32dbc41afc1f8cd77af1614f0caeb24deb7d7b -# Parent 94c84d919f83ba963ed1809f8e80c7bef32db55c -Avoid the x18 register since it is reserved on Darwin. - -diff -r 94c84d919f83 -r 5f32dbc41afc mpn/arm64/aors_n.asm ---- a/mpn/arm64/aors_n.asm Sat Nov 28 23:38:32 2020 +0100 -+++ b/mpn/arm64/aors_n.asm Sun Nov 29 22:31:40 2020 +0100 -@@ -68,7 +68,7 @@ - EPILOGUE() - PROLOGUE(func_n) - CLRCY --L(ent): lsr x18, n, #2 -+L(ent): lsr x17, n, #2 - tbz n, #0, L(bx0) - - L(bx1): ldr x7, [up] -@@ -77,7 +77,7 @@ - str x13, [rp],#8 - tbnz n, #1, L(b11) - --L(b01): cbz x18, L(ret) -+L(b01): cbz x17, L(ret) - ldp x4, x5, [up,#8] - ldp x8, x9, [vp,#8] - sub up, up, #8 -@@ -88,7 +88,7 @@ - ldp x10, x11, [vp,#8] - add up, up, #8 - add vp, vp, #8 -- cbz x18, L(end) -+ cbz x17, L(end) - b L(top) - - L(bx0): tbnz n, #1, L(b10) -@@ -101,7 +101,7 @@ - - L(b10): ldp x6, x7, [up] - ldp x10, x11, [vp] -- cbz x18, L(end) -+ cbz x17, L(end) - - ALIGN(16) - L(top): ldp x4, x5, [up,#16] -@@ -114,8 +114,8 @@ - ADDSUBC x12, x4, x8 - ADDSUBC x13, x5, x9 - stp x12, x13, [rp],#16 -- sub x18, x18, #1 -- cbnz x18, L(top) -+ sub x17, x17, #1 -+ cbnz x17, L(top) - - L(end): ADDSUBC x12, x6, x10 - ADDSUBC x13, x7, x11 -diff -r 94c84d919f83 -r 5f32dbc41afc mpn/arm64/aorsmul_1.asm ---- a/mpn/arm64/aorsmul_1.asm Sat Nov 28 23:38:32 2020 +0100 -+++ b/mpn/arm64/aorsmul_1.asm Sun Nov 29 22:31:40 2020 +0100 -@@ -32,10 +32,15 @@ - - include(`../config.m4') - --C cycles/limb --C Cortex-A53 9.3-9.8 --C Cortex-A57 7.0 --C X-Gene 5.0 -+C addmul_1 submul_1 -+C cycles/limb cycles/limb -+C Cortex-A53 9.3-9.8 9.3-9.8 -+C Cortex-A55 9.0-9.5 9.3-9.8 -+C Cortex-A57 7 7 -+C Cortex-A72 -+C Cortex-A73 6 6 -+C X-Gene 5 5 -+C Apple M1 1.75 1.75 - - C NOTES - C * It is possible to keep the carry chain alive between the addition blocks -diff -r 94c84d919f83 -r 5f32dbc41afc mpn/arm64/aorsorrlshC_n.asm ---- a/mpn/arm64/aorsorrlshC_n.asm Sat Nov 28 23:38:32 2020 +0100 -+++ b/mpn/arm64/aorsorrlshC_n.asm Sun Nov 29 22:31:40 2020 +0100 -@@ -65,14 +65,14 @@ - - ASM_START() - PROLOGUE(func_n) -- lsr x18, n, #2 -+ lsr x6, n, #2 - tbz n, #0, L(bx0) - - L(bx1): ldr x5, [up] - tbnz n, #1, L(b11) - - L(b01): ldr x11, [vp] -- cbz x18, L(1) -+ cbz x6, L(1) - ldp x8, x9, [vp,#8] - lsl x13, x11, #LSH - ADDSUB( x15, x13, x5) -@@ -94,7 +94,7 @@ - ADDSUB( x17, x13, x5) - str x17, [rp],#8 - sub up, up, #8 -- cbz x18, L(end) -+ cbz x6, L(end) - b L(top) - - L(bx0): tbnz n, #1, L(b10) -@@ -107,7 +107,7 @@ - L(b10): CLRRCY( x9) - ldp x10, x11, [vp] - sub up, up, #16 -- cbz x18, L(end) -+ cbz x6, L(end) - - ALIGN(16) - L(top): ldp x4, x5, [up,#16] -@@ -124,8 +124,8 @@ - ADDSUBC(x16, x12, x4) - ADDSUBC(x17, x13, x5) - stp x16, x17, [rp],#16 -- sub x18, x18, #1 -- cbnz x18, L(top) -+ sub x6, x6, #1 -+ cbnz x6, L(top) - - L(end): ldp x4, x5, [up,#16] - extr x12, x10, x9, #RSH -diff -r 94c84d919f83 -r 5f32dbc41afc mpn/arm64/cnd_aors_n.asm ---- a/mpn/arm64/cnd_aors_n.asm Sat Nov 28 23:38:32 2020 +0100 -+++ b/mpn/arm64/cnd_aors_n.asm Sun Nov 29 22:31:40 2020 +0100 -@@ -65,7 +65,7 @@ - - CLRCY - -- lsr x18, n, #2 -+ lsr x17, n, #2 - tbz n, #0, L(bx0) - - L(bx1): ldr x13, [vp] -@@ -75,7 +75,7 @@ - str x9, [rp] - tbnz n, #1, L(b11) - --L(b01): cbz x18, L(rt) -+L(b01): cbz x17, L(rt) - ldp x12, x13, [vp,#8] - ldp x10, x11, [up,#8] - sub up, up, #8 -@@ -86,7 +86,7 @@ - L(b11): ldp x12, x13, [vp,#8]! - ldp x10, x11, [up,#8]! - sub rp, rp, #8 -- cbz x18, L(end) -+ cbz x17, L(end) - b L(top) - - L(bx0): ldp x12, x13, [vp] -@@ -99,7 +99,7 @@ - b L(mid) - - L(b10): sub rp, rp, #16 -- cbz x18, L(end) -+ cbz x17, L(end) - - ALIGN(16) - L(top): bic x6, x12, cnd -@@ -116,8 +116,8 @@ - ADDSUBC x9, x11, x7 - ldp x10, x11, [up,#32]! - stp x8, x9, [rp,#32]! -- sub x18, x18, #1 -- cbnz x18, L(top) -+ sub x17, x17, #1 -+ cbnz x17, L(top) - - L(end): bic x6, x12, cnd - bic x7, x13, cnd -diff -r 94c84d919f83 -r 5f32dbc41afc mpn/arm64/logops_n.asm ---- a/mpn/arm64/logops_n.asm Sat Nov 28 23:38:32 2020 +0100 -+++ b/mpn/arm64/logops_n.asm Sun Nov 29 22:31:40 2020 +0100 -@@ -78,7 +78,7 @@ - - ASM_START() - PROLOGUE(func) -- lsr x18, n, #2 -+ lsr x17, n, #2 - tbz n, #0, L(bx0) - - L(bx1): ldr x7, [up] -@@ -88,7 +88,7 @@ - str x15, [rp],#8 - tbnz n, #1, L(b11) - --L(b01): cbz x18, L(ret) -+L(b01): cbz x17, L(ret) - ldp x4, x5, [up,#8] - ldp x8, x9, [vp,#8] - sub up, up, #8 -@@ -99,7 +99,7 @@ - ldp x10, x11, [vp,#8] - add up, up, #8 - add vp, vp, #8 -- cbz x18, L(end) -+ cbz x17, L(end) - b L(top) - - L(bx0): tbnz n, #1, L(b10) -@@ -110,7 +110,7 @@ - - L(b10): ldp x6, x7, [up] - ldp x10, x11, [vp] -- cbz x18, L(end) -+ cbz x17, L(end) - - ALIGN(16) - L(top): ldp x4, x5, [up,#16] -@@ -127,8 +127,8 @@ - POSTOP( x12) - POSTOP( x13) - stp x12, x13, [rp],#16 -- sub x18, x18, #1 -- cbnz x18, L(top) -+ sub x17, x17, #1 -+ cbnz x17, L(top) - - L(end): LOGOP( x12, x6, x10) - LOGOP( x13, x7, x11) -diff -r 94c84d919f83 -r 5f32dbc41afc mpn/arm64/lshift.asm ---- a/mpn/arm64/lshift.asm Sat Nov 28 23:38:32 2020 +0100 -+++ b/mpn/arm64/lshift.asm Sun Nov 29 22:31:40 2020 +0100 -@@ -61,7 +61,7 @@ - add rp, rp_arg, n, lsl #3 - add up, up, n, lsl #3 - sub tnc, xzr, cnt -- lsr x18, n, #2 -+ lsr x17, n, #2 - tbz n, #0, L(bx0) - - L(bx1): ldr x4, [up,#-8] -@@ -69,7 +69,7 @@ - - L(b01): NSHIFT x0, x4, tnc - PSHIFT x2, x4, cnt -- cbnz x18, L(gt1) -+ cbnz x17, L(gt1) - str x2, [rp,#-8] - ret - L(gt1): ldp x4, x5, [up,#-24] -@@ -89,7 +89,7 @@ - PSHIFT x13, x5, cnt - NSHIFT x10, x4, tnc - PSHIFT x2, x4, cnt -- cbnz x18, L(gt2) -+ cbnz x17, L(gt2) - orr x10, x10, x13 - stp x2, x10, [rp,#-16] - ret -@@ -123,11 +123,11 @@ - orr x11, x12, x2 - stp x10, x11, [rp,#-32]! - PSHIFT x2, x4, cnt --L(lo0): sub x18, x18, #1 -+L(lo0): sub x17, x17, #1 - L(lo3): NSHIFT x10, x6, tnc - PSHIFT x13, x7, cnt - NSHIFT x12, x7, tnc -- cbnz x18, L(top) -+ cbnz x17, L(top) - - L(end): orr x10, x10, x13 - orr x11, x12, x2 -diff -r 94c84d919f83 -r 5f32dbc41afc mpn/arm64/lshiftc.asm ---- a/mpn/arm64/lshiftc.asm Sat Nov 28 23:38:32 2020 +0100 -+++ b/mpn/arm64/lshiftc.asm Sun Nov 29 22:31:40 2020 +0100 -@@ -61,7 +61,7 @@ - add rp, rp_arg, n, lsl #3 - add up, up, n, lsl #3 - sub tnc, xzr, cnt -- lsr x18, n, #2 -+ lsr x17, n, #2 - tbz n, #0, L(bx0) - - L(bx1): ldr x4, [up,#-8] -@@ -69,7 +69,7 @@ - - L(b01): NSHIFT x0, x4, tnc - PSHIFT x2, x4, cnt -- cbnz x18, L(gt1) -+ cbnz x17, L(gt1) - mvn x2, x2 - str x2, [rp,#-8] - ret -@@ -90,7 +90,7 @@ - PSHIFT x13, x5, cnt - NSHIFT x10, x4, tnc - PSHIFT x2, x4, cnt -- cbnz x18, L(gt2) -+ cbnz x17, L(gt2) - eon x10, x10, x13 - mvn x2, x2 - stp x2, x10, [rp,#-16] -@@ -125,11 +125,11 @@ - eon x11, x12, x2 - stp x10, x11, [rp,#-32]! - PSHIFT x2, x4, cnt --L(lo0): sub x18, x18, #1 -+L(lo0): sub x17, x17, #1 - L(lo3): NSHIFT x10, x6, tnc - PSHIFT x13, x7, cnt - NSHIFT x12, x7, tnc -- cbnz x18, L(top) -+ cbnz x17, L(top) - - L(end): eon x10, x10, x13 - eon x11, x12, x2 -diff -r 94c84d919f83 -r 5f32dbc41afc mpn/arm64/mul_1.asm ---- a/mpn/arm64/mul_1.asm Sat Nov 28 23:38:32 2020 +0100 -+++ b/mpn/arm64/mul_1.asm Sun Nov 29 22:31:40 2020 +0100 -@@ -56,7 +56,7 @@ - - PROLOGUE(mpn_mul_1) - adds x4, xzr, xzr C clear register and cy flag --L(com): lsr x18, n, #2 -+L(com): lsr x17, n, #2 - tbnz n, #0, L(bx1) - - L(bx0): mov x11, x4 -@@ -65,7 +65,7 @@ - L(b10): ldp x4, x5, [up] - mul x8, x4, v0 - umulh x10, x4, v0 -- cbz x18, L(2) -+ cbz x17, L(2) - ldp x6, x7, [up,#16]! - mul x9, x5, v0 - b L(mid)-8 -@@ -80,7 +80,7 @@ - str x9, [rp],#8 - tbnz n, #1, L(b10) - --L(b01): cbz x18, L(1) -+L(b01): cbz x17, L(1) - - L(b00): ldp x6, x7, [up] - mul x8, x6, v0 -@@ -90,8 +90,8 @@ - adcs x12, x8, x11 - umulh x11, x7, v0 - add rp, rp, #16 -- sub x18, x18, #1 -- cbz x18, L(end) -+ sub x17, x17, #1 -+ cbz x17, L(end) - - ALIGN(16) - L(top): mul x8, x4, v0 -@@ -110,8 +110,8 @@ - stp x12, x13, [rp],#32 - adcs x12, x8, x11 - umulh x11, x7, v0 -- sub x18, x18, #1 -- cbnz x18, L(top) -+ sub x17, x17, #1 -+ cbnz x17, L(top) - - L(end): mul x8, x4, v0 - adcs x13, x9, x10 -diff -r 94c84d919f83 -r 5f32dbc41afc mpn/arm64/rsh1aors_n.asm ---- a/mpn/arm64/rsh1aors_n.asm Sat Nov 28 23:38:32 2020 +0100 -+++ b/mpn/arm64/rsh1aors_n.asm Sun Nov 29 22:31:40 2020 +0100 -@@ -59,7 +59,7 @@ - - ASM_START() - PROLOGUE(func_n) -- lsr x18, n, #2 -+ lsr x6, n, #2 - - tbz n, #0, L(bx0) - -@@ -69,7 +69,7 @@ - - L(b01): ADDSUB x13, x5, x9 - and x10, x13, #1 -- cbz x18, L(1) -+ cbz x6, L(1) - ldp x4, x5, [up],#48 - ldp x8, x9, [vp],#48 - ADDSUBC x14, x4, x8 -@@ -80,8 +80,8 @@ - ADDSUBC x12, x4, x8 - ADDSUBC x13, x5, x9 - str x17, [rp], #24 -- sub x18, x18, #1 -- cbz x18, L(end) -+ sub x6, x6, #1 -+ cbz x6, L(end) - b L(top) - - L(1): cset x14, COND -@@ -97,7 +97,7 @@ - ldp x8, x9, [vp],#32 - ADDSUBC x12, x4, x8 - ADDSUBC x13, x5, x9 -- cbz x18, L(3) -+ cbz x6, L(3) - ldp x4, x5, [up,#-16] - ldp x8, x9, [vp,#-16] - extr x17, x12, x15, #1 -@@ -117,7 +117,7 @@ - ADDSUB x12, x4, x8 - ADDSUBC x13, x5, x9 - and x10, x12, #1 -- cbz x18, L(2) -+ cbz x6, L(2) - ldp x4, x5, [up,#-16] - ldp x8, x9, [vp,#-16] - ADDSUBC x14, x4, x8 -@@ -134,8 +134,8 @@ - ADDSUBC x12, x4, x8 - ADDSUBC x13, x5, x9 - add rp, rp, #16 -- sub x18, x18, #1 -- cbz x18, L(end) -+ sub x6, x6, #1 -+ cbz x6, L(end) - - ALIGN(16) - L(top): ldp x4, x5, [up,#-16] -@@ -152,8 +152,8 @@ - ADDSUBC x12, x4, x8 - ADDSUBC x13, x5, x9 - stp x16, x17, [rp],#32 -- sub x18, x18, #1 -- cbnz x18, L(top) -+ sub x6, x6, #1 -+ cbnz x6, L(top) - - L(end): extr x16, x15, x14, #1 - extr x17, x12, x15, #1 -diff -r 94c84d919f83 -r 5f32dbc41afc mpn/arm64/rshift.asm ---- a/mpn/arm64/rshift.asm Sat Nov 28 23:38:32 2020 +0100 -+++ b/mpn/arm64/rshift.asm Sun Nov 29 22:31:40 2020 +0100 -@@ -60,7 +60,7 @@ - PROLOGUE(mpn_rshift) - mov rp, rp_arg - sub tnc, xzr, cnt -- lsr x18, n, #2 -+ lsr x17, n, #2 - tbz n, #0, L(bx0) - - L(bx1): ldr x5, [up] -@@ -68,7 +68,7 @@ - - L(b01): NSHIFT x0, x5, tnc - PSHIFT x2, x5, cnt -- cbnz x18, L(gt1) -+ cbnz x17, L(gt1) - str x2, [rp] - ret - L(gt1): ldp x4, x5, [up,#8] -@@ -89,7 +89,7 @@ - PSHIFT x13, x4, cnt - NSHIFT x10, x5, tnc - PSHIFT x2, x5, cnt -- cbnz x18, L(gt2) -+ cbnz x17, L(gt2) - orr x10, x10, x13 - stp x10, x2, [rp] - ret -@@ -121,11 +121,11 @@ - orr x11, x12, x2 - stp x11, x10, [rp,#32]! - PSHIFT x2, x5, cnt --L(lo0): sub x18, x18, #1 -+L(lo0): sub x17, x17, #1 - L(lo3): NSHIFT x10, x7, tnc - NSHIFT x12, x6, tnc - PSHIFT x13, x6, cnt -- cbnz x18, L(top) -+ cbnz x17, L(top) - - L(end): orr x10, x10, x13 - orr x11, x12, x2 -diff -r 94c84d919f83 -r 5f32dbc41afc mpn/arm64/sqr_diag_addlsh1.asm ---- a/mpn/arm64/sqr_diag_addlsh1.asm Sat Nov 28 23:38:32 2020 +0100 -+++ b/mpn/arm64/sqr_diag_addlsh1.asm Sun Nov 29 22:31:40 2020 +0100 -@@ -47,7 +47,7 @@ - ASM_START() - PROLOGUE(mpn_sqr_diag_addlsh1) - ldr x15, [up],#8 -- lsr x18, n, #1 -+ lsr x14, n, #1 - tbz n, #0, L(bx0) - - L(bx1): adds x7, xzr, xzr -@@ -62,8 +62,8 @@ - ldr x17, [up],#16 - ldp x6, x7, [tp],#32 - umulh x11, x15, x15 -- sub x18, x18, #1 -- cbz x18, L(end) -+ sub x14, x14, #1 -+ cbz x14, L(end) - - ALIGN(16) - L(top): extr x9, x6, x5, #63 -@@ -84,8 +84,8 @@ - extr x8, x5, x4, #63 - stp x12, x13, [rp],#16 - adcs x12, x8, x10 -- sub x18, x18, #1 -- cbnz x18, L(top) -+ sub x14, x14, #1 -+ cbnz x14, L(top) - - L(end): extr x9, x6, x5, #63 - mul x10, x17, x17 diff --git a/deps/patches/gmp_alloc_overflow_func.patch b/deps/patches/gmp-alloc_overflow.patch similarity index 54% rename from deps/patches/gmp_alloc_overflow_func.patch rename to deps/patches/gmp-alloc_overflow.patch index 51506d70d46fb..6a0f02c66e3f4 100644 --- a/deps/patches/gmp_alloc_overflow_func.patch +++ b/deps/patches/gmp-alloc_overflow.patch @@ -1,7 +1,7 @@ -diff --git a/gmp-h.in b/gmp-h.in ---- a/gmp-h.in -+++ b/gmp-h.in -@@ -479,6 +479,13 @@ using std::FILE; +diff -ru gmp-6.3.0/gmp-h.in gmp-6.3.0.new/gmp-h.in +--- gmp-6.3.0/gmp-h.in 2023-07-29 09:42:16 ++++ gmp-6.3.0.new/gmp-h.in 2023-12-29 15:33:34 +@@ -487,6 +487,12 @@ void *(**) (void *, size_t, size_t), void (**) (void *, size_t)) __GMP_NOTHROW; @@ -10,15 +10,23 @@ diff --git a/gmp-h.in b/gmp-h.in + +#define mp_get_alloc_overflow_function __gmp_get_alloc_overflow_function +__GMP_DECLSPEC void mp_get_alloc_overflow_function (void (**) (void)) __GMP_NOTHROW; -+ + #define mp_bits_per_limb __gmp_bits_per_limb __GMP_DECLSPEC extern const int mp_bits_per_limb; -diff --git a/gmp-impl.h b/gmp-impl.h ---- a/gmp-impl.h -+++ b/gmp-impl.h -@@ -696,10 +696,12 @@ struct tmp_debug_entry_t { +diff -ru gmp-6.3.0/gmp-impl.h gmp-6.3.0.new/gmp-impl.h +--- gmp-6.3.0/gmp-impl.h 2023-07-29 09:42:16 ++++ gmp-6.3.0.new/gmp-impl.h 2023-12-30 16:02:36 +@@ -58,6 +58,8 @@ + short can be 24, 32, 46 or 64 bits, and different for ushort. */ + + #include ++#include ++#include + + /* For fat.h and other fat binary stuff. + No need for __GMP_ATTRIBUTE_PURE or __GMP_NOTHROW, since functions +@@ -699,14 +701,22 @@ __GMP_DECLSPEC extern void * (*__gmp_allocate_func) (size_t); __GMP_DECLSPEC extern void * (*__gmp_reallocate_func) (void *, size_t, size_t); __GMP_DECLSPEC extern void (*__gmp_free_func) (void *, size_t); @@ -31,10 +39,7 @@ diff --git a/gmp-impl.h b/gmp-impl.h #define __GMP_ALLOCATE_FUNC_TYPE(n,type) \ ((type *) (*__gmp_allocate_func) ((n) * sizeof (type))) -@@ -727,6 +729,12 @@ struct tmp_debug_entry_t { - (ptr, (oldsize) * sizeof (type), (newsize) * sizeof (type)); \ - } while (0) - + #define __GMP_ALLOCATE_FUNC_LIMBS(n) __GMP_ALLOCATE_FUNC_TYPE (n, mp_limb_t) +#define __GMP_ALLOC_OVERFLOW_FUNC() \ + do { \ + (*__gmp_alloc_overflow_func) (); \ @@ -42,12 +47,12 @@ diff --git a/gmp-impl.h b/gmp-impl.h + abort (); \ + } while (0) - /* Dummy for non-gcc, code involving it will go dead. */ - #if ! defined (__GNUC__) || __GNUC__ < 2 -diff --git a/memory.c b/memory.c ---- a/memory.c -+++ b/memory.c -@@ -38,6 +38,7 @@ see https://www.gnu.org/licenses/. */ + #define __GMP_REALLOCATE_FUNC_TYPE(p, old_size, new_size, type) \ + ((type *) (*__gmp_reallocate_func) \ +diff -ru gmp-6.3.0/memory.c gmp-6.3.0.new/memory.c +--- gmp-6.3.0/memory.c 2023-07-29 09:42:16 ++++ gmp-6.3.0.new/memory.c 2023-12-29 15:43:27 +@@ -37,6 +37,7 @@ void * (*__gmp_allocate_func) (size_t) = __gmp_default_allocate; void * (*__gmp_reallocate_func) (void *, size_t, size_t) = __gmp_default_reallocate; void (*__gmp_free_func) (void *, size_t) = __gmp_default_free; @@ -55,21 +60,22 @@ diff --git a/memory.c b/memory.c /* Default allocation functions. In case of failure to allocate/reallocate -@@ -144,3 +145,10 @@ void +@@ -142,4 +143,11 @@ + } #endif free (blk_ptr); - } ++} + +void +__gmp_default_alloc_overflow(void) +{ + fprintf (stderr, "gmp: overflow in mpz type\n"); + abort(); -+} -diff --git a/mp_get_fns.c b/mp_get_fns.c ---- a/mp_get_fns.c -+++ b/mp_get_fns.c -@@ -46,3 +46,11 @@ mp_get_memory_functions (void *(**alloc_ + } +diff -ru gmp-6.3.0/mp_get_fns.c gmp-6.3.0.new/mp_get_fns.c +--- gmp-6.3.0/mp_get_fns.c 2023-07-29 09:42:16 ++++ gmp-6.3.0.new/mp_get_fns.c 2023-12-29 15:43:27 +@@ -45,3 +45,11 @@ if (free_func != NULL) *free_func = __gmp_free_func; } @@ -81,10 +87,10 @@ diff --git a/mp_get_fns.c b/mp_get_fns.c + if (alloc_overflow_func != NULL) + *alloc_overflow_func = __gmp_alloc_overflow_func; +} -diff --git a/mp_set_fns.c b/mp_set_fns.c ---- a/mp_set_fns.c -+++ b/mp_set_fns.c -@@ -48,3 +48,12 @@ mp_set_memory_functions (void *(*alloc_f +diff -ru gmp-6.3.0/mp_set_fns.c gmp-6.3.0.new/mp_set_fns.c +--- gmp-6.3.0/mp_set_fns.c 2023-07-29 09:42:16 ++++ gmp-6.3.0.new/mp_set_fns.c 2023-12-29 15:43:27 +@@ -47,3 +47,12 @@ __gmp_reallocate_func = realloc_func; __gmp_free_func = free_func; } @@ -97,58 +103,66 @@ diff --git a/mp_set_fns.c b/mp_set_fns.c + alloc_overflow_func = __gmp_default_alloc_overflow; + __gmp_alloc_overflow_func = alloc_overflow_func; +} -diff --git a/mpz/init2.c b/mpz/init2.c ---- a/mpz/init2.c -+++ b/mpz/init2.c -@@ -45,8 +45,7 @@ mpz_init2 (mpz_ptr x, mp_bitcnt_t bits) +diff -ru gmp-6.3.0/mpz/init2.c gmp-6.3.0.new/mpz/init2.c +--- gmp-6.3.0/mpz/init2.c 2023-07-29 09:42:17 ++++ gmp-6.3.0.new/mpz/init2.c 2023-12-30 12:22:34 +@@ -41,7 +41,7 @@ + if (sizeof (unsigned long) > sizeof (int)) /* param vs _mp_size field */ { if (UNLIKELY (new_alloc > INT_MAX)) - { -- fprintf (stderr, "gmp: overflow in mpz type\n"); -- abort (); -+ __GMP_ALLOC_OVERFLOW_FUNC (); - } +- MPZ_OVERFLOW; ++ __GMP_ALLOC_OVERFLOW_FUNC (); } -diff --git a/mpz/realloc.c b/mpz/realloc.c ---- a/mpz/realloc.c -+++ b/mpz/realloc.c -@@ -45,16 +45,14 @@ void * + PTR(x) = __GMP_ALLOCATE_FUNC_LIMBS (new_alloc); +diff -ru gmp-6.3.0/mpz/realloc.c gmp-6.3.0.new/mpz/realloc.c +--- gmp-6.3.0/mpz/realloc.c 2023-07-29 09:42:17 ++++ gmp-6.3.0.new/mpz/realloc.c 2023-12-30 12:22:47 +@@ -42,12 +42,12 @@ + if (sizeof (mp_size_t) == sizeof (int)) { if (UNLIKELY (new_alloc > ULONG_MAX / GMP_NUMB_BITS)) - { -- fprintf (stderr, "gmp: overflow in mpz type\n"); -- abort (); -+ __GMP_ALLOC_OVERFLOW_FUNC (); - } +- MPZ_OVERFLOW; ++ __GMP_ALLOC_OVERFLOW_FUNC (); } else { if (UNLIKELY (new_alloc > INT_MAX)) - { -- fprintf (stderr, "gmp: overflow in mpz type\n"); -- abort (); -+ __GMP_ALLOC_OVERFLOW_FUNC (); - } +- MPZ_OVERFLOW; ++ __GMP_ALLOC_OVERFLOW_FUNC (); } -diff --git a/mpz/realloc2.c b/mpz/realloc2.c ---- a/mpz/realloc2.c -+++ b/mpz/realloc2.c -@@ -45,8 +45,7 @@ mpz_realloc2 (mpz_ptr m, mp_bitcnt_t bit + if (ALLOC (m) == 0) +diff -ru gmp-6.3.0/mpz/realloc2.c gmp-6.3.0.new/mpz/realloc2.c +--- gmp-6.3.0/mpz/realloc2.c 2023-07-29 09:42:17 ++++ gmp-6.3.0.new/mpz/realloc2.c 2023-12-30 12:22:59 +@@ -42,7 +42,7 @@ + if (sizeof (unsigned long) > sizeof (int)) /* param vs _mp_size field */ { if (UNLIKELY (new_alloc > INT_MAX)) - { -- fprintf (stderr, "gmp: overflow in mpz type\n"); -- abort (); -+ __GMP_ALLOC_OVERFLOW_FUNC (); - } +- MPZ_OVERFLOW; ++ __GMP_ALLOC_OVERFLOW_FUNC (); } -diff --git a/tests/mpz/t-pow.c b/tests/mpz/t-pow.c ---- a/tests/mpz/t-pow.c -+++ b/tests/mpz/t-pow.c -@@ -195,6 +195,34 @@ check_random (int reps) + if (ALLOC (m) == 0) +diff -ru gmp-6.3.0/tal-reent.c gmp-6.3.0.new/tal-reent.c +--- gmp-6.3.0/tal-reent.c 2023-07-29 09:42:17 ++++ gmp-6.3.0.new/tal-reent.c 2023-12-30 12:19:40 +@@ -61,6 +61,10 @@ + + total_size = size + HSIZ; + p = __GMP_ALLOCATE_FUNC_TYPE (total_size, char); ++ if (!p) ++ { ++ __GMP_ALLOC_OVERFLOW_FUNC (); ++ } + P->size = total_size; + P->next = *markp; + *markp = P; +diff -ru gmp-6.3.0/tests/mpz/t-pow.c gmp-6.3.0.new/tests/mpz/t-pow.c +--- gmp-6.3.0/tests/mpz/t-pow.c 2023-07-29 09:42:17 ++++ gmp-6.3.0.new/tests/mpz/t-pow.c 2023-12-30 15:57:58 +@@ -194,6 +194,33 @@ mpz_clear (want); } @@ -178,12 +192,11 @@ diff --git a/tests/mpz/t-pow.c b/tests/mpz/t-pow.c + } + mpz_clear (x); +} -+ + int main (int argc, char **argv) { -@@ -212,6 +240,7 @@ main (int argc, char **argv) +@@ -211,6 +238,7 @@ check_various (); check_random (reps); diff --git a/deps/patches/gmp-more_alloc_overflow.patch b/deps/patches/gmp-more_alloc_overflow.patch deleted file mode 100644 index 597f0d52d73e7..0000000000000 --- a/deps/patches/gmp-more_alloc_overflow.patch +++ /dev/null @@ -1,37 +0,0 @@ -diff -ur a/mpz/n_pow_ui.c b/mpz/n_pow_ui.c ---- a/mpz/n_pow_ui.c -+++ b/mpz/n_pow_ui.c -@@ -220,8 +220,7 @@ - umul_ppmm (ovfl, rtwos_bits, e, btwos); - if (ovfl) - { -- fprintf (stderr, "gmp: overflow in mpz type\n"); -- abort (); -+ __GMP_ALLOC_OVERFLOW_FUNC (); - } - - rtwos_limbs += rtwos_bits / GMP_NUMB_BITS; -@@ -382,8 +381,7 @@ - umul_ppmm (ovfl, ralloc, (bsize*GMP_NUMB_BITS - cnt + GMP_NAIL_BITS), e); - if (ovfl) - { -- fprintf (stderr, "gmp: overflow in mpz type\n"); -- abort (); -+ __GMP_ALLOC_OVERFLOW_FUNC (); - } - ralloc = ralloc / GMP_NUMB_BITS + 5; - -diff -ur a/tal-reent.c b/tal-reent.c ---- a/tal-reent.c -+++ b/tal-reent.c -@@ -61,6 +61,10 @@ - - total_size = size + HSIZ; - p = __GMP_ALLOCATE_FUNC_TYPE (total_size, char); -+ if (!p) -+ { -+ __GMP_ALLOC_OVERFLOW_FUNC (); -+ } - P->size = total_size; - P->next = *markp; - *markp = P; diff --git a/deps/patches/llvm-libunwind-freebsd-libgcc-api-compat.patch b/deps/patches/llvm-libunwind-freebsd-libgcc-api-compat.patch index afb4b941d5b92..0e517d8ec7aa8 100644 --- a/deps/patches/llvm-libunwind-freebsd-libgcc-api-compat.patch +++ b/deps/patches/llvm-libunwind-freebsd-libgcc-api-compat.patch @@ -16,7 +16,7 @@ single FDE. I suspect this was just an Apple bug, compensated by Apple- specific code in LLVM. See lib/ExecutionEngine/RuntimeDyld/RTDyldMemoryManager.cpp and -http://lists.llvm.org/pipermail/llvm-dev/2013-April/061737.html +https://lists.llvm.org/pipermail/llvm-dev/2013-April/061737.html for more detail. This change is based on the LLVM RTDyldMemoryManager.cpp. It should diff --git a/deps/patches/neoverse-generic-kernels.patch b/deps/patches/neoverse-generic-kernels.patch deleted file mode 100644 index ab37e3783bf3e..0000000000000 --- a/deps/patches/neoverse-generic-kernels.patch +++ /dev/null @@ -1,19 +0,0 @@ -diff --git a/kernel/arm64/KERNEL.NEOVERSEN1 b/kernel/arm64/KERNEL.NEOVERSEN1 -index ea010db4..074d7215 100644 ---- a/kernel/arm64/KERNEL.NEOVERSEN1 -+++ b/kernel/arm64/KERNEL.NEOVERSEN1 -@@ -91,10 +91,10 @@ IDAMAXKERNEL = iamax_thunderx2t99.c - ICAMAXKERNEL = izamax_thunderx2t99.c - IZAMAXKERNEL = izamax_thunderx2t99.c - --SNRM2KERNEL = scnrm2_thunderx2t99.c --DNRM2KERNEL = dznrm2_thunderx2t99.c --CNRM2KERNEL = scnrm2_thunderx2t99.c --ZNRM2KERNEL = dznrm2_thunderx2t99.c -+SNRM2KERNEL = nrm2.S -+DNRM2KERNEL = nrm2.S -+CNRM2KERNEL = znrm2.S -+ZNRM2KERNEL = znrm2.S - - DDOTKERNEL = dot_thunderx2t99.c - SDOTKERNEL = dot_thunderx2t99.c diff --git a/deps/patches/openblas-avx512bf-kernels.patch b/deps/patches/openblas-avx512bf-kernels.patch new file mode 100644 index 0000000000000..7e99cdf7c53ee --- /dev/null +++ b/deps/patches/openblas-avx512bf-kernels.patch @@ -0,0 +1,107 @@ +From 1dada6d65d89d19b2cf89b12169f6b2196c90f1d Mon Sep 17 00:00:00 2001 +From: Martin Kroeker +Date: Fri, 12 Jan 2024 00:10:56 +0100 +Subject: [PATCH 1/2] Add compiler test and flag for AVX512BF16 capability + +--- + c_check | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + +diff --git a/c_check b/c_check +index b5e4a9ad00..3e507be818 100755 +--- a/c_check ++++ b/c_check +@@ -244,6 +244,7 @@ case "$data" in + esac + + no_avx512=0 ++no_avx512bf=0 + if [ "$architecture" = "x86" ] || [ "$architecture" = "x86_64" ]; then + tmpd=$(mktemp -d 2>/dev/null || mktemp -d -t 'OBC') + tmpf="$tmpd/a.c" +@@ -262,6 +263,25 @@ if [ "$architecture" = "x86" ] || [ "$architecture" = "x86_64" ]; then + } + + rm -rf "$tmpd" ++ if [ "$no_avx512" -eq 0 ]; then ++ tmpd=$(mktemp -d 2>/dev/null || mktemp -d -t 'OBC') ++ tmpf="$tmpd/a.c" ++ code='"__m512 a= _mm512_dpbf16_ps(a, (__m512bh) _mm512_loadu_si512(%1]), (__m512bh) _mm512_loadu_si512(%2]));"' ++ printf "#include \n\nint main(void){ %s; }\n" "$code" >> "$tmpf" ++ if [ "$compiler" = "PGI" ]; then ++ args=" -tp cooperlake -c -o $tmpf.o $tmpf" ++ else ++ args=" -march=cooperlake -c -o $tmpf.o $tmpf" ++ fi ++ no_avx512bf=0 ++ { ++ $compiler_name $flags $args >/dev/null 2>&1 ++ } || { ++ no_avx512bf=1 ++ } ++ ++ rm -rf "$tmpd" ++ fi + fi + + no_rv64gv=0 +@@ -409,6 +429,7 @@ done + [ "$makefile" = "-" ] && { + [ "$no_rv64gv" -eq 1 ] && printf "NO_RV64GV=1\n" + [ "$no_avx512" -eq 1 ] && printf "NO_AVX512=1\n" ++ [ "$no_avx512bf" -eq 1 ] && printf "NO_AVX512BF16=1\n" + [ "$no_avx2" -eq 1 ] && printf "NO_AVX2=1\n" + [ "$oldgcc" -eq 1 ] && printf "OLDGCC=1\n" + exit 0 +@@ -437,6 +458,7 @@ done + [ "$no_sve" -eq 1 ] && printf "NO_SVE=1\n" + [ "$no_rv64gv" -eq 1 ] && printf "NO_RV64GV=1\n" + [ "$no_avx512" -eq 1 ] && printf "NO_AVX512=1\n" ++ [ "$no_avx512bf" -eq 1 ] && printf "NO_AVX512BF16=1\n" + [ "$no_avx2" -eq 1 ] && printf "NO_AVX2=1\n" + [ "$oldgcc" -eq 1 ] && printf "OLDGCC=1\n" + [ "$no_lsx" -eq 1 ] && printf "NO_LSX=1\n" + +From 995a990e24fdcc8080128a8abc17b4ccc66bd4fd Mon Sep 17 00:00:00 2001 +From: Martin Kroeker +Date: Fri, 12 Jan 2024 00:12:46 +0100 +Subject: [PATCH 2/2] Make AVX512 BFLOAT16 kernels conditional on compiler + capability + +--- + kernel/x86_64/KERNEL.COOPERLAKE | 3 ++- + kernel/x86_64/KERNEL.SAPPHIRERAPIDS | 2 ++ + 2 files changed, 4 insertions(+), 1 deletion(-) + +diff --git a/kernel/x86_64/KERNEL.COOPERLAKE b/kernel/x86_64/KERNEL.COOPERLAKE +index dba94aea86..22b042029f 100644 +--- a/kernel/x86_64/KERNEL.COOPERLAKE ++++ b/kernel/x86_64/KERNEL.COOPERLAKE +@@ -1,5 +1,5 @@ + include $(KERNELDIR)/KERNEL.SKYLAKEX +- ++ifneq ($(NO_AVX512BF16), 1) + SBGEMM_SMALL_M_PERMIT = sbgemm_small_kernel_permit_cooperlake.c + SBGEMM_SMALL_K_NN = sbgemm_small_kernel_nn_cooperlake.c + SBGEMM_SMALL_K_B0_NN = sbgemm_small_kernel_nn_cooperlake.c +@@ -20,3 +20,4 @@ SBGEMMINCOPYOBJ = sbgemm_incopy$(TSUFFIX).$(SUFFIX) + SBGEMMITCOPYOBJ = sbgemm_itcopy$(TSUFFIX).$(SUFFIX) + SBGEMMONCOPYOBJ = sbgemm_oncopy$(TSUFFIX).$(SUFFIX) + SBGEMMOTCOPYOBJ = sbgemm_otcopy$(TSUFFIX).$(SUFFIX) ++endif +diff --git a/kernel/x86_64/KERNEL.SAPPHIRERAPIDS b/kernel/x86_64/KERNEL.SAPPHIRERAPIDS +index 3a832e9174..0ab2b4ddcf 100644 +--- a/kernel/x86_64/KERNEL.SAPPHIRERAPIDS ++++ b/kernel/x86_64/KERNEL.SAPPHIRERAPIDS +@@ -1,5 +1,6 @@ + include $(KERNELDIR)/KERNEL.COOPERLAKE + ++ifneq ($(NO_AVX512BF16), 1) + SBGEMM_SMALL_M_PERMIT = + SBGEMM_SMALL_K_NN = + SBGEMM_SMALL_K_B0_NN = +@@ -20,3 +21,4 @@ SBGEMMINCOPYOBJ = sbgemm_incopy$(TSUFFIX).$(SUFFIX) + SBGEMMITCOPYOBJ = sbgemm_itcopy$(TSUFFIX).$(SUFFIX) + SBGEMMONCOPYOBJ = sbgemm_oncopy$(TSUFFIX).$(SUFFIX) + SBGEMMOTCOPYOBJ = sbgemm_otcopy$(TSUFFIX).$(SUFFIX) ++endif diff --git a/deps/patches/openblas-darwin-sve.patch b/deps/patches/openblas-darwin-sve.patch new file mode 100644 index 0000000000000..a2166db9379f1 --- /dev/null +++ b/deps/patches/openblas-darwin-sve.patch @@ -0,0 +1,34 @@ +From 03688a42622cf76e696859ce384e45aa26d927fc Mon Sep 17 00:00:00 2001 +From: Ian McInerney +Date: Tue, 23 Jan 2024 10:29:57 +0000 +Subject: [PATCH] Build with proper aarch64 flags on Neoverse Darwin + +We aren't affected by the problems in AppleClang that prompted this +fallback to an older architecture. +--- + Makefile.arm64 | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/Makefile.arm64 b/Makefile.arm64 +index ed52a9424..a8f3cb0f0 100644 +--- a/Makefile.arm64 ++++ b/Makefile.arm64 +@@ -135,11 +135,11 @@ ifeq ($(CORE), NEOVERSEN2) + ifeq (1, $(filter 1,$(GCCVERSIONGTEQ7) $(ISCLANG))) + ifeq (1, $(filter 1,$(GCCVERSIONGTEQ10) $(ISCLANG))) + ifeq (1, $(filter 1,$(GCCMINORVERSIONGTEQ4) $(GCCVERSIONGTEQ11) $(ISCLANG))) +-ifneq ($(OSNAME), Darwin) ++#ifneq ($(OSNAME), Darwin) + CCOMMON_OPT += -march=armv8.5-a+sve+sve2+bf16 -mtune=neoverse-n2 +-else +-CCOMMON_OPT += -march=armv8.2-a -mtune=cortex-a72 +-endif ++#else ++#CCOMMON_OPT += -march=armv8.2-a -mtune=cortex-a72 ++#endif + ifneq ($(F_COMPILER), NAG) + FCOMMON_OPT += -march=armv8.5-a+sve+sve2+bf16 -mtune=neoverse-n2 + endif +-- +2.43.0 + diff --git a/deps/patches/openblas-gemv-multithreading.patch b/deps/patches/openblas-gemv-multithreading.patch new file mode 100644 index 0000000000000..827c72fa48b1a --- /dev/null +++ b/deps/patches/openblas-gemv-multithreading.patch @@ -0,0 +1,22 @@ +From d2fc4f3b4d7f41527bc7dc8f62e9aa6229cfac89 Mon Sep 17 00:00:00 2001 +From: Martin Kroeker +Date: Wed, 17 Jan 2024 20:59:24 +0100 +Subject: [PATCH] Increase multithreading threshold by a factor of 50 + +--- + interface/gemv.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/interface/gemv.c b/interface/gemv.c +index 1f07635799..2c121f1308 100644 +--- a/interface/gemv.c ++++ b/interface/gemv.c +@@ -226,7 +226,7 @@ void CNAME(enum CBLAS_ORDER order, + + #ifdef SMP + +- if ( 1L * m * n < 2304L * GEMM_MULTITHREAD_THRESHOLD ) ++ if ( 1L * m * n < 115200L * GEMM_MULTITHREAD_THRESHOLD ) + nthreads = 1; + else + nthreads = num_cpu_avail(2); diff --git a/deps/patches/openblas-ofast-power.patch b/deps/patches/openblas-ofast-power.patch index 405e3f7581331..01089286257f7 100644 --- a/deps/patches/openblas-ofast-power.patch +++ b/deps/patches/openblas-ofast-power.patch @@ -1,17 +1,29 @@ diff --git a/Makefile.power b/Makefile.power -index 28a0bae0..b4869fbd 100644 +index aa1ca080a..42c417a78 100644 --- a/Makefile.power +++ b/Makefile.power -@@ -11,7 +11,7 @@ endif - - ifeq ($(CORE), POWER10) +@@ -13,16 +13,16 @@ ifeq ($(CORE), POWER10) ifneq ($(C_COMPILER), PGI) + ifeq ($(C_COMPILER), GCC)) + ifeq ($(GCCVERSIONGTEQ10), 1) -CCOMMON_OPT += -Ofast -mcpu=power10 -mtune=power10 -mvsx -fno-fast-math +CCOMMON_OPT += -mcpu=power10 -mtune=power10 -mvsx -fno-fast-math - ifeq ($(F_COMPILER), IBM) - FCOMMON_OPT += -O2 -qrecur -qnosave + else ifneq ($(GCCVERSIONGT4), 1) + $(warning your compiler is too old to fully support POWER9, getting a newer version of gcc is recommended) +-CCOMMON_OPT += -Ofast -mcpu=power8 -mtune=power8 -mvsx -fno-fast-math ++CCOMMON_OPT += -mcpu=power8 -mtune=power8 -mvsx -fno-fast-math + else + $(warning your compiler is too old to fully support POWER10, getting a newer version of gcc is recommended) +-CCOMMON_OPT += -Ofast -mcpu=power9 -mtune=power9 -mvsx -fno-fast-math ++CCOMMON_OPT += -mcpu=power9 -mtune=power9 -mvsx -fno-fast-math + endif else -@@ -22,7 +22,7 @@ endif +-CCOMMON_OPT += -Ofast -mcpu=power10 -mtune=power10 -mvsx -fno-fast-math ++CCOMMON_OPT += -mcpu=power10 -mtune=power10 -mvsx -fno-fast-math + endif + ifeq ($(F_COMPILER), IBM) + FCOMMON_OPT += -O2 -qrecur -qnosave -qarch=pwr10 -qtune=pwr10 -qfloat=nomaf -qzerosize +@@ -34,7 +34,7 @@ endif ifeq ($(CORE), POWER9) ifneq ($(C_COMPILER), PGI) @@ -20,7 +32,7 @@ index 28a0bae0..b4869fbd 100644 ifeq ($(C_COMPILER), GCC) ifneq ($(GCCVERSIONGT4), 1) $(warning your compiler is too old to fully support POWER9, getting a newer version of gcc is recommended) -@@ -59,7 +59,7 @@ endif +@@ -70,7 +70,7 @@ endif ifeq ($(CORE), POWER8) ifneq ($(C_COMPILER), PGI) diff --git a/deps/pcre.mk b/deps/pcre.mk index cd1180d992885..3ff85d5569ad9 100644 --- a/deps/pcre.mk +++ b/deps/pcre.mk @@ -9,6 +9,9 @@ PCRE_LDFLAGS := $(RPATH_ESCAPED_ORIGIN) ifeq ($(OS),emscripten) PCRE_CFLAGS += -fPIC PCRE_JIT = --disable-jit +else ifeq ($(OS),OpenBSD) +# jit will need RWX memory +PCRE_JIT = --disable-jit else PCRE_JIT = --enable-jit endif diff --git a/deps/pcre.version b/deps/pcre.version index ce27921435e1d..e3ea507376105 100644 --- a/deps/pcre.version +++ b/deps/pcre.version @@ -2,4 +2,4 @@ PCRE_JLL_NAME := PCRE2 ## source build -PCRE_VER := 10.42 +PCRE_VER := 10.43 diff --git a/deps/sanitizers.mk b/deps/sanitizers.mk index 1a272321c05fa..d5bb6fce5b807 100644 --- a/deps/sanitizers.mk +++ b/deps/sanitizers.mk @@ -14,6 +14,8 @@ define copy_sanitizer_lib install-sanitizers: $$(addprefix $$(build_libdir)/, $$(notdir $$(call pathsearch_all,$(1),$$(SANITIZER_LIB_PATH)))) | $$(build_shlibdir) $$(addprefix $$(build_shlibdir)/,$(2)): $$(addprefix $$(SANITIZER_LIB_PATH)/,$(2)) | $$(build_shlibdir) -cp $$< $$@ + $(if $(filter $(OS), Linux), \ + -$(PATCHELF) $(PATCHELF_SET_RPATH_ARG) '$$$$ORIGIN' $$@ , 0) endef ifeq ($(USECLANG),1) diff --git a/deps/tools/jlchecksum b/deps/tools/jlchecksum index 87db805dbfab3..329d3a2a845d4 100755 --- a/deps/tools/jlchecksum +++ b/deps/tools/jlchecksum @@ -87,15 +87,17 @@ SHA512_PROG="" MD5_PROG="" find_checksum_progs() { - if [ ! -z $(which sha512sum) ]; then + if [ ! -z $(which sha512sum 2>/dev/null) ]; then SHA512_PROG="sha512sum $ARG1 | awk '{ print \$1; }'" - elif [ ! -z $(which shasum) ]; then + elif [ ! -z $(which shasum 2>/dev/null) ]; then SHA512_PROG="shasum -a 512 $ARG1 | awk '{ print \$1; }'" + elif [ ! -z $(which sha512 2>/dev/null) ]; then + SHA512_PROG="sha512 -q $ARG1" fi - if [ ! -z $(which md5sum) ]; then + if [ ! -z $(which md5sum 2>/dev/null) ]; then MD5_PROG="md5sum $ARG1 | awk '{ print \$1; }'" - elif [ ! -z $(which md5) ]; then + elif [ ! -z $(which md5 2>/dev/null) ]; then MD5_PROG="md5 -q $ARG1" fi } diff --git a/deps/unwind.mk b/deps/unwind.mk index 66607845428c4..f8e8260b431fa 100644 --- a/deps/unwind.mk +++ b/deps/unwind.mk @@ -73,7 +73,10 @@ check-unwind: $(BUILDDIR)/libunwind-$(UNWIND_VER)/build-checked ## LLVM libunwind ## -LLVMUNWIND_OPTS := $(CMAKE_COMMON) -DCMAKE_BUILD_TYPE=MinSizeRel -DLIBUNWIND_ENABLE_PEDANTIC=OFF -DLLVM_CONFIG_PATH=$(build_depsbindir)/llvm-config +LLVMUNWIND_OPTS := $(CMAKE_COMMON) \ + -DCMAKE_BUILD_TYPE=MinSizeRel \ + -DLIBUNWIND_ENABLE_PEDANTIC=OFF \ + -DLLVM_PATH=$(SRCCACHE)/$(LLVM_SRC_DIR)/llvm $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER).tar.xz: | $(SRCCACHE) $(JLDOWNLOAD) $@ https://github.com/llvm/llvm-project/releases/download/llvmorg-$(LLVMUNWIND_VER)/libunwind-$(LLVMUNWIND_VER).src.tar.xz diff --git a/deps/utf8proc.version b/deps/utf8proc.version index 659b995e8abaf..a026ca858cfd3 100644 --- a/deps/utf8proc.version +++ b/deps/utf8proc.version @@ -1,2 +1,2 @@ -UTF8PROC_BRANCH=v2.8.0 -UTF8PROC_SHA1=1cb28a66ca79a0845e99433fd1056257456cef8b +UTF8PROC_BRANCH=v2.9.0 +UTF8PROC_SHA1=34db3f7954e9298e89f42641ac78e0450f80a70d diff --git a/deps/valgrind/valgrind.h b/deps/valgrind/valgrind.h index 2e07a49d91dfa..b33fd70fab672 100644 --- a/deps/valgrind/valgrind.h +++ b/deps/valgrind/valgrind.h @@ -1065,7 +1065,7 @@ typedef /* Use these to write the name of your wrapper. NOTE: duplicates VG_WRAP_FUNCTION_Z{U,Z} in pub_tool_redir.h. NOTE also: inserts - the default behaviour equivalance class tag "0000" into the name. + the default behaviour equivalence class tag "0000" into the name. See pub_tool_redir.h for details -- normally you don't need to think about this, though. */ diff --git a/deps/zlib.version b/deps/zlib.version index 89a304c49b6dc..27d862a4cc35b 100644 --- a/deps/zlib.version +++ b/deps/zlib.version @@ -3,6 +3,6 @@ ZLIB_JLL_NAME := Zlib ## source build -ZLIB_VER := 1.2.13 -ZLIB_BRANCH=v1.2.13 -ZLIB_SHA1=04f42ceca40f73e2978b50e93806c2a18c1281fc +ZLIB_VER := 1.3.1 +ZLIB_BRANCH=v1.3.1 +ZLIB_SHA1=51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf diff --git a/doc/Manifest.toml b/doc/Manifest.toml index 31eb3634fa709..c55c983402830 100644 --- a/doc/Manifest.toml +++ b/doc/Manifest.toml @@ -9,52 +9,120 @@ git-tree-sha1 = "574baf8110975760d391c710b6341da1afa48d8c" uuid = "a4c015fc-c6ff-483c-b24f-f7ea428134e9" version = "0.0.1" +[[deps.AbstractTrees]] +git-tree-sha1 = "faa260e4cb5aba097a73fab382dd4b5819d8ec8c" +uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" +version = "0.4.4" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.2" + [[deps.Artifacts]] uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" +version = "1.11.0" [[deps.Base64]] uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +version = "1.11.0" + +[[deps.CodecZlib]] +deps = ["TranscodingStreams", "Zlib_jll"] +git-tree-sha1 = "59939d8a997469ee05c4b4944560a820f9ba0d73" +uuid = "944b1d66-785c-5afd-91f1-9de20f533193" +version = "0.7.4" [[deps.Dates]] deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" +version = "1.11.0" [[deps.DocStringExtensions]] deps = ["LibGit2"] -git-tree-sha1 = "5158c2b41018c5f7eb1470d558127ac274eca0c9" +git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d" uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.9.1" +version = "0.9.3" [[deps.Documenter]] -deps = ["ANSIColoredPrinters", "Base64", "Dates", "DocStringExtensions", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"] -git-tree-sha1 = "6030186b00a38e9d0434518627426570aac2ef95" +deps = ["ANSIColoredPrinters", "AbstractTrees", "Base64", "CodecZlib", "Dates", "DocStringExtensions", "Downloads", "Git", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "MarkdownAST", "Pkg", "PrecompileTools", "REPL", "RegistryInstances", "SHA", "TOML", "Test", "Unicode"] +git-tree-sha1 = "4a40af50e8b24333b9ec6892546d9ca5724228eb" uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -version = "0.27.23" +version = "1.3.0" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.Expat_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "4558ab818dcceaab612d1bb8c19cee87eda2b83c" +uuid = "2e619515-83b5-522b-bb60-26c02a35a201" +version = "2.5.0+0" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +version = "1.11.0" + +[[deps.Git]] +deps = ["Git_jll"] +git-tree-sha1 = "51764e6c2e84c37055e846c516e9015b4a291c7d" +uuid = "d7ba0133-e1db-5d97-8f8c-041e4b3a1eb2" +version = "1.3.0" + +[[deps.Git_jll]] +deps = ["Artifacts", "Expat_jll", "JLLWrappers", "LibCURL_jll", "Libdl", "Libiconv_jll", "OpenSSL_jll", "PCRE2_jll", "Zlib_jll"] +git-tree-sha1 = "bb8f7cc77ec1152414b2af6db533d9471cfbb2d1" +uuid = "f8c6e375-362e-5223-8a59-34ff63f689eb" +version = "2.42.0+0" [[deps.IOCapture]] deps = ["Logging", "Random"] -git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a" +git-tree-sha1 = "d75853a0bdbfb1ac815478bacd89cd27b550ace6" uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89" -version = "0.2.2" +version = "0.2.3" [[deps.InteractiveUtils]] deps = ["Markdown"] uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +version = "1.11.0" + +[[deps.JLLWrappers]] +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.5.0" [[deps.JSON]] deps = ["Dates", "Mmap", "Parsers", "Unicode"] -git-tree-sha1 = "3c837543ddb02250ef42f4738347454f95079d4e" +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" -version = "0.21.3" +version = "0.21.4" + +[[deps.LazilyInitializedFields]] +git-tree-sha1 = "8f7f3cabab0fd1800699663533b6d5cb3fc0e612" +uuid = "0e77f7df-68c5-4e49-93ce-4cd80f5598bf" +version = "1.2.2" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.4" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "8.6.0+0" [[deps.LibGit2]] deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"] uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" +version = "1.11.0" [[deps.LibGit2_jll]] deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"] uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" -version = "1.7.1+0" +version = "1.7.2+0" [[deps.LibSSH2_jll]] deps = ["Artifacts", "Libdl", "MbedTLS_jll"] @@ -63,43 +131,105 @@ version = "1.11.0+1" [[deps.Libdl]] uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +version = "1.11.0" + +[[deps.Libiconv_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "f9557a255370125b405568f9767d6d195822a175" +uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" +version = "1.17.0+0" [[deps.Logging]] +deps = ["StyledStrings"] uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" +version = "1.11.0" [[deps.Markdown]] deps = ["Base64"] uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" +version = "1.11.0" + +[[deps.MarkdownAST]] +deps = ["AbstractTrees", "Markdown"] +git-tree-sha1 = "465a70f0fc7d443a00dcdc3267a497397b8a3899" +uuid = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +version = "0.1.2" [[deps.MbedTLS_jll]] deps = ["Artifacts", "Libdl"] uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" -version = "2.28.2+1" +version = "2.28.6+0" [[deps.Mmap]] uuid = "a63ad114-7e13-5084-954f-fe012c677804" +version = "1.11.0" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2023.12.12" [[deps.NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" version = "1.2.0" +[[deps.OpenSSL_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "cc6e1927ac521b659af340e0ca45828a3ffc748f" +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "3.0.12+0" + +[[deps.PCRE2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" +version = "10.42.0+1" + [[deps.Parsers]] -deps = ["Dates"] -git-tree-sha1 = "3d5bf43e3e8b412656404ed9466f1dcbf7c50269" +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "a935806434c9d4c506ba941871b327b96d41f2bf" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.4.0" +version = "2.8.0" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.11.0" +weakdeps = ["REPL"] + + [deps.Pkg.extensions] + REPLExt = "REPL" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "03b4c25b43cb84cee5c90aa9b5ea0a78fd848d2f" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.2.0" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "00805cd429dcb4870060ff49ef443486c262e38e" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.4.1" [[deps.Printf]] deps = ["Unicode"] uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" +version = "1.11.0" [[deps.REPL]] -deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] +deps = ["InteractiveUtils", "Markdown", "Sockets", "StyledStrings", "Unicode"] uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" +version = "1.11.0" [[deps.Random]] deps = ["SHA"] uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +version = "1.11.0" + +[[deps.RegistryInstances]] +deps = ["LazilyInitializedFields", "Pkg", "TOML", "Tar"] +git-tree-sha1 = "ffd19052caf598b8653b99404058fce14828be51" +uuid = "2792f1a3-b283-48e8-9a74-f99dce5104f3" +version = "0.1.0" [[deps.SHA]] uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" @@ -107,13 +237,59 @@ version = "0.7.0" [[deps.Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" +version = "1.11.0" [[deps.Sockets]] uuid = "6462fe0b-24de-5631-8697-dd941f90decc" +version = "1.11.0" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.StyledStrings]] +uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" [[deps.Test]] deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +version = "1.11.0" + +[[deps.TranscodingStreams]] +git-tree-sha1 = "54194d92959d8ebaa8e26227dbe3cdefcdcd594f" +uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" +version = "0.10.3" +weakdeps = ["Random", "Test"] + + [deps.TranscodingStreams.extensions] + TestExt = ["Test", "Random"] + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" +version = "1.11.0" [[deps.Unicode]] uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" +version = "1.11.0" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.13+1" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.59.0+0" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.4.0+2" diff --git a/doc/make.jl b/doc/make.jl index 0ae74a55aceee..15205d6c2f89d 100644 --- a/doc/make.jl +++ b/doc/make.jl @@ -3,11 +3,13 @@ Base.ACTIVE_PROJECT[] = nothing empty!(LOAD_PATH) push!(LOAD_PATH, @__DIR__, "@stdlib") empty!(DEPOT_PATH) -pushfirst!(DEPOT_PATH, joinpath(@__DIR__, "deps")) +push!(DEPOT_PATH, joinpath(@__DIR__, "deps")) +push!(DEPOT_PATH, abspath(Sys.BINDIR, "..", "share", "julia")) using Pkg Pkg.instantiate() using Documenter +import LibGit2 baremodule GenStdLib end @@ -42,6 +44,68 @@ cd(joinpath(@__DIR__, "src")) do end end +# Because we have standard libraries that are hosted outside of the julia repo, +# but their docs are included in the manual, we need to populate the remotes argument +# of makedocs(), to make sure that Documenter knows how to resolve the directories +# in stdlib/ to the correct remote Git repositories (for source and edit links). +# +# This function parses the *.version files in stdlib/, returning a dictionary with +# all the key-value pairs from those files. *_GIT_URL and *_SHA1 fields are the ones +# we will actually be interested in. +function parse_stdlib_version_file(path) + values = Dict{String,String}() + for line in readlines(path) + m = match(r"^([A-Z0-9_]+)\s+:?=\s+(\S+)$", line) + if isnothing(m) + @warn "Unable to parse line in $(path)" line + else + values[m[1]] = m[2] + end + end + return values +end +# This generates the value that will be passed to the `remotes` argument of makedocs(), +# by looking through all *.version files in stdlib/. +documenter_stdlib_remotes = let stdlib_dir = realpath(joinpath(@__DIR__, "..", "stdlib")) + # Get a list of all *.version files in stdlib/.. + version_files = filter(readdir(stdlib_dir)) do fname + isfile(joinpath(stdlib_dir, fname)) && endswith(fname, ".version") + end + # .. and then parse them, each becoming an entry for makedocs's remotes. + # The values for each are of the form path => (remote, sha1), where + # - path: the path to the stdlib package's root directory, i.e. "stdlib/$PACKAGE" + # - remote: a Documenter.Remote object, pointing to the Git repository where package is hosted + # - sha1: the SHA1 of the commit that is included with the current Julia version + remotes_list = map(version_files) do version_fname + package = match(r"(.+)\.version", version_fname)[1] + versionfile = parse_stdlib_version_file(joinpath(stdlib_dir, version_fname)) + # From the (all uppercase) $(package)_GIT_URL and $(package)_SHA1 fields, we'll determine + # the necessary information. If this logic happens to fail for some reason for any of the + # standard libraries, we'll crash the documentation build, so that it could be fixed. + remote = let git_url_key = "$(uppercase(package))_GIT_URL" + haskey(versionfile, git_url_key) || error("Missing $(git_url_key) in $version_fname") + m = match(LibGit2.GITHUB_REGEX, versionfile[git_url_key]) + isnothing(m) && error("Unable to parse $(git_url_key)='$(versionfile[git_url_key])' in $version_fname") + Documenter.Remotes.GitHub(m[2], m[3]) + end + package_sha = let sha_key = "$(uppercase(package))_SHA1" + haskey(versionfile, sha_key) || error("Missing $(sha_key) in $version_fname") + versionfile[sha_key] + end + # Construct the absolute (local) path to the stdlib package's root directory + package_root_dir = joinpath(stdlib_dir, "$(package)-$(package_sha)") + # Documenter needs package_root_dir to exist --- it's just a sanity check it does on the remotes= keyword. + # In normal (local) builds, this will be the case, since the Makefiles will have unpacked the standard + # libraries. However, on CI we do this thing where we actually build docs in a clean worktree, just + # unpacking the `usr/` directory from the main build, and the unpacked stdlibs will be missing, and this + # will cause Documenter to throw an error. However, we don't _actually_ need the source files of the standard + # libraries to be present, so we just generate empty root directories to satisfy the check in Documenter. + isdir(package_root_dir) || mkpath(package_root_dir) + package_root_dir => (remote, package_sha) + end + Dict(remotes_list) +end + # Check if we are building a PDF const render_pdf = "pdf" in ARGS @@ -63,6 +127,7 @@ generate_markdown("NEWS") Manual = [ "manual/getting-started.md", + "manual/installation.md", "manual/variables.md", "manual/integers-and-floating-point-numbers.md", "manual/mathematical-operations.md", @@ -92,7 +157,6 @@ Manual = [ "manual/environment-variables.md", "manual/embedding.md", "manual/code-loading.md", - "manual/profile.md", "manual/stacktraces.md", "manual/performance-tips.md", "manual/workflow-tips.md", @@ -128,6 +192,11 @@ BaseDocs = [ StdlibDocs = [stdlib.targetfile for stdlib in STDLIB_DOCS] +Tutorials = [ + "tutorials/profile.md", + "tutorials/external.md", +] + DevDocs = [ "Documentation of Julia's Internals" => [ "devdocs/init.md", @@ -158,6 +227,8 @@ DevDocs = [ "devdocs/gc-sa.md", "devdocs/gc.md", "devdocs/jit.md", + "devdocs/builtins.md", + "devdocs/precompile_hang.md", ], "Developing/debugging Julia's C code" => [ "devdocs/backtraces.md", @@ -184,6 +255,7 @@ const PAGES = [ "Manual" => ["index.md", Manual...], "Base" => BaseDocs, "Standard Library" => StdlibDocs, + "Tutorials" => Tutorials, # Add "Release Notes" to devdocs "Developer Documentation" => [DevDocs..., hide("NEWS.md")], ] @@ -194,6 +266,7 @@ const PAGES = [ "Manual" => Manual, "Base" => BaseDocs, "Standard Library" => StdlibDocs, + "Tutorials" => Tutorials, "Developer Documentation" => DevDocs, ] end @@ -290,6 +363,9 @@ else collapselevel = 1, sidebar_sitename = false, ansicolor = true, + size_threshold = 800 * 2^10, # 800 KiB + size_threshold_warn = 200 * 2^10, # the manual has quite a few large pages, so we warn at 200+ KiB only + inventory_version = VERSION, ) end @@ -301,12 +377,12 @@ makedocs( doctest = ("doctest=fix" in ARGS) ? (:fix) : ("doctest=only" in ARGS) ? (:only) : ("doctest=true" in ARGS) ? true : false, linkcheck = "linkcheck=true" in ARGS, linkcheck_ignore = ["https://bugs.kde.org/show_bug.cgi?id=136779"], # fails to load from nanosoldier? - strict = true, checkdocs = :none, format = format, sitename = "The Julia Language", authors = "The Julia Project", pages = PAGES, + remotes = documenter_stdlib_remotes, ) # Update URLs to external stdlibs (JuliaLang/julia#43199) diff --git a/doc/man/julia.1 b/doc/man/julia.1 index 95cf73afd0556..6320536cbbc74 100644 --- a/doc/man/julia.1 +++ b/doc/man/julia.1 @@ -59,7 +59,7 @@ Display version information .TP -h, --help -Print help message +Print command-line options (this message) .TP --help-hidden @@ -77,7 +77,7 @@ Start up with the given system image file .TP -H, --home -Set location of julia executable +Set location of `julia` executable .TP --startup-file={yes*|no} @@ -93,8 +93,16 @@ Enable or disable Julia's default signal handlers Use native code from system image if available .TP ---compiled-modules={yes*|no} -Enable or disable incremental precompilation of modules +--compiled-modules={yes*|no|existing|strict} +Enable or disable incremental precompilation of modules. +The `existing` option allows use of existing compiled modules that were +previously precompiled, but disallows creation of new precompile files. +The `strict` option is similar, but will error if no precompile file is found. + +.TP +--pkgimages={yes*|no|existing} +Enable or disable usage of native code caching in the form of pkgimages +The `existing` option allows use of existing pkgimages but disallows creation of new ones .TP -e, --eval @@ -104,24 +112,30 @@ Evaluate -E, --print Evaluate and display the result +.TP +-m, --module [args] +Run entry point of `Package` (`@main` function) with `args' + .TP -L, --load Load immediately on all processors .TP --t, --threads -Enable n threads; "auto" tries to infer a useful default number -of threads to use but the exact behavior might change in the future. -Currently, "auto" uses the number of CPUs assigned to this julia -process based on the OS-specific affinity assignment interface, if -supported (Linux and Windows). If this is not supported (macOS) or -process affinity is not configured, it uses the number of CPU -threads. +-t, --threads {auto|N[,auto|M]} +Enable N[+M] threads; N threads are assigned to the `default` +threadpool, and if M is specified, M threads are assigned to the +`interactive` threadpool; `auto` tries to infer a useful +default number of threads to use but the exact behavior might change +in the future. Currently sets N to the number of CPUs assigned to +this Julia process based on the OS-specific affinity assignment +interface if supported (Linux and Windows) or to the number of CPU +threads if not supported (MacOS) or if process affinity is not +configured, and sets M to 1. .TP ---gcthreads -Enable n GC threads; If unspecified is set to half of the -compute worker threads. +--gcthreads=N[,M] +Use N threads for the mark phase of GC and M (0 or 1) threads for the concurrent sweeping phase of GC. +N is set to half of the number of compute threads and M is set to 0 if unspecified. .TP -p, --procs {N|auto} @@ -133,7 +147,7 @@ as the number of local CPU threads (logical cores) Run processes on hosts listed in .TP --i +-i, --interactive Interactive mode; REPL runs and `isinteractive()` is true .TP @@ -141,7 +155,7 @@ Interactive mode; REPL runs and `isinteractive()` is true Quiet startup: no banner, suppress REPL warnings .TP ---banner={yes|no|auto*} +--banner={yes|no|short|auto*} Enable or disable startup banner .TP @@ -169,15 +183,15 @@ Enable or disable warning for ambiguous top-level scope Limit usage of CPU features up to ; set to `help` to see the available options .TP --O, --optimize={0,1,2*,3} +-O, --optimize={0|1|2*|3} Set the optimization level (level 3 if `-O` is used without a level) .TP ---min-optlevel={0*,1,2,3} +--min-optlevel={0*|1|2|3} Set a lower bound on the optimization level .TP --g {0,1*,2} +-g, --debug-info={0|1*|2} Set the level of debug info generation (level 2 if `-g` is used without a level) .TP @@ -189,8 +203,12 @@ Control whether inlining is permitted, including overriding @inline declarations Emit bounds checks always, never, or respect @inbounds declarations .TP ---math-mode={ieee|user} -Disallow or enable unsafe floating point optimizations (overrides @fastmath declaration) +--math-mode={ieee|user*} +Always follow `ieee` floating point semantics or respect `@fastmath` declarations + +.TP +--polly={yes*|no} +Enable or disable the polyhedral optimizer Polly (overrides @polly declaration) .TP --code-coverage[={none*|user|all}] @@ -202,8 +220,8 @@ Count executions of source lines in a file or files under a given directory. A ` be placed before the path to indicate this option. A `@` with no path will track the current directory. .TP - --code-coverage=tracefile.info - Append coverage information to the LCOV tracefile (filename supports format tokens) +--code-coverage=tracefile.info +Append coverage information to the LCOV tracefile (filename supports format tokens) .TP --track-allocation[={none*|user|all}] @@ -211,8 +229,8 @@ Count bytes allocated by each source line (omitting setting is equivalent to `us .TP --track-allocation=@ -Count bytes allocated by each source line in a file or files under a given directory. A `@` -must be placed before the path to indicate this option. A `@` with no path will track the current directory. +Count bytes but only in files that fall under the given file path/directory. +The `@` prefix is required to select this option. A `@` with no path will track the current directory. .TP --bug-report=KIND @@ -223,8 +241,9 @@ fallbacks to the latest compatible BugReporting.jl if not. For more information, .TP --heap-size-hint= -Forces garbage collection if memory usage is higher than that value. The memory hint might be -specified in megabytes (500M) or gigabytes (1.5G) +Forces garbage collection if memory usage is higher than the given value. +The value may be specified as a number of bytes, optionally in units of +KB, MB, GB, or TB, or as a percentage of physical memory with %. .TP --compile={yes*|no|all|min} @@ -263,13 +282,17 @@ Generate an assembly file (.s) Generate an incremental output file (rather than complete) .TP ---trace-compile={stderr,name} +--trace-compile={stderr|name} Print precompile statements for methods compiled during execution or save to a path .TP -image-codegen Force generate code in imaging mode +.TP +--permalloc-pkgimg={yes|no*} +Copy the data section of package images into memory + .SH FILES AND ENVIRONMENT See https://docs.julialang.org/en/v1/manual/environment-variables/ diff --git a/doc/src/assets/cover.tex b/doc/src/assets/cover.tex index 67b77e520acd3..b959477913f59 100644 --- a/doc/src/assets/cover.tex +++ b/doc/src/assets/cover.tex @@ -15,7 +15,7 @@ %% ---- reset page geometry for cover page \newgeometry{left=2cm,right=2cm,bottom=3cm} % ref: memman@v3.7q, P65, "4.1. Styling the titling" -% http://mirrors.ctan.org/macros/latex/contrib/memoir/memman.pdf +% https://mirrors.ctan.org/macros/latex/contrib/memoir/memman.pdf \begin{titlingpage} % set background image \BgThispage diff --git a/doc/src/assets/custom.sty b/doc/src/assets/custom.sty index 03e6ff805cd3f..ebc11f0414945 100644 --- a/doc/src/assets/custom.sty +++ b/doc/src/assets/custom.sty @@ -6,7 +6,7 @@ \usepackage{geometry} % "some": use \BgThispage to change background % ref: background@v2.1,# 2.1 Options, "pages=" -% http://mirrors.ctan.org/macros/latex/contrib/background/background.pdf +% https://mirrors.ctan.org/macros/latex/contrib/background/background.pdf \usepackage[pages=some]{background} %% Color definitions for Julia @@ -27,7 +27,7 @@ contents={ %% Place the background image `title-bg' in the right place via `tikz'. % tikz option "remember picture", "overlay" % ref: pgfmanual@3.1.9a, #17.13.1 Referencing a Node in a Different Picture\ -% http://mirrors.ctan.org/graphics/pgf/base/doc/pgfmanual.pdf +% https://mirrors.ctan.org/graphics/pgf/base/doc/pgfmanual.pdf \begin{tikzpicture}[remember picture,overlay,draw=white] \draw [path picture={ % ref: pgfmanual, 15.6, "Predefined node path picture bounding box" diff --git a/doc/src/base/arrays.md b/doc/src/base/arrays.md index 6585f98360585..20e8e81614b9e 100644 --- a/doc/src/base/arrays.md +++ b/doc/src/base/arrays.md @@ -30,6 +30,8 @@ Base.StridedArray Base.StridedVector Base.StridedMatrix Base.StridedVecOrMat +Base.Memory +Base.MemoryRef Base.Slices Base.RowSlices Base.ColumnSlices @@ -95,6 +97,8 @@ Base.Broadcast.result_style ```@docs Base.getindex(::AbstractArray, ::Any...) Base.setindex!(::AbstractArray, ::Any, ::Any...) +Base.nextind +Base.prevind Base.copyto!(::AbstractArray, ::CartesianIndices, ::AbstractArray, ::CartesianIndices) Base.copy! Base.isassigned diff --git a/doc/src/base/base.md b/doc/src/base/base.md index 39e61738bf1ed..739ee97d1dd43 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -30,7 +30,7 @@ Base.isinteractive Base.summarysize Base.__precompile__ Base.include -Base.MainInclude.include +Main.include Base.include_string Base.include_dependency __init__ @@ -148,6 +148,7 @@ Base.setproperty! Base.replaceproperty! Base.swapproperty! Base.modifyproperty! +Base.setpropertyonce! Base.propertynames Base.hasproperty Core.getfield @@ -155,9 +156,8 @@ Core.setfield! Core.modifyfield! Core.replacefield! Core.swapfield! +Core.setfieldonce! Core.isdefined -Core.getglobal -Core.setglobal! Base.@isdefined Base.convert Base.promote @@ -285,7 +285,7 @@ Base.Fix2 ```@docs Core.eval -Base.MainInclude.eval +Main.eval Base.@eval Base.evalfile Base.esc @@ -352,6 +352,7 @@ Base.@timed Base.@elapsed Base.@allocated Base.@allocations +Base.@lock_conflicts Base.EnvDict Base.ENV Base.Sys.STDLIB @@ -373,6 +374,9 @@ Base.Sys.uptime Base.Sys.isjsvm Base.Sys.loadavg Base.Sys.isexecutable +Base.Sys.isreadable +Base.Sys.iswritable +Base.Sys.username Base.@static ``` @@ -459,6 +463,22 @@ Base.nameof(::Function) Base.functionloc(::Any, ::Any) Base.functionloc(::Method) Base.@locals +Core.getglobal +Core.setglobal! +Core.modifyglobal! +Core.swapglobal! +Core.setglobalonce! +Core.replaceglobal! +``` + +## Documentation +(See also the [documentation](@ref man-documentation) chapter.) +```@docs +Base.@doc +Docs.HTML +Docs.Text +Docs.hasdoc +Docs.undocumented_names ``` ## Code loading @@ -480,6 +500,7 @@ Base.GC.enable Base.GC.@preserve Base.GC.safepoint Base.GC.enable_logging +Base.GC.logging_enabled Meta.lower Meta.@lower Meta.parse(::AbstractString, ::Int) diff --git a/doc/src/base/c.md b/doc/src/base/c.md index e221a6432542f..bf7e2577029fe 100644 --- a/doc/src/base/c.md +++ b/doc/src/base/c.md @@ -14,7 +14,7 @@ Base.unsafe_modify! Base.unsafe_replace! Base.unsafe_swap! Base.unsafe_copyto!{T}(::Ptr{T}, ::Ptr{T}, ::Any) -Base.unsafe_copyto!{T}(::Array{T}, ::Any, ::Array{T}, ::Any, ::Any) +Base.unsafe_copyto!(::Array, ::Any, ::Array, ::Any, ::Any) Base.copyto! Base.pointer Base.unsafe_wrap{T,N}(::Union{Type{Array},Type{Array{T}},Type{Array{T,N}}}, ::Ptr{T}, ::NTuple{N,Int}) diff --git a/doc/src/base/collections.md b/doc/src/base/collections.md index a8f2bdc6b7d7d..9d68d55be3459 100644 --- a/doc/src/base/collections.md +++ b/doc/src/base/collections.md @@ -36,15 +36,15 @@ Fully implemented by: * [`AbstractRange`](@ref) * [`UnitRange`](@ref) - * `Tuple` - * `Number` + * [`Tuple`](@ref) + * [`Number`](@ref) * [`AbstractArray`](@ref) * [`BitSet`](@ref) * [`IdDict`](@ref) * [`Dict`](@ref) * [`WeakKeyDict`](@ref) * `EachLine` - * `AbstractString` + * [`AbstractString`](@ref) * [`Set`](@ref) * [`Pair`](@ref) * [`NamedTuple`](@ref) @@ -74,14 +74,14 @@ Fully implemented by: * [`AbstractRange`](@ref) * [`UnitRange`](@ref) - * `Tuple` - * `Number` + * [`Tuple`](@ref) + * [`Number`](@ref) * [`AbstractArray`](@ref) * [`BitSet`](@ref) * [`IdDict`](@ref) * [`Dict`](@ref) * [`WeakKeyDict`](@ref) - * `AbstractString` + * [`AbstractString`](@ref) * [`Set`](@ref) * [`NamedTuple`](@ref) @@ -166,8 +166,8 @@ Partially implemented by: * [`AbstractRange`](@ref) * [`UnitRange`](@ref) - * `Tuple` - * `AbstractString` + * [`Tuple`](@ref) + * [`AbstractString`](@ref) * [`Dict`](@ref) * [`IdDict`](@ref) * [`WeakKeyDict`](@ref) @@ -225,14 +225,15 @@ Base.valtype Fully implemented by: - * [`IdDict`](@ref) * [`Dict`](@ref) + * [`IdDict`](@ref) * [`WeakKeyDict`](@ref) Partially implemented by: - * [`BitSet`](@ref) * [`Set`](@ref) + * [`BitSet`](@ref) + * [`IdSet`](@ref) * [`EnvDict`](@ref Base.EnvDict) * [`Array`](@ref) * [`BitArray`](@ref) @@ -246,6 +247,7 @@ Partially implemented by: Base.AbstractSet Base.Set Base.BitSet +Base.IdSet Base.union Base.union! Base.intersect @@ -255,6 +257,7 @@ Base.symdiff Base.symdiff! Base.intersect! Base.issubset +Base.in! Base.:⊈ Base.:⊊ Base.issetequal @@ -263,8 +266,10 @@ Base.isdisjoint Fully implemented by: - * [`BitSet`](@ref) * [`Set`](@ref) + * [`BitSet`](@ref) + * [`IdSet`](@ref) + Partially implemented by: diff --git a/doc/src/base/file.md b/doc/src/base/file.md index 9a9dc5d8a72f8..22799f882bb26 100644 --- a/doc/src/base/file.md +++ b/doc/src/base/file.md @@ -1,6 +1,8 @@ # Filesystem ```@docs +Base.read(::String) +Base.write(::String, ::Any) Base.Filesystem.pwd Base.Filesystem.cd(::AbstractString) Base.Filesystem.cd(::Function) diff --git a/doc/src/base/io-network.md b/doc/src/base/io-network.md index 68f144427a892..123d494c396a0 100644 --- a/doc/src/base/io-network.md +++ b/doc/src/base/io-network.md @@ -6,10 +6,14 @@ Base.stdout Base.stderr Base.stdin +Base.read(::AbstractString) +Base.write(::AbstractString, ::Any) Base.open Base.IOStream Base.IOBuffer Base.take!(::Base.GenericIOBuffer) +Base.Pipe +Base.link_pipe! Base.fdio Base.flush Base.close @@ -35,6 +39,7 @@ Base.eof Base.isreadonly Base.iswritable Base.isreadable +Base.isexecutable Base.isopen Base.fd Base.redirect_stdio diff --git a/doc/src/base/math.md b/doc/src/base/math.md index 573a2fc0d7e90..c1b18c0b66d86 100644 --- a/doc/src/base/math.md +++ b/doc/src/base/math.md @@ -14,10 +14,12 @@ Base.fma Base.muladd Base.inv(::Number) Base.div +Base.div(::Any, ::Any, ::RoundingMode) Base.fld Base.cld Base.mod Base.rem +Base.rem(::Any, ::Any, ::RoundingMode) Base.rem2pi Base.Math.mod2pi Base.divrem @@ -37,6 +39,8 @@ Base.:(:) Base.range Base.OneTo Base.StepRangeLen +Base.logrange +Base.LogRange Base.:(==) Base.:(!=) Base.:(!==) @@ -138,6 +142,7 @@ Base.minmax Base.Math.clamp Base.Math.clamp! Base.abs +Base.Checked Base.Checked.checked_abs Base.Checked.checked_neg Base.Checked.checked_add @@ -148,6 +153,7 @@ Base.Checked.checked_rem Base.Checked.checked_fld Base.Checked.checked_mod Base.Checked.checked_cld +Base.Checked.checked_pow Base.Checked.add_with_overflow Base.Checked.sub_with_overflow Base.Checked.mul_with_overflow @@ -158,7 +164,7 @@ Base.signbit Base.flipsign Base.sqrt(::Number) Base.isqrt -Base.Math.cbrt +Base.Math.cbrt(::AbstractFloat) Base.real Base.imag Base.reim diff --git a/doc/src/base/multi-threading.md b/doc/src/base/multi-threading.md index 45a60b14d541a..9e3bc49acf6dc 100644 --- a/doc/src/base/multi-threading.md +++ b/doc/src/base/multi-threading.md @@ -25,19 +25,13 @@ atomic Base.@atomic Base.@atomicswap Base.@atomicreplace +Base.@atomiconce +Base.AtomicMemory ``` -!!! note - - The following APIs are fairly primitive, and will likely be exposed through an `unsafe_*`-like wrapper. - -``` -Core.Intrinsics.atomic_pointerref(pointer::Ptr{T}, order::Symbol) --> T -Core.Intrinsics.atomic_pointerset(pointer::Ptr{T}, new::T, order::Symbol) --> pointer -Core.Intrinsics.atomic_pointerswap(pointer::Ptr{T}, new::T, order::Symbol) --> old -Core.Intrinsics.atomic_pointermodify(pointer::Ptr{T}, function::(old::T,arg::S)->T, arg::S, order::Symbol) --> old -Core.Intrinsics.atomic_pointerreplace(pointer::Ptr{T}, expected::Any, new::T, success_order::Symbol, failure_order::Symbol) --> (old, cmp) -``` +There are also optional memory ordering parameters for the `unsafe` set of functions, that +select the C/C++-compatible versions of these atomic operations, if that parameter is specified to +[`unsafe_load`](@ref), [`unsafe_store!`](@ref), [`unsafe_swap!`](@ref), [`unsafe_replace!`](@ref), and [`unsafe_modify!`](@ref). !!! warning diff --git a/doc/src/base/numbers.md b/doc/src/base/numbers.md index 8167650ac17d1..aad4e94901054 100644 --- a/doc/src/base/numbers.md +++ b/doc/src/base/numbers.md @@ -63,6 +63,8 @@ Core.Int64 Core.UInt64 Core.Int128 Core.UInt128 +Base.Int +Base.UInt Base.BigInt Base.Complex Base.Rational diff --git a/doc/src/base/parallel.md b/doc/src/base/parallel.md index c3106b8caf8c7..c0f427cc00bb2 100644 --- a/doc/src/base/parallel.md +++ b/doc/src/base/parallel.md @@ -30,6 +30,8 @@ Base.schedule Base.errormonitor Base.@sync Base.wait +Base.waitany +Base.waitall Base.fetch(t::Task) Base.fetch(x::Any) Base.timedwait @@ -51,6 +53,7 @@ Base.trylock Base.islocked Base.ReentrantLock Base.@lock +Base.Lockable ``` ## Channels @@ -61,6 +64,7 @@ Base.Channel Base.Channel(::Function) Base.put!(::Channel, ::Any) Base.take!(::Channel) +Base.isfull(::Channel) Base.isready(::Channel) Base.fetch(::Channel) Base.close(::Channel) diff --git a/doc/src/base/reflection.md b/doc/src/base/reflection.md index 2798cfe2e7530..0f0af140b605f 100644 --- a/doc/src/base/reflection.md +++ b/doc/src/base/reflection.md @@ -51,7 +51,7 @@ The *direct* subtypes of any `DataType` may be listed using [`subtypes`](@ref). the abstract `DataType` [`AbstractFloat`](@ref) has four (concrete) subtypes: ```jldoctest; setup = :(using InteractiveUtils) -julia> subtypes(AbstractFloat) +julia> InteractiveUtils.subtypes(AbstractFloat) 5-element Vector{Any}: BigFloat Core.BFloat16 @@ -63,6 +63,9 @@ julia> subtypes(AbstractFloat) Any abstract subtype will also be included in this list, but further subtypes thereof will not; recursive application of [`subtypes`](@ref) may be used to inspect the full type tree. +Note that [`subtypes`](@ref) is located inside [`InteractiveUtils`](@ref man-interactive-utils) but +is automatically exported when using the REPL. + ## DataType layout The internal representation of a `DataType` is critically important when interfacing with C code @@ -83,7 +86,7 @@ the unquoted and interpolated expression ([`Expr`](@ref)) form for a given macro be passed instead!). For example: ```jldoctest; setup = :(using InteractiveUtils) -julia> macroexpand(@__MODULE__, :(@edit println("")) ) +julia> InteractiveUtils.macroexpand(@__MODULE__, :(@edit println("")) ) :(InteractiveUtils.edit(println, (Base.typesof)(""))) ``` @@ -94,10 +97,9 @@ Finally, the [`Meta.lower`](@ref) function gives the `lowered` form of any expre particular interest for understanding how language constructs map to primitive operations such as assignments, branches, and calls: -```jldoctest +```jldoctest; setup = (using Base: +, sin) julia> Meta.lower(@__MODULE__, :( [1+2, sin(0.5)] )) :($(Expr(:thunk, CodeInfo( - @ none within `top-level scope` 1 ─ %1 = 1 + 2 │ %2 = sin(0.5) │ %3 = Base.vect(%1, %2) @@ -140,10 +142,10 @@ For more information see [`@code_lowered`](@ref), [`@code_typed`](@ref), [`@code The aforementioned functions and macros take the keyword argument `debuginfo` that controls the level debug information printed. -```julia-repl -julia> @code_typed debuginfo=:source +(1,1) +```jldoctest; setup = :(using InteractiveUtils), filter = r"int.jl:\d+" +julia> InteractiveUtils.@code_typed debuginfo=:source +(1,1) CodeInfo( - @ int.jl:53 within `+' + @ int.jl:87 within `+` 1 ─ %1 = Base.add_int(x, y)::Int64 └── return %1 ) => Int64 diff --git a/doc/src/base/scopedvalues.md b/doc/src/base/scopedvalues.md index 5c3318259ca55..6ad553429bb1f 100644 --- a/doc/src/base/scopedvalues.md +++ b/doc/src/base/scopedvalues.md @@ -17,18 +17,15 @@ concurrently. Scoped values were introduced in Julia 1.11. In Julia 1.8+ a compatible implementation is available from the package ScopedValues.jl. -In its simplest form you can create a [`ScopedValue`](@ref) with a -default value and then use [`with`](@ref Base.with) or [`@with`](@ref) to -enter a new dynamic scope. +In its simplest form you can create a [`ScopedValue`](@ref Base.ScopedValues.ScopedValue) +with a default value and then use [`with`](@ref Base.ScopedValues.with) or +[`@with`](@ref Base.ScopedValues.@with) to enter a new dynamic scope. The new scope will +inherit all values from the parent scope (and recursively from all outer scopes) with the +provided scoped value taking priority over previous definitions. -The new scope will inherit all values from the parent scope -(and recursively from all outer scopes) with the provided scoped -value taking priority over previous definitions. - -Let's first look at an example of **lexical** scope: - -A `let` statements begins a new lexical scope within which the outer definition -of `x` is shadowed by it's inner definition. +Let's first look at an example of **lexical** scope. A `let` statement begins +a new lexical scope within which the outer definition of `x` is shadowed by +it's inner definition. ```julia x = 1 @@ -38,9 +35,9 @@ end @show x # 1 ``` -Since Julia uses lexical scope the variable `x` is bound within the function `f` -to the global scope and entering a `let` scope does not change the value `f` -observes. +In the following example, since Julia uses lexical scope, the variable `x` in the body +of `f` refers to the `x` defined in the global scope, and entering a `let` scope does +not change the value `f` observes. ```julia x = 1 @@ -54,6 +51,8 @@ f() # 1 Now using a `ScopedValue` we can use **dynamic** scoping. ```julia +using Base.ScopedValues + x = ScopedValue(1) f() = @show x[] with(x=>5) do @@ -62,7 +61,7 @@ end f() # 1 ``` -Not that the observed value of the `ScopedValue` is dependent on the execution +Note that the observed value of the `ScopedValue` is dependent on the execution path of the program. It often makes sense to use a `const` variable to point to a scoped value, @@ -70,32 +69,56 @@ and you can set the value of multiple `ScopedValue`s with one call to `with`. ```julia -const scoped_val = ScopedValue(1) -const scoped_val2 = ScopedValue(0) - -# Enter a new dynamic scope and set value -@show scoped_val[] # 1 -@show scoped_val2[] # 0 -with(scoped_val => 2) do - @show scoped_val[] # 2 - @show scoped_val2[] # 0 - with(scoped_val => 3, scoped_val2 => 5) do - @show scoped_val[] # 3 - @show scoped_val2[] # 5 +using Base.ScopedValues + +f() = @show a[] +g() = @show b[] + +const a = ScopedValue(1) +const b = ScopedValue(2) + +f() # a[] = 1 +g() # b[] = 2 + +# Enter a new dynamic scope and set value. +with(a => 3) do + f() # a[] = 3 + g() # b[] = 2 + with(a => 4, b => 5) do + f() # a[] = 4 + g() # b[] = 5 end - @show scoped_val[] # 2 - @show scoped_val2[] # 0 + f() # a[] = 3 + g() # b[] = 2 end -@show scoped_val[] # 1 -@show scoped_val2[] # 0 + +f() # a[] = 1 +g() # b[] = 2 ``` -Since `with` requires a closure or a function and creates another call-frame, -it can sometimes be beneficial to use the macro form. +`ScopedValues` provides a macro version of `with`. The expression `@with var=>val expr` +evaluates `expr` in a new dynamic scope with `var` set to `val`. `@with var=>val expr` +is equivalent to `with(var=>val) do expr end`. However, `with` requires a zero-argument +closure or function, which results in an extra call-frame. As an example, consider the +following function `f`: ```julia -const STATE = ScopedValue{State}() -with_state(f, state::State) = @with(STATE => state, f()) +using Base.ScopedValues +const a = ScopedValue(1) +f(x) = a[] + x +``` + +If you wish to run `f` in a dynamic scope with `a` set to `2`, then you can use `with`: + +```julia +with(() -> f(10), a=>2) +``` + +However, this requires wrapping `f` in a zero-argument function. If you wish to avoid +the extra call-frame, then you can use the `@with` macro: + +```julia +@with a=>2 f(10) ``` !!! note @@ -106,7 +129,9 @@ The parent task and the two child tasks observe independent values of the same scoped value at the same time. ```julia +using Base.ScopedValues import Base.Threads: @spawn + const scoped_val = ScopedValue(1) @sync begin with(scoped_val => 2) @@ -128,7 +153,9 @@ values. You might want to explicitly [unshare mutable state](@ref unshare_mutabl when entering a new dynamic scope. ```julia +using Base.ScopedValues import Base.Threads: @spawn + const sval_dict = ScopedValue(Dict()) # Example of using a mutable value wrongly @@ -140,7 +167,7 @@ end @sync begin # If we instead pass a unique dictionary to each - # task we can access the dictonaries race free. + # task we can access the dictionaries race free. with(sval_dict => Dict()) do @spawn (sval_dict[][:a] = 3) end @@ -161,12 +188,14 @@ are not well suited for this kind of propagation; our only alternative would hav been to thread a value through the entire call-chain. ```julia +using Base.ScopedValues + const LEVEL = ScopedValue(:GUEST) function serve(request, response) level = isAdmin(request) ? :ADMIN : :GUEST with(LEVEL => level) do - Threads.@spawn handle(request, respone) + Threads.@spawn handle(request, response) end end @@ -189,12 +218,14 @@ end ### [Unshare mutable state](@id unshare_mutable_state) ```julia +using Base.ScopedValues import Base.Threads: @spawn + const sval_dict = ScopedValue(Dict()) # If you want to add new values to the dict, instead of replacing # it, unshare the values explicitly. In this example we use `merge` -# to unshare the state of the dictonary in parent scope. +# to unshare the state of the dictionary in parent scope. @sync begin with(sval_dict => merge(sval_dict[], Dict(:a => 10))) do @spawn @show sval_dict[][:a] @@ -210,6 +241,7 @@ be in (lexical) scope. This means most often you likely want to use scoped value as constant globals. ```julia +using Base.ScopedValues const sval = ScopedValue(1) ``` @@ -218,7 +250,9 @@ Indeed one can think of scoped values as hidden function arguments. This does not preclude their use as non-globals. ```julia +using Base.ScopedValues import Base.Threads: @spawn + function main() role = ScopedValue(:client) @@ -241,16 +275,18 @@ If you find yourself creating many `ScopedValue`'s for one given module, it may be better to use a dedicated struct to hold them. ```julia +using Base.ScopedValues + Base.@kwdef struct Configuration color::Bool = false verbose::Bool = false end -const CONFIG = ScopedValue(Configuration()) +const CONFIG = ScopedValue(Configuration(color=true)) -@with CONFIG => Configuration(CONFIG[], color=true) begin +@with CONFIG => Configuration(color=CONFIG[].color, verbose=true) begin @show CONFIG[].color # true - @show CONFIG[].verbose # false + @show CONFIG[].verbose # true end ``` @@ -260,7 +296,7 @@ end Base.ScopedValues.ScopedValue Base.ScopedValues.with Base.ScopedValues.@with -Base.isassigned(::ScopedValue) +Base.isassigned(::Base.ScopedValues.ScopedValue) Base.ScopedValues.get ``` @@ -276,6 +312,6 @@ version of Julia. ## Design inspiration This design was heavily inspired by [JEPS-429](https://openjdk.org/jeps/429), -which in turn was inspired by dynamically scoped free variables in many Lisp dialects. In particular Interlisp-D and it's deep binding strategy. +which in turn was inspired by dynamically scoped free variables in many Lisp dialects. In particular Interlisp-D and its deep binding strategy. A prior design discussed was context variables ala [PEPS-567](https://peps.python.org/pep-0567/) and implemented in Julia as [ContextVariablesX.jl](https://github.com/tkf/ContextVariablesX.jl). diff --git a/doc/src/base/strings.md b/doc/src/base/strings.md index 979ba1157fb23..ef470be6b55cc 100644 --- a/doc/src/base/strings.md +++ b/doc/src/base/strings.md @@ -17,6 +17,11 @@ Core.String(::AbstractString) Base.SubString Base.LazyString Base.@lazy_str +Base.AnnotatedString +Base.AnnotatedChar +Base.annotatedstring +Base.annotations +Base.annotate! Base.transcode Base.unsafe_string Base.ncodeunits(::AbstractString) @@ -75,8 +80,8 @@ Base.chopprefix Base.chopsuffix Base.chomp Base.thisind -Base.nextind -Base.prevind +Base.nextind(::AbstractString, ::Integer, ::Integer) +Base.prevind(::AbstractString, ::Integer, ::Integer) Base.textwidth Base.isascii Base.iscntrl @@ -90,5 +95,6 @@ Base.isspace Base.isuppercase Base.isxdigit Base.escape_string +Base.escape_raw_string Base.unescape_string ``` diff --git a/doc/src/devdocs/EscapeAnalysis.md b/doc/src/devdocs/EscapeAnalysis.md index ed91ee64e8bdc..1bd7868790f7f 100644 --- a/doc/src/devdocs/EscapeAnalysis.md +++ b/doc/src/devdocs/EscapeAnalysis.md @@ -18,7 +18,7 @@ This escape analysis aims to: ## Try it out! You can give a try to the escape analysis by loading the `EAUtils.jl` utility script that -define the convenience entries `code_escapes` and `@code_escapes` for testing and debugging purposes: +defines the convenience entries `code_escapes` and `@code_escapes` for testing and debugging purposes: ```@repl EAUtils let JULIA_DIR = normpath(Sys.BINDIR, "..", "share", "julia") # load `EscapeAnalysis` module to define the core analysis code @@ -54,7 +54,7 @@ result = code_escapes((String,String,String,String)) do s1, s2, s3, s4 end ``` -The symbols in the side of each call argument and SSA statements represents the following meaning: +The symbols on the side of each call argument and SSA statements represent the following meaning: - `◌` (plain): this value is not analyzed because escape information of it won't be used anyway (when the object is `isbitstype` for example) - `✓` (green or cyan): this value never escapes (`has_no_escape(result.state[x])` holds), colored blue if it has arg escape also (`has_arg_escape(result.state[x])` holds) - `↑` (blue or yellow): this value can escape to the caller via return (`has_return_escape(result.state[x])` holds), colored yellow if it has unhandled thrown escape also (`has_thrown_escape(result.state[x])` holds) diff --git a/doc/src/devdocs/aot.md b/doc/src/devdocs/aot.md index e74f121437b80..cdaf1880ab927 100644 --- a/doc/src/devdocs/aot.md +++ b/doc/src/devdocs/aot.md @@ -23,7 +23,7 @@ Firstly, the methods that need to be compiled to native code must be identified. Once the methods to be compiled have been identified, they are passed to the `jl_create_system_image` function. This function sets up a number of data structures that will be used when serializing native code to a file, and then calls `jl_create_native` with the array of methods. `jl_create_native` runs codegen on the methods produces one or more LLVM modules. `jl_create_system_image` then records some useful information about what codegen produced from the module(s). -The module(s) are then passed to `jl_dump_native`, along with the information recorded by `jl_create_system_image`. `jl_dump_native` contains the code necessary to serialize the module(s) to bitcode, object, or assembly files depending on the command-line options passed to Julia. The serialized code and information is then written to a file as an archive. +The module(s) are then passed to `jl_dump_native`, along with the information recorded by `jl_create_system_image`. `jl_dump_native` contains the code necessary to serialize the module(s) to bitcode, object, or assembly files depending on the command-line options passed to Julia. The serialized code and information are then written to a file as an archive. The final step is to run a system linker on the object files in the archive produced by `jl_dump_native`. Once this step is complete, a shared library containing the compiled code is produced. diff --git a/doc/src/devdocs/ast.md b/doc/src/devdocs/ast.md index 4bd3959cf0ff6..a87929e6ff4d6 100644 --- a/doc/src/devdocs/ast.md +++ b/doc/src/devdocs/ast.md @@ -431,7 +431,7 @@ These symbols appear in the `head` field of [`Expr`](@ref)s in lowered form. * `the_exception` - Yields the caught exception inside a `catch` block, as returned by `jl_current_exception()`. + Yields the caught exception inside a `catch` block, as returned by `jl_current_exception(ct)`. * `enter` @@ -601,10 +601,9 @@ for important details on how to modify these fields safely. * `sparam_vals` - The values of the static parameters in `specTypes` indexed by `def.sparam_syms`. For the - `MethodInstance` at `Method.unspecialized`, this is the empty `SimpleVector`. But for a - runtime `MethodInstance` from the `MethodTable` cache, this will always be defined and - indexable. + The values of the static parameters in `specTypes`. + For the `MethodInstance` at `Method.unspecialized`, this is the empty `SimpleVector`. + But for a runtime `MethodInstance` from the `MethodTable` cache, this will always be defined and indexable. * `uninferred` @@ -628,6 +627,10 @@ for important details on how to modify these fields safely. The `MethodInstance` that this cache entry is derived from. + * `owner` + + A token that represents the owner of this `CodeInstance`. Will use `jl_egal` to match. + * `rettype`/`rettype_const` @@ -690,24 +693,13 @@ A (usually temporary) container for holding lowered source code. * `ssaflags` - Statement-level flags for each expression in the function. Many of these are reserved, but not yet implemented: - - * 0x01 << 0 = statement is marked as `@inbounds` - * 0x01 << 1 = statement is marked as `@inline` - * 0x01 << 2 = statement is marked as `@noinline` - * 0x01 << 3 = statement is within a block that leads to `throw` call - * 0x01 << 4 = statement may be removed if its result is unused, in particular it is thus be both pure and effect free - * 0x01 << 5-6 = - * 0x01 << 7 = has out-of-band info - - * `linetable` - - An array of source location objects + Statement-level 32 bits flags for each expression in the function. + See the definition of `jl_code_info_t` in julia.h for more details. - * `codelocs` + * `debuginfo` - An array of integer indices into the `linetable`, giving the location associated - with each statement. + An object to retrieve source information for each statements, see + [How to interpret line numbers in a `CodeInfo` object](@ref). Optional Fields: @@ -771,3 +763,79 @@ Boolean properties: * 0x01 << 4 = the syntactic control flow within this method is guaranteed to terminate (`:terminates_locally`) See the documentation of `Base.@assume_effects` for more details. + + +#### How to interpret line numbers in a `CodeInfo` object + +There are 2 common forms for this data: one used internally that compresses the data somewhat and one used in the compiler. +They contain the same basic info, but the compiler version is all mutable while the version used internally is not. + +Many consumers may be able to call `Base.IRShow.buildLineInfoNode`, +`Base.IRShow.append_scopes!`, or `Stacktraces.lookup(::InterpreterIP)` to avoid needing to +(re-)implement these details specifically. + +The definitions of each of these are: + +```julia +struct Core.DebugInfo + @noinline + def::Union{Method,MethodInstance,Symbol} + linetable::Union{Nothing,DebugInfo} + edges::SimpleVector{DebugInfo} + codelocs::String # compressed data +end +mutable struct Core.Compiler.DebugInfoStream + def::Union{Method,MethodInstance,Symbol} + linetable::Union{Nothing,DebugInfo} + edges::Vector{DebugInfo} + firstline::Int32 # the starting line for this block (specified by an index of 0) + codelocs::Vector{Int32} # for each statement: + # index into linetable (if defined), else a line number (in the file represented by def) + # then index into edges + # then index into edges[linetable] +end +``` + + + * `def` : where this `DebugInfo` was defined (the `Method`, `MethodInstance`, or `Symbol` of file scope, for example) + + * `linetable` + + Another `DebugInfo` that this was derived from, which contains the actual line numbers, + such that this DebugInfo contains only the indexes into it. This avoids making copies, + as well as makes it possible to track how each individual statement transformed from + source to optimized, not just the separate line numbers. If `def` is not a Symbol, then + that object replaces the current function object for the metadata on what function is + conceptually being executed (e.g. think Cassette transforms here). The `codelocs` values + described below also are interpreted as an index into the `codelocs` in this object, + instead of being a line number itself. + + * `edges` : Vector of the unique DebugInfo for every function inlined into this (which + recursively have the edges for everything inlined into them). + + * `firstline` (when uncompressed to DebugInfoStream) + + The line number associated with the `begin` statement (or other keyword such as + `function` or `quote`) that delineates where this code definition "starts". + + * `codelocs` (when uncompressed to `DebugInfoStream`) + + A vector of indices, with 3 values for each statement in the IR plus one for the + starting point of the block, that describe the stacktrace from that point: + 1. the integer index into the `linetable.codelocs` field, giving the + original location associated with each statement (including its syntactic edges), + or zero indicating no change to the line number from the previously + executed statement (which is not necessarily syntactic or lexical prior), + or the line number itself if the `linetable` field is `nothing`. + 2. the integer index into `edges`, giving the `DebugInfo` inlined there, + or zero if there are no edges. + 3. (if entry 2 is non-zero) the integer index into `edges[].codelocs`, + to interpret recursively for each function in the inlining stack, + or zero indicating to use `edges[].firstline` as the line number. + + Special codes include: + - `(zero, zero, *) `: no change to the line number or edges from the previous statement + (you may choose to interpret this either syntactically or lexically). The inlining + depth also might have changed, though most callers should ignore that. + - `(zero, non-zero, *)` : no line number, just edges (usually because of + macro-expansion into top-level code). diff --git a/doc/src/devdocs/build/build.md b/doc/src/devdocs/build/build.md index 51bcf7d0ee469..e8092df493df7 100644 --- a/doc/src/devdocs/build/build.md +++ b/doc/src/devdocs/build/build.md @@ -60,6 +60,16 @@ To run julia from anywhere you can: - write `prefix=/path/to/install/folder` into `Make.user` and then run `make install`. If there is a version of Julia already installed in this folder, you should delete it before running `make install`. +Some of the options you can set to control the build of Julia are listed and documented at the beginning of the file `Make.inc`, but you should never edit it for this purpose, use `Make.user` instead. + +Julia's Makefiles define convenient automatic rules called `print-` for printing the value of variables, replacing `` with the name of the variable to print the value of. +For example +```console +$ make print-JULIA_PRECOMPILE +JULIA_PRECOMPILE=1 +``` +These rules are useful for debugging purposes. + Now you should be able to run Julia like this: julia @@ -239,10 +249,48 @@ For packaging Julia with LLVM, we recommend either: - bundling a Julia-only LLVM library inside the Julia package, or - adding the patches to the LLVM package of the distribution. * A complete list of patches is available in on [Github](https://github.com/JuliaLang/llvm-project) see the `julia-release/15.x` branch. - * The only Julia-specific patch is the lib renaming (`llvm-symver-jlprefix.patch`), which should _not_ be applied to a system LLVM. + * The only Julia-specific patch is the lib renaming (`llvm7-symver-jlprefix.patch`), which should _not_ be applied to a system LLVM. * The remaining patches are all upstream bug fixes, and have been contributed into upstream LLVM. -Using an unpatched or different version of LLVM will result in errors and/or poor performance. Though Julia can be built with newer LLVM versions, support for this should be regarded as experimental and not suitable for packaging. +Using an unpatched or different version of LLVM will result in errors and/or poor performance. +You can build a different version of LLVM from a remote Git repository with the following options in the `Make.user` file: + +```make +# Force source build of LLVM +USE_BINARYBUILDER_LLVM = 0 +# Use Git for fetching LLVM source code +# this is either `1` to get all of them +DEPS_GIT = 1 +# or a space-separated list of specific dependencies to download with git +DEPS_GIT = llvm + +# Other useful options: +#URL of the Git repository you want to obtain LLVM from: +# LLVM_GIT_URL = ... +#Name of the alternate branch to clone from git +# LLVM_BRANCH = julia-16.0.6-0 +#SHA hash of the alternate commit to check out automatically +# LLVM_SHA1 = $(LLVM_BRANCH) +#List of LLVM targets to build. It is strongly recommended to keep at least all the +#default targets listed in `deps/llvm.mk`, even if you don't necessarily need all of them. +# LLVM_TARGETS = ... +#Use ccache for faster recompilation in case you need to restart a build. +# USECCACHE = 1 +# CMAKE_GENERATOR=Ninja +# LLVM_ASSERTIONS=1 +# LLVM_DEBUG=Symbols +``` + +The various build phases are controlled by specific files: + * `deps/llvm.version` : touch or change to checkout a new version, `make get-llvm check-llvm` + * `deps/srccache/llvm/source-extracted` : result of `make extract-llvm` + * `deps/llvm/build_Release*/build-configured` : result of `make configure-llvm` + * `deps/llvm/build_Release*/build-configured` : result of `make compile-llvm` + * `usr-staging/llvm/build_Release*.tgz` : result of `make stage-llvm` (regenerate with `make reinstall-llvm`) + * `usr/manifest/llvm` : result of `make install-llvm` (regenerate with `make uninstall-llvm`) + * `make version-check-llvm` : runs every time to warn the user if there are local modifications + +Though Julia can be built with newer LLVM versions, support for this should be regarded as experimental and not suitable for packaging. ### libuv diff --git a/doc/src/devdocs/builtins.md b/doc/src/devdocs/builtins.md new file mode 100644 index 0000000000000..5ce7fae16f488 --- /dev/null +++ b/doc/src/devdocs/builtins.md @@ -0,0 +1,29 @@ +# [Core.Builtins](@id lib-builtins) + +## Builtin Function APIs + +The following Builtin function APIs are considered unstable, but provide the basic +definitions for what defines the abilities and behaviors of a Julia program. They are +typically accessed through a higher level generic API. + +```@docs +Core.memoryref +Core.memoryrefoffset +Core.memoryrefget +Core.memoryrefset! +Core.memoryref_isassigned +Core.memoryrefswap! +Core.memoryrefmodify! +Core.memoryrefreplace! +Core.memoryrefsetonce! +Core.Intrinsics.atomic_pointerref +Core.Intrinsics.atomic_pointerset +Core.Intrinsics.atomic_pointerswap +Core.Intrinsics.atomic_pointermodify +Core.Intrinsics.atomic_pointerreplace +Core.get_binding_type +Core.set_binding_type! +Core.IntrinsicFunction +Core.Intrinsics +Core.IR +``` diff --git a/doc/src/devdocs/cartesian.md b/doc/src/devdocs/cartesian.md index 1d338cbd8fab3..604f04f2a39e5 100644 --- a/doc/src/devdocs/cartesian.md +++ b/doc/src/devdocs/cartesian.md @@ -133,6 +133,7 @@ Base.Cartesian.@nref Base.Cartesian.@nextract Base.Cartesian.@nexprs Base.Cartesian.@ncall +Base.Cartesian.@ncallkw Base.Cartesian.@ntuple Base.Cartesian.@nall Base.Cartesian.@nany diff --git a/doc/src/devdocs/debuggingtips.md b/doc/src/devdocs/debuggingtips.md index 7639e8be2ef96..a7740b1780b06 100644 --- a/doc/src/devdocs/debuggingtips.md +++ b/doc/src/devdocs/debuggingtips.md @@ -41,11 +41,16 @@ useful. ## Useful Julia functions for Inspecting those variables - * `jl_gdblookup($rip)` :: For looking up the current function and line. (use `$eip` on i686 platforms) + * `jl_print_task_backtraces(0)` :: Similar to gdb's `thread apply all bt` or lldb's `thread backtrace + all`. Runs all threads while printing backtraces for all existing tasks. + * `jl_gdblookup($pc)` :: For looking up the current function and line. + * `jl_gdblookupinfo($pc)` :: For looking up the current method instance object. + * `jl_gdbdumpcode(mi)` :: For dumping all of `code_typed/code_llvm/code_asm` when the REPL is not working right. * `jlbacktrace()` :: For dumping the current Julia backtrace stack to stderr. Only usable after `record_backtrace()` has been called. * `jl_dump_llvm_value(Value*)` :: For invoking `Value->dump()` in gdb, where it doesn't work natively. For example, `f->linfo->functionObject`, `f->linfo->specFunctionObject`, and `to_function(f->linfo)`. + * `jl_dump_llvm_module(Module*)` :: For invoking `Module->dump()` in gdb, where it doesn't work natively. * `Type->dump()` :: only works in lldb. Note: add something like `;1` to prevent lldb from printing its prompt over the output * `jl_eval_string("expr")` :: for invoking side-effects to modify the current state or to lookup diff --git a/doc/src/devdocs/functions.md b/doc/src/devdocs/functions.md index 283f63b2d0dce..777afaa56348d 100644 --- a/doc/src/devdocs/functions.md +++ b/doc/src/devdocs/functions.md @@ -15,14 +15,14 @@ has a `TypeName`. ## [Function calls](@id Function-calls) -Given the call `f(x,y)`, the following steps are performed: first, the method table to use is +Given the call `f(x, y)`, the following steps are performed: first, the method table to use is accessed as `typeof(f).name.mt`. Second, an argument tuple type is formed, `Tuple{typeof(f), typeof(x), typeof(y)}`. Note that the type of the function itself is the first element. This is because the type might have parameters, and so needs to take part in dispatch. This tuple type is looked up in the method table. This dispatch process is performed by `jl_apply_generic`, which takes two arguments: a pointer -to an array of the values f, x, and y, and the number of values (in this case 3). +to an array of the values `f`, `x`, and `y`, and the number of values (in this case 3). Throughout the system, there are two kinds of APIs that handle functions and argument lists: those that accept the function and arguments separately, and those that accept a single argument structure. @@ -214,16 +214,16 @@ use keyword arguments are dispatched directly to the called function's kwsorter. call: ```julia -circle((0,0), 1.0, color = red; other...) +circle((0, 0), 1.0, color = red; other...) ``` is lowered to: ```julia -kwcall(merge((color = red,), other), circle, (0,0), 1.0) +kwcall(merge((color = red,), other), circle, (0, 0), 1.0) ``` - `kwcall` (also in`Core`) denotes a kwcall signature and dispatch. +`kwcall` (also in`Core`) denotes a kwcall signature and dispatch. The keyword splatting operation (written as `other...`) calls the named tuple `merge` function. This function further unpacks each *element* of `other`, expecting each one to contain two values (a symbol and a value). @@ -267,7 +267,7 @@ element instead of the second. The front end generates type declarations for all closures. Initially, this was implemented by generating normal type declarations. However, this produced an extremely large number of constructors, all of which were trivial (simply passing all arguments through to [`new`](@ref)). Since methods are partially -ordered, inserting all of these methods is O(n^2), plus there are just too many of them to keep +ordered, inserting all of these methods is O(n²), plus there are just too many of them to keep around. This was optimized by generating `struct_type` expressions directly (bypassing default constructor generation), and using `new` directly to create closure instances. Not the prettiest thing ever, but you do what you gotta do. diff --git a/doc/src/devdocs/gc.md b/doc/src/devdocs/gc.md index 942535f426b34..9b9038c9445f3 100644 --- a/doc/src/devdocs/gc.md +++ b/doc/src/devdocs/gc.md @@ -2,77 +2,60 @@ ## Introduction -Julia has a serial, stop-the-world, generational, non-moving mark-sweep garbage collector. -Native objects are precisely scanned and foreign ones are conservatively marked. +Julia has a non-moving, partially concurrent, parallel, generational and mostly precise mark-sweep collector (an interface +for conservative stack scanning is provided as an option for users who wish to call Julia from C). -## Memory layout of objects and GC bits +## Allocation -An opaque tag is stored in the front of GC managed objects, and its lowest two bits are -used for garbage collection. The lowest bit is set for marked objects and the second -lowest bit stores age information (e.g. it's only set for old objects). +Julia uses two types of allocators, the size of the allocation request determining which one is used. Objects up to 2k +bytes are allocated on a per-thread free-list pool allocator, while objects larger than 2k bytes are allocated through libc +malloc. -Objects are aligned by a multiple of 4 bytes to ensure this pointer tagging is legal. +Julia’s pool allocator partitions objects on different size classes, so that a memory page managed by the pool allocator +(which spans 4 operating system pages on 64bit platforms) only contains objects of the same size class. Each memory +page from the pool allocator is paired with some page metadata stored on per-thread lock-free lists. The page metadata contains information such as whether the page has live objects at all, number of free slots, and offsets to the first and last objects in the free-list contained in that page. These metadata are used to optimize the collection phase: a page which has no live objects at all may be returned to the operating system without any need of scanning it, for example. -## Pool allocation +While a page that has no objects may be returned to the operating system, its associated metadata is permanently +allocated and may outlive the given page. As mentioned above, metadata for allocated pages are stored on per-thread lock-free +lists. Metadata for free pages, however, may be stored into three separate lock-free lists depending on whether the page has been mapped but never accessed (`page_pool_clean`), or whether the page has been lazily sweeped and it's waiting to be madvised by a background GC thread (`page_pool_lazily_freed`), or whether the page has been madvised (`page_pool_freed`). -Sufficiently small objects (up to 2032 bytes) are allocated on per-thread object -pools. +Julia's pool allocator follows a "tiered" allocation discipline. When requesting a memory page for the pool allocator, Julia will: -A three-level tree (analogous to a three-level page-table) is used to keep metadata -(e.g. whether a page has been allocated, whether contains marked objects, number of free objects etc.) -about address ranges spanning at least one page. -Sweeping a pool allocated object consists of inserting it back into the free list -maintained by its pool. +- Try to claim a page from `page_pool_lazily_freed`, which contains pages which were empty on the last stop-the-world phase, but not yet madivsed by a concurrent sweeper GC thread. -## Malloc'd arrays and big objects +- If it failed claiming a page from `page_pool_lazily_freed`, it will try to claim a page from `the page_pool_clean`, which contains pages which were mmaped on a previous page allocation request but never accessed. -Two lists are used to keep track of the remaining allocated objects: -one for sufficiently large malloc'd arrays (`mallocarray_t`) and one for -sufficiently large objects (`bigval_t`). +- If it failed claiming a page from `pool_page_clean` and from `page_pool_lazily_freed`, it will try to claim a page + from `page_pool_freed`, which contains pages which have already been madvised by a concurrent sweeper GC thread and whose underlying virtual address can be recycled. -Sweeping these objects consists of unlinking them from their list and calling `free` on the -corresponding address. +- If it failed in all of the attempts mentioned above, it will mmap a batch of pages, claim one page for itself, and + insert the remaining pages into `page_pool_clean`. -## Generational and remembered sets +![Diagram of tiered pool allocation](./img/gc-tiered-allocation.jpg) -Field writes into old objects trigger a write barrier if the written field -points to a young object and if a write barrier has not been triggered on the old object yet. -In this case, the old object being written to is enqueued into a remembered set, and -its mark bit is set to indicate that a write barrier has already been triggered on it. +## Marking and Generational Collection -There is no explicit flag to determine whether a marking pass will scan the -entire heap or only through young objects and remembered set. -The mark bits of the objects themselves are used to determine whether a full mark happens. -The mark-sweep algorithm follows this sequence of steps: +Julia’s mark phase is implemented through a parallel iterative depth-first-search over the object graph. Julia’s collector is non-moving, so object age information can’t be determined through the memory region in which the object resides alone, but has to be somehow encoded in the object header or on a side table. The lowest two bits of an object’s header are used to store, respectively, a mark bit that is set when an object is scanned during the mark phase and an age bit for the generational collection. -- Objects in the remembered set have their GC mark bits reset -(these are set once write barrier is triggered, as described above) and are enqueued. +Generational collection is implemented through sticky bits: objects are only pushed to the mark-stack, and therefore +traced, if their mark-bits are not set. When objects reach the oldest generation, their mark-bits are not reset during +the so-called "quick-sweep", which leads to these objects not being traced in a subsequent mark phase. A "full-sweep", +however, causes the mark-bits of all objects to be reset, leading to all objects being traced in a subsequent mark phase. +Objects are promoted to the next generation during every sweep phase they survive. On the mutator side, field writes +are intercepted through a write barrier that pushes an object’s address into a per-thread remembered set if the object is +in the last generation, and if the object at the field being written is not. Objects in this remembered set are then traced +during the mark phase. -- Roots (e.g. thread locals) are enqueued. +## Sweeping -- Object graph is traversed and mark bits are set. +Sweeping of object pools for Julia may fall into two categories: if a given page managed by the pool allocator contains at least one live object, then a free-list must be threaded through its dead objects; if a given page contains no live objects at all, then its underlying physical memory may be returned to the operating system through, for instance, the use of madvise system calls on Linux. -- Object pools, malloc'd arrays and big objects are sweeped. On a full sweep, -the mark bits of all marked objects are reset. On a generational sweep, -only the mark bits of marked young objects are reset. - -- Mark bits of objects in the remembered set are set, -so we don't trigger the write barrier on them again. - -After these stages, old objects will be left with their mark bits set, -so that references from them are not explored in a subsequent generational collection. -This scheme eliminates the need of explicitly keeping a flag to indicate a full mark -(though a flag to indicate a full sweep is necessary). +The first category of sweeping is parallelized through work-stealing. For the second category of sweeping, if concurrent page sweeping is enabled through the flag `--gcthreads=X,1` we perform the madvise system calls in a background sweeper thread, concurrently with the mutator threads. During the stop-the-world phase of the collector, pool allocated pages which contain no live objects are initially pushed into the `pool_page_lazily_freed`. The background sweeping thread is then woken up and is responsible for removing pages from `pool_page_lazily_freed`, calling madvise on them, and inserting them into `pool_page_freed`. As described above, `pool_page_lazily_freed` is also shared with mutator threads. This implies that on allocation-heavy multithreaded workloads, mutator threads would often avoid a page fault on allocation (coming from accessing a fresh mmaped page or accessing a madvised page) by directly allocating from a page in `pool_page_lazily_freed`, while the background sweeper thread needs to madvise a reduce number of pages given some of them were already claimed by the mutators. ## Heuristics GC heuristics tune the GC by changing the size of the allocation interval between garbage collections. -The GC heuristics measure how big the heap size is after a collection and set the next -collection according to the algorithm described by https://dl.acm.org/doi/10.1145/3563323, -in summary, it argues that the heap target should have a square root relationship with the live heap, and that it should also be scaled by how fast the GC is freeing objects and how fast the mutators are allocating. -The heuristics measure the heap size by counting the number of pages that are in use and the objects that use malloc. Previously we measured the heap size by counting -the alive objects, but that doesn't take into account fragmentation which could lead to bad decisions, that also meant that we used thread local information (allocations) to make -decisions about a process wide (when to GC), measuring pages means the decision is global. +The GC heuristics measure how big the heap size is after a collection and set the next collection according to the algorithm described by https://dl.acm.org/doi/10.1145/3563323, in summary, it argues that the heap target should have a square root relationship with the live heap, and that it should also be scaled by how fast the GC is freeing objects and how fast the mutators are allocating. The heuristics measure the heap size by counting the number of pages that are in use and the objects that use malloc. Previously we measured the heap size by counting the alive objects, but that doesn't take into account fragmentation which could lead to bad decisions, that also meant that we used thread local information (allocations) to make decisions about a process wide (when to GC), measuring pages means the decision is global. The GC will do full collections when the heap size reaches 80% of the maximum allowed size. diff --git a/doc/src/devdocs/img/gc-tiered-allocation.jpg b/doc/src/devdocs/img/gc-tiered-allocation.jpg new file mode 100644 index 0000000000000..4ab0e1298364c Binary files /dev/null and b/doc/src/devdocs/img/gc-tiered-allocation.jpg differ diff --git a/doc/src/devdocs/img/precompilation_hang.png b/doc/src/devdocs/img/precompilation_hang.png new file mode 100644 index 0000000000000..d076b7697f271 Binary files /dev/null and b/doc/src/devdocs/img/precompilation_hang.png differ diff --git a/doc/src/devdocs/isbitsunionarrays.md b/doc/src/devdocs/isbitsunionarrays.md index 2a25c033ec9fd..f01afe50985ec 100644 --- a/doc/src/devdocs/isbitsunionarrays.md +++ b/doc/src/devdocs/isbitsunionarrays.md @@ -18,6 +18,12 @@ Lastly, a value of `0x00` signals that the `nothing` value will be returned for type with a single type instance, it technically has a size of 0. The type tag byte for a type's Union field is stored directly after the field's computed Union memory. -## isbits Union Arrays +## isbits Union Memory -Julia can now also store "isbits Union" values inline in an Array, as opposed to requiring an indirection box. The optimization is accomplished by storing an extra "type tag array" of bytes, one byte per array element, alongside the bytes of the actual array data. This type tag array serves the same function as the type field case: its value signals the type of the actual stored Union value in the array. In terms of layout, a Julia Array can include extra "buffer" space before and after its actual data values, which are tracked in the `a->offset` and `a->maxsize` fields of the `jl_array_t*` type. The "type tag array" is treated exactly as another `jl_array_t*`, but which shares the same `a->offset`, `a->maxsize`, and `a->len` fields. So the formula to access an isbits Union Array's type tag bytes is `a->data + (a->maxsize - a->offset) * a->elsize + a->offset`; i.e. the Array's `a->data` pointer is already shifted by `a->offset`, so correcting for that, we follow the data all the way to the max of what it can hold `a->maxsize`, then adjust by `a->offset` more bytes to account for any present "front buffering" the array might be doing. This layout in particular allows for very efficient resizing operations as the type tag data only ever has to move when the actual array's data has to move. +Julia can now also store "isbits Union" values inline in a Memory, as opposed to requiring +an indirection box. The optimization is accomplished by storing an extra "type tag memory" +of bytes, one byte per element, alongside the bytes of the actual data. This type tag memory +serves the same function as the type field case: its value signals the type of the actual +stored Union value. The "type tag memory" directly follows the regular data space. So the +formula to access an isbits Union Array's type tag bytes is `a->data + a->length * +a->elsize`. diff --git a/doc/src/devdocs/llvm.md b/doc/src/devdocs/llvm.md index 82ab97f47ff57..170a812c09994 100644 --- a/doc/src/devdocs/llvm.md +++ b/doc/src/devdocs/llvm.md @@ -341,8 +341,8 @@ ccall(:foo, Cvoid, (Ptr{Float64},), A) In lowering, the compiler will insert a conversion from the array to the pointer which drops the reference to the array value. However, we of course need to make sure that the array does stay alive while we're doing the -[`ccall`](@ref). To understand how this is done, first recall the lowering of the -above code: +[`ccall`](@ref). To understand how this is done, lets look at a hypothetical +approximate possible lowering of the above code: ```julia return $(Expr(:foreigncall, :(:foo), Cvoid, svec(Ptr{Float64}), 0, :(:ccall), Expr(:foreigncall, :(:jl_array_ptr), Ptr{Float64}, svec(Any), 0, :(:ccall), :(A)), :(A))) ``` diff --git a/doc/src/devdocs/locks.md b/doc/src/devdocs/locks.md index 26de9351e40cd..50cdd738e3b34 100644 --- a/doc/src/devdocs/locks.md +++ b/doc/src/devdocs/locks.md @@ -30,6 +30,7 @@ The following are definitely leaf locks (level 1), and must not try to acquire a > * jl_in_stackwalk (Win32) > * ResourcePool::mutex > * RLST_mutex +> * llvm_printing_mutex > * jl_locked_stream::mutex > * debuginfo_asyncsafe > * inference_timing_mutex @@ -40,7 +41,7 @@ The following are definitely leaf locks (level 1), and must not try to acquire a The following is a leaf lock (level 2), and only acquires level 1 locks (safepoint) internally: -> * typecache +> * global_roots_lock > * Module->lock > * JLDebuginfoPlugin::PluginMutex > * newly_inferred_mutex @@ -48,6 +49,7 @@ The following is a leaf lock (level 2), and only acquires level 1 locks (safepoi The following is a level 3 lock, which can only acquire level 1 or level 2 locks internally: > * Method->writelock +> * typecache The following is a level 4 lock, which can only recurse to acquire level 1, 2, or 3 locks: @@ -153,6 +155,7 @@ MethodInstance/CodeInstance updates : Method->writelock, codegen lock > * specTypes > * sparam_vals > * def +> * owner > * These are set by `jl_type_infer` (while holding codegen lock): > * cache diff --git a/doc/src/devdocs/meta.md b/doc/src/devdocs/meta.md index 7a58578b3e53e..726f471c228e0 100644 --- a/doc/src/devdocs/meta.md +++ b/doc/src/devdocs/meta.md @@ -34,9 +34,8 @@ quote end ``` -`Base.pushmeta!(ex, :symbol, args...)` appends `:symbol` to the end of the `:meta` expression, -creating a new `:meta` expression if necessary. If `args` is specified, a nested expression containing -`:symbol` and these arguments is appended instead, which can be used to specify additional information. +`Base.pushmeta!(ex, tag::Union{Symbol,Expr})` appends `:tag` to the end of the `:meta` expression, +creating a new `:meta` expression if necessary. To use the metadata, you have to parse these `:meta` expressions. If your implementation can be performed within Julia, `Base.popmeta!` is very handy: `Base.popmeta!(body, :symbol)` will scan diff --git a/doc/src/devdocs/object.md b/doc/src/devdocs/object.md index caba6c3f12190..a2f72d623ab21 100644 --- a/doc/src/devdocs/object.md +++ b/doc/src/devdocs/object.md @@ -163,11 +163,8 @@ Arrays: ```c jl_array_t *jl_new_array(jl_value_t *atype, jl_tuple_t *dims); -jl_array_t *jl_new_arrayv(jl_value_t *atype, ...); jl_array_t *jl_alloc_array_1d(jl_value_t *atype, size_t nr); -jl_array_t *jl_alloc_array_2d(jl_value_t *atype, size_t nr, size_t nc); -jl_array_t *jl_alloc_array_3d(jl_value_t *atype, size_t nr, size_t nc, size_t z); -jl_array_t *jl_alloc_vec_any(size_t n); +jl_array_t *jl_alloc_array_nd(jl_value_t *atype, size_t *dims, size_t ndims); ``` Note that many of these have alternative allocation functions for various special-purposes. The diff --git a/doc/src/devdocs/pkgimg.md b/doc/src/devdocs/pkgimg.md index 0e2cffd91a26f..64f4e640b7c19 100644 --- a/doc/src/devdocs/pkgimg.md +++ b/doc/src/devdocs/pkgimg.md @@ -33,7 +33,7 @@ Dynamic libraries on macOS need to link against `-lSystem`. On recent macOS vers To that effect we link with `-undefined dynamic_lookup`. ## [Package images optimized for multiple microarchitectures](@id pkgimgs-multi-versioning) -Similar to [multi-versioning](@ref sysimg-multi-versioning) for system images, package images support multi-versioning. If you are in a heterogenous environment, with a unified cache, +Similar to [multi-versioning](@ref sysimg-multi-versioning) for system images, package images support multi-versioning. If you are in a heterogeneous environment, with a unified cache, you can set the environment variable `JULIA_CPU_TARGET=generic` to multi-version the object caches. ## Flags that impact package image creation and selection diff --git a/doc/src/devdocs/precompile_hang.md b/doc/src/devdocs/precompile_hang.md new file mode 100644 index 0000000000000..0de9c99792b64 --- /dev/null +++ b/doc/src/devdocs/precompile_hang.md @@ -0,0 +1,98 @@ +# Fixing precompilation hangs due to open tasks or IO + +On Julia 1.10 or higher, you might see the following message: + +![Screenshot of precompilation hang](./img/precompilation_hang.png) + +This may repeat. If it continues to repeat with no hints that it will +resolve itself, you may have a "precompilation hang" that requires +fixing. Even if it's transient, you might prefer to resolve it so that +users will not be bothered by this warning. This page walks you +through how to analyze and fix such issues. + +If you follow the advice and hit `Ctrl-C`, you might see + +``` +^C Interrupted: Exiting precompilation... + + 1 dependency had warnings during precompilation: +┌ Test1 [ac89d554-e2ba-40bc-bc5c-de68b658c982] +│ [pid 2745] waiting for IO to finish: +│ Handle type uv_handle_t->data +│ timer 0x55580decd1e0->0x7f94c3a4c340 +``` + +This message conveys two key pieces of information: + +- the hang is occurring during precompilation of `Test1`, a dependency of `Test2` (the package we were trying to load with `using Test2`) +- during precompilation of `Test1`, Julia created a `Timer` object (use `?Timer` if you're unfamiliar with Timers) which is still open; until that closes, the process is hung + +If this is enough of a hint for you to figure out how `timer = Timer(args...)` is being created, one good solution is to add `wait(timer)` if `timer` eventually finishes on its own, or `close(timer)` if you need to force-close it, before the final `end` of the module. + +However, there are cases that may not be that straightforward. Usually the best option is to start by determining whether the hang is due to code in Test1 or whether it is due to one of Test1's dependencies: + +- Option 1: `Pkg.add("Aqua")` and use [`Aqua.test_persistent_tasks`](https://juliatesting.github.io/Aqua.jl/dev/#Aqua.test_persistent_tasks-Tuple{Base.PkgId}). This should help you identify which package is causing the problem, after which the instructions [below](@ref pchang_fix) should be followed. If needed, you can create a `PkgId` as `Base.PkgId(UUID("..."), "Test1")`, where `...` comes from the `uuid` entry in `Test1/Project.toml`. +- Option 2: manually diagnose the source of the hang. + +To manually diagnose: + +1. `Pkg.develop("Test1")` +2. Comment out all the code `include`d or defined in `Test1`, *except* the `using/import` statements. +3. Try `using Test2` (or even `using Test1` assuming that hangs too) again + +Now we arrive at a fork in the road: either + +- the hang persists, indicating it is [due to one of your dependencies](@ref pchang_deps) +- the hang disappears, indicating that it is [due to something in your code](@ref pchang_fix). + +## [Diagnosing and fixing hangs due to a package dependency](@id pchang_deps) + +Use a binary search to identify the problematic dependency: start by commenting out half your dependencies, then when you isolate which half is responsible comment out half of that half, etc. (You don't have to remove them from the project, just comment out the `using`/`import` statements.) + +Once you've identified a suspect (here we'll call it `ThePackageYouThinkIsCausingTheProblem`), first try precompiling that package. If it also hangs during precompilation, continue chasing the problem backwards. + +However, most likely `ThePackageYouThinkIsCausingTheProblem` will precompile fine. This suggests it's in the function `ThePackageYouThinkIsCausingTheProblem.__init__`, which does not run during precompilation of `ThePackageYouThinkIsCausingTheProblem` but *does* in any package that loads `ThePackageYouThinkIsCausingTheProblem`. To test this theory, set up a minimal working example (MWE), something like + +```julia +(@v1.10) pkg> generate MWE + Generating project MWE: + MWE\Project.toml + MWE\src\MWE.jl +``` + +where the source code of `MWE.jl` is + +```julia +module MWE +using ThePackageYouThinkIsCausingTheProblem +end +``` + +and you've added `ThePackageYouThinkIsCausingTheProblem` to MWE's dependencies. + +If that MWE reproduces the hang, you've found your culprit: +`ThePackageYouThinkIsCausingTheProblem.__init__` must be creating the `Timer` object. If the timer object can be safely `close`d, that's a good option. Otherwise, the most common solution is to avoid creating the timer while *any* package is being precompiled: add + +```julia +ccall(:jl_generating_output, Cint, ()) == 1 && return nothing +``` + +as the first line of `ThePackageYouThinkIsCausingTheProblem.__init__`, and it will avoid doing any initialization in any Julia process whose purpose is to precompile packages. + +## [Fixing package code to avoid hangs](@id pchang_fix) + +Search your package for suggestive words (here like "Timer") and see if you can identify where the problem is being created. Note that a method *definition* like + +```julia +maketimer() = Timer(timer -> println("hi"), 0; interval=1) +``` + +is not problematic in and of itself: it can cause this problem only if `maketimer` gets called while the module is being defined. This might be happening from a top-level statement such as + +```julia +const GLOBAL_TIMER = maketimer() +``` + +or it might conceivably occur in a [precompile workload](https://github.com/JuliaLang/PrecompileTools.jl). + +If you struggle to identify the causative lines, then consider doing a binary search: comment out sections of your package (or `include` lines to omit entire files) until you've reduced the problem in scope. diff --git a/doc/src/devdocs/probes.md b/doc/src/devdocs/probes.md index d15723e945462..5a1af0d897bc6 100644 --- a/doc/src/devdocs/probes.md +++ b/doc/src/devdocs/probes.md @@ -137,8 +137,8 @@ fib(x) = x <= 1 ? 1 : fib(x-1) + fib(x-2) beaver = @spawn begin while true fib(30) - # This safepoint is necessary until #41616, since otherwise this - # loop will never yield to GC. + # A manual safepoint is necessary since otherwise this loop + # may never yield to GC. GC.safepoint() end end @@ -188,7 +188,7 @@ Julia session and get the PID and REPL's task address: _ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help. | | | | | | |/ _` | | | | |_| | | | (_| | | Version 1.6.2 (2021-07-14) - _/ |\__'_|_|_|\__'_| | Official https://julialang.org/ release + _/ |\__'_|_|_|\__'_| | Official https://julialang.org release |__/ | 1> getpid() @@ -264,7 +264,7 @@ We can see this problem illustrated with `bpftrace` quite easily. First, in one _ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help. | | | | | | |/ _` | | | | |_| | | | (_| | | Version 1.6.2 (2021-07-14) - _/ |\__'_|_|_|\__'_| | Official https://julialang.org/ release + _/ |\__'_|_|_|\__'_| | Official https://julialang.org release |__/ | 1> getpid() diff --git a/doc/src/devdocs/ssair.md b/doc/src/devdocs/ssair.md index d00f1ebb809c3..2eb065a62e4bf 100644 --- a/doc/src/devdocs/ssair.md +++ b/doc/src/devdocs/ssair.md @@ -83,6 +83,15 @@ may assume that any use of a Phi node will have an assigned value in the corresp for the mapping to be incomplete, i.e. for a Phi node to have missing incoming edges. In that case, it must be dynamically guaranteed that the corresponding value will not be used. +Note that SSA uses semantically occur after the terminator of the corresponding predecessor ("on the edge"). +Consequently, if multiple Phi nodes appear at the start of a basic block, they are run simultaneously. +This means that in the following IR snippet, if we came from block `23`, `%46` will take the value associated to +`%45` _before_ we entered this block. +```julia +%45 = φ (#18 => %23, #23 => %50) +%46 = φ (#18 => 1.0, #23 => %45) +``` + PiNodes encode statically proven information that may be implicitly assumed in basic blocks dominated by a given pi node. They are conceptually equivalent to the technique introduced in the paper [ABCD: Eliminating Array Bounds Checks on Demand](https://dl.acm.org/citation.cfm?id=358438.349342) or the predicate info nodes in LLVM. To see how they work, consider, @@ -225,7 +234,7 @@ Instead, we do the following: - RAUW style operations are performed by setting the corresponding statement index to the replacement value. - Statements are erased by setting the corresponding statement to `nothing` (this is essentially just a special-case - convention of the above. + convention of the above). - If there are any uses of the statement being erased, they will be set to `nothing`. There is a `compact!` function that compacts the above data structure by performing the insertion of nodes in the appropriate place, trivial copy propagation, and renaming of uses to any changed SSA values. However, the clever part diff --git a/doc/src/devdocs/types.md b/doc/src/devdocs/types.md index c3afc26600c65..7a5ede63b1989 100644 --- a/doc/src/devdocs/types.md +++ b/doc/src/devdocs/types.md @@ -93,13 +93,15 @@ UnionAll var: TypeVar name: Symbol T lb: Union{} - ub: Any + ub: abstract type Any body: UnionAll var: TypeVar name: Symbol N lb: Union{} - ub: Any - body: Array{T, N} <: DenseArray{T, N} + ub: abstract type Any + body: mutable struct Array{T, N} <: DenseArray{T, N} + ref::MemoryRef{T} + size::NTuple{N, Int64} ``` This indicates that `Array` actually names a `UnionAll` type. There is one `UnionAll` type for @@ -179,13 +181,13 @@ TypeName var: TypeVar name: Symbol T lb: Union{} - ub: Any + ub: abstract type Any body: UnionAll var: TypeVar name: Symbol N lb: Union{} - ub: Any - body: Array{T, N} <: DenseArray{T, N} + ub: abstract type Any + body: mutable struct Array{T, N} <: DenseArray{T, N} cache: SimpleVector ... @@ -519,10 +521,6 @@ than the other.) Likewise, `Tuple{Int,Vararg{Int}}` is not a subtype of `Tuple{ considered more specific. However, `morespecific` does get a bonus for length: in particular, `Tuple{Int,Int}` is more specific than `Tuple{Int,Vararg{Int}}`. -If you're debugging how methods get sorted, it can be convenient to define the function: - -```julia -type_morespecific(a, b) = ccall(:jl_type_morespecific, Cint, (Any,Any), a, b) -``` - -which allows you to test whether tuple type `a` is more specific than tuple type `b`. +Additionally, if 2 methods are defined with identical signatures, per type-equal, then they +will instead by compared by order of addition, such that the later method is more specific +than the earlier one. diff --git a/doc/src/index.md b/doc/src/index.md index bb758d14b4cf2..8c88af424e8e3 100644 --- a/doc/src/index.md +++ b/doc/src/index.md @@ -34,7 +34,7 @@ Markdown.parse(""" ## [Important Links](@id man-important-links) -Below is a non-exhasutive list of links that will be useful as you learn and use the Julia programming language. +Below is a non-exhaustive list of links that will be useful as you learn and use the Julia programming language. - [Julia Homepage](https://julialang.org) - [Download Julia](https://julialang.org/downloads/) diff --git a/doc/src/manual/arrays.md b/doc/src/manual/arrays.md index 0b4532e1b423d..bda778594df96 100644 --- a/doc/src/manual/arrays.md +++ b/doc/src/manual/arrays.md @@ -326,8 +326,8 @@ These syntaxes are shorthands for function calls that themselves are convenience | Syntax | Function | Description | |:---------------------- |:---------------- |:---------------------------------------------------------------------------------------------------------- | | | [`cat`](@ref) | concatenate input arrays along dimension(s) `k` | -| `[A; B; C; ...]` | [`vcat`](@ref) | shorthand for `cat(A...; dims=1)` | -| `[A B C ...]` | [`hcat`](@ref) | shorthand for `cat(A...; dims=2)` | +| `[A; B; C; ...]` | [`vcat`](@ref) | shorthand for `cat(A...; dims=1)` | +| `[A B C ...]` | [`hcat`](@ref) | shorthand for `cat(A...; dims=2)` | | `[A B; C D; ...]` | [`hvcat`](@ref) | simultaneous vertical and horizontal concatenation | | `[A; C;; B; D;;; ...]` | [`hvncat`](@ref) | simultaneous n-dimensional concatenation, where number of semicolons indicate the dimension to concatenate | @@ -398,7 +398,7 @@ the result in single precision by writing: Float32[ 0.25*x[i-1] + 0.5*x[i] + 0.25*x[i+1] for i=2:length(x)-1 ] ``` -## Generator Expressions +## [Generator Expressions](@id man-generators) Comprehensions can also be written without the enclosing square brackets, producing an object known as a generator. This object can be iterated to produce values on demand, instead of allocating @@ -603,7 +603,7 @@ overwritten with the value of `X`, [`convert`](@ref)ing to the If any index `I_k` is itself an array, then the right hand side `X` must also be an array with the same shape as the result of indexing `A[I_1, I_2, ..., I_n]` or a vector with the same number of elements. The value in location `I_1[i_1], I_2[i_2], ..., I_n[i_n]` of -`A` is overwritten with the value `X[I_1, I_2, ..., I_n]`, converting if necessary. The +`A` is overwritten with the value `X[i_1, i_2, ..., i_n]`, converting if necessary. The element-wise assignment operator `.=` may be used to [broadcast](@ref Broadcasting) `X` across the selected locations: @@ -793,38 +793,46 @@ Indexing by a boolean vector `B` is effectively the same as indexing by the vector of integers that is returned by [`findall(B)`](@ref). Similarly, indexing by a `N`-dimensional boolean array is effectively the same as indexing by the vector of `CartesianIndex{N}`s where its values are `true`. A logical index -must be a vector of the same length as the dimension it indexes into, or it -must be the only index provided and match the size and dimensionality of the -array it indexes into. It is generally more efficient to use boolean arrays as -indices directly instead of first calling [`findall`](@ref). +must be an array of the same shape as the dimension(s) it indexes into, or it +must be the only index provided and match the shape of the one-dimensional +reshaped view of the array it indexes into. It is generally more efficient +to use boolean arrays as indices directly instead of first calling [`findall`](@ref). ```jldoctest -julia> x = reshape(1:16, 4, 4) -4×4 reshape(::UnitRange{Int64}, 4, 4) with eltype Int64: - 1 5 9 13 - 2 6 10 14 - 3 7 11 15 - 4 8 12 16 +julia> x = reshape(1:12, 2, 3, 2) +2×3×2 reshape(::UnitRange{Int64}, 2, 3, 2) with eltype Int64: +[:, :, 1] = + 1 3 5 + 2 4 6 -julia> x[[false, true, true, false], :] -2×4 Matrix{Int64}: - 2 6 10 14 - 3 7 11 15 +[:, :, 2] = + 7 9 11 + 8 10 12 + +julia> x[:, [true false; false true; true false]] +2×3 Matrix{Int64}: + 1 5 9 + 2 6 10 julia> mask = map(ispow2, x) -4×4 Matrix{Bool}: - 1 0 0 0 - 1 0 0 0 - 0 0 0 0 - 1 1 0 1 +2×3×2 Array{Bool, 3}: +[:, :, 1] = + 1 0 0 + 1 1 0 + +[:, :, 2] = + 0 0 0 + 1 0 0 julia> x[mask] -5-element Vector{Int64}: - 1 - 2 - 4 - 8 - 16 +4-element Vector{Int64}: + 1 + 2 + 4 + 8 + +julia> x[vec(mask)] == x[mask] # we can also index with a single Boolean vector +true ``` ### Number of indices @@ -881,7 +889,7 @@ in their implementations, other arrays — like [`Diagonal`](@ref) — need the full set of cartesian indices to do their lookup (see [`IndexStyle`](@ref) to introspect which is which). -!!! warnings +!!! warning When iterating over all the indices for an array, it is better to iterate over [`eachindex(A)`](@ref) instead of `1:length(A)`. @@ -935,13 +943,13 @@ element of `axes(A, d)` where `d` is that particular dimension number). This allows vectors to be indexed like one-column matrices, for example: ```jldoctest -julia> A = [8,6,7] +julia> A = [8, 6, 7] 3-element Vector{Int64}: 8 6 7 -julia> A[2,1] +julia> A[2, 1] 6 ``` @@ -1006,7 +1014,7 @@ The following operators are supported for arrays: To enable convenient vectorization of mathematical and other operations, Julia [provides the dot syntax](@ref man-vectorized) `f.(args...)`, e.g. `sin.(x)` -or `min.(x,y)`, for elementwise operations over arrays or mixtures of arrays and +or `min.(x, y)`, for elementwise operations over arrays or mixtures of arrays and scalars (a [Broadcasting](@ref) operation); these have the additional advantage of "fusing" into a single loop when combined with other dot calls, e.g. `sin.(cos.(x))`. @@ -1020,7 +1028,7 @@ operations like `<`, *only* the elementwise `.<` version is applicable to arrays Also notice the difference between `max.(a,b)`, which [`broadcast`](@ref)s [`max`](@ref) elementwise over `a` and `b`, and [`maximum(a)`](@ref), which finds the largest value within -`a`. The same relationship holds for `min.(a,b)` and `minimum(a)`. +`a`. The same relationship holds for `min.(a, b)` and `minimum(a)`. ## Broadcasting @@ -1111,10 +1119,10 @@ generally work correctly as a fallback for any specific array implementation. The `AbstractArray` type includes anything vaguely array-like, and implementations of it might be quite different from conventional arrays. For example, elements might be computed on request rather than stored. However, any concrete `AbstractArray{T,N}` type should generally implement -at least [`size(A)`](@ref) (returning an `Int` tuple), [`getindex(A,i)`](@ref) and [`getindex(A,i1,...,iN)`](@ref getindex); -mutable arrays should also implement [`setindex!`](@ref). It is recommended that these operations -have nearly constant time complexity, as otherwise some array -functions may be unexpectedly slow. Concrete types should also typically provide a [`similar(A,T=eltype(A),dims=size(A))`](@ref) +at least [`size(A)`](@ref) (returning an `Int` tuple), [`getindex(A, i)`](@ref) and +[`getindex(A, i1, ..., iN)`](@ref getindex); mutable arrays should also implement [`setindex!`](@ref). +It is recommended that these operations have nearly constant time complexity, as otherwise some array +functions may be unexpectedly slow. Concrete types should also typically provide a [`similar(A, T=eltype(A), dims=size(A))`](@ref) method, which is used to allocate a similar array for [`copy`](@ref) and other out-of-place operations. No matter how an `AbstractArray{T,N}` is represented internally, `T` is the type of object returned by *integer* indexing (`A[1, ..., 1]`, when `A` is not empty) and `N` should be @@ -1161,7 +1169,7 @@ julia> stride(A, 1) The stride of the second dimension is the spacing between elements in the same row, skipping as many elements as there are in a single column (`5`). Similarly, jumping between the two -"pages" (in the third dimension) requires skipping `5*7 == 35` elements. The [`strides`](@ref) +"pages" (in the third dimension) requires skipping `5*7 == 35` elements. The [`strides`](@ref) of this array is the tuple of these three numbers together: ```julia-repl diff --git a/doc/src/manual/asynchronous-programming.md b/doc/src/manual/asynchronous-programming.md index 5b43ba971ee1c..15db6eda5f807 100644 --- a/doc/src/manual/asynchronous-programming.md +++ b/doc/src/manual/asynchronous-programming.md @@ -194,10 +194,11 @@ A channel can be visualized as a pipe, i.e., it has a write end and a read end : to the maximum number of elements that can be held in the channel at any time. For example, `Channel(32)` creates a channel that can hold a maximum of 32 objects of any type. A `Channel{MyType}(64)` can hold up to 64 objects of `MyType` at any time. - * If a [`Channel`](@ref) is empty, readers (on a [`take!`](@ref) call) will block until data is available. - * If a [`Channel`](@ref) is full, writers (on a [`put!`](@ref) call) will block until space becomes available. + * If a [`Channel`](@ref) is empty, readers (on a [`take!`](@ref) call) will block until data is available (see [`isempty`](@ref)). + * If a [`Channel`](@ref) is full, writers (on a [`put!`](@ref) call) will block until space becomes available (see [`isfull`](@ref)). * [`isready`](@ref) tests for the presence of any object in the channel, while [`wait`](@ref) waits for an object to become available. + * Note that if another task is currently waiting to `put!` an object into a channel, a channel can have more items available than its capacity. * A [`Channel`](@ref) is in an open state initially. This means that it can be read from and written to freely via [`take!`](@ref) and [`put!`](@ref) calls. [`close`](@ref) closes a [`Channel`](@ref). On a closed [`Channel`](@ref), [`put!`](@ref) will fail. For example: diff --git a/doc/src/manual/calling-c-and-fortran-code.md b/doc/src/manual/calling-c-and-fortran-code.md index 7b889589c592d..6f4d69b16bc81 100644 --- a/doc/src/manual/calling-c-and-fortran-code.md +++ b/doc/src/manual/calling-c-and-fortran-code.md @@ -253,10 +253,14 @@ to the specified type. For example, the following call: will behave as if it were written like this: ```julia -@ccall "libfoo".foo( - Base.unsafe_convert(Int32, Base.cconvert(Int32, x))::Int32, - Base.unsafe_convert(Float64, Base.cconvert(Float64, y))::Float64 +c_x = Base.cconvert(Int32, x) +c_y = Base.cconvert(Float64, y) +GC.@preserve c_x c_y begin + @ccall "libfoo".foo( + Base.unsafe_convert(Int32, c_x)::Int32, + Base.unsafe_convert(Float64, c_y)::Float64 )::Cvoid +end ``` [`Base.cconvert`](@ref) normally just calls [`convert`](@ref), but can be defined to return an @@ -995,7 +999,7 @@ A table of translations between the macro and function interfaces is given below | `@ccall "mylib".f(a::Cint, b::Cdouble)::Cvoid` | `ccall((:f, "mylib"), Cvoid, (Cint, Cdouble), (a, b))` | | `@ccall $fptr.f()::Cvoid` | `ccall(fptr, f, Cvoid, ())` | | `@ccall printf("%s = %d\n"::Cstring ; "foo"::Cstring, foo::Cint)::Cint` | `` | -| `@ccall printf("%s = %d\n"::Cstring ; "2 + 2"::Cstring, "5"::Cstring)::Cint` | `ccall(:printf, Cint, (Cstring, Cstring...), "%s = %s\n", "2 + 2", "5")` | +| `@ccall printf("%s = %s\n"::Cstring ; "2 + 2"::Cstring, "5"::Cstring)::Cint` | `ccall(:printf, Cint, (Cstring, Cstring...), "%s = %s\n", "2 + 2", "5")` | | `` | `ccall(:gethostname, stdcall, Int32, (Ptr{UInt8}, UInt32), hn, length(hn))` | ## [Calling Convention](@id calling-convention) diff --git a/doc/src/manual/code-loading.md b/doc/src/manual/code-loading.md index 743ee83c333a4..5c8315693c71e 100644 --- a/doc/src/manual/code-loading.md +++ b/doc/src/manual/code-loading.md @@ -14,7 +14,7 @@ Code inclusion is quite straightforward and simple: it evaluates the given sourc A *package* is a source tree with a standard layout providing functionality that can be reused by other Julia projects. A package is loaded by `import X` or `using X` statements. These statements also make the module named `X`—which results from loading the package code—available within the module where the import statement occurs. The meaning of `X` in `import X` is context-dependent: which `X` package is loaded depends on what code the statement occurs in. Thus, handling of `import X` happens in two stages: first, it determines **what** package is defined to be `X` in this context; second, it determines **where** that particular `X` package is found. -These questions are answered by searching through the project environments listed in [`LOAD_PATH`](@ref) for project files (`Project.toml` or `JuliaProject.toml`), manifest files (`Manifest.toml` or `JuliaManifest.toml`), or folders of source files. +These questions are answered by searching through the project environments listed in [`LOAD_PATH`](@ref) for project files (`Project.toml` or `JuliaProject.toml`), manifest files (`Manifest.toml` or `JuliaManifest.toml`, or the same names suffixed by `-v{major}.{minor}.toml` for specific versions), or folders of source files. ## Federation of packages @@ -63,7 +63,7 @@ Each kind of environment defines these three maps differently, as detailed in th ### Project environments -A project environment is determined by a directory containing a project file called `Project.toml`, and optionally a manifest file called `Manifest.toml`. These files may also be called `JuliaProject.toml` and `JuliaManifest.toml`, in which case `Project.toml` and `Manifest.toml` are ignored. This allows for coexistence with other tools that might consider files called `Project.toml` and `Manifest.toml` significant. For pure Julia projects, however, the names `Project.toml` and `Manifest.toml` are preferred. +A project environment is determined by a directory containing a project file called `Project.toml`, and optionally a manifest file called `Manifest.toml`. These files may also be called `JuliaProject.toml` and `JuliaManifest.toml`, in which case `Project.toml` and `Manifest.toml` are ignored. This allows for coexistence with other tools that might consider files called `Project.toml` and `Manifest.toml` significant. For pure Julia projects, however, the names `Project.toml` and `Manifest.toml` are preferred. However, from Julia v1.11 onwards, `(Julia)Manifest-v{major}.{minor}.toml` is recognized as a format to make a given julia version use a specific manifest file i.e. in the same folder, a `Manifest-v1.11.toml` would be used by v1.11 and `Manifest.toml` by any other julia version. The roots, graph and paths maps of a project environment are defined as follows: @@ -160,11 +160,12 @@ What happens if `import Zebra` is evaluated in the main `App` code base? Since ` **The paths map** of a project environment is extracted from the manifest file. The path of a package `uuid` named `X` is determined by these rules (in order): 1. If the project file in the directory matches `uuid` and name `X`, then either: - - It has a toplevel `path` entry, then `uuid` will be mapped to that path, interpreted relative to the directory containing the project file. - - Otherwise, `uuid` is mapped to `src/X.jl` relative to the directory containing the project file. -2. If the above is not the case and the project file has a corresponding manifest file and the manifest contains a stanza matching `uuid` then: - - If it has a `path` entry, use that path (relative to the directory containing the manifest file). - - If it has a `git-tree-sha1` entry, compute a deterministic hash function of `uuid` and `git-tree-sha1`—call it `slug`—and look for a directory named `packages/X/$slug` in each directory in the Julia `DEPOT_PATH` global array. Use the first such directory that exists. + - It has a toplevel `entryfile` entry, then `uuid` will be mapped to that path, interpreted relative to the directory containing the project file. + - Otherwise, `uuid` is mapped to `src/X.jl` relative to the directory containing the project file. +2. 1. If the above is not the case and the project file has a corresponding manifest file and the manifest contains a stanza matching `uuid` then: + - If it has a `path` entry, use that path (relative to the directory containing the manifest file). + - If it has a `git-tree-sha1` entry, compute a deterministic hash function of `uuid` and `git-tree-sha1`—call it `slug`—and look for a directory named `packages/X/$slug` in each directory in the Julia `DEPOT_PATH` global array. Use the first such directory that exists. + 2. If this is a directory then `uuid` is mapped to `src/X.jl` unless the matching manifest stanza has an `entryfile` entry in which case this is used. In both cases, these are relative to the directory in 2.1. If any of these result in success, the path to the source code entry point will be either that result, the relative path from that result plus `src/X.jl`; otherwise, there is no path mapping for `uuid`. When loading `X`, if no source code path is found, the lookup will fail, and the user may be prompted to install the appropriate package version or to take other corrective action (e.g. declaring `X` as a dependency). @@ -208,7 +209,6 @@ This example map includes three different kinds of package locations (the first 2. The public `Priv` and `Zebra` packages are in the system depot, where packages installed and managed by the system administrator live. These are available to all users on the system. 3. The `Pub` package is in the user depot, where packages installed by the user live. These are only available to the user who installed them. - ### Package directories Package directories provide a simpler kind of environment without the ability to handle name collisions. In a package directory, the set of top-level packages is the set of subdirectories that "look like" packages. A package `X` exists in a package directory if the directory contains one of the following "entry point" files: @@ -351,7 +351,7 @@ Since the primary environment is typically the environment of a project you're w ### [Package Extensions](@id man-extensions) -A package "extension" is a module that is automatically loaded when a specified set of other packages (its "extension dependencies") are loaded in the current Julia session. Extensions are defined under the `[extensions]` section in the project file. The extension dependencies of an extension are a subset of those packages listed under the `[weakdeps]` section of the project file. Those packages can have compat entries like other packages. +A package "extension" is a module that is automatically loaded when a specified set of other packages (its "triggers") are loaded in the current Julia session. Extensions are defined under the `[extensions]` section in the project file. The triggers of an extension are a subset of those packages listed under the `[weakdeps]` (and possibly, but uncommonly the `[deps]`) section of the project file. Those packages can have compat entries like other packages. ```toml name = "MyPackage" @@ -371,8 +371,8 @@ FooExt = "ExtDep" ``` The keys under `extensions` are the names of the extensions. -They are loaded when all the packages on the right hand side (the extension dependencies) of that extension are loaded. -If an extension only has one extension dependency the list of extension dependencies can be written as just a string for brevity. +They are loaded when all the packages on the right hand side (the triggers) of that extension are loaded. +If an extension only has one trigger the list of triggers can be written as just a string for brevity. The location for the entry point of the extension is either in `ext/FooExt.jl` or `ext/FooExt/FooExt.jl` for extension `FooExt`. The content of an extension is often structured as: @@ -380,10 +380,10 @@ The content of an extension is often structured as: ``` module FooExt -# Load main package and extension dependencies +# Load main package and triggers using MyPackage, ExtDep -# Extend functionality in main package with types from the extension dependencies +# Extend functionality in main package with types from the triggers MyPackage.func(x::ExtDep.SomeStruct) = ... end @@ -391,9 +391,31 @@ end When a package with extensions is added to an environment, the `weakdeps` and `extensions` sections are stored in the manifest file in the section for that package. The dependency lookup rules for -a package are the same as for its "parent" except that the listed extension dependencies are also considered as +a package are the same as for its "parent" except that the listed triggers are also considered as dependencies. +### [Workspaces](@id workspaces) + +A project file can define a workspace by giving a set of projects that is part of that workspace: + +```toml +[workspace] +projects = ["test", "benchmarks", "docs", "SomePackage"] +``` + +Each subfolder contains its own `Project.toml` file, which may include additional dependencies and compatibility constraints. In such cases, the package manager gathers all dependency information from all the projects in the workspace generating a single manifest file that combines the versions of all dependencies. + +Furthermore, workspaces can be "nested", meaning a project defining a workspace can also be part of another workspace. In this scenario, a single manifest file is still utilized, stored alongside the "root project" (the project that doesn't have another workspace including it). An example file structure could look like this: + +``` +Project.toml # projects = ["MyPackage"] +Manifest.toml +MyPackage/ + Project.toml # projects = ["test"] + test/ + Project.toml +``` + ### [Package/Environment Preferences](@id preferences) Preferences are dictionaries of metadata that influence package behavior within an environment. diff --git a/doc/src/manual/command-line-interface.md b/doc/src/manual/command-line-interface.md index 7427cfc4a950a..50002801e06f8 100644 --- a/doc/src/manual/command-line-interface.md +++ b/doc/src/manual/command-line-interface.md @@ -41,10 +41,12 @@ See also [Scripting](@ref man-scripting) for more information on writing Julia s ## The `Main.main` entry point -As of Julia, 1.11, Base export a special macro `@main`. This macro simply expands to the symbol `main`, +As of Julia, 1.11, `Base` exports the macro `@main`. This macro expands to the symbol `main`, but at the conclusion of executing a script or expression, `julia` will attempt to execute the function `Main.main(ARGS)` if such a function has been defined and this behavior was opted into -using the `@main macro`. This feature is intended to aid in the unification +by using the `@main` macro. + +This feature is intended to aid in the unification of compiled and interactive workflows. In compiled workflows, loading the code that defines the `main` function may be spatially and temporally separated from the invocation. However, for interactive workflows, the behavior is equivalent to explicitly calling `exit(main(ARGS))` at the end of the evaluated script or @@ -62,10 +64,10 @@ Hello World! $ ``` -Only the `main` binding in the `Main`, module has this special behavior and only if +Only the `main` binding in the `Main` module has this behavior and only if the macro `@main` was used within the defining module. -For example, using `hello` instead of `main` will result not result in the `hello` function executing: +For example, using `hello` instead of `main` will not result in the `hello` function executing: ``` $ julia -e 'hello(ARGS) = println("Hello World!")' @@ -79,11 +81,13 @@ $ ``` However, the opt-in need not occur at definition time: +``` $ julia -e 'main(ARGS) = println("Hello World!"); @main' Hello World! $ +``` -The `main` binding may be imported from a package. A hello package defined as +The `main` binding may be imported from a package. A *hello world* package defined as ``` module Hello @@ -104,7 +108,7 @@ $ ``` However, note that the current best practice recommendation is to not mix application and reusable library -code in the same package. Helper applications may be distributed as separate pacakges or as scripts with +code in the same package. Helper applications may be distributed as separate packages or as scripts with separate `main` entry points in a package's `bin` folder. ## Parallel mode @@ -160,44 +164,47 @@ The following is a complete list of command-line switches available when launchi |Switch |Description| |:--- |:---| |`-v`, `--version` |Display version information| -|`-h`, `--help` |Print command-line options (this message).| -|`--help-hidden` |Uncommon options not shown by `-h`| +|`-h`, `--help` |Print command-line options (this message)| +|`--help-hidden` |Print uncommon options not shown by `-h`| |`--project[={\|@.}]` |Set `` as the active project/environment. The default `@.` option will search through parent directories until a `Project.toml` or `JuliaProject.toml` file is found.| |`-J`, `--sysimage ` |Start up with the given system image file| |`-H`, `--home ` |Set location of `julia` executable| |`--startup-file={yes*\|no}` |Load `JULIA_DEPOT_PATH/config/startup.jl`; if [`JULIA_DEPOT_PATH`](@ref JULIA_DEPOT_PATH) environment variable is unset, load `~/.julia/config/startup.jl`| |`--handle-signals={yes*\|no}` |Enable or disable Julia's default signal handlers| |`--sysimage-native-code={yes*\|no}` |Use native code from system image if available| -|`--compiled-modules={yes*\|no\|existing}` |Enable or disable incremental precompilation of modules. The `existing` option allows use of existing compiled modules that were previously precompiled, but disallows creation of new precompile files.| -|`--pkgimages={yes*\|no}` |Enable or disable usage of native code caching in the form of pkgimages| +|`--compiled-modules={yes*\|no\|existing\|strict}` |Enable or disable incremental precompilation of modules. The `existing` option allows use of existing compiled modules that were previously precompiled, but disallows creation of new precompile files. The `strict` option is similar, but will error if no precompile file is found. | +|`--pkgimages={yes*\|no\|existing}` |Enable or disable usage of native code caching in the form of pkgimages. The `existing` option allows use of existing pkgimages but disallows creation of new ones| |`-e`, `--eval ` |Evaluate ``| |`-E`, `--print ` |Evaluate `` and display the result| +|`-m`, `--module [args]` |Run entry point of `Package` (`@main` function) with `args'| |`-L`, `--load ` |Load `` immediately on all processors| -|`-t`, `--threads {N\|auto}` |Enable N threads; `auto` tries to infer a useful default number of threads to use but the exact behavior might change in the future. Currently, `auto` uses the number of CPUs assigned to this julia process based on the OS-specific affinity assignment interface, if supported (Linux and Windows). If this is not supported (macOS) or process affinity is not configured, it uses the number of CPU threads.| -| `--gcthreads {N}` |Enable N GC threads; If unspecified is set to half of the compute worker threads.| +|`-t`, `--threads {auto\|N[,auto\|M]}` |Enable N[+M] threads; N threads are assigned to the `default` threadpool, and if M is specified, M threads are assigned to the `interactive` threadpool; `auto` tries to infer a useful default number of threads to use but the exact behavior might change in the future. Currently sets N to the number of CPUs assigned to this Julia process based on the OS-specific affinity assignment interface if supported (Linux and Windows) or to the number of CPU threads if not supported (MacOS) or if process affinity is not configured, and sets M to 1.| +| `--gcthreads=N[,M]` |Use N threads for the mark phase of GC and M (0 or 1) threads for the concurrent sweeping phase of GC. N is set to half of the number of compute threads and M is set to 0 if unspecified.| |`-p`, `--procs {N\|auto}` |Integer value N launches N additional local worker processes; `auto` launches as many workers as the number of local CPU threads (logical cores)| |`--machine-file ` |Run processes on hosts listed in ``| -|`-i` |Interactive mode; REPL runs and `isinteractive()` is true| +|`-i`, `--interactive` |Interactive mode; REPL runs and `isinteractive()` is true| |`-q`, `--quiet` |Quiet startup: no banner, suppress REPL warnings| -|`--banner={yes\|no\|auto*}` |Enable or disable startup banner| +|`--banner={yes\|no\|short\|auto*}` |Enable or disable startup banner| |`--color={yes\|no\|auto*}` |Enable or disable color text| |`--history-file={yes*\|no}` |Load or save history| |`--depwarn={yes\|no*\|error}` |Enable or disable syntax and method deprecation warnings (`error` turns warnings into errors)| |`--warn-overwrite={yes\|no*}` |Enable or disable method overwrite warnings| |`--warn-scope={yes*\|no}` |Enable or disable warning for ambiguous top-level scope| |`-C`, `--cpu-target ` |Limit usage of CPU features up to ``; set to `help` to see the available options| -|`-O`, `--optimize={0,1,2*,3}` |Set the optimization level (level is 3 if `-O` is used without a level) ($)| -|`--min-optlevel={0*,1,2,3}` |Set the lower bound on per-module optimization| -|`-g`, `--debug-info={0,1*,2}` |Set the level of debug info generation (level is 2 if `-g` is used without a level) ($)| +|`-O`, `--optimize={0\|1\|2*\|3}` |Set the optimization level (level is 3 if `-O` is used without a level) ($)| +|`--min-optlevel={0*\|1\|2\|3}` |Set the lower bound on per-module optimization| +|`-g`, `--debug-info={0\|1*\|2}` |Set the level of debug info generation (level is 2 if `-g` is used without a level) ($)| |`--inline={yes\|no}` |Control whether inlining is permitted, including overriding `@inline` declarations| |`--check-bounds={yes\|no\|auto*}` |Emit bounds checks always, never, or respect `@inbounds` declarations ($)| -|`--math-mode={ieee,fast}` |Disallow or enable unsafe floating point optimizations (overrides `@fastmath` declaration)| +|`--math-mode={ieee\|user*}` |Always follow `ieee` floating point semantics or respect `@fastmath` declarations| +|`--polly={yes*\|no}` |Enable or disable the polyhedral optimizer Polly (overrides @polly declaration)| |`--code-coverage[={none*\|user\|all}]` |Count executions of source lines (omitting setting is equivalent to `user`)| |`--code-coverage=@` |Count executions but only in files that fall under the given file path/directory. The `@` prefix is required to select this option. A `@` with no path will track the current directory.| |`--code-coverage=tracefile.info` |Append coverage information to the LCOV tracefile (filename supports format tokens).| |`--track-allocation[={none*\|user\|all}]` |Count bytes allocated by each source line (omitting setting is equivalent to "user")| |`--track-allocation=@` |Count bytes but only in files that fall under the given file path/directory. The `@` prefix is required to select this option. A `@` with no path will track the current directory.| |`--bug-report=KIND` |Launch a bug report session. It can be used to start a REPL, run a script, or evaluate expressions. It first tries to use BugReporting.jl installed in current environment and falls back to the latest compatible BugReporting.jl if not. For more information, see `--bug-report=help`.| +|`--heap-size-hint=` |Forces garbage collection if memory usage is higher than the given value. The value may be specified as a number of bytes, optionally in units of KB, MB, GB, or TB, or as a percentage of physical memory with %.| |`--compile={yes*\|no\|all\|min}` |Enable or disable JIT compiler, or request exhaustive or minimal compilation| |`--output-o ` |Generate an object file (including system image data)| |`--output-ji ` |Generate a system image data file (.ji)| @@ -207,8 +214,9 @@ The following is a complete list of command-line switches available when launchi |`--output-bc ` |Generate LLVM bitcode (.bc)| |`--output-asm ` |Generate an assembly file (.s)| |`--output-incremental={yes\|no*}` |Generate an incremental output file (rather than complete)| -|`--trace-compile={stderr,name}` |Print precompile statements for methods compiled during execution or save to a path| +|`--trace-compile={stderr\|name}` |Print precompile statements for methods compiled during execution or save to a path| |`--image-codegen` |Force generate code in imaging mode| +|`--permalloc-pkgimg={yes\|no*}` |Copy the data section of package images into memory| !!! compat "Julia 1.1" diff --git a/doc/src/manual/constructors.md b/doc/src/manual/constructors.md index 6ec206dade335..9f9afca3e076c 100644 --- a/doc/src/manual/constructors.md +++ b/doc/src/manual/constructors.md @@ -293,6 +293,8 @@ Point{Float64}(1.0, 2.5) julia> Point(1,2.5) ## implicit T ## ERROR: MethodError: no method matching Point(::Int64, ::Float64) +The type `Point` exists, but no method is defined for this combination of argument types when trying to construct it. + Closest candidates are: Point(::T, ::T) where T<:Real at none:2 @@ -372,10 +374,13 @@ However, other similar calls still don't work: ```jldoctest parametric2 julia> Point(1.5,2) ERROR: MethodError: no method matching Point(::Float64, ::Int64) +The type `Point` exists, but no method is defined for this combination of argument types when trying to construct it. Closest candidates are: Point(::T, !Matched::T) where T<:Real @ Main none:1 + Point(!Matched::Int64, !Matched::Float64) + @ Main none:1 Stacktrace: [...] @@ -491,6 +496,7 @@ operator, which provides a syntax for writing rationals (e.g. `1 ⊘ 2`). Julia' type uses the [`//`](@ref) operator for this purpose. Before these definitions, `⊘` is a completely undefined operator with only syntax and no meaning. Afterwards, it behaves just as described in [Rational Numbers](@ref) -- its entire behavior is defined in these few lines. +Note that the infix use of `⊘` works because Julia has a set of symbols that are recognized to be infix operators. The first and most basic definition just makes `a ⊘ b` construct a `OurRational` by applying the `OurRational` constructor to `a` and `b` when they are integers. When one of the operands of `⊘` is already a rational number, we construct a new rational for the resulting ratio slightly differently; @@ -555,6 +561,7 @@ julia> struct SummedArray{T<:Number,S<:Number} julia> SummedArray(Int32[1; 2; 3], Int32(6)) ERROR: MethodError: no method matching SummedArray(::Vector{Int32}, ::Int32) +The type `SummedArray` exists, but no method is defined for this combination of argument types when trying to construct it. Closest candidates are: SummedArray(::Vector{T}) where T @@ -568,3 +575,53 @@ This constructor will be invoked by the syntax `SummedArray(a)`. The syntax `new specifying parameters for the type to be constructed, i.e. this call will return a `SummedArray{T,S}`. `new{T,S}` can be used in any constructor definition, but for convenience the parameters to `new{}` are automatically derived from the type being constructed when possible. + +## Constructors are just callable objects + +An object of any type may be [made callable](@ref "Function-like objects") by defining a +method. This includes types, i.e., objects of type [`Type`](@ref); and constructors may, +in fact, be viewed as just callable type objects. For example, there are many methods +defined on `Bool` and various supertypes of it: + +```julia-repl +julia> methods(Bool) +# 10 methods for type constructor: + [1] Bool(x::BigFloat) + @ Base.MPFR mpfr.jl:393 + [2] Bool(x::Float16) + @ Base float.jl:338 + [3] Bool(x::Rational) + @ Base rational.jl:138 + [4] Bool(x::Real) + @ Base float.jl:233 + [5] (dt::Type{<:Integer})(ip::Sockets.IPAddr) + @ Sockets ~/tmp/jl/jl/julia-nightly-assert/share/julia/stdlib/v1.11/Sockets/src/IPAddr.jl:11 + [6] (::Type{T})(x::Enum{T2}) where {T<:Integer, T2<:Integer} + @ Base.Enums Enums.jl:19 + [7] (::Type{T})(z::Complex) where T<:Real + @ Base complex.jl:44 + [8] (::Type{T})(x::Base.TwicePrecision) where T<:Number + @ Base twiceprecision.jl:265 + [9] (::Type{T})(x::T) where T<:Number + @ boot.jl:894 + [10] (::Type{T})(x::AbstractChar) where T<:Union{AbstractChar, Number} + @ char.jl:50 +``` + +The usual constructor syntax is exactly equivalent to the function-like object +syntax, so trying to define a method with each syntax will cause the first method +to be overwritten by the next one: + +```jldoctest +julia> struct S + f::Int + end + +julia> S() = S(7) +S + +julia> (::Type{S})() = S(8) # overwrites the previous constructor method + +julia> S() +S(8) +``` diff --git a/doc/src/manual/control-flow.md b/doc/src/manual/control-flow.md index 84b7f141dfd13..4ab611f0cafae 100644 --- a/doc/src/manual/control-flow.md +++ b/doc/src/manual/control-flow.md @@ -139,7 +139,7 @@ julia> test(1,2) x is less than y. julia> test(2,1) -ERROR: UndefVarError: `relation` not defined +ERROR: UndefVarError: `relation` not defined in local scope Stacktrace: [1] test(::Int64, ::Int64) at ./none:7 ``` @@ -458,7 +458,7 @@ julia> for j = 1:3 3 julia> j -ERROR: UndefVarError: `j` not defined +ERROR: UndefVarError: `j` not defined in `Main` ``` ```jldoctest @@ -862,7 +862,8 @@ end else foo end - ERROR: UndefVarError: `foo` not defined + ERROR: UndefVarError: `foo` not defined in `Main` + Suggestion: check for spelling errors or missing imports. ``` Use the [`local` keyword](@ref local-scope) outside the `try` block to make the variable accessible from anywhere within the outer scope. diff --git a/doc/src/manual/conversion-and-promotion.md b/doc/src/manual/conversion-and-promotion.md index f0c156f21ea62..9f785a560bfcc 100644 --- a/doc/src/manual/conversion-and-promotion.md +++ b/doc/src/manual/conversion-and-promotion.md @@ -165,6 +165,7 @@ constructor. Such a definition might look like this: ```julia +import Base: convert convert(::Type{MyType}, x) = MyType(x) ``` @@ -195,6 +196,8 @@ convert(::Type{T}, x::T) where {T<:Number} = x Similar definitions exist for `AbstractString`, [`AbstractArray`](@ref), and [`AbstractDict`](@ref). + + ## Promotion Promotion refers to converting values of mixed types to a single common type. Although it is not @@ -291,6 +294,7 @@ another type object, such that instances of the argument types will be promoted type. Thus, by defining the rule: ```julia +import Base: promote_rule promote_rule(::Type{Float64}, ::Type{Float32}) = Float64 ``` @@ -336,6 +340,7 @@ Finally, we finish off our ongoing case study of Julia's rational number type, w sophisticated use of the promotion mechanism with the following promotion rules: ```julia +import Base: promote_rule promote_rule(::Type{Rational{T}}, ::Type{S}) where {T<:Integer,S<:Integer} = Rational{promote_type(T,S)} promote_rule(::Type{Rational{T}}, ::Type{Rational{S}}) where {T<:Integer,S<:Integer} = Rational{promote_type(T,S)} promote_rule(::Type{Rational{T}}, ::Type{S}) where {T<:Integer,S<:AbstractFloat} = promote_type(T,S) diff --git a/doc/src/manual/distributed-computing.md b/doc/src/manual/distributed-computing.md index 4531506d5c49d..020181dd0a08c 100644 --- a/doc/src/manual/distributed-computing.md +++ b/doc/src/manual/distributed-computing.md @@ -158,7 +158,7 @@ julia> rand2(2,2) 1.15119 0.918912 julia> fetch(@spawnat :any rand2(2,2)) -ERROR: RemoteException(2, CapturedException(UndefVarError(Symbol("#rand2")) +ERROR: RemoteException(2, CapturedException(UndefVarError(Symbol("#rand2")))) Stacktrace: [...] ``` @@ -209,7 +209,7 @@ MyType(7) julia> fetch(@spawnat 2 MyType(7)) ERROR: On worker 2: -UndefVarError: `MyType` not defined +UndefVarError: `MyType` not defined in `Main` ⋮ julia> fetch(@spawnat 2 DummyModule.MyType(7)) @@ -270,10 +270,11 @@ julia> addprocs(2) Module [`Distributed`](@ref man-distributed) must be explicitly loaded on the master process before invoking [`addprocs`](@ref). It is automatically made available on the worker processes. -Note that workers do not run a `~/.julia/config/startup.jl` startup script, nor do they synchronize -their global state (such as global variables, new method definitions, and loaded modules) with any -of the other running processes. You may use `addprocs(exeflags="--project")` to initialize a worker with -a particular environment, and then `@everywhere using ` or `@everywhere include("file.jl")`. +!!! note + Note that workers do not run a `~/.julia/config/startup.jl` startup script, nor do they synchronize + their global state (such as command-line switches, global variables, new method definitions, and loaded modules) with any + of the other running processes. You may use `addprocs(exeflags="--project")` to initialize a worker with + a particular environment, and then `@everywhere using ` or `@everywhere include("file.jl")`. Other types of clusters can be supported by writing your own custom `ClusterManager`, as described below in the [ClusterManagers](@ref) section. @@ -539,9 +540,72 @@ Methods [`put!`](@ref), [`take!`](@ref), [`fetch`](@ref), [`isready`](@ref) and on a [`RemoteChannel`](@ref) are proxied onto the backing store on the remote process. [`RemoteChannel`](@ref) can thus be used to refer to user implemented `AbstractChannel` objects. -A simple example of this is provided in `dictchannel.jl` in the -[Examples repository](https://github.com/JuliaAttic/Examples), which uses a dictionary as its -remote store. +A simple example of this is the following `DictChannel` which uses a dictionary as its +remote store: + +```jldoctest +julia> struct DictChannel{T} <: AbstractChannel{T} + d::Dict + cond_take::Threads.Condition # waiting for data to become available + DictChannel{T}() where {T} = new(Dict(), Threads.Condition()) + DictChannel() = DictChannel{Any}() + end + +julia> begin + function Base.put!(D::DictChannel, k, v) + @lock D.cond_take begin + D.d[k] = v + notify(D.cond_take) + end + return D + end + function Base.take!(D::DictChannel, k) + @lock D.cond_take begin + v = fetch(D, k) + delete!(D.d, k) + return v + end + end + Base.isready(D::DictChannel) = @lock D.cond_take !isempty(D.d) + Base.isready(D::DictChannel, k) = @lock D.cond_take haskey(D.d, k) + function Base.fetch(D::DictChannel, k) + @lock D.cond_take begin + wait(D, k) + return D.d[k] + end + end + function Base.wait(D::DictChannel, k) + @lock D.cond_take begin + while !isready(D, k) + wait(D.cond_take) + end + end + end + end; + +julia> d = DictChannel(); + +julia> isready(d) +false + +julia> put!(d, :k, :v); + +julia> isready(d, :k) +true + +julia> fetch(d, :k) +:v + +julia> wait(d, :k) + +julia> take!(d, :k) +:v + +julia> isready(d, :k) +false +``` + + ## Channels and RemoteChannels @@ -750,16 +814,18 @@ will always operate on copies of arguments. ## [Shared Arrays](@id man-shared-arrays) -Shared Arrays use system shared memory to map the same array across many processes. While there -are some similarities to a [`DArray`](https://github.com/JuliaParallel/DistributedArrays.jl), the -behavior of a [`SharedArray`](@ref) is quite different. In a [`DArray`](https://github.com/JuliaParallel/DistributedArrays.jl), -each process has local access to just a chunk of the data, and no two processes share the same -chunk; in contrast, in a [`SharedArray`](@ref) each "participating" process has access to the -entire array. A [`SharedArray`](@ref) is a good choice when you want to have a large amount of -data jointly accessible to two or more processes on the same machine. +Shared Arrays use system shared memory to map the same array across many processes. A +[`SharedArray`](@ref) is a good choice when you want to have a large amount of data jointly +accessible to two or more processes on the same machine. Shared Array support is available via the +module `SharedArrays`, which must be explicitly loaded on all participating workers. -Shared Array support is available via module `SharedArrays` which must be explicitly loaded on -all participating workers. +A complementary data structure is provided by the external package +[`DistributedArrays.jl`](https://github.com/JuliaParallel/DistributedArrays.jl) in the form of a +`DArray`. While there are some similarities to a [`SharedArray`](@ref), the behavior of a +[`DArray`](https://github.com/JuliaParallel/DistributedArrays.jl) is quite different. In a +[`SharedArray`](@ref), each "participating" process has access to the entire array; in contrast, in +a [`DArray`](https://github.com/JuliaParallel/DistributedArrays.jl), each process has local access +to just a chunk of the data, and no two processes share the same chunk. [`SharedArray`](@ref) indexing (assignment and accessing values) works just as with regular arrays, and is efficient because the underlying memory is available to the local process. Therefore, @@ -1263,8 +1329,11 @@ in future releases. ## Noteworthy external packages Outside of Julia parallelism there are plenty of external packages that should be mentioned. -For example [MPI.jl](https://github.com/JuliaParallel/MPI.jl) is a Julia wrapper for the `MPI` protocol, [Dagger.jl](https://github.com/JuliaParallel/Dagger.jl) provides functionality similar to Python's [Dask](https://dask.org/), and -[DistributedArrays.jl](https://github.com/JuliaParallel/Distributedarrays.jl) provides array operations distributed across workers, as presented in [Shared Arrays](@ref). +For example, [`MPI.jl`](https://github.com/JuliaParallel/MPI.jl) is a Julia wrapper for the `MPI` +protocol, [`Dagger.jl`](https://github.com/JuliaParallel/Dagger.jl) provides functionality similar to +Python's [Dask](https://dask.org/), and +[`DistributedArrays.jl`](https://github.com/JuliaParallel/Distributedarrays.jl) provides array +operations distributed across workers, as [outlined above](@ref man-shared-arrays). A mention must be made of Julia's GPU programming ecosystem, which includes: diff --git a/doc/src/manual/documentation.md b/doc/src/manual/documentation.md index 4c724e1deaaeb..47b8b84dda1b6 100644 --- a/doc/src/manual/documentation.md +++ b/doc/src/manual/documentation.md @@ -19,6 +19,10 @@ environments provide a way to access documentation directly: - In [Juno](https://junolab.org) using `Ctrl-J, Ctrl-D` will show the documentation for the object under the cursor. + +`Docs.hasdoc(module, name)::Bool` tells whether a name has a docstring. `Docs.undocumented_names(module; all)` +returns the undocumented names in a module. + ## Writing Documentation Julia enables package developers and users to document functions, types and other objects easily @@ -303,15 +307,16 @@ Or for use with Julia's metaprogramming functionality: ```julia for (f, op) in ((:add, :+), (:subtract, :-), (:multiply, :*), (:divide, :/)) @eval begin - $f(a,b) = $op(a,b) + $f(a, b) = $op(a, b) end end -@doc "`add(a,b)` adds `a` and `b` together" add -@doc "`subtract(a,b)` subtracts `b` from `a`" subtract +@doc "`add(a, b)` adds `a` and `b` together" add +@doc "`subtract(a, b)` subtracts `b` from `a`" subtract ``` -Documentation in non-toplevel blocks, such as `begin`, `if`, `for`, and `let`, should be -added to the documentation system via `@doc` as well. For example: +Documentation in non-toplevel blocks, such as `begin`, `if`, `for`, `let`, and +inner constructors, should be added to the documentation system via `@doc` as +well. For example: ```julia if condition() @@ -402,7 +407,7 @@ f(x) = x "..." function f(x) - x + return x end "..." @@ -429,10 +434,13 @@ Adds docstring `"..."` to the `@m(::Any)` macro definition. ```julia "..." -:(@m) +:(@m1) + +"..." +macro m2 end ``` -Adds docstring `"..."` to the macro named `@m`. +Adds docstring `"..."` to the macros named `@m1` and `@m2`. ### Types @@ -453,6 +461,20 @@ end Adds the docstring `"..."` to types `T1`, `T2`, and `T3`. +``` +"..." +T1 + +"..." +T2 + +"..." +T3 +``` + +Adds the docstring `"..."` to types `T1`, `T2`, and `T3`. +The previous version is the preferred syntax, however both are equivalent. + ```julia "..." struct T @@ -460,11 +482,17 @@ struct T x "y" y + + @doc "Inner constructor" + function T() + new(...) + end end ``` -Adds docstring `"..."` to type `T`, `"x"` to field `T.x` and `"y"` to field `T.y`. Also applicable -to `mutable struct` types. +Adds docstring `"..."` to type `T`, `"x"` to field `T.x`, `"y"` to field `T.y`, +and `"Inner constructor"` to the inner constructor `T()`. Also applicable to +`mutable struct` types. ### Modules diff --git a/doc/src/manual/embedding.md b/doc/src/manual/embedding.md index 2b6e48c533849..9df9a6c198003 100644 --- a/doc/src/manual/embedding.md +++ b/doc/src/manual/embedding.md @@ -247,7 +247,7 @@ Its second argument `args` is an array of `jl_value_t*` arguments and `nargs` is arguments. There is also an alternative, possibly simpler, way of calling Julia functions and that is via [`@cfunction`](@ref). -Using `@cfunction` allows you to do the type conversions on the Julia side which typically is easier than doing it on +Using `@cfunction` allows you to do the type conversions on the Julia side, which is typically easier than doing it on the C side. The `sqrt` example above would with `@cfunction` be written as: ```c @@ -255,7 +255,10 @@ double (*sqrt_jl)(double) = jl_unbox_voidpointer(jl_eval_string("@cfunction(sqrt double ret = sqrt_jl(2.0); ``` -where we first define a C callable function in Julia, extract the function pointer from it and finally call it. +where we first define a C callable function in Julia, extract the function pointer from it, and finally call it. +In addition to simplifying type conversions by doing them in the higher-level language, calling Julia functions +via `@cfunction` pointers eliminates the dynamic-dispatch overhead required by `jl_call` (for which all of the +arguments are "boxed"), and should have performance equivalent to native C function pointers. ## Memory Management @@ -432,14 +435,14 @@ object has just been allocated and no garbage collection has run since then. Not `jl_...` functions can sometimes invoke garbage collection. The write barrier is also necessary for arrays of pointers when updating their data directly. -For example: +Calling `jl_array_ptr_set` is usually much preferred. But direct updates can be done. For example: ```c jl_array_t *some_array = ...; // e.g. a Vector{Any} -void **data = (void**)jl_array_data(some_array); +void **data = jl_array_data(some_array, void*); jl_value_t *some_value = ...; data[0] = some_value; -jl_gc_wb(some_array, some_value); +jl_gc_wb(jl_array_owner(some_array), some_value); ``` ### Controlling the Garbage Collector @@ -487,13 +490,13 @@ referenced. In order to access the data of `x`, we can use `jl_array_data`: ```c -double *xData = (double*)jl_array_data(x); +double *xData = jl_array_data(x, double); ``` Now we can fill the array: ```c -for(size_t i=0; i module Foo julia> Foo.foo() ERROR: On worker 2: -UndefVarError: `Foo` not defined +UndefVarError: `Foo` not defined in `Main` Stacktrace: [...] ``` @@ -746,7 +746,7 @@ julia> @everywhere module Foo julia> Foo.foo() ERROR: On worker 2: -UndefVarError: `gvar` not defined +UndefVarError: `gvar` not defined in `Main.Foo` Stacktrace: [...] ``` @@ -782,7 +782,7 @@ bar (generic function with 1 method) julia> remotecall_fetch(bar, 2) ERROR: On worker 2: -UndefVarError: `#bar` not defined +UndefVarError: `#bar` not defined in `Main` [...] julia> anon_bar = ()->1 @@ -804,6 +804,7 @@ foo (generic function with 1 method) julia> foo([1]) ERROR: MethodError: no method matching foo(::Vector{Int64}) +The function `foo` exists, but no method is defined for this combination of argument types. Closest candidates are: foo(!Matched::Vector{Real}) diff --git a/doc/src/manual/functions.md b/doc/src/manual/functions.md index 3719dfc568913..58d093d200bc9 100644 --- a/doc/src/manual/functions.md +++ b/doc/src/manual/functions.md @@ -102,6 +102,9 @@ As a common convention in Julia (not a syntactic requirement), such a function w [typically be named `f!(x, y)`](@ref man-punctuation) rather than `f(x, y)`, as a visual reminder at the call site that at least one of the arguments (often the first one) is being mutated. +!!! warning "Shared memory between arguments" + The behavior of a mutating function can be unexpected when a mutated argument shares memory with another argument, a situation known as aliasing (e.g. when one is a view of the other). + Unless the function docstring explicitly indicates that aliasing produces the expected result, it is the responsibility of the caller to ensure proper behavior on such inputs. ## Argument-type declarations @@ -186,7 +189,7 @@ There are three possible points of return from this function, returning the valu expressions, depending on the values of `x` and `y`. The `return` on the last line could be omitted since it is the last expression. -### Return type +### [Return type](@id man-functions-return-type) A return type can be specified in the function declaration using the `::` operator. This converts the return value to the specified type. @@ -327,31 +330,17 @@ julia> map(x -> x^2 + 2x - 1, [1, 3, -1]) ``` An anonymous function accepting multiple arguments can be written using the syntax `(x,y,z)->2x+y-z`. + Argument-type declarations for anonymous functions work as for named functions, for example `x::Integer->2x`. The return type of an anonymous function cannot be specified. -A zero-argument anonymous function is written as `()->3`. The idea of a function with no arguments -may seem strange, but is useful for "delaying" a computation. In this usage, a block of code is -wrapped in a zero-argument function, which is later invoked by calling it as `f`. - -As an example, consider this call to [`get`](@ref): - -```julia -get(dict, key) do - # default value calculated here - time() -end -``` - -The code above is equivalent to calling `get` with an anonymous function containing the code -enclosed between `do` and `end`, like so: - -```julia -get(()->time(), dict, key) -``` - -The call to [`time`](@ref) is delayed by wrapping it in a 0-argument anonymous function -that is called only if the requested key is absent from `dict`. +A zero-argument anonymous function can be written as `()->2+2`. The idea of a function with +no arguments may seem strange, but is useful in cases where a result cannot (or should not) +be precomputed. For example, Julia has a zero-argument [`time`](@ref) function that returns +the current time in seconds, and thus `seconds = ()->round(Int, time())` is an anonymous +function that returns this time rounded to the nearest integer assigned to the variable +`seconds`. Each time this anonymous function is called as `seconds()` the current time will +be calculated and returned. ## Tuples @@ -741,6 +730,7 @@ julia> args = [1, 2, 3] julia> baz(args...) ERROR: MethodError: no method matching baz(::Int64, ::Int64, ::Int64) +The function `baz` exists, but no method is defined for this combination of argument types. Closest candidates are: baz(::Any, ::Any) @@ -934,8 +924,10 @@ map([A, B, C]) do x end ``` -The `do x` syntax creates an anonymous function with argument `x` and passes it as the first argument -to [`map`](@ref). Similarly, `do a,b` would create a two-argument anonymous function. Note that `do (a,b)` would create a one-argument anonymous function, +The `do x` syntax creates an anonymous function with argument `x` and passes +the anonymous function as the first argument +to the "outer" function - [`map`](@ref) in this example. +Similarly, `do a,b` would create a two-argument anonymous function. Note that `do (a,b)` would create a one-argument anonymous function, whose argument is a tuple to be deconstructed. A plain `do` would declare that what follows is an anonymous function of the form `() -> ...`. How these arguments are initialized depends on the "outer" function; here, [`map`](@ref) will diff --git a/doc/src/manual/getting-started.md b/doc/src/manual/getting-started.md index e972788022de6..36d54650388cd 100644 --- a/doc/src/manual/getting-started.md +++ b/doc/src/manual/getting-started.md @@ -10,8 +10,9 @@ known as a read-eval-print loop or "REPL") by double-clicking the Julia executab `julia` from the command line: ```@eval +using REPL io = IOBuffer() -Base.banner(io) +REPL.banner(io) banner = String(take!(io)) import Markdown Markdown.parse("```\n\$ julia\n\n$(banner)\njulia> 1 + 2\n3\n\njulia> ans\n3\n```") diff --git a/doc/src/manual/installation.md b/doc/src/manual/installation.md new file mode 100644 index 0000000000000..07acfd1c62681 --- /dev/null +++ b/doc/src/manual/installation.md @@ -0,0 +1,126 @@ +# [Installation](@id man-installation) + +There are many ways to install Julia. The following sections highlight the +recommended method for each of the main supported platforms, and then present +alternative ways that might be useful in specialized situations. + +The current installation recommendation is a solution based on Juliaup. If you +installed Julia previously with a method that is _not_ based on Juliaup and want +to switch your system to an installation that is based on Juliaup, we recommend +that you uninstall all previous Julia versions, ensure that you remove anything +Julia related from your `PATH` variable and then install Julia with one of the +methods described below. + +## Windows + +On Windows Julia can be installed directly from the Windows store +[here](https://www.microsoft.com/store/apps/9NJNWW8PVKMN). One can also install +exactly the same version by executing + +``` +winget install julia -s msstore +``` + +in any shell. + +## Mac and Linux + +Julia can be installed on Linux or Mac by executing + +``` +curl -fsSL https://install.julialang.org | sh +``` + +in a shell. + +### Command line arguments + +One can pass various command line arguments to the Julia installer. The syntax +for installer arguments is + +```bash +curl -fsSL https://install.julialang.org | sh -s -- +``` + +Here `` should be replaced with one or more of the following arguments: +- `--yes` (or `-y`): Run the installer in a non-interactive mode. All +configuration values use their default or a value supplied as a command line +argument. +- `--default-channel=`: Configure the default Juliaup channel. For +example `--default-channel lts` would install the `lts` channel and configure it +as the default. +- `--add-to-path=`: Configure whether Julia should be added to the `PATH` +environment variable. Valid values are `yes` (default) and `no`. +- `--background-selfupdate=`: Configure an optional CRON job that +auto-updates Juliaup if `` has a value larger than 0. The actual value +controls how often the CRON job will run to check for a new Juliaup version in +seconds. The default value is 0, i.e. no CRON job will be created. +- `--startup-selfupdate=`: Configure how often Julia will check for new +versions of Juliaup when Julia is started. The default is every 1440 minutes. +- `-p=` (or `--path`): Configure where the Julia and Juliaup binaries are +installed. The default is `~/.juliaup`. + +## Alternative installation methods + +Note that we recommend the following methods _only_ if none of the installation +methods described above work for your system. + +Some of the installation methods described below recommend installing a package +called `juliaup`. Note that this nevertheless installs a fully functional +Julia system, not just Juliaup. + +### App Installer (Windows) + +If the Windows Store is blocked on a system, we have an alternative +[MSIX App Installer](https://learn.microsoft.com/en-us/windows/msix/app-installer/app-installer-file-overview) +based setup. To use the App Installer version, download +[this](https://install.julialang.org/Julia.appinstaller) file and open it by +double clicking on it. + +### MSI Installer (Windows) + +If neither the Windows Store nor the App Installer version work on your Windows +system, you can also use a MSI based installer. Note that this installation +methods comes with serious limitations and is generally not recommended unless +no other method works. For example, there is no automatic update mechanism for +Juliaup with this installation method. The 64 bit version of the MSI installer +can be downloaded from [here](https://install.julialang.org/Julia-x64.msi) and +the 32 bit version from [here](https://install.julialang.org/Julia-x86.msi). + + By default the install will be a per-user install that does not require + elevation. You can also do a system install by running the following command + from a shell: + +``` +msiexec /i ALLUSERS=1 +``` + +### [Homebrew](https://brew.sh) (Mac and Linux) + +On systems with brew, you can install Julia by running +``` +brew install juliaup +``` +in a shell. Note that you will have to update Juliaup with standard brew +commands. + +### [Arch Linux - AUR](https://aur.archlinux.org/packages/juliaup/) (Linux) + +On Arch Linux, Juliaup is available [in the Arch User Repository (AUR)](https://aur.archlinux.org/packages/juliaup/). + +### [openSUSE Tumbleweed](https://get.opensuse.org/tumbleweed/) (Linux) + +On openSUSE Tumbleweed, you can install Julia by running + +```sh +zypper install juliaup +``` +in a shell with root privileges. + +### [cargo](https://crates.io/crates/juliaup/) (Windows, Mac and Linux) + +To install Julia via Rust's cargo, run: + +```sh +cargo install juliaup +``` diff --git a/doc/src/manual/integers-and-floating-point-numbers.md b/doc/src/manual/integers-and-floating-point-numbers.md index 8b45288f38efa..4c31871374aa2 100644 --- a/doc/src/manual/integers-and-floating-point-numbers.md +++ b/doc/src/manual/integers-and-floating-point-numbers.md @@ -243,11 +243,10 @@ julia> x + 1 == typemin(Int64) true ``` -Thus, arithmetic with Julia integers is actually a form of [modular arithmetic](https://en.wikipedia.org/wiki/Modular_arithmetic). -This reflects the characteristics of the underlying arithmetic of integers as implemented on modern -computers. In applications where overflow is possible, explicit checking for wraparound produced -by overflow is essential; otherwise, the [`BigInt`](@ref) type in [Arbitrary Precision Arithmetic](@ref) -is recommended instead. +Arithmetic operations with Julia's integer types inherently perform [modular arithmetic](https://en.wikipedia.org/wiki/Modular_arithmetic), +mirroring the characteristics of integer arithmetic on modern computer hardware. In scenarios where overflow is a possibility, +it is crucial to explicitly check for wraparound effects that can result from such overflows. +The [`Base.Checked`](@ref) module provides a suite of arithmetic operations equipped with overflow checks, which trigger errors if an overflow occurs. For use cases where overflow cannot be tolerated under any circumstances, utilizing the [`BigInt`](@ref) type, as detailed in [Arbitrary Precision Arithmetic](@ref), is advisable. An example of overflow behavior and how to potentially resolve it is as follows: diff --git a/doc/src/manual/mathematical-operations.md b/doc/src/manual/mathematical-operations.md index 59af063ce8487..c333aeb56c5d9 100644 --- a/doc/src/manual/mathematical-operations.md +++ b/doc/src/manual/mathematical-operations.md @@ -394,7 +394,7 @@ Julia applies the following order and associativity of operations, from highest |:-------------- |:------------------------------------------------------------------------------------------------- |:-------------------------- | | Syntax | `.` followed by `::` | Left | | Exponentiation | `^` | Right | -| Unary | `+ - √` | Right[^1] | +| Unary | `+ - ! ~ ¬ √ ∛ ∜ ⋆ ± ∓ <: >:` | Right[^1] | | Bitshifts | `<< >> >>>` | Left | | Fractions | `//` | Left | | Multiplication | `* / % & \ ÷` | Left[^2] | diff --git a/doc/src/manual/metaprogramming.md b/doc/src/manual/metaprogramming.md index 4936e4fbe550f..b619021fcef92 100644 --- a/doc/src/manual/metaprogramming.md +++ b/doc/src/manual/metaprogramming.md @@ -379,7 +379,7 @@ julia> ex = :(a + b) :(a + b) julia> eval(ex) -ERROR: UndefVarError: `b` not defined +ERROR: UndefVarError: `b` not defined in `Main` [...] julia> a = 1; b = 2; @@ -397,7 +397,7 @@ julia> ex = :(x = 1) :(x = 1) julia> x -ERROR: UndefVarError: `x` not defined +ERROR: UndefVarError: `x` not defined in `Main` julia> eval(ex) 1 @@ -629,6 +629,15 @@ julia> @showarg(1+1) julia> @showarg(println("Yo!")) :(println("Yo!")) + +julia> @showarg(1) # Numeric literal +1 + +julia> @showarg("Yo!") # String literal +"Yo!" + +julia> @showarg("Yo! $("hello")") # String with interpolation is an Expr rather than a String +:("Yo! $("hello")") ``` In addition to the given argument list, every macro is passed extra arguments named `__source__` and `__module__`. diff --git a/doc/src/manual/methods.md b/doc/src/manual/methods.md index 8ca00aa1cfe76..810f81f3e9c8f 100644 --- a/doc/src/manual/methods.md +++ b/doc/src/manual/methods.md @@ -76,6 +76,7 @@ Applying it to any other types of arguments will result in a [`MethodError`](@re ```jldoctest fofxy julia> f(2.0, 3) ERROR: MethodError: no method matching f(::Float64, ::Int64) +The function `f` exists, but no method is defined for this combination of argument types. Closest candidates are: f(::Float64, !Matched::Float64) @@ -86,6 +87,7 @@ Stacktrace: julia> f(Float32(2.0), 3.0) ERROR: MethodError: no method matching f(::Float32, ::Float64) +The function `f` exists, but no method is defined for this combination of argument types. Closest candidates are: f(!Matched::Float64, ::Float64) @@ -96,6 +98,7 @@ Stacktrace: julia> f(2.0, "3.0") ERROR: MethodError: no method matching f(::Float64, ::String) +The function `f` exists, but no method is defined for this combination of argument types. Closest candidates are: f(::Float64, !Matched::Float64) @@ -106,6 +109,7 @@ Stacktrace: julia> f("2.0", "3.0") ERROR: MethodError: no method matching f(::String, ::String) +The function `f` exists, but no method is defined for this combination of argument types. ``` As you can see, the arguments must be precisely of type [`Float64`](@ref). Other numeric @@ -164,16 +168,20 @@ and applying it will still result in a [`MethodError`](@ref): ```jldoctest fofxy julia> f("foo", 3) ERROR: MethodError: no method matching f(::String, ::Int64) +The function `f` exists, but no method is defined for this combination of argument types. Closest candidates are: f(!Matched::Number, ::Number) @ Main none:1 + f(!Matched::Float64, !Matched::Float64) + @ Main none:1 Stacktrace: [...] julia> f() ERROR: MethodError: no method matching f() +The function `f` exists, but no method is defined for this combination of argument types. Closest candidates are: f(!Matched::Float64, !Matched::Float64) @@ -334,10 +342,11 @@ Stacktrace: [...] ``` -Here the call `g(2.0, 3.0)` could be handled by either the `g(Float64, Any)` or the `g(Any, Float64)` -method, and neither is more specific than the other. In such cases, Julia raises a [`MethodError`](@ref) -rather than arbitrarily picking a method. You can avoid method ambiguities by specifying an appropriate -method for the intersection case: +Here the call `g(2.0, 3.0)` could be handled by either the `g(::Float64, ::Any)` or the +`g(::Any, ::Float64)` method. The order in which the methods are defined does not matter and +neither is more specific than the other. In such cases, Julia raises a +[`MethodError`](@ref) rather than arbitrarily picking a method. You can avoid method +ambiguities by specifying an appropriate method for the intersection case: ```jldoctest gofxy julia> g(x::Float64, y::Float64) = 2x + 2y @@ -406,7 +415,20 @@ Here's an example where the method type parameter `T` is used as the type parame type `Vector{T}` in the method signature: ```jldoctest -julia> myappend(v::Vector{T}, x::T) where {T} = [v..., x] +julia> function myappend(v::Vector{T}, x::T) where {T} + return [v..., x] + end +myappend (generic function with 1 method) +``` + +The type parameter `T` in this example ensures that the added element `x` is a subtype of the +existing eltype of the vector `v`. +The `where` keyword introduces a list of those constraints after the method signature definition. +This works the same for one-line definitions, as seen above, and must appear _before_ the [return +type declaration](@ref man-functions-return-type), if present, as illustrated below: + +```jldoctest +julia> (myappend(v::Vector{T}, x::T)::Vector) where {T} = [v..., x] myappend (generic function with 1 method) julia> myappend([1,2,3],4) @@ -418,6 +440,7 @@ julia> myappend([1,2,3],4) julia> myappend([1,2,3],2.5) ERROR: MethodError: no method matching myappend(::Vector{Int64}, ::Float64) +The function `myappend` exists, but no method is defined for this combination of argument types. Closest candidates are: myappend(::Vector{T}, !Matched::T) where T @@ -435,6 +458,7 @@ julia> myappend([1.0,2.0,3.0],4.0) julia> myappend([1.0,2.0,3.0],4) ERROR: MethodError: no method matching myappend(::Vector{Float64}, ::Int64) +The function `myappend` exists, but no method is defined for this combination of argument types. Closest candidates are: myappend(::Vector{T}, !Matched::T) where T @@ -444,9 +468,9 @@ Stacktrace: [...] ``` -As you can see, the type of the appended element must match the element type of the vector it -is appended to, or else a [`MethodError`](@ref) is raised. In the following example, the method type parameter -`T` is used as the return value: +If the type of the appended element does not match the element type of the vector it is appended to, +a [`MethodError`](@ref) is raised. +In the following example, the method's type parameter `T` is used as the return value: ```jldoctest julia> mytypeof(x::T) where {T} = T @@ -480,6 +504,7 @@ true julia> same_type_numeric("foo", 2.0) ERROR: MethodError: no method matching same_type_numeric(::String, ::Float64) +The function `same_type_numeric` exists, but no method is defined for this combination of argument types. Closest candidates are: same_type_numeric(!Matched::T, ::T) where T<:Number @@ -492,6 +517,7 @@ Stacktrace: julia> same_type_numeric("foo", "bar") ERROR: MethodError: no method matching same_type_numeric(::String, ::String) +The function `same_type_numeric` exists, but no method is defined for this combination of argument types. julia> same_type_numeric(Int32(1), Int64(2)) false @@ -719,8 +745,8 @@ often it is best to separate each level of dispatch into distinct functions. This may sound similar in approach to single-dispatch, but as we shall see below, it is still more flexible. For example, trying to dispatch on the element-type of an array will often run into ambiguous situations. -Instead, commonly code will dispatch first on the container type, -then recurse down to a more specific method based on eltype. +Instead, common code will dispatch first on the container type, +then recurse down to a more specific method based on `eltype`. In most cases, the algorithms lend themselves conveniently to this hierarchical approach, while in other cases, this rigor must be resolved manually. This dispatching branching can be observed, for example, in the logic to sum two matrices: @@ -874,6 +900,7 @@ bar (generic function with 1 method) julia> bar(1,2,3) ERROR: MethodError: no method matching bar(::Int64, ::Int64, ::Int64) +The function `bar` exists, but no method is defined for this combination of argument types. Closest candidates are: bar(::Any, ::Any, ::Any, !Matched::Any) @@ -887,6 +914,7 @@ julia> bar(1,2,3,4) julia> bar(1,2,3,4,5) ERROR: MethodError: no method matching bar(::Int64, ::Int64, ::Int64, ::Int64, ::Int64) +The function `bar` exists, but no method is defined for this combination of argument types. Closest candidates are: bar(::Any, ::Any, ::Any, ::Any) diff --git a/doc/src/manual/modules.md b/doc/src/manual/modules.md index 6ed8e3c8bc8e0..64befb03c1ad5 100644 --- a/doc/src/manual/modules.md +++ b/doc/src/manual/modules.md @@ -106,7 +106,7 @@ modules. We will see how to manage name clashes below. To mark a name as public without exporting it into the namespace of folks who call `using NiceStuff`, one can use `public` instead of `export`. This marks the public name(s) as part of the public API, -but does not have any namespace implications. The `public` keyword is only availiable in Julia 1.11 +but does not have any namespace implications. The `public` keyword is only available in Julia 1.11 and above. To maintain compatibility with Julia 1.10 and below, use the `@compat` macro from the [Compat](https://github.com/JuliaLang/Compat.jl) package. @@ -184,7 +184,6 @@ Stacktrace: @ none:0 [2] top-level scope @ none:1 - ``` This error prevents accidentally adding methods to functions in other modules that you only intended to use. @@ -196,17 +195,16 @@ julia> using .NiceStuff julia> struct Cat end julia> NiceStuff.nice(::Cat) = "nice 😸" - ``` Alternatively, you can `import` the specific function name: ```jldoctest module_manual julia> import .NiceStuff: nice -julia> struct Cat end +julia> struct Mouse end -julia> nice(::Cat) = "nice 😸" -nice (generic function with 2 methods) +julia> nice(::Mouse) = "nice 🐭" +nice (generic function with 3 methods) ``` Which one you choose is a matter of style. The first form makes it clear that you are adding a @@ -288,7 +286,7 @@ julia> using .A, .B julia> f WARNING: both B and A export "f"; uses of it in module Main must be qualified -ERROR: UndefVarError: `f` not defined +ERROR: UndefVarError: `f` not defined in `Main` ``` Here, Julia cannot decide which `f` you are referring to, so you have to make a choice. The following solutions are commonly used: @@ -404,7 +402,7 @@ x = 0 module Sub using ..TestPackage -z = y # ERROR: UndefVarError: `y` not defined +z = y # ERROR: UndefVarError: `y` not defined in `Main` end y = 1 @@ -420,7 +418,7 @@ For similar reasons, you cannot use a cyclic ordering: module A module B -using ..C # ERROR: UndefVarError: `C` not defined +using ..C # ERROR: UndefVarError: `C` not defined in `Main.A` end module C @@ -447,10 +445,12 @@ recompiled upon `using` or `import`. Dependencies are modules it imports, the Julia build, files it includes, or explicit dependencies declared by [`include_dependency(path)`](@ref) in the module file(s). -For file dependencies, a change is determined by examining whether the modification time (`mtime`) -of each file loaded by `include` or added explicitly by `include_dependency` is unchanged, or equal -to the modification time truncated to the nearest second (to accommodate systems that can't copy -mtime with sub-second accuracy). It also takes into account whether the path to the file chosen +For file dependencies loaded by `include`, a change is determined by examining whether the +file size (`fsize`) or content (condensed into a hash) is unchanged. +For file dependencies loaded by `include_dependency` a change is determined by examining whether the modification time (`mtime`) +is unchanged, or equal to the modification time truncated to the nearest second +(to accommodate systems that can't copy mtime with sub-second accuracy). +It also takes into account whether the path to the file chosen by the search logic in `require` matches the path that had created the precompile file. It also takes into account the set of dependencies already loaded into the current process and won't recompile those modules, even if their files change or disappear, in order to avoid creating incompatibilities between @@ -597,15 +597,19 @@ A few other points to be aware of: an error to do this, but you simply need to be prepared that the system will try to copy some of these and to create a single unique instance of others. -It is sometimes helpful during module development to turn off incremental precompilation. The -command line flag `--compiled-modules={yes|no}` enables you to toggle module precompilation on and -off. When Julia is started with `--compiled-modules=no` the serialized modules in the compile cache -are ignored when loading modules and module dependencies. -More fine-grained control is available with `--pkgimages=no`, which suppresses only -native-code storage during precompilation. `Base.compilecache` can still be called -manually. The state of this command line flag is passed to `Pkg.build` to disable automatic -precompilation triggering when installing, updating, and explicitly building packages. +It is sometimes helpful during module development to turn off incremental precompilation. +The command line flag `--compiled-modules={yes|no|existing}` enables you to toggle module +precompilation on and off. When Julia is started with `--compiled-modules=no` the serialized +modules in the compile cache are ignored when loading modules and module dependencies. In +some cases, you may want to load existing precompiled modules, but not create new ones. This +can be done by starting Julia with `--compiled-modules=existing`. More fine-grained control +is available with `--pkgimages={yes|no|existing}`, which only affects native-code storage +during precompilation. `Base.compilecache` can still be called manually. The state of this +command line flag is passed to `Pkg.build` to disable automatic precompilation triggering +when installing, updating, and explicitly building packages. You can also debug some precompilation failures with environment variables. Setting -`JULIA_VERBOSE_LINKING=true` may help resolve failures in linking shared libraries of compiled -native code. See the **Developer Documentation** part of the Julia manual, where you will find further details in the section documenting Julia's internals under "Package Images". +`JULIA_VERBOSE_LINKING=true` may help resolve failures in linking shared libraries of +compiled native code. See the **Developer Documentation** part of the Julia manual, where +you will find further details in the section documenting Julia's internals under "Package +Images". diff --git a/doc/src/manual/multi-threading.md b/doc/src/manual/multi-threading.md index f3b65c74b31d3..d16407efc3dcf 100644 --- a/doc/src/manual/multi-threading.md +++ b/doc/src/manual/multi-threading.md @@ -116,8 +116,8 @@ julia> using Base.Threads julia> nthreadpools() 2 -julia> threadpool() -:default +julia> threadpool() # the main thread is in the interactive thread pool +:interactive julia> nthreads(:default) 3 @@ -133,63 +133,13 @@ julia> nthreads() The zero-argument version of `nthreads` returns the number of threads in the default pool. +!!! note + Depending on whether Julia has been started with interactive threads, + the main thread is either in the default or interactive thread pool. + Either or both numbers can be replaced with the word `auto`, which causes Julia to choose a reasonable default. -## Communication and synchronization - -Although Julia's threads can communicate through shared memory, it is notoriously -difficult to write correct and data-race free multi-threaded code. Julia's -[`Channel`](@ref)s are thread-safe and may be used to communicate safely. - -### Data-race freedom - -You are entirely responsible for ensuring that your program is data-race free, -and nothing promised here can be assumed if you do not observe that -requirement. The observed results may be highly unintuitive. - -The best way to ensure this is to acquire a lock around any access to data that -can be observed from multiple threads. For example, in most cases you should -use the following code pattern: - -```julia-repl -julia> lock(lk) do - use(a) - end - -julia> begin - lock(lk) - try - use(a) - finally - unlock(lk) - end - end - -julia> @lock lk use(a) -``` -where `lk` is a lock (e.g. `ReentrantLock()`) and `a` data. - -Additionally, Julia is not memory safe in the presence of a data race. Be very -careful about reading _any_ data if another thread might write to it! -Instead, always use the lock pattern above when changing data (such as assigning -to a global or closure variable) accessed by other threads. - -```julia -Thread 1: -global b = false -global a = rand() -global b = true - -Thread 2: -while !b; end -bad_read1(a) # it is NOT safe to access `a` here! - -Thread 3: -while !@isdefined(a); end -bad_read2(a) # it is NOT safe to access `a` here -``` - ## The `@threads` Macro Let's work a simple example using our native threads. Let us create an array of zeros: @@ -241,10 +191,10 @@ julia> a Note that [`Threads.@threads`](@ref) does not have an optional reduction parameter like [`@distributed`](@ref). -### Using `@threads` without data races - -Taking the example of a naive sum +### Using `@threads` without data-races +The concept of a data-race is elaborated on in ["Communication and data races between threads"](@ref man-communication-and-data-races). For now, just known that a data race can result in incorrect results and dangerous errors. +Lets say we want to make the function `sum_single` below multithreaded. ```julia-repl julia> function sum_single(a) s = 0 @@ -277,12 +227,11 @@ julia> sum_multi_bad(1:1_000_000) Note that the result is not `500000500000` as it should be, and will most likely change each evaluation. To fix this, buffers that are specific to the task may be used to segment the sum into chunks that are race-free. -Here `sum_single` is reused, with its own internal buffer `s`, and vector `a` is split into `nthreads()` -chunks for parallel work via `nthreads()` `@spawn`-ed tasks. - +Here `sum_single` is reused, with its own internal buffer `s`. The input vector `a` is split into at most `nthreads()` +chunks for parallel work. We then use `Threads.@spawn` to create tasks that individually sum each chunk. Finally, we sum the results from each task using `sum_single` again: ```julia-repl julia> function sum_multi_good(a) - chunks = Iterators.partition(a, length(a) ÷ Threads.nthreads()) + chunks = Iterators.partition(a, cld(length(a), Threads.nthreads())) tasks = map(chunks) do chunk Threads.@spawn sum_single(chunk) end @@ -303,7 +252,72 @@ julia> sum_multi_good(1:1_000_000) Another option is the use of atomic operations on variables shared across tasks/threads, which may be more performant depending on the characteristics of the operations. -## Atomic Operations +## [Communication and data-races between threads](@id man-communication-and-data-races) + +Although Julia's threads can communicate through shared memory, it is notoriously difficult to write correct and data-race free multi-threaded code. Julia's +[`Channel`](@ref)s are thread-safe and may be used to communicate safely. There are also sections below that explain how to use [locks](@ref man-using-locks) and [atomics](@ref man-atomic-operations) to avoid data-races. + +### Data-race freedom + +You are entirely responsible for ensuring that your program is data-race free, +and nothing promised here can be assumed if you do not observe that +requirement. The observed results may be highly unintuitive. + +If data-races are introduced, Julia is not memory safe. **Be very +careful about reading _any_ data if another thread might write to it, as it could result in segmentation faults or worse**. Below are a couple of unsafe ways to access global variables from different threads: +```julia +Thread 1: +global b = false +global a = rand() +global b = true + +Thread 2: +while !b; end +bad_read1(a) # it is NOT safe to access `a` here! + +Thread 3: +while !@isdefined(a); end +bad_read2(a) # it is NOT safe to access `a` here +``` + +### [Using locks to avoid data-races](@id man-using-locks) +An important tool to avoid data-races, and thereby write thread-safe code, is the concept of a "lock". A lock can be locked and unlocked. If a thread has locked a lock, and not unlocked it, it is said to "hold" the lock. If there is only one lock, and we write code the requires holding the lock to access some data, we can ensure that multiple threads will never access the same data simultaneously. Note that the link between a lock and a variable is made by the programmer, and not the program. + +For example, we can create a lock `my_lock`, and lock it while we mutate a variable `my_variable`. This is done most simply with the `@lock` macro: + +```julia-repl +julia> my_lock = ReentrantLock(); + +julia> my_variable = [1, 2, 3]; + +julia> @lock my_lock my_variable[1] = 100 +100 +``` + +By using a similar pattern with the same lock and variable, but on another thread, the operations are free from data-races. + +We could have performed the operation above with the functional version of `lock`, in the following two ways: +```julia-repl +julia> lock(my_lock) do + my_variable[1] = 100 + end +100 + +julia> begin + lock(my_lock) + try + my_variable[1] = 100 + finally + unlock(my_lock) + end + end +100 +``` + +All three options are equivalent. Note how the final version requires an explicit `try`-block to ensure that the lock is always unlocked, whereas the first two version do this internally. One should always use the lock pattern above when changing data (such as assigning +to a global or closure variable) accessed by other threads. Failing to do this could have unforeseen and serious consequences. + +### [Atomic Operations](@id man-atomic-operations) Julia supports accessing and modifying values *atomically*, that is, in a thread-safe way to avoid [race conditions](https://en.wikipedia.org/wiki/Race_condition). A value (which must be of a primitive @@ -372,11 +386,12 @@ julia> acc[] ``` -## [Per-field atomics](@id man-atomics) +#### [Per-field atomics](@id man-atomics) We can also use atomics on a more granular level using the [`@atomic`](@ref -Base.@atomic), [`@atomicswap`](@ref Base.@atomicswap), and -[`@atomicreplace`](@ref Base.@atomicreplace) macros. +Base.@atomic), [`@atomicswap`](@ref Base.@atomicswap), +[`@atomicreplace`](@ref Base.@atomicreplace) macros, and +[`@atomiconce`](@ref Base.@atomiconce) macros. Specific details of the memory model and other details of the design are written in the [Julia Atomics @@ -450,7 +465,8 @@ threads in Julia: method, and module definitions in parallel. * Be aware that finalizers registered by a library may break if threads are enabled. This may require some transitional work across the ecosystem before threading - can be widely adopted with confidence. See the next section for further details. + can be widely adopted with confidence. See the section on + [the safe use of finalizers](@ref man-finalizers) for further details. ## [Task Migration](@id man-task-migration) @@ -466,7 +482,7 @@ and therefore should not be used to index into a vector of buffers or stateful o Task migration was introduced in Julia 1.7. Before this tasks always remained on the same thread that they were started on. -## Safe use of Finalizers +## [Safe use of Finalizers](@id man-finalizers) Because finalizers can interrupt any code, they must be very careful in how they interact with any global state. Unfortunately, the main reason that diff --git a/doc/src/manual/networking-and-streams.md b/doc/src/manual/networking-and-streams.md index 00a10177b2155..45bf60a7944d2 100644 --- a/doc/src/manual/networking-and-streams.md +++ b/doc/src/manual/networking-and-streams.md @@ -1,9 +1,10 @@ # Networking and Streams Julia provides a rich interface to deal with streaming I/O objects such as terminals, pipes and -TCP sockets. This interface, though asynchronous at the system level, is presented in a synchronous -manner to the programmer and it is usually unnecessary to think about the underlying asynchronous -operation. This is achieved by making heavy use of Julia cooperative threading ([coroutine](@ref man-tasks)) +TCP sockets. +These objects allow data to be sent and received in a stream-like fashion, which means that data is processed sequentially as it becomes available. +This interface, though asynchronous at the system level, is presented in a synchronous manner to the programmer. +This is achieved by making heavy use of Julia cooperative threading ([coroutine](@ref man-tasks)) functionality. ## Basic Stream I/O @@ -66,8 +67,8 @@ abcd "abcd" ``` -Note that depending on your terminal settings, your TTY may be line buffered and might thus require -an additional enter before the data is sent to Julia. +Note that depending on your terminal settings, your TTY ("teletype terminal") may be line buffered and might thus require an additional enter before `stdin` data is sent to Julia. +When running Julia from the command line in a TTY, output is sent to the console by default, and standard input is read from the keyboard. To read every line from [`stdin`](@ref) you can use [`eachline`](@ref): @@ -205,6 +206,24 @@ julia> open("hello.txt") do f "HELLO AGAIN." ``` +If you want to redirect stdout to a file + +```# Open file for writing +out_file = open("output.txt", "w") + +# Redirect stdout to file +redirect_stdout(out_file) do + # Your code here + println("This output goes to `out_file` via the `stdout` variable.") +end + +# Close file +close(out_file) + +``` + +Redirecting stdout to a file can help you save and analyze program output, automate processes, and meet compliance requirements. + ## A simple TCP example Let's jump right in with a simple example involving TCP sockets. @@ -336,7 +355,6 @@ ip"74.125.226.225" ## Asynchronous I/O - All I/O operations exposed by [`Base.read`](@ref) and [`Base.write`](@ref) can be performed asynchronously through the use of [coroutines](@ref man-tasks). You can create a new coroutine to read from or write to a stream using the [`@async`](@ref) macro: @@ -418,6 +436,7 @@ close(socket) This example gives the same functionality as the previous program, but uses IPv6 as the network-layer protocol. Listener: + ```julia using Sockets group = Sockets.IPv6("ff05::5:6:7") @@ -430,6 +449,7 @@ close(socket) ``` Sender: + ```julia using Sockets group = Sockets.IPv6("ff05::5:6:7") diff --git a/doc/src/manual/noteworthy-differences.md b/doc/src/manual/noteworthy-differences.md index 6c55bee59cadf..7db1a201f39ca 100644 --- a/doc/src/manual/noteworthy-differences.md +++ b/doc/src/manual/noteworthy-differences.md @@ -56,6 +56,10 @@ may trip up Julia users accustomed to MATLAB: * In Julia, if `A` and `B` are arrays, logical comparison operations like `A == B` do not return an array of booleans. Instead, use `A .== B`, and similarly for the other boolean operators like [`<`](@ref), [`>`](@ref). + * In Julia, when you want to apply a scalar-valued function elementwise to an array, use broadcasting + syntax: `f.(A)` instead of `f(A)`. In some cases, both operations are defined but mean different things: + in MATLAB `exp(A)` applies elementwise and `expm(A)` is the [matrix exponential](https://en.wikipedia.org/wiki/Matrix_exponential), + but in Julia `exp.(A)` applies elementwise and `exp(A)` is the matrix exponential. * In Julia, the operators [`&`](@ref), [`|`](@ref), and [`⊻`](@ref xor) ([`xor`](@ref)) perform the bitwise operations equivalent to `and`, `or`, and `xor` respectively in MATLAB, and have precedence similar to Python's bitwise operators (unlike C). They can operate on scalars or element-wise @@ -245,12 +249,17 @@ For users coming to Julia from R, these are some noteworthy differences: * In Julia, the exponentiation operator is `^`, not `**` as in Python. * Julia uses `nothing` of type `Nothing` to represent a null value, whereas Python uses `None` of type `NoneType`. * In Julia, the standard operators over a matrix type are matrix operations, whereas, in Python, the standard operators are element-wise operations. When both `A` and `B` are matrices, `A * B` in Julia performs matrix multiplication, not element-wise multiplication as in Python. `A * B` in Julia is equivalent with `A @ B` in Python, whereas `A * B` in Python is equivalent with `A .* B` in Julia. + * In Julia, when you want to apply a scalar-valued function elementwise to an array, use broadcasting + syntax: `f.(A)` instead of `f(A)`. In some cases, both operations are defined but mean different things: + `numpy.exp(A)` applies elementwise and `scipy.linalg.expm(A)` is the [matrix exponential](https://en.wikipedia.org/wiki/Matrix_exponential), + but in Julia `exp.(A)` applies elementwise and `exp(A)` is the matrix exponential. * The adjoint operator `'` in Julia returns an adjoint of a vector (a lazy representation of row vector), whereas the transpose operator `.T` over a vector in Python returns the original vector (non-op). * In Julia, a function may contain multiple concrete implementations (called *methods*), which are selected via multiple dispatch based on the types of all arguments to the call, as compared to functions in Python, which have a single implementation and no polymorphism (as opposed to Python method calls which use a different syntax and allows dispatch on the receiver of the method). * There are no classes in Julia. Instead there are structures (mutable or immutable), containing data but no methods. * Calling a method of a class instance in Python (`x = MyClass(*args); x.f(y)`) corresponds to a function call in Julia, e.g. `x = MyType(args...); f(x, y)`. In general, multiple dispatch is more flexible and powerful than the Python class system. * Julia structures may have exactly one abstract supertype, whereas Python classes can inherit from one or more (abstract or concrete) superclasses. - * The logical Julia program structure (Packages and Modules) is independent of the file structure (`include` for additional files), whereas the Python code structure is defined by directories (Packages) and files (Modules). + * The logical Julia program structure (Packages and Modules) is independent of the file structure, whereas the Python code structure is defined by directories (Packages) and files (Modules). + * In Julia, it is idiomatic to split the text of large modules into multiple files, without introducing a new module per file. The code is reassembled inside a single module in a main file via `include`. While the Python equivalent (`exec`) is not typical for this use (it will silently clobber prior definitions), Julia programs are defined as a unit at the `module` level with `using` or `import`, which will only get executed once when first needed--like `include` in Python. Within those modules, the individual files that make up that module are loaded with `include` by listing them once in the intended order. * The ternary operator `x > 0 ? 1 : -1` in Julia corresponds to a conditional expression in Python `1 if x > 0 else -1`. * In Julia the `@` symbol refers to a macro, whereas in Python it refers to a decorator. * Exception handling in Julia is done using `try` — `catch` — `finally`, instead of `try` — `except` — `finally`. In contrast to Python, it is not recommended to use exception handling as part of the normal workflow in Julia (compared with Python, Julia is faster at ordinary control flow but slower at exception-catching). @@ -352,7 +361,7 @@ For users coming to Julia from R, these are some noteworthy differences: it's more general than that since methods are dispatched on every argument type, not only `this`, using the most-specific-declaration rule). -### Julia ⇔ C/C++: Namespaces +### Julia ⇔ C/C++: Namespaces * C/C++ `namespace`s correspond roughly to Julia `module`s. * There are no private globals or fields in Julia. Everything is publicly accessible through fully qualified paths (or relative paths, if desired). @@ -364,7 +373,7 @@ For users coming to Julia from R, these are some noteworthy differences: * Caveat: `import`/`using` (Julia) works only at the global scope level (`module`s) * In C++, `using namespace X` works within arbitrary scopes (ex: function scope). -### Julia ⇔ C/C++: Module loading +### Julia ⇔ C/C++: Module loading * When you think of a C/C++ "**library**", you are likely looking for a Julia "**package**". * Caveat: C/C++ libraries often house multiple "software modules" whereas Julia "packages" typically house one. @@ -392,10 +401,10 @@ For users coming to Julia from R, these are some noteworthy differences: paths to the `Base.LOAD_PATH` array. * Packages from directory-based repositories do not require the `Pkg.add()` tool prior to being loaded with `import` or `using`. They are simply available to the project. - * Directory-based package repositories are the **quickest solution** to developping local + * Directory-based package repositories are the **quickest solution** to developing local libraries of "software modules". -### Julia ⇔ C/C++: Assembling modules +### Julia ⇔ C/C++: Assembling modules * In C/C++, `.c`/`.cpp` files are compiled & added to a library with build/`make` scripts. * In Julia, `import [PkgName]`/`using [PkgName]` statements load `[PkgName].jl` located in a package's `[PkgName]/src/` subdirectory. @@ -412,7 +421,7 @@ For users coming to Julia from R, these are some noteworthy differences: Julia package* ("software module"). It is therefore relatively straightforward to ensure file are `include`d only once (No `#ifdef` confusion). -### Julia ⇔ C/C++: Module interface +### Julia ⇔ C/C++: Module interface * C++ exposes interfaces using "public" `.h`/`.hpp` files whereas Julia `module`s mark specific symbols that are intended for their users as `public`or `export`ed. * Often, Julia `module`s simply add functionality by generating new "methods" to existing @@ -426,7 +435,7 @@ For users coming to Julia from R, these are some noteworthy differences: * Users might be expected to access these components by qualifying functions/structs/... with the package/module name (ex: `MyModule.run_this_task(...)`). -### Julia ⇔ C/C++: Quick reference +### Julia ⇔ C/C++: Quick reference | Software Concept | Julia | C/C++ | | :--- | :--- | :--- | diff --git a/doc/src/manual/performance-tips.md b/doc/src/manual/performance-tips.md index 68ee5132f8592..960ba51a16850 100644 --- a/doc/src/manual/performance-tips.md +++ b/doc/src/manual/performance-tips.md @@ -94,8 +94,8 @@ a vector of 64-bit floats so there should be no need to allocate (heap) memory. We should clarify that what `@time` reports is specifically *heap* allocations, which are typically needed for either mutable objects or for creating/growing variable-sized containers (such as `Array` or `Dict`, strings, or "type-unstable" -objects whose type is only known at runtime). Allocating (or deallocating) such blocks of memory may require an expensive -system call (e.g. via `malloc` in C), and they must be tracked for garbage collection. In contrast, immutable values like +objects whose type is only known at runtime). Allocating (or deallocating) such blocks of memory may require an expensive function +call to libc (e.g. via `malloc` in C), and they must be tracked for garbage collection. In contrast, immutable values like numbers (except bignums), tuples, and immutable `struct`s can be stored much more cheaply, e.g. in stack or CPU-register memory, so one doesn’t typically worry about the performance cost of "allocating" them. @@ -157,7 +157,7 @@ the performance of your code: * [Profiling](@ref) allows you to measure the performance of your running code and identify lines that serve as bottlenecks. For complex projects, the [ProfileView](https://github.com/timholy/ProfileView.jl) package can help you visualize your profiling results. - * The [Traceur](https://github.com/JunoLab/Traceur.jl) package can help you find common performance problems in your code. + * The [JET](https://github.com/aviatesk/JET.jl) package can help you find common performance problems in your code. * Unexpectedly-large memory allocations--as reported by [`@time`](@ref), [`@allocated`](@ref), or the profiler (through calls to the garbage-collection routines)--hint that there might be issues with your code. If you don't see another reason for the allocations, suspect a type problem. @@ -1166,7 +1166,7 @@ and fewer memory accesses due to caching. These are the same reasons that it is to access arrays in column-major order (see above). Irregular access patterns and non-contiguous views can drastically slow down computations on arrays because of non-sequential memory access. -Copying irregularly-accessed data into a contiguous array before repeated access it can result +Copying irregularly-accessed data into a contiguous array before repeatedly accessing it can result in a large speedup, such as in the example below. Here, a matrix is being accessed at randomly-shuffled indices before being multiplied. Copying into plain arrays speeds up the multiplication even with the added cost of copying and allocation. @@ -1239,6 +1239,34 @@ versus: println(file, f(a), f(b)) ``` +## Avoid eager string materialization + +In settings where a string representation of an object is only needed +conditionally (e.g. in error paths of functions or conditional warnings such as +deprecations), it is advisable to avoid the overhead of eagerly materializing +the string. Since Julia 1.8, this can be achieved via +[`LazyString`](@ref) and the corresponding string macro [`@lazy_str`](@ref). + +For example, instead of: + +```julia +Base.depwarn("`foo` is deprecated for type $(typeof(x))", :bar) +``` + +use: + +```julia +Base.depwarn(lazy"`foo` is deprecated for type $(typeof(x))", :bar) +``` + +or the equivalent macro-free version: + +```julia +Base.depwarn(LazyString("`foo` is deprecated for type ", typeof(x)), :bar) +``` + +Through this approach, the interpolated string will only be constructed when it is actually displayed. + ## Optimize network I/O during parallel execution When executing a remote function in parallel: @@ -1647,7 +1675,7 @@ as opposed to the later phase when it is first invoked. The parser does not "kno The magic of type inference takes place in the later phase of compilation. Thus, the parser does not know that `r` has a fixed type (`Int`). -nor that `r` does not change value once the inner function is created (so that +Nor that `r` does not change value once the inner function is created (so that the box is unneeded). Therefore, the parser emits code for box that holds an object with an abstract type such as `Any`, which requires run-time type dispatch for each occurrence of `r`. This can be diff --git a/doc/src/manual/strings.md b/doc/src/manual/strings.md index fca4fc75d9e0f..1087c7f8b5ff1 100644 --- a/doc/src/manual/strings.md +++ b/doc/src/manual/strings.md @@ -832,7 +832,7 @@ of the substring that matches, but perhaps we want to capture any non-blank text character. We could do the following: ```jldoctest -julia> m = match(r"^\s*(?:#\s*(.*?)\s*$|$)", "# a comment ") +julia> m = match(r"^\s*(?:#\s*(.*?)\s*$)", "# a comment ") RegexMatch("# a comment ", 1="a comment") ``` @@ -981,10 +981,10 @@ x Tells the regular expression parser to ignore most whitespace For example, the following regex has all three flags turned on: ```jldoctest -julia> r"a+.*b+.*?d$"ism -r"a+.*b+.*?d$"ims +julia> r"a+.*b+.*d$"ism +r"a+.*b+.*d$"ims -julia> match(r"a+.*b+.*?d$"ism, "Goodbye,\nOh, angry,\nBad world\n") +julia> match(r"a+.*b+.*d$"ism, "Goodbye,\nOh, angry,\nBad world\n") RegexMatch("angry,\nBad world") ``` @@ -1142,9 +1142,9 @@ some confusion regarding the matter. Version numbers can easily be expressed with non-standard string literals of the form [`v"..."`](@ref @v_str). Version number literals create [`VersionNumber`](@ref) objects which follow the -specifications of [semantic versioning](https://semver.org/), +specifications of [semantic versioning 2.0.0-rc2](https://semver.org/spec/v2.0.0-rc.2.html), and therefore are composed of major, minor and patch numeric values, followed by pre-release and -build alpha-numeric annotations. For example, `v"0.2.1-rc1+win64"` is broken into major version +build alphanumeric annotations. For example, `v"0.2.1-rc1+win64"` is broken into major version `0`, minor version `2`, patch version `1`, pre-release `rc1` and build `win64`. When entering a version literal, everything except the major version number is optional, therefore e.g. `v"0.2"` is equivalent to `v"0.2.0"` (with empty pre-release/build annotations), `v"2"` is equivalent to @@ -1203,3 +1203,51 @@ Notice that the first two backslashes appear verbatim in the output, since they precede a quote character. However, the next backslash character escapes the backslash that follows it, and the last backslash escapes a quote, since these backslashes appear before a quote. + + +## [Annotated Strings](@id man-annotated-strings) + +It is sometimes useful to be able to hold metadata relating to regions of a +string. A [`AnnotatedString`](@ref Base.AnnotatedString) wraps another string and +allows for regions of it to be annotated with labelled values (`:label => value`). +All generic string operations are applied to the underlying string. However, +when possible, styling information is preserved. This means you can manipulate a +[`AnnotatedString`](@ref Base.AnnotatedString) —taking substrings, padding them, +concatenating them with other strings— and the metadata annotations will "come +along for the ride". + +This string type is fundamental to the [StyledStrings stdlib](@ref +stdlib-styledstrings), which uses `:face`-labelled annotations to hold styling +information. + +When concatenating a [`AnnotatedString`](@ref Base.AnnotatedString), take care to use +[`annotatedstring`](@ref Base.annotatedstring) instead of [`string`](@ref) if you want +to keep the string annotations. + +```jldoctest +julia> str = Base.AnnotatedString("hello there", + [(1:5, :word => :greeting), (7:11, :label => 1)]) +"hello there" + +julia> length(str) +11 + +julia> lpad(str, 14) +" hello there" + +julia> typeof(lpad(str, 7)) +Base.AnnotatedString{String} + +julia> str2 = Base.AnnotatedString(" julia", [(2:6, :face => :magenta)]) +" julia" + +julia> Base.annotatedstring(str, str2) +"hello there julia" + +julia> str * str2 == Base.annotatedstring(str, str2) # *-concatenation still works +true +``` + +The annotations of a [`AnnotatedString`](@ref Base.AnnotatedString) can be accessed +and modified via the [`annotations`](@ref Base.annotations) and +[`annotate!`](@ref Base.annotate!) functions. diff --git a/doc/src/manual/style-guide.md b/doc/src/manual/style-guide.md index f892d6284fb10..92516623724b1 100644 --- a/doc/src/manual/style-guide.md +++ b/doc/src/manual/style-guide.md @@ -262,6 +262,29 @@ Splicing function arguments can be addictive. Instead of `[a..., b...]`, use sim which already concatenates arrays. [`collect(a)`](@ref) is better than `[a...]`, but since `a` is already iterable it is often even better to leave it alone, and not convert it to an array. +## Ensure constructors return an instance of their own type + +When a method `T(x)` is called on a type `T`, it is generally expected to return a value of type T. +Defining a [constructor](@ref man-constructors) that returns an unexpected type can lead to confusing and unpredictable behavior: + +```jldoctest +julia> struct Foo{T} + x::T + end + +julia> Base.Float64(foo::Foo) = Foo(Float64(foo.x)) # Do not define methods like this + +julia> Float64(Foo(3)) # Should return `Float64` +Foo{Float64}(3.0) + +julia> Foo{Int}(x) = Foo{Float64}(x) # Do not define methods like this + +julia> Foo{Int}(3) # Should return `Foo{Int}` +Foo{Float64}(3.0) +``` + +To maintain code clarity and ensure type consistency, always design constructors to return an instance of the type they are supposed to construct. + ## Don't use unnecessary static parameters A function signature: @@ -346,7 +369,7 @@ This would provide custom showing of vectors with a specific new element type. W this should be avoided. The trouble is that users will expect a well-known type like `Vector()` to behave in a certain way, and overly customizing its behavior can make it harder to work with. -## Avoid type piracy +## [Avoid type piracy](@id avoid-type-piracy) "Type piracy" refers to the practice of extending or redefining methods in Base or other packages on types that you have not defined. In extreme cases, you can crash Julia diff --git a/doc/src/manual/types.md b/doc/src/manual/types.md index f3ce600091aca..0d7b2795401f1 100644 --- a/doc/src/manual/types.md +++ b/doc/src/manual/types.md @@ -713,10 +713,12 @@ For the default constructor, exactly one argument must be supplied for each fiel ```jldoctest pointtype julia> Point{Float64}(1.0) ERROR: MethodError: no method matching Point{Float64}(::Float64) +The type `Point{Float64}` exists, but no method is defined for this combination of argument types when trying to construct it. [...] -julia> Point{Float64}(1.0,2.0,3.0) +julia> Point{Float64}(1.0, 2.0, 3.0) ERROR: MethodError: no method matching Point{Float64}(::Float64, ::Float64, ::Float64) +The type `Point{Float64}` exists, but no method is defined for this combination of argument types when trying to construct it. [...] ``` @@ -748,6 +750,7 @@ to `Point` have the same type. When this isn't the case, the constructor will fa ```jldoctest pointtype julia> Point(1,2.5) ERROR: MethodError: no method matching Point(::Int64, ::Float64) +The type `Point` exists, but no method is defined for this combination of argument types when trying to construct it. Closest candidates are: Point(::T, !Matched::T) where T @@ -1413,6 +1416,8 @@ is raised: ```jldoctest; filter = r"Closest candidates.*"s julia> supertype(Union{Float64,Int64}) ERROR: MethodError: no method matching supertype(::Type{Union{Float64, Int64}}) +The function `supertype` exists, but no method is defined for this combination of argument types. + Closest candidates are: [...] ``` diff --git a/doc/src/manual/variables-and-scoping.md b/doc/src/manual/variables-and-scoping.md index c763d62680091..e921ed8931e84 100644 --- a/doc/src/manual/variables-and-scoping.md +++ b/doc/src/manual/variables-and-scoping.md @@ -67,31 +67,7 @@ Each module introduces a new global scope, separate from the global scope of all is no all-encompassing global scope. Modules can introduce variables of other modules into their scope through the [using or import](@ref modules) statements or through qualified access using the dot-notation, i.e. each module is a so-called *namespace* as well as a first-class data structure -associating names with values. Note that while variable bindings can be read externally, they can only -be changed within the module to which they belong. As an escape hatch, you can always evaluate code -inside that module to modify a variable; this guarantees, in particular, that module bindings cannot -be modified externally by code that never calls `eval`. - -```jldoctest -julia> module A - a = 1 # a global in A's scope - end; - -julia> module B - module C - c = 2 - end - b = C.c # can access the namespace of a nested global scope - # through a qualified access - import ..A # makes module A available - d = A.a - end; - -julia> module D - b = a # errors as D's global scope is separate from A's - end; -ERROR: UndefVarError: `a` not defined -``` +associating names with values. If a top-level expression contains a variable declaration with keyword `local`, then that variable is not accessible outside that expression. @@ -187,7 +163,7 @@ julia> greet() hello julia> x # global -ERROR: UndefVarError: `x` not defined +ERROR: UndefVarError: `x` not defined in `Main` ``` Inside of the `greet` function, the assignment `x = "hello"` causes `x` to be a new local variable @@ -256,7 +232,7 @@ julia> sum_to(10) 55 julia> s # global -ERROR: UndefVarError: `s` not defined +ERROR: UndefVarError: `s` not defined in `Main` ``` Since `s` is local to the function `sum_to`, calling the function has no effect on the global @@ -343,7 +319,7 @@ hello hello julia> x -ERROR: UndefVarError: `x` not defined +ERROR: UndefVarError: `x` not defined in `Main` ``` Since the global `x` is not defined when the `for` loop is evaluated, the first clause of the soft @@ -408,7 +384,7 @@ julia> code = """ julia> include_string(Main, code) ┌ Warning: Assignment to `s` in soft scope is ambiguous because a global variable by the same name exists: `s` will be treated as a new local. Disambiguate by using `local s` to suppress this warning or `global s` to assign to the existing global variable. └ @ string:4 -ERROR: LoadError: UndefVarError: `s` not defined +ERROR: LoadError: UndefVarError: `s` not defined in local scope ``` Here we use [`include_string`](@ref), to evaluate `code` as though it were the contents of a file. @@ -559,7 +535,7 @@ julia> let x = 1, z println("z: $z") # errors as z has not been assigned yet but is local end x: 1, y: -1 -ERROR: UndefVarError: `z` not defined +ERROR: UndefVarError: `z` not defined in local scope ``` The assignments are evaluated in order, with each right-hand side evaluated in the scope before diff --git a/doc/src/manual/variables.md b/doc/src/manual/variables.md index 2c707784a8831..75c2163896d9c 100644 --- a/doc/src/manual/variables.md +++ b/doc/src/manual/variables.md @@ -59,10 +59,10 @@ name `δ` can be entered by typing `\delta`-*tab*, or even `α̂⁽²⁾` by `\a that you don't know how to type, the REPL help will tell you: just type `?` and then paste the symbol.) -Julia will even let you redefine built-in constants and functions if needed (although -this is not recommended to avoid potential confusions): +Julia will even let you shadow existing exported constants and functions with local ones +(although this is not recommended to avoid potential confusions): -```jldoctest +```jldoctest; filter = r"with \d+ methods" julia> pi = 3 3 @@ -71,6 +71,12 @@ julia> pi julia> sqrt = 4 4 + +julia> length() = 5 +length (generic function with 1 method) + +julia> Base.length +length (generic function with 79 methods) ``` However, if you try to redefine a built-in constant or function already in use, Julia will give diff --git a/doc/src/manual/workflow-tips.md b/doc/src/manual/workflow-tips.md index 4085a51ff9131..c3bbbbae8146f 100644 --- a/doc/src/manual/workflow-tips.md +++ b/doc/src/manual/workflow-tips.md @@ -10,57 +10,40 @@ your experience at the command line. ### A basic editor/REPL workflow -The most basic Julia workflows involve using a text editor in conjunction with the `julia` command -line. A common pattern includes the following elements: +The most basic Julia workflows involve using a text editor in conjunction with the `julia` command line. - * **Put code under development in a temporary module.** Create a file, say `Tmp.jl`, and include - within it +Create a file, say `Tmp.jl`, and include within it +```julia +module Tmp - ```julia - module Tmp - export say_hello +say_hello() = println("Hello!") - say_hello() = println("Hello!") +# Your other definitions here - # your other definitions here +end # module - end - ``` - * **Put your test code in another file.** Create another file, say `tst.jl`, which looks like +using .Tmp +``` +Then, in the same directory, start the Julia REPL (using the `julia` command). +Run the new file as follows: +``` +julia> include("Tmp.jl") - ```julia - include("Tmp.jl") - import .Tmp - # using .Tmp # we can use `using` to bring the exported symbols in `Tmp` into our namespace +julia> Tmp.say_hello() +Hello! +``` +Explore ideas in the REPL. Save good ideas in `Tmp.jl`. +To reload the file after it has been changed, just `include` it again. - Tmp.say_hello() - # say_hello() +The key in the above is that your code is encapsulated in a module. +That allows you to edit `struct` definitions and remove methods, without restarting Julia. - # your other test code here - ``` +(Explanation: `struct`s cannot be edited after definition, nor can methods be deleted. +But you _can_ overwrite the definition of a module, which is what we do when we re-`include("Tmp.jl")`). - and includes tests for the contents of `Tmp`. - Alternatively, you can wrap the contents of your test file in a module, as +In addition, the encapsulation of code in a module protects it from being influenced +by previous state in the REPL, protecting you from hard-to-detect errors. - ```julia - module Tst - include("Tmp.jl") - import .Tmp - #using .Tmp - - Tmp.say_hello() - # say_hello() - - # your other test code here - end - ``` - - The advantage is that your testing code is now contained in a module and does not use the global scope in `Main` for - definitions, which is a bit more tidy. - - * `include` the `tst.jl` file in the Julia REPL with `include("tst.jl")`. - - * **Lather. Rinse. Repeat.** Explore ideas at the `julia` command prompt. Save good ideas in `tst.jl`. To execute `tst.jl` after it has been changed, just `include` it again. ## Browser-based workflow diff --git a/doc/src/tutorials/external.md b/doc/src/tutorials/external.md new file mode 100644 index 0000000000000..0211db3d63a5e --- /dev/null +++ b/doc/src/tutorials/external.md @@ -0,0 +1,4 @@ +# External Tutorials + +We have created a non-exhaustive list of community provided Julia tutorials. +[Check them out to learn Julia through the lens of someone from the community](https://julialang.org/learning/tutorials/). diff --git a/doc/src/manual/profile.md b/doc/src/tutorials/profile.md similarity index 99% rename from doc/src/manual/profile.md rename to doc/src/tutorials/profile.md index 3a1812cb6d187..a3baec593a53d 100644 --- a/doc/src/manual/profile.md +++ b/doc/src/tutorials/profile.md @@ -300,7 +300,7 @@ on the author's laptop). ## Memory allocation analysis One of the most common techniques to improve performance is to reduce memory allocation. Julia -provides several tools measure this: +provides several tools to measure this: ### `@time` diff --git a/julia.spdx.json b/julia.spdx.json index bea7bdc6c3a5d..63683dd302a39 100644 --- a/julia.spdx.json +++ b/julia.spdx.json @@ -370,6 +370,32 @@ "copyrightText": "Copyright © 2014-2019 by Steven G. Johnson, Jiahao Chen, Tony Kelman, Jonas Fonseca, and other contributors listed in the git history.", "summary": "utf8proc is a small, clean C library that provides Unicode normalization, case-folding, and other operations for data in the UTF-8 encoding." }, + { + "name": "LibTracyClient", + "SPDXID": "SPDXRef-LibTracyClient", + "downloadLocation": "git+https://github.com/wolfpld/tracy.git", + "filesAnalyzed": false, + "homepage": "https://github.com/wolfpld/tracy", + "sourceInfo": "The git hash of the version in use can be found in the file deps/libtracyclient.version", + "licenseConcluded": "BSD-3-Clause", + "licenseDeclared": "BSD-3-Clause", + "copyrightText": "Copyright (c) 2017-2024, Bartosz Taudul ", + "summary": "A real time, nanosecond resolution, remote telemetry, hybrid frame and sampling profiler for games and other applications.", + "comment": "LibTracyClient is an optional dependency that is not built by default" + }, + { + "name": "ittapi", + "SPDXID": "SPDXRef-ittapi", + "downloadLocation": "git+https://github.com/intel/ittapi.git", + "filesAnalyzed": false, + "homepage": "https://github.com/intel/ittapi", + "sourceInfo": "The git hash of the version in use can be found in the file deps/ittapi.version", + "licenseConcluded": "BSD-3-Clause AND GPL-2.0-only", + "licenseDeclared": "BSD-3-Clause AND GPL-2.0-only", + "copyrightText": "Copyright (c) 2019 Intel Corporation", + "summary": "The Instrumentation and Tracing Technology (ITT) API enables your application to generate and control the collection of trace data during its execution across different Intel tools.", + "comment": "ITTAPI is an optional dependency that is not built by default" + }, { "name": "7-Zip", "SPDXID": "SPDXRef-7zip", @@ -581,6 +607,16 @@ "relationshipType": "BUILD_DEPENDENCY_OF", "relatedSpdxElement": "SPDXRef-JuliaMain" }, + { + "spdxElementId": "SPDXRef-LibTracyClient", + "relationshipType": "OPTIONAL_DEPENDENCY_OF", + "relatedSpdxElement": "SPDXRef-JuliaMain" + }, + { + "spdxElementId": "SPDXRef-ittapi", + "relationshipType": "OPTIONAL_DEPENDENCY_OF", + "relatedSpdxElement": "SPDXRef-JuliaMain" + }, { "spdxElementId": "SPDXRef-7zip", "relationshipType": "RUNTIME_DEPENDENCY_OF", diff --git a/pkgimage.mk b/pkgimage.mk index 9a91488955420..740b9760cab48 100644 --- a/pkgimage.mk +++ b/pkgimage.mk @@ -6,126 +6,32 @@ include $(JULIAHOME)/stdlib/stdlib.mk # set some influential environment variables -export JULIA_DEPOT_PATH := $(build_prefix)/share/julia -export JULIA_LOAD_PATH := @stdlib +export JULIA_DEPOT_PATH := $(shell echo $(call cygpath_w,$(build_prefix)/share/julia)) +export JULIA_LOAD_PATH := @stdlib$(PATHSEP)$(shell echo $(call cygpath_w,$(JULIAHOME)/stdlib)) unexport JULIA_PROJECT := unexport JULIA_BINDIR := export JULIA_FALLBACK_REPL := true default: release -release: all-release -debug: all-debug +release: $(BUILDDIR)/stdlib/release.image +debug: $(BUILDDIR)/stdlib/debug.image all: release debug -$(JULIA_DEPOT_PATH): +$(JULIA_DEPOT_PATH)/compiled: mkdir -p $@ print-depot-path: @$(call PRINT_JULIA, $(call spawn,$(JULIA_EXECUTABLE)) --startup-file=no -e '@show Base.DEPOT_PATH') -all-release: $(addprefix cache-release-, $(STDLIBS)) -all-debug: $(addprefix cache-debug-, $(STDLIBS)) +$(BUILDDIR)/stdlib/%.image: $(JULIAHOME)/stdlib/Project.toml $(JULIAHOME)/stdlib/Manifest.toml $(INDEPENDENT_STDLIBS_SRCS) $(JULIA_DEPOT_PATH)/compiled + export JULIA_CPU_TARGET="$(JULIA_CPU_TARGET)" + @$(call PRINT_JULIA, $(call spawn,$(JULIA_EXECUTABLE)) --startup-file=no -e 'Base.Precompilation.precompilepkgs(;configs=[``=>Base.CacheFlags(), `--check-bounds=yes`=>Base.CacheFlags(;check_bounds=1)])') + touch $@ -define stdlib_builder -ifneq ($(filter $(1),$(INDEPENDENT_STDLIBS)),) -$$(BUILDDIR)/stdlib/$1.release.image: $$($1_SRCS) $$(addsuffix .release.image,$$(addprefix $$(BUILDDIR)/stdlib/,$2)) $(build_private_libdir)/sys.$(SHLIB_EXT) - @$$(call PRINT_JULIA, $$(call spawn,$$(JULIA_EXECUTABLE)) --startup-file=no --check-bounds=yes -e 'Base.compilecache(Base.identify_package("$1"))') - @$$(call PRINT_JULIA, $$(call spawn,$$(JULIA_EXECUTABLE)) --startup-file=no --pkgimage=no -e 'Base.compilecache(Base.identify_package("$1"))') - @$$(call PRINT_JULIA, $$(call spawn,$$(JULIA_EXECUTABLE)) --startup-file=no -e 'Base.compilecache(Base.identify_package("$1"))') - touch $$@ -$$(BUILDDIR)/stdlib/$1.debug.image: $$($1_SRCS) $$(addsuffix .debug.image,$$(addprefix $$(BUILDDIR)/stdlib/,$2)) $(build_private_libdir)/sys-debug.$(SHLIB_EXT) - @$$(call PRINT_JULIA, $$(call spawn,$$(JULIA_EXECUTABLE)) --startup-file=no --check-bounds=yes -e 'Base.compilecache(Base.identify_package("$1"))') - @$$(call PRINT_JULIA, $$(call spawn,$$(JULIA_EXECUTABLE)) --startup-file=no --pkgimage=no -e 'Base.compilecache(Base.identify_package("$1"))') - @$$(call PRINT_JULIA, $$(call spawn,$$(JULIA_EXECUTABLE)) --startup-file=no -e 'Base.compilecache(Base.identify_package("$1"))') - touch $$@ -else -ifneq ($(filter $(1),$(STDLIBS_WITHIN_SYSIMG)),) -$$(BUILDDIR)/stdlib/$1.release.image: - touch $$@ -$$(BUILDDIR)/stdlib/$1.debug.image: - touch $$@ -else -$$(error $(1) neither in STDLIBS_WITHIN_SYSIMG nor INDEPENDENT_STDLIBS) -endif -endif -cache-release-$1: $$(BUILDDIR)/stdlib/$1.release.image -cache-debug-$1: $$(BUILDDIR)/stdlib/$1.debug.image -.SECONDARY: $$(BUILDDIR)/stdlib/$1.release.image $$(BUILDDIR)/stdlib/$1.debug.image -endef +$(BUILDDIR)/stdlib/release.image: $(build_private_libdir)/sys.$(SHLIB_EXT) +$(BUILDDIR)/stdlib/debug.image: $(build_private_libdir)/sys-debug.$(SHLIB_EXT) -# no dependencies -$(eval $(call stdlib_builder,MozillaCACerts_jll,)) -$(eval $(call stdlib_builder,ArgTools,)) -$(eval $(call stdlib_builder,Artifacts,)) -$(eval $(call stdlib_builder,Base64,)) -$(eval $(call stdlib_builder,CRC32c,)) -$(eval $(call stdlib_builder,FileWatching,)) -$(eval $(call stdlib_builder,Libdl,)) -$(eval $(call stdlib_builder,Logging,)) -$(eval $(call stdlib_builder,Mmap,)) -$(eval $(call stdlib_builder,NetworkOptions,)) -$(eval $(call stdlib_builder,SHA,)) -$(eval $(call stdlib_builder,Serialization,)) -$(eval $(call stdlib_builder,Sockets,)) -$(eval $(call stdlib_builder,Unicode,)) -$(eval $(call stdlib_builder,Profile,)) - -# 1-depth packages -$(eval $(call stdlib_builder,GMP_jll,Artifacts Libdl)) -$(eval $(call stdlib_builder,LLVMLibUnwind_jll,Artifacts Libdl)) -$(eval $(call stdlib_builder,LibUV_jll,Artifacts Libdl)) -$(eval $(call stdlib_builder,LibUnwind_jll,Artifacts Libdl)) -$(eval $(call stdlib_builder,MbedTLS_jll,Artifacts Libdl)) -$(eval $(call stdlib_builder,nghttp2_jll,Artifacts Libdl)) -$(eval $(call stdlib_builder,OpenLibm_jll,Artifacts Libdl)) -$(eval $(call stdlib_builder,PCRE2_jll,Artifacts Libdl)) -$(eval $(call stdlib_builder,Zlib_jll,Artifacts Libdl)) -$(eval $(call stdlib_builder,dSFMT_jll,Artifacts Libdl)) -$(eval $(call stdlib_builder,libLLVM_jll,Artifacts Libdl)) -$(eval $(call stdlib_builder,libblastrampoline_jll,Artifacts Libdl)) -$(eval $(call stdlib_builder,p7zip_jll,Artifacts Libdl)) -$(eval $(call stdlib_builder,OpenBLAS_jll,Artifacts Libdl)) -$(eval $(call stdlib_builder,Markdown,Base64)) -$(eval $(call stdlib_builder,Printf,Unicode)) -$(eval $(call stdlib_builder,Random,SHA)) -$(eval $(call stdlib_builder,Tar,ArgTools,SHA)) -$(eval $(call stdlib_builder,DelimitedFiles,Mmap)) - -# 2-depth packages -$(eval $(call stdlib_builder,LLD_jll,Zlib_jll libLLVM_jll Artifacts Libdl)) -$(eval $(call stdlib_builder,LibSSH2_jll,Artifacts Libdl MbedTLS_jll)) -$(eval $(call stdlib_builder,MPFR_jll,Artifacts Libdl GMP_jll)) -$(eval $(call stdlib_builder,LinearAlgebra,Libdl libblastrampoline_jll OpenBLAS_jll)) -$(eval $(call stdlib_builder,Dates,Printf)) -$(eval $(call stdlib_builder,Distributed,Random Serialization Sockets)) -$(eval $(call stdlib_builder,Future,Random)) -$(eval $(call stdlib_builder,UUIDs,Random SHA)) -$(eval $(call stdlib_builder,InteractiveUtils,Markdown)) - - # 3-depth packages -$(eval $(call stdlib_builder,LibGit2_jll,MbedTLS_jll LibSSH2_jll Artifacts Libdl)) -$(eval $(call stdlib_builder,LibCURL_jll,LibSSH2_jll nghttp2_jll MbedTLS_jll Zlib_jll Artifacts Libdl)) -$(eval $(call stdlib_builder,REPL,InteractiveUtils Markdown Sockets Unicode)) -$(eval $(call stdlib_builder,SharedArrays,Distributed Mmap Random Serialization)) -$(eval $(call stdlib_builder,TOML,Dates)) -$(eval $(call stdlib_builder,Test,Logging Random Serialization InteractiveUtils)) - -# 4-depth packages -$(eval $(call stdlib_builder,LibGit2,LibGit2_jll NetworkOptions Printf SHA Base64)) -$(eval $(call stdlib_builder,LibCURL,LibCURL_jll MozillaCACerts_jll)) - -# 5-depth packages -$(eval $(call stdlib_builder,Downloads,ArgTools FileWatching LibCURL NetworkOptions)) - -# 6-depth packages -$(eval $(call stdlib_builder,Pkg, Artifacts Dates Downloads FileWatching LibGit2 Libdl\ - Logging Markdown Printf REPL Random SHA Serialization\ - TOML Tar UUIDs p7zip_jll)) - -# 7-depth packages -$(eval $(call stdlib_builder,LazyArtifacts,Artifacts Pkg)) - -$(eval $(call stdlib_builder,SparseArrays,Libdl LinearAlgebra Random Serialization)) -$(eval $(call stdlib_builder,Statistics,LinearAlgebra SparseArrays)) -# SuiteSparse_jll +clean: + rm -rf $(JULIA_DEPOT_PATH)/compiled + rm -f $(BUILDDIR)/stdlib/*.image diff --git a/src/APInt-C.cpp b/src/APInt-C.cpp index f06d4362bf958..22b3beef996db 100644 --- a/src/APInt-C.cpp +++ b/src/APInt-C.cpp @@ -7,16 +7,11 @@ #include #include "APInt-C.h" -#include "julia.h" #include "julia_assert.h" #include "julia_internal.h" using namespace llvm; -inline uint64_t RoundUpToAlignment(uint64_t Value, uint64_t Align, uint64_t Skew = 0) { - return alignTo(Value, Align, Skew); -} - const unsigned int integerPartWidth = llvm::APInt::APINT_BITS_PER_WORD; const unsigned int host_char_bit = 8; @@ -25,15 +20,15 @@ const unsigned int host_char_bit = 8; APInt s; \ if ((numbits % integerPartWidth) != 0) { \ /* use LLT_ALIGN to round the memory area up to the nearest integerPart-sized chunk */ \ - unsigned nbytes = RoundUpToAlignment(numbits, integerPartWidth) / host_char_bit; \ + unsigned nbytes = alignTo(numbits, integerPartWidth) / host_char_bit; \ integerPart *data_a64 = (integerPart*)alloca(nbytes); \ /* TODO: this memcpy assumes little-endian, * for big-endian, need to align the copy to the other end */ \ - memcpy(data_a64, p##s, RoundUpToAlignment(numbits, host_char_bit) / host_char_bit); \ - s = APInt(numbits, makeArrayRef(data_a64, nbytes / sizeof(integerPart))); \ + memcpy(data_a64, p##s, alignTo(numbits, host_char_bit) / host_char_bit); \ + s = APInt(numbits, ArrayRef(data_a64, nbytes / sizeof(integerPart))); \ } \ else { \ - s = APInt(numbits, makeArrayRef(p##s, numbits / integerPartWidth)); \ + s = APInt(numbits, ArrayRef(p##s, numbits / integerPartWidth)); \ } /* assign to "integerPart *pr" from "APInt a" */ @@ -47,7 +42,7 @@ const unsigned int host_char_bit = 8; else if (numbits <= 64) \ *(uint64_t*)p##r = a.getZExtValue(); \ else \ - memcpy(p##r, a.getRawData(), RoundUpToAlignment(numbits, host_char_bit) / host_char_bit); \ + memcpy(p##r, a.getRawData(), alignTo(numbits, host_char_bit) / host_char_bit); \ extern "C" JL_DLLEXPORT void LLVMNeg(unsigned numbits, integerPart *pa, integerPart *pr) { @@ -313,17 +308,25 @@ void LLVMByteSwap(unsigned numbits, integerPart *pa, integerPart *pr) { ASSIGN(r, a) } -void LLVMFPtoInt(unsigned numbits, void *pa, unsigned onumbits, integerPart *pr, bool isSigned, bool *isExact) { +extern "C" float julia_half_to_float(uint16_t ival) JL_NOTSAFEPOINT; +extern "C" uint16_t julia_float_to_half(float param) JL_NOTSAFEPOINT; +extern "C" float julia_bfloat_to_float(uint16_t ival) JL_NOTSAFEPOINT; +extern "C" uint16_t julia_float_to_bfloat(float param) JL_NOTSAFEPOINT; + +void LLVMFPtoInt(jl_datatype_t *ty, void *pa, jl_datatype_t *oty, integerPart *pr, bool isSigned, bool *isExact) { double Val; - if (numbits == 16) - Val = julia__gnu_h2f_ieee(*(uint16_t*)pa); - else if (numbits == 32) + if (ty == jl_float16_type) + Val = julia_half_to_float(*(uint16_t*)pa); + else if (ty == jl_bfloat16_type) + Val = julia_bfloat_to_float(*(uint16_t*)pa); + else if (ty == jl_float32_type) Val = *(float*)pa; - else if (numbits == 64) + else if (jl_float64_type) Val = *(double*)pa; else jl_error("FPtoSI: runtime floating point intrinsics are not implemented for bit sizes other than 16, 32 and 64"); - unsigned onumbytes = RoundUpToAlignment(onumbits, host_char_bit) / host_char_bit; + unsigned onumbytes = jl_datatype_size(oty); + unsigned onumbits = onumbytes * host_char_bit; if (onumbits <= 64) { // fast-path, if possible if (isSigned) { int64_t ia = Val; @@ -350,7 +353,7 @@ void LLVMFPtoInt(unsigned numbits, void *pa, unsigned onumbits, integerPart *pr, APFloat a(Val); bool isVeryExact; APFloat::roundingMode rounding_mode = APFloat::rmNearestTiesToEven; - unsigned nbytes = RoundUpToAlignment(onumbits, integerPartWidth) / host_char_bit; + unsigned nbytes = alignTo(onumbits, integerPartWidth) / host_char_bit; integerPart *parts = (integerPart*)alloca(nbytes); APFloat::opStatus status = a.convertToInteger(MutableArrayRef(parts, nbytes), onumbits, isSigned, rounding_mode, &isVeryExact); memcpy(pr, parts, onumbytes); @@ -360,69 +363,78 @@ void LLVMFPtoInt(unsigned numbits, void *pa, unsigned onumbits, integerPart *pr, } extern "C" JL_DLLEXPORT -void LLVMFPtoSI(unsigned numbits, integerPart *pa, unsigned onumbits, integerPart *pr) { - LLVMFPtoInt(numbits, pa, onumbits, pr, true, NULL); +void LLVMFPtoSI(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *oty, integerPart *pr) { + LLVMFPtoInt(ty, pa, oty, pr, true, NULL); } extern "C" JL_DLLEXPORT -void LLVMFPtoUI(unsigned numbits, integerPart *pa, unsigned onumbits, integerPart *pr) { - LLVMFPtoInt(numbits, pa, onumbits, pr, false, NULL); +void LLVMFPtoUI(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *oty, integerPart *pr) { + LLVMFPtoInt(ty, pa, oty, pr, false, NULL); } extern "C" JL_DLLEXPORT -int LLVMFPtoSI_exact(unsigned numbits, integerPart *pa, unsigned onumbits, integerPart *pr) { +int LLVMFPtoSI_exact(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *oty, integerPart *pr) { bool isExact; - LLVMFPtoInt(numbits, pa, onumbits, pr, true, &isExact); + LLVMFPtoInt(ty, pa, oty, pr, true, &isExact); return isExact; } extern "C" JL_DLLEXPORT -int LLVMFPtoUI_exact(unsigned numbits, integerPart *pa, unsigned onumbits, integerPart *pr) { +int LLVMFPtoUI_exact(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *oty, integerPart *pr) { bool isExact; - LLVMFPtoInt(numbits, pa, onumbits, pr, false, &isExact); + LLVMFPtoInt(ty, pa, oty, pr, false, &isExact); return isExact; } extern "C" JL_DLLEXPORT -void LLVMSItoFP(unsigned numbits, integerPart *pa, unsigned onumbits, integerPart *pr) { +void LLVMSItoFP(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *oty, integerPart *pr) { double val; { // end scope before jl_error call + unsigned numbytes = jl_datatype_size(ty); + unsigned numbits = numbytes * host_char_bit; CREATE(a) val = a.roundToDouble(true); } - if (onumbits == 16) - *(uint16_t*)pr = julia__gnu_f2h_ieee(val); - else if (onumbits == 32) + if (oty == jl_float16_type) + *(uint16_t*)pr = julia_float_to_half(val); + else if (oty == jl_bfloat16_type) + *(uint16_t*)pr = julia_float_to_bfloat(val); + else if (oty == jl_float32_type) *(float*)pr = val; - else if (onumbits == 64) + else if (oty == jl_float64_type) *(double*)pr = val; else jl_error("SItoFP: runtime floating point intrinsics are not implemented for bit sizes other than 16, 32 and 64"); } extern "C" JL_DLLEXPORT -void LLVMUItoFP(unsigned numbits, integerPart *pa, unsigned onumbits, integerPart *pr) { +void LLVMUItoFP(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *oty, integerPart *pr) { double val; { // end scope before jl_error call + unsigned numbytes = jl_datatype_size(ty); + unsigned numbits = numbytes * host_char_bit; CREATE(a) val = a.roundToDouble(false); } - if (onumbits == 16) - *(uint16_t*)pr = julia__gnu_f2h_ieee(val); - else if (onumbits == 32) + if (oty == jl_float16_type) + *(uint16_t*)pr = julia_float_to_half(val); + else if (oty == jl_bfloat16_type) + *(uint16_t*)pr = julia_float_to_bfloat(val); + else if (oty == jl_float32_type) *(float*)pr = val; - else if (onumbits == 64) + else if (oty == jl_float64_type) *(double*)pr = val; else jl_error("UItoFP: runtime floating point intrinsics are not implemented for bit sizes other than 32 and 64"); } extern "C" JL_DLLEXPORT -void LLVMSExt(unsigned inumbits, integerPart *pa, unsigned onumbits, integerPart *pr) { - if (!(onumbits > inumbits)) +void LLVMSExt(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *otys, integerPart *pr) { + unsigned inumbytes = jl_datatype_size(ty); + unsigned onumbytes = jl_datatype_size(otys); + if (!(onumbytes > inumbytes)) jl_error("SExt: output bitsize must be > input bitsize"); - unsigned inumbytes = RoundUpToAlignment(inumbits, host_char_bit) / host_char_bit; - unsigned onumbytes = RoundUpToAlignment(onumbits, host_char_bit) / host_char_bit; + unsigned inumbits = inumbytes * host_char_bit; int bits = (0 - inumbits) % host_char_bit; int signbit = (inumbits - 1) % host_char_bit; int sign = ((unsigned char*)pa)[inumbytes - 1] & (1 << signbit) ? -1 : 0; @@ -437,11 +449,12 @@ void LLVMSExt(unsigned inumbits, integerPart *pa, unsigned onumbits, integerPart } extern "C" JL_DLLEXPORT -void LLVMZExt(unsigned inumbits, integerPart *pa, unsigned onumbits, integerPart *pr) { - if (!(onumbits > inumbits)) +void LLVMZExt(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *otys, integerPart *pr) { + unsigned inumbytes = jl_datatype_size(ty); + unsigned onumbytes = jl_datatype_size(otys); + if (!(onumbytes > inumbytes)) jl_error("ZExt: output bitsize must be > input bitsize"); - unsigned inumbytes = RoundUpToAlignment(inumbits, host_char_bit) / host_char_bit; - unsigned onumbytes = RoundUpToAlignment(onumbits, host_char_bit) / host_char_bit; + unsigned inumbits = inumbytes * host_char_bit; int bits = (0 - inumbits) % host_char_bit; // copy over the input bytes memcpy(pr, pa, inumbytes); @@ -454,10 +467,11 @@ void LLVMZExt(unsigned inumbits, integerPart *pa, unsigned onumbits, integerPart } extern "C" JL_DLLEXPORT -void LLVMTrunc(unsigned inumbits, integerPart *pa, unsigned onumbits, integerPart *pr) { - if (!(onumbits < inumbits)) +void LLVMTrunc(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *otys, integerPart *pr) { + unsigned inumbytes = jl_datatype_size(ty); + unsigned onumbytes = jl_datatype_size(otys); + if (!(onumbytes < inumbytes)) jl_error("Trunc: output bitsize must be < input bitsize"); - unsigned onumbytes = RoundUpToAlignment(onumbits, host_char_bit) / host_char_bit; memcpy(pr, pa, onumbytes); } diff --git a/src/APInt-C.h b/src/APInt-C.h index e71d49e82e99a..816d40ccc6529 100644 --- a/src/APInt-C.h +++ b/src/APInt-C.h @@ -3,10 +3,12 @@ #ifndef JL_APINT_C_H #define JL_APINT_C_H +#include "julia.h" +#include "dtypes.h" + #ifdef __cplusplus extern "C" { #endif -#include "dtypes.h" #ifdef LLVM_VERSION_MAJOR using integerPart = llvm::APInt::WordType; @@ -57,16 +59,16 @@ JL_DLLEXPORT unsigned LLVMCountTrailingZeros(unsigned numbits, integerPart *pa); JL_DLLEXPORT unsigned LLVMCountLeadingOnes(unsigned numbits, integerPart *pa); JL_DLLEXPORT unsigned LLVMCountLeadingZeros(unsigned numbits, integerPart *pa); -JL_DLLEXPORT void LLVMFPtoSI(unsigned numbits, integerPart *pa, unsigned onumbits, integerPart *pr); -JL_DLLEXPORT void LLVMFPtoUI(unsigned numbits, integerPart *pa, unsigned onumbits, integerPart *pr); -JL_DLLEXPORT void LLVMSItoFP(unsigned numbits, integerPart *pa, unsigned onumbits, integerPart *pr); -JL_DLLEXPORT void LLVMUItoFP(unsigned numbits, integerPart *pa, unsigned onumbits, integerPart *pr); -JL_DLLEXPORT void LLVMSExt(unsigned numbits, integerPart *pa, unsigned onumbits, integerPart *pr); -JL_DLLEXPORT void LLVMZExt(unsigned numbits, integerPart *pa, unsigned onumbits, integerPart *pr); -JL_DLLEXPORT void LLVMTrunc(unsigned numbits, integerPart *pa, unsigned onumbits, integerPart *pr); +JL_DLLEXPORT void LLVMFPtoSI(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *oty, integerPart *pr); +JL_DLLEXPORT void LLVMFPtoUI(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *oty, integerPart *pr); +JL_DLLEXPORT void LLVMSItoFP(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *oty, integerPart *pr); +JL_DLLEXPORT void LLVMUItoFP(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *oty, integerPart *pr); +JL_DLLEXPORT void LLVMSExt(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *oty, integerPart *pr); +JL_DLLEXPORT void LLVMZExt(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *oty, integerPart *pr); +JL_DLLEXPORT void LLVMTrunc(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *oty, integerPart *pr); -JL_DLLEXPORT int LLVMFPtoSI_exact(unsigned numbits, integerPart *pa, unsigned onumbits, integerPart *pr); -JL_DLLEXPORT int LLVMFPtoUI_exact(unsigned numbits, integerPart *pa, unsigned onumbits, integerPart *pr); +JL_DLLEXPORT int LLVMFPtoSI_exact(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *oty, integerPart *pr); +JL_DLLEXPORT int LLVMFPtoUI_exact(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *oty, integerPart *pr); JL_DLLEXPORT void jl_LLVMSMod(unsigned numbits, integerPart *pa, integerPart *pb, integerPart *pr); JL_DLLEXPORT void jl_LLVMFlipSign(unsigned numbits, integerPart *pa, integerPart *pb, integerPart *pr); diff --git a/src/Makefile b/src/Makefile index 906c4357a7d6e..323aaea61c47d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -26,7 +26,7 @@ endif JCFLAGS += -Wold-style-definition -Wstrict-prototypes -Wc++-compat ifeq ($(USECLANG),1) -FLAGS += -Wno-return-type-c-linkage +FLAGS += -Wno-return-type-c-linkage -Wno-atomic-alignment endif FLAGS += -DJL_BUILD_ARCH='"$(ARCH)"' @@ -42,9 +42,9 @@ endif SRCS := \ jltypes gf typemap smallintset ast builtins module interpreter symbol \ - dlload sys init task array staticdata toplevel jl_uv datatype \ + dlload sys init task array genericmemory staticdata toplevel jl_uv datatype \ simplevector runtime_intrinsics precompile jloptions mtarraylist \ - threading partr stackwalk gc gc-debug gc-pages gc-stacks gc-alloc-profiler method \ + threading scheduler stackwalk gc gc-debug gc-pages gc-stacks gc-alloc-profiler gc-page-profiler method \ jlapi signal-handling safepoint timing subtype rtutils gc-heap-snapshot \ crc32c APInt-C processor ircode opaque_closure codegen-stubs coverage runtime_ccall @@ -77,7 +77,10 @@ else # JULIACODEGEN != LLVM endif -RT_LLVM_LIBS := support +RT_LLVM_LIBS := support # for APMath and some other useful ADT +ifneq ($(LLVM_VER_MAJ),15) +RT_LLVM_LIBS += targetparser # for getHostCPUName on LLVM 16+ +endif ifeq ($(OS),WINNT) SRCS += win32_ucontext @@ -110,6 +113,10 @@ PUBLIC_HEADER_TARGETS := $(addprefix $(build_includedir)/julia/,$(notdir $(PUBLI LLVM_LDFLAGS := $(shell $(LLVM_CONFIG_HOST) --ldflags) LLVM_CXXFLAGS := $(shell $(LLVM_CONFIG_HOST) --cxxflags) +ifeq ($(OS)_$(BINARY),WINNT_32) +LLVM_CXXFLAGS += -I$(SRCDIR)/support/win32-clang-ABI-bug +endif + # llvm-config --cxxflags does not return -DNDEBUG ifeq ($(shell $(LLVM_CONFIG_HOST) --assertion-mode),OFF) LLVM_CXXFLAGS += -DNDEBUG @@ -223,7 +230,7 @@ $(BUILDDIR)/jl_internal_funcs.inc: $(SRCDIR)/jl_exported_funcs.inc # to have a `ijl_` prefix instead of `jl_`, to denote that they are coming from `libjulia-internal`. This avoids # potential confusion with debugging tools, when inspecting a process that has both `libjulia` and `libjulia-internal` # loaded at the same time. - grep 'XX(.\+)' $< | sed -E 's/.*XX\((.+)\).*/#define \1 i\1/g' >$@ + grep 'XX(..*)' $< | sed -E 's/.*XX\((.+)\).*/#define \1 i\1/g' >$@ # source file rules $(BUILDDIR)/%.o: $(SRCDIR)/%.c $(HEADERS) | $(BUILDDIR) @@ -302,7 +309,7 @@ $(BUILDDIR)/julia_flisp.boot: $(addprefix $(SRCDIR)/,jlfrontend.scm flisp/aliase $(BUILDDIR)/codegen-stubs.o $(BUILDDIR)/codegen-stubs.dbg.obj: $(SRCDIR)/intrinsics.h $(BUILDDIR)/aotcompile.o $(BUILDDIR)/aotcompile.dbg.obj: $(SRCDIR)/jitlayers.h $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/processor.h $(BUILDDIR)/ast.o $(BUILDDIR)/ast.dbg.obj: $(BUILDDIR)/julia_flisp.boot.inc $(SRCDIR)/flisp/*.h -$(BUILDDIR)/builtins.o $(BUILDDIR)/builtins.dbg.obj: $(SRCDIR)/iddict.c $(SRCDIR)/builtin_proto.h +$(BUILDDIR)/builtins.o $(BUILDDIR)/builtins.dbg.obj: $(SRCDIR)/iddict.c $(SRCDIR)/idset.c $(SRCDIR)/builtin_proto.h $(BUILDDIR)/codegen.o $(BUILDDIR)/codegen.dbg.obj: $(addprefix $(SRCDIR)/,\ intrinsics.cpp jitlayers.h intrinsics.h llvm-codegen-shared.h cgutils.cpp ccall.cpp abi_*.cpp processor.h builtin_proto.h) $(BUILDDIR)/datatype.o $(BUILDDIR)/datatype.dbg.obj: $(SRCDIR)/support/htable.h $(SRCDIR)/support/htable.inc @@ -336,7 +343,7 @@ $(BUILDDIR)/processor.o $(BUILDDIR)/processor.dbg.obj: $(addprefix $(SRCDIR)/,pr $(BUILDDIR)/signal-handling.o $(BUILDDIR)/signal-handling.dbg.obj: $(addprefix $(SRCDIR)/,signals-*.c) $(BUILDDIR)/staticdata.o $(BUILDDIR)/staticdata.dbg.obj: $(SRCDIR)/staticdata_utils.c $(SRCDIR)/precompile_utils.c $(SRCDIR)/processor.h $(SRCDIR)/builtin_proto.h $(BUILDDIR)/toplevel.o $(BUILDDIR)/toplevel.dbg.obj: $(SRCDIR)/builtin_proto.h -$(BUILDDIR)/ircode.o $(BUILDDIR)/ircode.dbg.obj: $(SRCDIR)/serialize.h +$(BUILDDIR)/ircode.o $(BUILDDIR)/ircode.dbg.obj: $(SRCDIR)/serialize.h $(SRCDIR)/common_symbols1.inc $(SRCDIR)/common_symbols2.inc $(BUILDDIR)/pipeline.o $(BUILDDIR)/pipeline.dbg.obj: $(SRCDIR)/passes.h $(SRCDIR)/jitlayers.h $(addprefix $(BUILDDIR)/,threading.o threading.dbg.obj gc.o gc.dbg.obj init.c init.dbg.obj task.o task.dbg.obj): $(addprefix $(SRCDIR)/,threading.h) @@ -467,6 +474,8 @@ $(build_shlibdir)/lib%Plugin.$(SHLIB_EXT): $(SRCDIR)/clangsa/%.cpp $(LLVM_CONFIG ANALYSIS_DEPS := llvm clang llvm-tools libuv utf8proc ifeq ($(OS),Darwin) ANALYSIS_DEPS += llvmunwind +else ifeq ($(OS),OpenBSD) +ANALYSIS_DEPS += llvmunwind else ifneq ($(OS),WINNT) ANALYSIS_DEPS += unwind endif @@ -489,8 +498,6 @@ SA_EXCEPTIONS-jloptions.c := -Xanalyzer -analyzer-config -Xana SA_EXCEPTIONS-subtype.c := -Xanalyzer -analyzer-config -Xanalyzer silence-checkers="core.uninitialized.Assign;core.UndefinedBinaryOperatorResult" SA_EXCEPTIONS-codegen.c := -Xanalyzer -analyzer-config -Xanalyzer silence-checkers="core" # these need to be annotated (and possibly fixed) -SKIP_IMPLICIT_ATOMICS := staticdata.c - # these need to be annotated (and possibly fixed) SKIP_GC_CHECK := codegen.cpp rtutils.c # make sure LLVM's invariant information is not discarded with -DNDEBUG @@ -536,7 +543,7 @@ $(addprefix clang-sagc-,$(CODEGEN_SRCS)): DEBUGFLAGS_CLANG += -DJL_LIBRARY_EXPOR $(addprefix clang-tidy-,$(CODEGEN_SRCS)): DEBUGFLAGS_CLANG += -DJL_LIBRARY_EXPORTS_CODEGEN # Add C files as a target of `analyzesrc` and `analyzegc` and `tidysrc` -tidysrc: $(addprefix clang-tidy-,$(filter-out $(basename $(SKIP_IMPLICIT_ATOMICS)),$(CODEGEN_SRCS) $(SRCS))) +tidysrc: $(addprefix clang-tidy-,$(CODEGEN_SRCS) $(SRCS)) analyzesrc: $(addprefix clang-sa-,$(CODEGEN_SRCS) $(SRCS)) analyzegc: $(addprefix clang-sagc-,$(filter-out $(basename $(SKIP_GC_CHECK)),$(CODEGEN_SRCS) $(SRCS))) analyze: analyzesrc analyzegc tidysrc diff --git a/src/abi_aarch64.cpp b/src/abi_aarch64.cpp index 514c3c5a81a6d..7c31b6606139a 100644 --- a/src/abi_aarch64.cpp +++ b/src/abi_aarch64.cpp @@ -88,7 +88,7 @@ Type *get_llvm_fptype(jl_datatype_t *dt, LLVMContext &ctx) const Type *get_llvm_fp_or_vectype(jl_datatype_t *dt, LLVMContext &ctx) const { // Assume jl_is_datatype(dt) && !jl_is_abstracttype(dt) - if (dt->name->mutabl || dt->layout->npointers || dt->layout->haspadding) + if (dt->name->mutabl || dt->layout->npointers || dt->layout->flags.haspadding) return nullptr; return dt->layout->nfields ? get_llvm_vectype(dt, ctx) : get_llvm_fptype(dt, ctx); } @@ -184,7 +184,7 @@ Type *isHFAorHVA(jl_datatype_t *dt, size_t &nele, LLVMContext &ctx) const // uniquely addressable members. // Maximum HFA and HVA size is 64 bytes (4 x fp128 or 16bytes vector) size_t dsz = jl_datatype_size(dt); - if (dsz > 64 || !dt->layout || dt->layout->npointers || dt->layout->haspadding) + if (dsz > 64 || !dt->layout || dt->layout->npointers || dt->layout->flags.haspadding) return NULL; nele = 0; ElementType eltype; diff --git a/src/abi_arm.cpp b/src/abi_arm.cpp index 441aa95b1fdf6..68f980d7b40da 100644 --- a/src/abi_arm.cpp +++ b/src/abi_arm.cpp @@ -82,7 +82,7 @@ size_t isLegalHA(jl_datatype_t *dt, Type *&base, LLVMContext &ctx) const if (jl_is_structtype(dt)) { // Fast path checks before descending the type hierarchy // (4 x 128b vector == 64B max size) - if (jl_datatype_size(dt) > 64 || dt->layout->npointers || dt->layout->haspadding) + if (jl_datatype_size(dt) > 64 || dt->layout->npointers || dt->layout->flags.haspadding) return 0; base = NULL; diff --git a/src/abi_ppc64le.cpp b/src/abi_ppc64le.cpp index 2e18acdbd4f4b..1f10817cfeeee 100644 --- a/src/abi_ppc64le.cpp +++ b/src/abi_ppc64le.cpp @@ -44,7 +44,7 @@ struct ABI_PPC64leLayout : AbiLayout { // count the homogeneous floating aggregate size (saturating at max count of 8) unsigned isHFA(jl_datatype_t *ty, jl_datatype_t **ty0, bool *hva) const { - if (jl_datatype_size(ty) > 128 || ty->layout->npointers || ty->layout->haspadding) + if (jl_datatype_size(ty) > 128 || ty->layout->npointers || ty->layout->flags.haspadding) return 9; size_t i, l = ty->layout->nfields; @@ -118,7 +118,12 @@ bool needPassByRef(jl_datatype_t *dt, AttrBuilder &ab, LLVMContext &ctx, Type *T Type *preferred_llvm_type(jl_datatype_t *dt, bool isret, LLVMContext &ctx) const override { // Arguments are either scalar or passed by value - size_t size = jl_datatype_size(dt); + + // LLVM passes Float16 in floating-point registers, but this doesn't match the ABI. + // No C compiler seems to support _Float16 yet, so in the meantime, pass as i16 + if (dt == jl_float16_type || dt == jl_bfloat16_type) + return Type::getInt16Ty(ctx); + // don't need to change bitstypes if (!jl_datatype_nfields(dt)) return NULL; @@ -143,6 +148,7 @@ Type *preferred_llvm_type(jl_datatype_t *dt, bool isret, LLVMContext &ctx) const } // rewrite integer-sized (non-HFA) struct to an array // the bitsize of the integer gives the desired alignment + size_t size = jl_datatype_size(dt); if (size > 8) { if (jl_datatype_align(dt) <= 8) { Type *T_int64 = Type::getInt64Ty(ctx); diff --git a/src/abi_win32.cpp b/src/abi_win32.cpp index 078d9b6df4e44..ccfc6a16ebee3 100644 --- a/src/abi_win32.cpp +++ b/src/abi_win32.cpp @@ -52,7 +52,7 @@ bool use_sret(jl_datatype_t *dt, LLVMContext &ctx) override bool needPassByRef(jl_datatype_t *dt, AttrBuilder &ab, LLVMContext &ctx, Type *Ty) override { // Use pass by reference for all structs - if (dt->layout->nfields > 0) { + if (dt->layout->nfields > 0 || dt->layout->npointers) { ab.addByValAttr(Ty); return true; } @@ -63,7 +63,7 @@ Type *preferred_llvm_type(jl_datatype_t *dt, bool isret, LLVMContext &ctx) const { // Arguments are either scalar or passed by value // rewrite integer sized (non-sret) struct to the corresponding integer - if (!dt->layout->nfields) + if (!dt->layout->nfields && !dt->layout->npointers) return NULL; return Type::getIntNTy(ctx, jl_datatype_nbits(dt)); } diff --git a/src/abi_x86_64.cpp b/src/abi_x86_64.cpp index 7800c44b4d3ae..6a853421dbccd 100644 --- a/src/abi_x86_64.cpp +++ b/src/abi_x86_64.cpp @@ -118,7 +118,8 @@ struct Classification { void classifyType(Classification& accum, jl_datatype_t *dt, uint64_t offset) const { // Floating point types - if (dt == jl_float64_type || dt == jl_float32_type || dt == jl_bfloat16_type) { + if (dt == jl_float64_type || dt == jl_float32_type || dt == jl_float16_type || + dt == jl_bfloat16_type) { accum.addField(offset, Sse); } // Misc types @@ -147,7 +148,7 @@ void classifyType(Classification& accum, jl_datatype_t *dt, uint64_t offset) con accum.addField(offset, Sse); } // Other struct types - else if (jl_datatype_size(dt) <= 16 && dt->layout) { + else if (jl_datatype_size(dt) <= 16 && dt->layout && !jl_is_layout_opaque(dt->layout)) { size_t i; for (i = 0; i < jl_datatype_nfields(dt); ++i) { jl_value_t *ty = jl_field_type(dt, i); diff --git a/src/aotcompile.cpp b/src/aotcompile.cpp index ae9ebca7a8b7a..edef391af6708 100644 --- a/src/aotcompile.cpp +++ b/src/aotcompile.cpp @@ -4,6 +4,7 @@ #include "platform.h" // target support +#include "llvm/Support/CodeGen.h" #include #include #include @@ -62,7 +63,6 @@ using namespace llvm; #include "jitlayers.h" #include "serialize.h" #include "julia_assert.h" -#include "llvm-codegen-shared.h" #include "processor.h" #define DEBUG_TYPE "julia_aotcompile" @@ -145,11 +145,76 @@ GlobalValue* jl_get_llvm_function_impl(void *native_code, uint32_t idx) } -static void emit_offset_table(Module &mod, ArrayRef vars, - StringRef name, Type *T_psize) + +template +static inline SmallVector consume_gv(Module &M, const char *name, bool allow_bad_fvars) +{ + // Get information about sysimg export functions from the two global variables. + // Strip them from the Module so that it's easier to handle the uses. + GlobalVariable *gv = M.getGlobalVariable(name); + assert(gv && gv->hasInitializer()); + ArrayType *Ty = cast(gv->getInitializer()->getType()); + unsigned nele = Ty->getArrayNumElements(); + SmallVector res(nele); + ConstantArray *ary = nullptr; + if (gv->getInitializer()->isNullValue()) { + for (unsigned i = 0; i < nele; ++i) + res[i] = cast(Constant::getNullValue(Ty->getArrayElementType())); + } + else { + ary = cast(gv->getInitializer()); + unsigned i = 0; + while (i < nele) { + llvm::Value *val = ary->getOperand(i)->stripPointerCasts(); + if (allow_bad_fvars && (!isa(val) || (isa(val) && cast(val)->isDeclaration()))) { + // Shouldn't happen in regular use, but can happen in bugpoint. + nele--; + continue; + } + res[i++] = cast(val); + } + res.resize(nele); + } + assert(gv->use_empty()); + gv->eraseFromParent(); + if (ary && ary->use_empty()) + ary->destroyConstant(); + return res; +} + +static Constant *get_ptrdiff32(Type *T_size, Constant *ptr, Constant *base) +{ + if (ptr->getType()->isPointerTy()) + ptr = ConstantExpr::getPtrToInt(ptr, T_size); + auto ptrdiff = ConstantExpr::getSub(ptr, base); + return T_size->getPrimitiveSizeInBits() > 32 ? ConstantExpr::getTrunc(ptrdiff, Type::getInt32Ty(ptr->getContext())) : ptrdiff; +} + +static Constant *emit_offset_table(Module &M, Type *T_size, ArrayRef vars, + StringRef name, StringRef suffix) +{ + auto T_int32 = Type::getInt32Ty(M.getContext()); + uint32_t nvars = vars.size(); + ArrayType *vars_type = ArrayType::get(T_int32, nvars + 1); + auto gv = new GlobalVariable(M, vars_type, true, + GlobalVariable::ExternalLinkage, + nullptr, + name + "_offsets" + suffix); + auto vbase = ConstantExpr::getPtrToInt(gv, T_size); + SmallVector offsets(nvars + 1); + offsets[0] = ConstantInt::get(T_int32, nvars); + for (uint32_t i = 0; i < nvars; i++) + offsets[i + 1] = get_ptrdiff32(T_size, vars[i], vbase); + gv->setInitializer(ConstantArray::get(vars_type, offsets)); + gv->setVisibility(GlobalValue::HiddenVisibility); + gv->setDSOLocal(true); + return vbase; +} + +static void emit_table(Module &mod, ArrayRef vars, + StringRef name, Type *T_psize) { // Emit a global variable with all the variable addresses. - // The cloning pass will convert them into offsets. size_t nvars = vars.size(); SmallVector addrs(nvars); for (size_t i = 0; i < nvars; i++) { @@ -224,7 +289,7 @@ static void makeSafeName(GlobalObject &G) G.setName(StringRef(SafeName.data(), SafeName.size())); } -static void jl_ci_cache_lookup(const jl_cgparams_t &cgparams, jl_method_instance_t *mi, size_t world, jl_code_instance_t **ci_out, jl_code_info_t **src_out) +jl_code_instance_t *jl_ci_cache_lookup(const jl_cgparams_t &cgparams, jl_method_instance_t *mi, size_t world) { ++CICacheLookups; jl_value_t *ci = cgparams.lookup(mi, world, world); @@ -232,29 +297,22 @@ static void jl_ci_cache_lookup(const jl_cgparams_t &cgparams, jl_method_instance jl_code_instance_t *codeinst = NULL; if (ci != jl_nothing) { codeinst = (jl_code_instance_t*)ci; - *src_out = (jl_code_info_t*)jl_atomic_load_relaxed(&codeinst->inferred); - jl_method_t *def = codeinst->def->def.method; - if ((jl_value_t*)*src_out == jl_nothing) - *src_out = NULL; - if (*src_out && jl_is_method(def)) - *src_out = jl_uncompress_ir(def, codeinst, (jl_value_t*)*src_out); - } - if (*src_out == NULL || !jl_is_code_info(*src_out)) { + } + else { if (cgparams.lookup != jl_rettype_inferred_addr) { jl_error("Refusing to automatically run type inference with custom cache lookup."); } else { - *src_out = jl_type_infer(mi, world, 0); - if (*src_out) { - codeinst = jl_get_method_inferred(mi, (*src_out)->rettype, (*src_out)->min_world, (*src_out)->max_world); - if ((*src_out)->inferred) { - jl_value_t *null = nullptr; - jl_atomic_cmpswap_relaxed(&codeinst->inferred, &null, jl_nothing); - } - } + codeinst = jl_type_infer(mi, world, 0, SOURCE_MODE_ABI); + /* Even if this codeinst is ordinarily not cacheable, we need to force + * it into the cache here, since it was explicitly requested and is + * otherwise not reachable from anywhere in the system image. + */ + if (!jl_mi_cache_has_ci(mi, codeinst)) + jl_mi_cache_insert(mi, codeinst); } } - *ci_out = codeinst; + return codeinst; } // takes the running content that has collected in the shadow module and dump it to disk @@ -269,7 +327,7 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm { JL_TIMING(NATIVE_AOT, NATIVE_Create); ++CreateNativeCalls; - CreateNativeMax.updateMax(jl_array_len(methods)); + CreateNativeMax.updateMax(jl_array_nrows(methods)); if (cgparams == NULL) cgparams = &jl_default_cgparams; jl_native_code_desc_t *data = new jl_native_code_desc_t; @@ -310,14 +368,14 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm size_t compile_for[] = { jl_typeinf_world, _world }; for (int worlds = 0; worlds < 2; worlds++) { JL_TIMING(NATIVE_AOT, NATIVE_Codegen); - params.world = compile_for[worlds]; - if (!params.world) + size_t this_world = compile_for[worlds]; + if (!this_world) continue; // Don't emit methods for the typeinf_world with extern policy - if (policy != CompilationPolicy::Default && params.world == jl_typeinf_world) + if (policy != CompilationPolicy::Default && this_world == jl_typeinf_world) continue; size_t i, l; - for (i = 0, l = jl_array_len(methods); i < l; i++) { + for (i = 0, l = jl_array_nrows(methods); i < l; i++) { // each item in this list is either a MethodInstance indicating something // to compile, or an svec(rettype, sig) describing a C-callable alias to create. jl_value_t *item = jl_array_ptr_ref(methods, i); @@ -331,17 +389,16 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm // if this method is generally visible to the current compilation world, // and this is either the primary world, or not applicable in the primary world // then we want to compile and emit this - if (mi->def.method->primary_world <= params.world && params.world <= mi->def.method->deleted_world) { + if (jl_atomic_load_relaxed(&mi->def.method->primary_world) <= this_world && this_world <= jl_atomic_load_relaxed(&mi->def.method->deleted_world)) { // find and prepare the source code to compile - jl_code_instance_t *codeinst = NULL; - jl_ci_cache_lookup(*cgparams, mi, params.world, &codeinst, &src); - if (src && !params.compiled_functions.count(codeinst)) { + jl_code_instance_t *codeinst = jl_ci_cache_lookup(*cgparams, mi, this_world); + if (codeinst && !params.compiled_functions.count(codeinst)) { // now add it to our compilation results JL_GC_PROMISE_ROOTED(codeinst->rettype); orc::ThreadSafeModule result_m = jl_create_ts_module(name_from_method_instance(codeinst->def), params.tsctx, clone.getModuleUnlocked()->getDataLayout(), Triple(clone.getModuleUnlocked()->getTargetTriple())); - jl_llvm_functions_t decls = jl_emit_code(result_m, mi, src, codeinst->rettype, params); + jl_llvm_functions_t decls = jl_emit_codeinst(result_m, codeinst, NULL, params); if (result_m) params.compiled_functions[codeinst] = {std::move(result_m), std::move(decls)}; } @@ -530,14 +587,13 @@ static GlobalVariable *emit_shard_table(Module &M, Type *T_size, Type *T_psize, return gv; }; auto table = tables.data() + i * sizeof(jl_image_shard_t) / sizeof(void *); - table[offsetof(jl_image_shard_t, fvar_base) / sizeof(void*)] = create_gv("jl_fvar_base", false); - table[offsetof(jl_image_shard_t, fvar_offsets) / sizeof(void*)] = create_gv("jl_fvar_offsets", true); + table[offsetof(jl_image_shard_t, fvar_count) / sizeof(void*)] = create_gv("jl_fvar_count", true); + table[offsetof(jl_image_shard_t, fvar_ptrs) / sizeof(void*)] = create_gv("jl_fvar_ptrs", true); table[offsetof(jl_image_shard_t, fvar_idxs) / sizeof(void*)] = create_gv("jl_fvar_idxs", true); - table[offsetof(jl_image_shard_t, gvar_base) / sizeof(void*)] = create_gv("jl_gvar_base", false); table[offsetof(jl_image_shard_t, gvar_offsets) / sizeof(void*)] = create_gv("jl_gvar_offsets", true); table[offsetof(jl_image_shard_t, gvar_idxs) / sizeof(void*)] = create_gv("jl_gvar_idxs", true); table[offsetof(jl_image_shard_t, clone_slots) / sizeof(void*)] = create_gv("jl_clone_slots", true); - table[offsetof(jl_image_shard_t, clone_offsets) / sizeof(void*)] = create_gv("jl_clone_offsets", true); + table[offsetof(jl_image_shard_t, clone_ptrs) / sizeof(void*)] = create_gv("jl_clone_ptrs", true); table[offsetof(jl_image_shard_t, clone_idxs) / sizeof(void*)] = create_gv("jl_clone_idxs", true); } auto tables_arr = ConstantArray::get(ArrayType::get(T_psize, tables.size()), tables); @@ -986,8 +1042,6 @@ struct ShardTimers { } }; -void emitFloat16Wrappers(Module &M, bool external); - struct AOTOutputs { SmallVector unopt, opt, obj, asm_; }; @@ -1006,7 +1060,7 @@ static AOTOutputs add_output_impl(Module &M, TargetMachine &SourceTM, ShardTimer SourceTM.getRelocationModel(), SourceTM.getCodeModel(), SourceTM.getOptLevel())); - + fixupTM(*TM); if (unopt) { timers.unopt.startTimer(); raw_svector_ostream OS(out.unopt); @@ -1034,6 +1088,7 @@ static AOTOutputs add_output_impl(Module &M, TargetMachine &SourceTM, ShardTimer SourceTM.getRelocationModel(), SourceTM.getCodeModel(), SourceTM.getOptLevel())); + fixupTM(*PMTM); NewPM optimizer{std::move(PMTM), getOptLevel(jl_options.opt_level), OptimizationOptions::defaults(true, true)}; optimizer.run(M); assert(!verifyLLVMIR(M)); @@ -1047,11 +1102,25 @@ static AOTOutputs add_output_impl(Module &M, TargetMachine &SourceTM, ShardTimer // no need to inject aliases if we have no functions if (inject_aliases) { -#if JULIA_FLOAT16_ABI == 1 // We would like to emit an alias or an weakref alias to redirect these symbols // but LLVM doesn't let us emit a GlobalAlias to a declaration... // So for now we inject a definition of these functions that calls our runtime // functions. We do so after optimization to avoid cloning these functions. + // Float16 conversion routines +#if defined(_CPU_X86_64_) && defined(_OS_DARWIN_) && JL_LLVM_VERSION >= 160000 + // LLVM 16 reverted to soft-float ABI for passing half on x86_64 Darwin + // https://github.com/llvm/llvm-project/commit/2bcf51c7f82ca7752d1bba390a2e0cb5fdd05ca9 + injectCRTAlias(M, "__gnu_h2f_ieee", "julia_half_to_float", + FunctionType::get(Type::getFloatTy(M.getContext()), { Type::getInt16Ty(M.getContext()) }, false)); + injectCRTAlias(M, "__extendhfsf2", "julia_half_to_float", + FunctionType::get(Type::getFloatTy(M.getContext()), { Type::getInt16Ty(M.getContext()) }, false)); + injectCRTAlias(M, "__gnu_f2h_ieee", "julia_float_to_half", + FunctionType::get(Type::getInt16Ty(M.getContext()), { Type::getFloatTy(M.getContext()) }, false)); + injectCRTAlias(M, "__truncsfhf2", "julia_float_to_half", + FunctionType::get(Type::getInt16Ty(M.getContext()), { Type::getFloatTy(M.getContext()) }, false)); + injectCRTAlias(M, "__truncdfhf2", "julia_double_to_half", + FunctionType::get(Type::getInt16Ty(M.getContext()), { Type::getDoubleTy(M.getContext()) }, false)); +#else injectCRTAlias(M, "__gnu_h2f_ieee", "julia__gnu_h2f_ieee", FunctionType::get(Type::getFloatTy(M.getContext()), { Type::getHalfTy(M.getContext()) }, false)); injectCRTAlias(M, "__extendhfsf2", "julia__gnu_h2f_ieee", @@ -1062,10 +1131,9 @@ static AOTOutputs add_output_impl(Module &M, TargetMachine &SourceTM, ShardTimer FunctionType::get(Type::getHalfTy(M.getContext()), { Type::getFloatTy(M.getContext()) }, false)); injectCRTAlias(M, "__truncdfhf2", "julia__truncdfhf2", FunctionType::get(Type::getHalfTy(M.getContext()), { Type::getDoubleTy(M.getContext()) }, false)); -#else - emitFloat16Wrappers(M, false); #endif + // BFloat16 conversion routines injectCRTAlias(M, "__truncsfbf2", "julia__truncsfbf2", FunctionType::get(Type::getBFloatTy(M.getContext()), { Type::getFloatTy(M.getContext()) }, false)); injectCRTAlias(M, "__truncsdbf2", "julia__truncdfbf2", @@ -1124,7 +1192,7 @@ static auto serializeModule(const Module &M) { // Modules are deserialized lazily by LLVM, to avoid deserializing // unnecessary functions. We take advantage of this by serializing // the entire module once, then deleting the bodies of functions -// that are not in this partition. Once unnecesary functions are +// that are not in this partition. Once unnecessary functions are // deleted, we then materialize the entire module to make use-lists // consistent. static void materializePreserved(Module &M, Partition &partition) { @@ -1212,7 +1280,7 @@ static void materializePreserved(Module &M, Partition &partition) { } // Reconstruct jl_fvars, jl_gvars, jl_fvars_idxs, and jl_gvars_idxs from the partition -static void construct_vars(Module &M, Partition &partition) { +static void construct_vars(Module &M, Partition &partition, StringRef suffix) { SmallVector> fvar_pairs; fvar_pairs.reserve(partition.fvars.size()); for (auto &fvar : partition.fvars) { @@ -1238,7 +1306,7 @@ static void construct_vars(Module &M, Partition &partition) { assert(!GV->isDeclaration()); gvar_pairs.push_back({ gvar.second, GV }); } - SmallVector gvars; + SmallVector gvars; SmallVector gvar_idxs; gvars.reserve(gvar_pairs.size()); gvar_idxs.reserve(gvar_pairs.size()); @@ -1249,9 +1317,9 @@ static void construct_vars(Module &M, Partition &partition) { } // Now commit the fvars, gvars, and idxs - auto T_psize = M.getDataLayout().getIntPtrType(M.getContext())->getPointerTo(); - emit_offset_table(M, fvars, "jl_fvars", T_psize); - emit_offset_table(M, gvars, "jl_gvars", T_psize); + auto T_size = M.getDataLayout().getIntPtrType(M.getContext()); + emit_table(M, fvars, "jl_fvars", T_size->getPointerTo()); + emit_offset_table(M, T_size, gvars, "jl_gvar", suffix); auto fidxs = ConstantDataArray::get(M.getContext(), fvar_idxs); auto fidxs_var = new GlobalVariable(M, fidxs->getType(), true, GlobalVariable::ExternalLinkage, @@ -1261,11 +1329,17 @@ static void construct_vars(Module &M, Partition &partition) { auto gidxs = ConstantDataArray::get(M.getContext(), gvar_idxs); auto gidxs_var = new GlobalVariable(M, gidxs->getType(), true, GlobalVariable::ExternalLinkage, - gidxs, "jl_gvar_idxs"); + gidxs, "jl_gvar_idxs" + suffix); gidxs_var->setVisibility(GlobalValue::HiddenVisibility); gidxs_var->setDSOLocal(true); } +extern "C" void lambda_trampoline(void* arg) { + std::function* func = static_cast*>(arg); + (*func)(); + delete func; +} + // Entrypoint to optionally-multithreaded image compilation. This handles global coordination of the threading, // as well as partitioning, serialization, and deserialization. template @@ -1313,6 +1387,13 @@ static SmallVector add_output(Module &M, TargetMachine &TM, Stri output_timer.startTimer(); { JL_TIMING(NATIVE_AOT, NATIVE_Opt); + // convert gvars to the expected offset table format for shard 0 + if (M.getGlobalVariable("jl_gvars")) { + auto gvars = consume_gv(M, "jl_gvars", false); + Type *T_size = M.getDataLayout().getIntPtrType(M.getContext()); + emit_offset_table(M, T_size, gvars, "jl_gvar", "_0"); // module flag "julia.mv.suffix" + M.getGlobalVariable("jl_gvar_idxs")->setName("jl_gvar_idxs_0"); + } outputs[0] = add_output_impl(M, TM, timers[0], unopt_out, opt_out, obj_out, asm_out); } output_timer.stopTimer(); @@ -1354,13 +1435,20 @@ static SmallVector add_output(Module &M, TargetMachine &TM, Stri // Start all of the worker threads { JL_TIMING(NATIVE_AOT, NATIVE_Opt); - std::vector workers(threads); + std::vector workers(threads); for (unsigned i = 0; i < threads; i++) { - workers[i] = std::thread([&, i]() { + std::function func = [&, i]() { LLVMContext ctx; + SetOpaquePointer(ctx); // Lazily deserialize the entire module timers[i].deserialize.startTimer(); - auto M = cantFail(getLazyBitcodeModule(MemoryBufferRef(StringRef(serialized.data(), serialized.size()), "Optimized"), ctx), "Error loading module"); + auto EM = getLazyBitcodeModule(MemoryBufferRef(StringRef(serialized.data(), serialized.size()), "Optimized"), ctx); + // Make sure this also fails with only julia, but not LLVM assertions enabled, + // otherwise, the first error we hit is the LLVM module verification failure, + // which will look very confusing, because the module was partially deserialized. + bool deser_succeeded = (bool)EM; + auto M = cantFail(std::move(EM), "Error loading module"); + assert(deser_succeeded); (void)deser_succeeded; timers[i].deserialize.stopTimer(); timers[i].materialize.startTimer(); @@ -1368,8 +1456,9 @@ static SmallVector add_output(Module &M, TargetMachine &TM, Stri timers[i].materialize.stopTimer(); timers[i].construct.startTimer(); - construct_vars(*M, partitions[i]); - M->setModuleFlag(Module::Error, "julia.mv.suffix", MDString::get(M->getContext(), "_" + std::to_string(i))); + std::string suffix = "_" + std::to_string(i); + construct_vars(*M, partitions[i], suffix); + M->setModuleFlag(Module::Error, "julia.mv.suffix", MDString::get(M->getContext(), suffix)); // The DICompileUnit file is not used for anything, but ld64 requires it be a unique string per object file // or it may skip emitting debug info for that file. Here set it to ./julia#N DIFile *topfile = DIFile::get(M->getContext(), "julia#" + std::to_string(i), "."); @@ -1378,12 +1467,14 @@ static SmallVector add_output(Module &M, TargetMachine &TM, Stri timers[i].construct.stopTimer(); outputs[i] = add_output_impl(*M, TM, timers[i], unopt_out, opt_out, obj_out, asm_out); - }); + }; + auto arg = new std::function(func); + uv_thread_create(&workers[i], lambda_trampoline, arg); // Use libuv thread to avoid issues with stack sizes } // Wait for all of the worker threads to finish - for (auto &w : workers) - w.join(); + for (unsigned i = 0; i < threads; i++) + uv_thread_join(&workers[i]); } output_timer.stopTimer(); @@ -1409,19 +1500,21 @@ static SmallVector add_output(Module &M, TargetMachine &TM, Stri return outputs; } +extern int jl_is_timing_passes; static unsigned compute_image_thread_count(const ModuleInfo &info) { // 32-bit systems are very memory-constrained #ifdef _P32 LLVM_DEBUG(dbgs() << "32-bit systems are restricted to a single thread\n"); return 1; #endif + if (jl_is_timing_passes) // LLVM isn't thread safe when timing the passes https://github.com/llvm/llvm-project/issues/44417 + return 1; // COFF has limits on external symbols (even hidden) up to 65536. We reserve the last few // for any of our other symbols that we insert during compilation. if (info.triple.isOSBinFormatCOFF() && info.globals > 64000) { LLVM_DEBUG(dbgs() << "COFF is restricted to a single thread for large images\n"); return 1; } - // This is not overridable because empty modules do occasionally appear, but they'll be very small and thus exit early to // known easy behavior. Plus they really don't warrant multiple threads if (info.weight < 1000) { @@ -1503,16 +1596,23 @@ void jl_dump_native_impl(void *native_code, TheTriple.setObjectFormat(Triple::COFF); } else if (TheTriple.isOSDarwin()) { TheTriple.setObjectFormat(Triple::MachO); - TheTriple.setOS(llvm::Triple::MacOSX); + SmallString<16> Str; + Str += "macosx"; + if (TheTriple.isAArch64()) + Str += "11.0.0"; // Update this if MACOSX_VERSION_MIN changes + else + Str += "10.14.0"; + TheTriple.setOSName(Str); } Optional RelocModel; - if (TheTriple.isOSLinux() || TheTriple.isOSFreeBSD()) { + if (TheTriple.isOSLinux() || TheTriple.isOSFreeBSD() || TheTriple.isOSOpenBSD()) { RelocModel = Reloc::PIC_; } + CodeModel::Model CMModel = CodeModel::Small; - if (TheTriple.isPPC()) { - // On PPC the small model is limited to 16bit offsets - CMModel = CodeModel::Medium; + if (TheTriple.isPPC() || (TheTriple.isX86() && TheTriple.isArch64Bit() && TheTriple.isOSLinux())) { + // On PPC the small model is limited to 16bit offsets. For very large images the small code model + CMModel = CodeModel::Medium; // isn't good enough on x86 so use Medium, it has no cost because only the image goes in .ldata } std::unique_ptr SourceTM( jl_ExecutionEngine->getTarget().createTargetMachine( @@ -1524,6 +1624,7 @@ void jl_dump_native_impl(void *native_code, CMModel, CodeGenOpt::Aggressive // -O3 TODO: respect command -O0 flag? )); + fixupTM(*SourceTM); auto DL = jl_create_datalayout(*SourceTM); std::string StackProtectorGuard; unsigned OverrideStackAlignment; @@ -1542,6 +1643,7 @@ void jl_dump_native_impl(void *native_code, if (z) { JL_TIMING(NATIVE_AOT, NATIVE_Sysimg); LLVMContext Context; + SetOpaquePointer(Context); Module sysimgM("sysimg", Context); sysimgM.setTargetTriple(TheTriple.str()); sysimgM.setDataLayout(DL); @@ -1553,6 +1655,12 @@ void jl_dump_native_impl(void *native_code, GlobalVariable::ExternalLinkage, data, "jl_system_image_data"); sysdata->setAlignment(Align(64)); +#if JL_LLVM_VERSION >= 180000 + sysdata->setCodeModel(CodeModel::Large); +#else + if (TheTriple.isX86() && TheTriple.isArch64Bit() && TheTriple.isOSLinux()) + sysdata->setSection(".ldata"); +#endif addComdat(sysdata, TheTriple); Constant *len = ConstantInt::get(sysimgM.getDataLayout().getIntPtrType(Context), z->size); addComdat(new GlobalVariable(sysimgM, len->getType(), true, @@ -1629,8 +1737,8 @@ void jl_dump_native_impl(void *native_code, LLVM_DEBUG(dbgs() << "Using " << threads << " to emit aot image\n"); nfvars = data->jl_sysimg_fvars.size(); ngvars = data->jl_sysimg_gvars.size(); - emit_offset_table(dataM, data->jl_sysimg_gvars, "jl_gvars", T_psize); - emit_offset_table(dataM, data->jl_sysimg_fvars, "jl_fvars", T_psize); + emit_table(dataM, data->jl_sysimg_gvars, "jl_gvars", T_psize); + emit_table(dataM, data->jl_sysimg_fvars, "jl_fvars", T_psize); SmallVector idxs; idxs.resize(data->jl_sysimg_gvars.size()); std::iota(idxs.begin(), idxs.end(), 0); @@ -1678,6 +1786,7 @@ void jl_dump_native_impl(void *native_code, { JL_TIMING(NATIVE_AOT, NATIVE_Metadata); LLVMContext Context; + SetOpaquePointer(Context); Module metadataM("metadata", Context); metadataM.setTargetTriple(TheTriple.str()); metadataM.setDataLayout(DL); @@ -1687,6 +1796,9 @@ void jl_dump_native_impl(void *native_code, // reflect the address of the jl_RTLD_DEFAULT_handle variable // back to the caller, so that we can check for consistency issues GlobalValue *jlRTLD_DEFAULT_var = jl_emit_RTLD_DEFAULT_var(&metadataM); + if (TheTriple.isOSBinFormatCOFF()) { + jlRTLD_DEFAULT_var->setDLLStorageClass(GlobalValue::DLLImportStorageClass); + } addComdat(new GlobalVariable(metadataM, jlRTLD_DEFAULT_var->getType(), true, @@ -1800,6 +1912,60 @@ void addTargetPasses(legacy::PassManagerBase *PM, const Triple &triple, TargetIR PM->add(createTargetTransformInfoWrapperPass(std::move(analysis))); } +// sometimes in GDB you want to find out what code was created from a mi +extern "C" JL_DLLEXPORT_CODEGEN jl_code_info_t *jl_gdbdumpcode(jl_method_instance_t *mi) +{ + jl_llvmf_dump_t llvmf_dump; + size_t world = jl_current_task->world_age; + JL_STREAM *stream = (JL_STREAM*)STDERR_FILENO; + + jl_printf(stream, "---- dumping IR for ----\n"); + jl_static_show(stream, (jl_value_t*)mi); + jl_printf(stream, "\n----\n"); + + jl_printf(stream, "\n---- unoptimized IR ----"); + jl_get_llvmf_defn(&llvmf_dump, mi, world, 0, false, jl_default_cgparams); + if (llvmf_dump.F) { + jl_value_t *ir = jl_dump_function_ir(&llvmf_dump, 0, 1, "source"); + jl_static_show(stream, ir); + } + jl_printf(stream, "----\n"); + + jl_printf(stream, "\n---- optimized IR ----"); + jl_get_llvmf_defn(&llvmf_dump, mi, world, 0, true, jl_default_cgparams); + if (llvmf_dump.F) { + jl_value_t *ir = jl_dump_function_ir(&llvmf_dump, 0, 1, "source"); + jl_static_show(stream, ir); + } + jl_printf(stream, "----\n"); + + jl_printf(stream, "\n---- assembly ----"); + jl_get_llvmf_defn(&llvmf_dump, mi, world, 0, true, jl_default_cgparams); + if (llvmf_dump.F) { + jl_value_t *ir = jl_dump_function_asm(&llvmf_dump, 0, "", "source", 0, true); + jl_static_show(stream, ir); + } + jl_printf(stream, "----\n"); + + jl_code_info_t *src = NULL; + jl_value_t *ci = jl_default_cgparams.lookup(mi, world, world); + if (ci == jl_nothing) { + ci = (jl_value_t*)jl_type_infer(mi, world, 0, SOURCE_MODE_FORCE_SOURCE_UNCACHED); + } else { + ci = NULL; + } + if (ci) { + jl_code_instance_t *codeinst = (jl_code_instance_t*)ci; + src = (jl_code_info_t*)jl_atomic_load_relaxed(&codeinst->inferred); + if ((jl_value_t*)src != jl_nothing && !jl_is_code_info(src) && jl_is_method(mi->def.method)) { + JL_GC_PUSH2(&codeinst, &src); + src = jl_uncompress_ir(mi->def.method, codeinst, (jl_value_t*)src); + JL_GC_POP(); + } + } + return src; +} + // --- native code info, and dump function to IR and ASM --- // Get pointer to llvm::Function instance, compiling if necessary // for use in reflection from Julia. @@ -1809,48 +1975,34 @@ extern "C" JL_DLLEXPORT_CODEGEN void jl_get_llvmf_defn_impl(jl_llvmf_dump_t* dump, jl_method_instance_t *mi, size_t world, char getwrapper, char optimize, const jl_cgparams_t params) { if (jl_is_method(mi->def.method) && mi->def.method->source == NULL && - mi->def.method->generator == NULL) { + mi->def.method->generator == NULL && !mi->def.method->is_for_opaque_closure) { // not a generic function dump->F = NULL; return; } // get the source code for this function - jl_value_t *jlrettype = (jl_value_t*)jl_any_type; jl_code_info_t *src = NULL; jl_code_instance_t *codeinst = NULL; - JL_GC_PUSH3(&src, &jlrettype, &codeinst); - if (jl_is_method(mi->def.method) && mi->def.method->source != NULL && mi->def.method->source != jl_nothing && jl_ir_flag_inferred(mi->def.method->source)) { - // uninferred opaque closure - src = (jl_code_info_t*)mi->def.method->source; - if (src && !jl_is_code_info(src)) - src = jl_uncompress_ir(mi->def.method, NULL, (jl_value_t*)src); + JL_GC_PUSH2(&src, &codeinst); + jl_value_t *ci = params.lookup(mi, world, world); + if (ci && ci != jl_nothing) { + codeinst = (jl_code_instance_t*)ci; + src = (jl_code_info_t*)jl_atomic_load_relaxed(&codeinst->inferred); } - else { - jl_value_t *ci = params.lookup(mi, world, world); - if (ci != jl_nothing) { - codeinst = (jl_code_instance_t*)ci; + if (!src || (jl_value_t*)src == jl_nothing) { + codeinst = jl_type_infer(mi, world, 0, SOURCE_MODE_FORCE_SOURCE_UNCACHED); + if (codeinst) { src = (jl_code_info_t*)jl_atomic_load_relaxed(&codeinst->inferred); - if ((jl_value_t*)src != jl_nothing && !jl_is_code_info(src) && jl_is_method(mi->def.method)) - src = jl_uncompress_ir(mi->def.method, codeinst, (jl_value_t*)src); - jlrettype = codeinst->rettype; - codeinst = NULL; // not needed outside of this branch - } - if (!src || (jl_value_t*)src == jl_nothing) { - src = jl_type_infer(mi, world, 0); - if (src) - jlrettype = src->rettype; - else if (jl_is_method(mi->def.method)) { - src = mi->def.method->generator ? jl_code_for_staged(mi, world) : (jl_code_info_t*)mi->def.method->source; - if (src && (jl_value_t*)src != jl_nothing && !jl_is_code_info(src) && jl_is_method(mi->def.method)) - src = jl_uncompress_ir(mi->def.method, NULL, (jl_value_t*)src); - } - // TODO: use mi->uninferred } } + if (src) { + if ((jl_value_t*)src != jl_nothing && !jl_is_code_info(src) && jl_is_method(mi->def.method)) + src = jl_uncompress_ir(mi->def.method, codeinst, (jl_value_t*)src); + } // emit this function into a new llvm module - if (src && jl_is_code_info(src)) { + if (codeinst && src && jl_is_code_info(src)) { auto ctx = jl_ExecutionEngine->getContext(); orc::ThreadSafeModule m = jl_create_ts_module(name_from_method_instance(mi), *ctx); uint64_t compiler_start_time = 0; @@ -1862,7 +2014,6 @@ void jl_get_llvmf_defn_impl(jl_llvmf_dump_t* dump, jl_method_instance_t *mi, siz return std::make_pair(M.getDataLayout(), Triple(M.getTargetTriple())); }); jl_codegen_params_t output(*ctx, std::move(target_info.first), std::move(target_info.second)); - output.world = world; output.params = ¶ms; output.imaging_mode = imaging_default(); // This would be nice, but currently it causes some assembly regressions that make printed output @@ -1872,7 +2023,7 @@ void jl_get_llvmf_defn_impl(jl_llvmf_dump_t* dump, jl_method_instance_t *mi, siz // This would also be nice, but it seems to cause OOMs on the windows32 builder // To get correct names in the IR this needs to be at least 2 output.debug_level = params.debug_info_level; - auto decls = jl_emit_code(m, mi, src, jlrettype, output); + auto decls = jl_emit_code(m, mi, src, codeinst->rettype, output, jl_atomic_load_relaxed(&codeinst->min_world), jl_atomic_load_relaxed(&codeinst->max_world)); JL_UNLOCK(&jl_codegen_lock); // Might GC Function *F = NULL; @@ -1892,7 +2043,7 @@ void jl_get_llvmf_defn_impl(jl_llvmf_dump_t* dump, jl_method_instance_t *mi, siz elty = p->getType()->getNonOpaquePointerElementType(); } // For pretty printing, when LLVM inlines the global initializer into its loads - auto alias = GlobalAlias::create(elty, 0, GlobalValue::PrivateLinkage, global.second->getName() + ".jit", p, m.getModuleUnlocked()); + auto alias = GlobalAlias::create(elty, 0, GlobalValue::PrivateLinkage, global.second->getName() + ".jit", p, global.second->getParent()); global.second->setInitializer(ConstantExpr::getBitCast(alias, global.second->getValueType())); global.second->setConstant(true); global.second->setLinkage(GlobalValue::PrivateLinkage); diff --git a/src/array.c b/src/array.c index 65efbb7e9df19..979772e649727 100644 --- a/src/array.c +++ b/src/array.c @@ -16,66 +16,6 @@ extern "C" { #endif -// similar to MEMDEBUG's purpose, this ensures there is a garbage byte after a -// most UInt8 arrays (even String-backed) so that almost any C string -// processing on it is guaranteed to run too far and return some garbage -// answers to warn the user of their bug. -#ifdef NDEBUG -#define JL_ARRAY_MEMDEBUG_TERMINATOR 0 -#else -#define JL_ARRAY_MEMDEBUG_TERMINATOR 1 -#endif - -#define JL_ARRAY_ALIGN(jl_value, nbytes) LLT_ALIGN(jl_value, nbytes) - -static inline void arrayassign_safe(int hasptr, jl_value_t *parent, char *dst, const jl_value_t *src, size_t nb) JL_NOTSAFEPOINT -{ - // array can assume more alignment than a field would normally have - assert(nb >= jl_datatype_size(jl_typeof(src))); // nb might move some undefined bits, but we should be okay with that - if (hasptr) { - size_t nptr = nb / sizeof(void*); - memmove_refs((void**)dst, (void* const*)src, nptr); - jl_gc_multi_wb(parent, src); - } - else { - switch (nb) { - case 0: break; - case 1: *(uint8_t*)dst = *(uint8_t*)src; break; - case 2: *(uint16_t*)dst = *(uint16_t*)src; break; - case 4: *(uint32_t*)dst = *(uint32_t*)src; break; - case 8: *(uint64_t*)dst = *(uint64_t*)src; break; - case 16: - memcpy(jl_assume_aligned(dst, 16), jl_assume_aligned(src, 16), 16); - break; - default: memcpy(dst, src, nb); - } - } -} - -static inline void memmove_safe(int hasptr, char *dst, const char *src, size_t nb) JL_NOTSAFEPOINT -{ - if (hasptr) - memmove_refs((void**)dst, (void**)src, nb / sizeof(void*)); - else - memmove(dst, src, nb); -} - -// array constructors --------------------------------------------------------- -JL_DLLEXPORT char *jl_array_typetagdata(jl_array_t *a) JL_NOTSAFEPOINT -{ - assert(jl_array_isbitsunion(a)); - return ((char*)jl_array_data(a)) + ((jl_array_ndims(a) == 1 ? (a->maxsize - a->offset) : jl_array_len(a)) * a->elsize) + a->offset; -} - -STATIC_INLINE jl_value_t *jl_array_owner(jl_array_t *a JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT -{ - if (a->flags.how == 3) { - a = (jl_array_t*)jl_array_data_owner(a); - assert(jl_is_string(a) || a->flags.how != 3); - } - return (jl_value_t*)a; -} - #if defined(_P64) && defined(UINT128MAX) typedef __uint128_t wideint_t; #else @@ -84,142 +24,27 @@ typedef uint64_t wideint_t; #define MAXINTVAL (((size_t)-1)>>1) -JL_DLLEXPORT int jl_array_validate_dims(size_t *nel, size_t *tot, uint32_t ndims, size_t *dims, size_t elsz) +JL_DLLEXPORT int jl_array_validate_dims(size_t *nel, uint32_t ndims, size_t *dims) { size_t i; size_t _nel = 1; - for(i=0; i < ndims; i++) { + for (i = 0; i < ndims; i++) { size_t di = dims[i]; wideint_t prod = (wideint_t)_nel * (wideint_t)di; if (prod >= (wideint_t) MAXINTVAL || di >= MAXINTVAL) return 1; _nel = prod; } - wideint_t prod = (wideint_t)elsz * (wideint_t)_nel; - if (prod >= (wideint_t) MAXINTVAL) - return 2; *nel = _nel; - *tot = (size_t)prod; return 0; } -static jl_array_t *_new_array_(jl_value_t *atype, uint32_t ndims, size_t *dims, - int8_t isunboxed, int8_t hasptr, int8_t isunion, int8_t zeroinit, size_t elsz) -{ - jl_task_t *ct = jl_current_task; - size_t i, tot, nel; - void *data; - jl_array_t *a; - assert(isunboxed || elsz == sizeof(void*)); - assert(atype == NULL || isunion == jl_is_uniontype(jl_tparam0(atype))); - int validated = jl_array_validate_dims(&nel, &tot, ndims, dims, elsz); - if (validated == 1) - jl_exceptionf(jl_argumenterror_type, "invalid Array dimensions"); - else if (validated == 2) - jl_error("invalid Array size"); - if (isunboxed) { - if (JL_ARRAY_MEMDEBUG_TERMINATOR && elsz == 1 && !isunion) { - // extra byte for all julia allocated byte arrays - tot++; - } - if (isunion) { - // an extra byte for each isbits union array element, stored after a->maxsize - tot += nel; - } - } - - int ndimwords = jl_array_ndimwords(ndims); - int tsz = sizeof(jl_array_t) + ndimwords*sizeof(size_t); - if (tot <= ARRAY_INLINE_NBYTES) { - // align data area - if (tot >= ARRAY_CACHE_ALIGN_THRESHOLD) - tsz = JL_ARRAY_ALIGN(tsz, JL_CACHE_BYTE_ALIGNMENT); - else if (isunboxed && elsz >= 4) - tsz = JL_ARRAY_ALIGN(tsz, JL_SMALL_BYTE_ALIGNMENT); - size_t doffs = tsz; - tsz += tot; - // jl_array_t is large enough that objects will always be aligned 16 - a = (jl_array_t*)jl_gc_alloc(ct->ptls, tsz, atype); - assert(((size_t)a & 15) == 0); - // No allocation or safepoint allowed after this - a->flags.how = 0; - data = (char*)a + doffs; - } - else { - data = jl_gc_managed_malloc(tot); - // Allocate the Array **after** allocating the data - // to make sure the array is still young - a = (jl_array_t*)jl_gc_alloc(ct->ptls, tsz, atype); - // No allocation or safepoint allowed after this - a->flags.how = 2; - jl_gc_track_malloced_array(ct->ptls, a); - } - a->flags.pooled = tsz <= GC_MAX_SZCLASS; - - if (zeroinit) - memset(data, 0, tot); - if (JL_ARRAY_MEMDEBUG_TERMINATOR && elsz == 1 && !isunion) { - ((char*)data)[tot - 1] = 0xfe; - msan_allocated_memory(&((char*)data)[tot - 1], 1); - } - a->data = data; - a->length = nel; - a->flags.ndims = ndims; - a->flags.ptrarray = !isunboxed; - a->flags.hasptr = hasptr; - a->elsize = elsz; - a->flags.isshared = 0; - a->flags.isaligned = 1; - a->offset = 0; - if (ndims == 1) { - a->nrows = nel; - a->maxsize = nel; - } - else if (a->flags.ndims != ndims) { - jl_exceptionf(jl_argumenterror_type, "invalid Array dimensions"); - } - else { - size_t *adims = &a->nrows; - for (i = 0; i < ndims; i++) - adims[i] = dims[i]; - } - - return a; -} - -static inline jl_array_t *_new_array(jl_value_t *atype, uint32_t ndims, size_t *dims) -{ - jl_value_t *eltype = jl_tparam0(atype); - size_t elsz = 0, al = 0; - if (!jl_is_kind(jl_typeof(eltype))) - jl_type_error_rt("Array", "element type", (jl_value_t*)jl_type_type, eltype); - int isunboxed = jl_islayout_inline(eltype, &elsz, &al); - int isunion = jl_is_uniontype(eltype); - int hasptr = isunboxed && (jl_is_datatype(eltype) && ((jl_datatype_t*)eltype)->layout->npointers > 0); - if (!isunboxed) { - elsz = sizeof(void*); - al = elsz; - } - else { - elsz = LLT_ALIGN(elsz, al); - } - int zi = !isunboxed || hasptr || isunion || (jl_is_datatype(eltype) && ((jl_datatype_t*)eltype)->zeroinit); - - return _new_array_(atype, ndims, dims, isunboxed, hasptr, isunion, zi, elsz); -} - -jl_array_t *jl_new_array_for_deserialization(jl_value_t *atype, uint32_t ndims, size_t *dims, - int isunboxed, int hasptr, int isunion, int elsz) -{ - return _new_array_(atype, ndims, dims, isunboxed, hasptr, isunion, 0, (size_t)elsz); -} - #ifndef JL_NDEBUG static inline int is_ntuple_long(jl_value_t *v) { if (!jl_is_tuple(v)) return 0; - jl_value_t *tt = jl_typeof(v); + jl_value_t *tt = (jl_value_t*)jl_typetagof(v); size_t i, nfields = jl_nparams(tt); for (i = 0; i < nfields; i++) { if (jl_tparam(tt, i) != (jl_value_t*)jl_long_type) { @@ -230,315 +55,130 @@ static inline int is_ntuple_long(jl_value_t *v) } #endif -JL_DLLEXPORT jl_array_t *jl_reshape_array(jl_value_t *atype, jl_array_t *data, - jl_value_t *_dims) +#define jl_array_elsize(a) (((jl_datatype_t*)jl_typetagof((a)->ref.mem))->layout->size) + +static char *jl_array_typetagdata(jl_array_t *a) JL_NOTSAFEPOINT { - jl_task_t *ct = jl_current_task; - assert(jl_types_equal(jl_tparam0(jl_typeof(data)), jl_tparam0(atype))); + assert(jl_genericmemory_isbitsunion(a->ref.mem)); + return jl_genericmemory_typetagdata(a->ref.mem) + (uintptr_t)a->ref.ptr_or_offset; +} - size_t ndims = jl_nfields(_dims); - assert(is_ntuple_long(_dims)); - size_t *dims = (size_t*)_dims; - int ndimwords = jl_array_ndimwords(ndims); - int tsz = sizeof(jl_array_t) + ndimwords * sizeof(size_t) + sizeof(void*); +STATIC_INLINE jl_array_t *_new_array(jl_value_t *atype, jl_genericmemory_t *mem, const jl_datatype_layout_t *layout, uint32_t ndims, size_t *dims) +{ + jl_task_t *ct = jl_current_task; + size_t i; + int tsz = sizeof(jl_array_t) + ndims*sizeof(size_t); jl_array_t *a = (jl_array_t*)jl_gc_alloc(ct->ptls, tsz, atype); - // No allocation or safepoint allowed after this - // copy data (except dims) from the old object - a->flags.pooled = tsz <= GC_MAX_SZCLASS; - a->flags.ndims = ndims; - a->offset = 0; - a->data = NULL; - a->flags.isaligned = data->flags.isaligned; - a->elsize = data->elsize; - a->flags.ptrarray = data->flags.ptrarray; - a->flags.hasptr = data->flags.hasptr; - - // if data is itself a shared wrapper, - // owner should point back to the original array - jl_array_t *owner = (jl_array_t*)jl_array_owner(data); - jl_array_data_owner(a) = (jl_value_t*)owner; - - a->flags.how = 3; - a->data = data->data; - a->flags.isshared = 1; - data->flags.isshared = 1; - - if (ndims == 1) { - size_t l = dims[0]; - a->length = l; - a->nrows = l; - a->maxsize = l; - } - else if (a->flags.ndims != ndims) { - jl_exceptionf(jl_argumenterror_type, "invalid Array dimensions"); - } - else { - size_t *adims = &a->nrows; - size_t l = 1; - wideint_t prod; - for (size_t i = 0; i < ndims; i++) { - adims[i] = dims[i]; - prod = (wideint_t)l * (wideint_t)adims[i]; - if (prod > (wideint_t) MAXINTVAL) - jl_exceptionf(jl_argumenterror_type, "invalid Array dimensions"); - l = prod; - } - a->length = l; - } - + a->ref.mem = mem; + if (layout->flags.arrayelem_isunion || layout->size == 0) + a->ref.ptr_or_offset = 0; + else + a->ref.ptr_or_offset = mem->ptr; + for (i = 0; i < ndims; i++) + a->dimsize[i] = dims[i]; return a; } -JL_DLLEXPORT jl_array_t *jl_string_to_array(jl_value_t *str) +STATIC_INLINE jl_array_t *new_array(jl_value_t *atype, uint32_t ndims, size_t *dims) { - jl_task_t *ct = jl_current_task; - jl_array_t *a; - - int ndimwords = jl_array_ndimwords(1); - int tsz = sizeof(jl_array_t) + ndimwords*sizeof(size_t) + sizeof(void*); - a = (jl_array_t*)jl_gc_alloc(ct->ptls, tsz, jl_array_uint8_type); - a->flags.pooled = tsz <= GC_MAX_SZCLASS; - a->flags.ndims = 1; - a->offset = 0; - a->data = jl_string_data(str); - a->flags.isaligned = 0; - a->elsize = 1; - a->flags.ptrarray = 0; - a->flags.hasptr = 0; - jl_array_data_owner(a) = str; - a->flags.how = 3; - a->flags.isshared = 1; - size_t l = jl_string_len(str); - a->length = l; - a->nrows = a->maxsize = l; + size_t nel; + if (jl_array_validate_dims(&nel, ndims, dims)) + jl_exceptionf(jl_argumenterror_type, "invalid Array dimensions: too large for system address width"); + if (*(size_t*)jl_tparam1(atype) != ndims) + jl_exceptionf(jl_argumenterror_type, "invalid Array dimensions"); + jl_value_t *mtype = jl_field_type_concrete((jl_datatype_t*)jl_field_type_concrete((jl_datatype_t*)atype, 0), 1); + // extra byte for all julia allocated byte vectors + jl_genericmemory_t *mem = jl_alloc_genericmemory(mtype, nel); + JL_GC_PUSH1(&mem); + jl_array_t *a = _new_array(atype, mem, ((jl_datatype_t*)mtype)->layout, ndims, dims); + JL_GC_POP(); return a; } -// own_buffer != 0 iff GC should call free() on this pointer eventually +jl_genericmemory_t *_new_genericmemory_(jl_value_t *mtype, size_t nel, int8_t isunion, int8_t zeroinit, size_t elsz); + +JL_DLLEXPORT jl_genericmemory_t *jl_string_to_genericmemory(jl_value_t *str); + JL_DLLEXPORT jl_array_t *jl_ptr_to_array_1d(jl_value_t *atype, void *data, size_t nel, int own_buffer) { - jl_task_t *ct = jl_current_task; - jl_array_t *a; - jl_value_t *eltype = jl_tparam0(atype); - - int isunboxed = jl_stored_inline(eltype); - if (isunboxed && jl_is_uniontype(eltype)) - jl_exceptionf(jl_argumenterror_type, - "unsafe_wrap: unspecified layout for union element type"); - size_t elsz; - unsigned align; - if (isunboxed) { - elsz = jl_datatype_size(eltype); - align = jl_datatype_align(eltype); - } - else { - align = elsz = sizeof(void*); - } - if (((uintptr_t)data) & ((align > JL_HEAP_ALIGNMENT ? JL_HEAP_ALIGNMENT : align) - 1)) - jl_exceptionf(jl_argumenterror_type, - "unsafe_wrap: pointer %p is not properly aligned to %u bytes", data, align); - - int ndimwords = jl_array_ndimwords(1); - int tsz = sizeof(jl_array_t) + ndimwords*sizeof(size_t); - a = (jl_array_t*)jl_gc_alloc(ct->ptls, tsz, atype); - // No allocation or safepoint allowed after this - a->flags.pooled = tsz <= GC_MAX_SZCLASS; - a->data = data; - a->length = nel; - a->elsize = LLT_ALIGN(elsz, align); - a->flags.ptrarray = !isunboxed; - a->flags.hasptr = isunboxed && (jl_is_datatype(eltype) && ((jl_datatype_t*)eltype)->layout->npointers > 0); - a->flags.ndims = 1; - a->flags.isshared = 1; - a->flags.isaligned = 0; // TODO: allow passing memalign'd buffers - if (own_buffer) { - a->flags.how = 2; - jl_gc_track_malloced_array(ct->ptls, a); - jl_gc_count_allocd(nel*elsz); - } - else { - a->flags.how = 0; - } - - a->nrows = nel; - a->maxsize = nel; - a->offset = 0; + if (*(size_t*)jl_tparam1(atype) != 1) + jl_exceptionf(jl_argumenterror_type, "invalid Array dimensions"); + jl_value_t *mtype = jl_field_type_concrete((jl_datatype_t*)jl_field_type_concrete((jl_datatype_t*)atype, 0), 1); + jl_genericmemory_t *mem = jl_ptr_to_genericmemory(mtype, data, nel, own_buffer); + JL_GC_PUSH1(&mem); + jl_array_t *a = _new_array(atype, mem, ((jl_datatype_t*)mtype)->layout, 1, &nel); + JL_GC_POP(); return a; } JL_DLLEXPORT jl_array_t *jl_ptr_to_array(jl_value_t *atype, void *data, jl_value_t *_dims, int own_buffer) { - jl_task_t *ct = jl_current_task; - size_t nel = 1; - jl_array_t *a; size_t ndims = jl_nfields(_dims); - wideint_t prod; assert(is_ntuple_long(_dims)); size_t *dims = (size_t*)_dims; - for (size_t i = 0; i < ndims; i++) { - prod = (wideint_t)nel * (wideint_t)dims[i]; - if (prod > (wideint_t) MAXINTVAL) - jl_exceptionf(jl_argumenterror_type, "invalid Array dimensions"); - nel = prod; - } - if (__unlikely(ndims == 1)) - return jl_ptr_to_array_1d(atype, data, nel, own_buffer); - jl_value_t *eltype = jl_tparam0(atype); - - int isunboxed = jl_stored_inline(eltype); - if (isunboxed && jl_is_uniontype(eltype)) - jl_exceptionf(jl_argumenterror_type, - "unsafe_wrap: unspecified layout for union element type"); - size_t elsz; - unsigned align; - if (isunboxed) { - elsz = jl_datatype_size(eltype); - align = jl_datatype_align(eltype); - } - else { - align = elsz = sizeof(void*); - } - if (((uintptr_t)data) & ((align > JL_HEAP_ALIGNMENT ? JL_HEAP_ALIGNMENT : align) - 1)) - jl_exceptionf(jl_argumenterror_type, - "unsafe_wrap: pointer %p is not properly aligned to %u bytes", data, align); - - int ndimwords = jl_array_ndimwords(ndims); - int tsz = sizeof(jl_array_t) + ndimwords*sizeof(size_t); - a = (jl_array_t*)jl_gc_alloc(ct->ptls, tsz, atype); - // No allocation or safepoint allowed after this - a->flags.pooled = tsz <= GC_MAX_SZCLASS; - a->data = data; - a->length = nel; - a->elsize = LLT_ALIGN(elsz, align); - a->flags.ptrarray = !isunboxed; - a->flags.hasptr = isunboxed && (jl_is_datatype(eltype) && ((jl_datatype_t*)eltype)->layout->npointers > 0); - a->flags.ndims = ndims; - a->offset = 0; - a->flags.isshared = 1; - a->flags.isaligned = 0; - if (own_buffer) { - a->flags.how = 2; - jl_gc_track_malloced_array(ct->ptls, a); - jl_gc_count_allocd(nel*elsz); - } - else { - a->flags.how = 0; - } - - assert(ndims != 1); // handled above - if (a->flags.ndims != ndims) + size_t nel; + if (jl_array_validate_dims(&nel, ndims, dims)) + jl_exceptionf(jl_argumenterror_type, "invalid Array dimensions: too large for system address width"); + if (*(size_t*)jl_tparam1(atype) != ndims) jl_exceptionf(jl_argumenterror_type, "invalid Array dimensions"); - memcpy(&a->nrows, dims, ndims * sizeof(size_t)); - return a; -} - -JL_DLLEXPORT jl_array_t *jl_new_array(jl_value_t *atype, jl_value_t *_dims) -{ - size_t ndims = jl_nfields(_dims); - assert(is_ntuple_long(_dims)); - return _new_array(atype, ndims, (size_t*)_dims); -} - -JL_DLLEXPORT jl_array_t *jl_alloc_array_1d(jl_value_t *atype, size_t nr) -{ - return _new_array(atype, 1, &nr); -} - -JL_DLLEXPORT jl_array_t *jl_alloc_array_2d(jl_value_t *atype, size_t nr, - size_t nc) -{ - size_t d[2] = {nr, nc}; - return _new_array(atype, 2, &d[0]); -} - -JL_DLLEXPORT jl_array_t *jl_alloc_array_3d(jl_value_t *atype, size_t nr, - size_t nc, size_t z) -{ - size_t d[3] = {nr, nc, z}; - return _new_array(atype, 3, &d[0]); -} - -JL_DLLEXPORT jl_array_t *jl_pchar_to_array(const char *str, size_t len) -{ - jl_array_t *a = jl_alloc_array_1d(jl_array_uint8_type, len); - memcpy(a->data, str, len); + jl_value_t *mtype = jl_field_type_concrete((jl_datatype_t*)jl_field_type_concrete((jl_datatype_t*)atype, 0), 1); + jl_genericmemory_t *mem = jl_ptr_to_genericmemory(mtype, data, nel, own_buffer); + JL_GC_PUSH1(&mem); + jl_array_t *a = _new_array(atype, mem, ((jl_datatype_t*)mtype)->layout, ndims, dims); + JL_GC_POP(); return a; } JL_DLLEXPORT jl_value_t *jl_array_to_string(jl_array_t *a) { - size_t len = jl_array_len(a); + size_t len = jl_array_nrows(a); // only for Vector if (len == 0) { // this may seem like purely an optimization (which it also is), but it // also ensures that calling `String(a)` doesn't corrupt a previous // string also created the same way, where `a = StringVector(_)`. return jl_an_empty_string; } - if (a->flags.how == 3 && a->offset == 0 && a->elsize == 1 && - (jl_array_ndims(a) != 1 || - ((a->maxsize + sizeof(void*) + 1 <= GC_MAX_SZCLASS) == (len + sizeof(void*) + 1 <= GC_MAX_SZCLASS)))) { - jl_value_t *o = jl_array_data_owner(a); - if (jl_is_string(o)) { - a->flags.isshared = 1; - *(size_t*)o = len; - a->nrows = 0; - a->length = 0; - a->maxsize = 0; - if (jl_string_data(o)[len] != '\0') - jl_string_data(o)[len] = '\0'; - return o; - } - } - a->nrows = 0; - a->length = 0; - a->maxsize = 0; - return jl_pchar_to_string((const char*)jl_array_data(a), len); + jl_value_t *str; + if (a->ref.ptr_or_offset == a->ref.mem->ptr) + str = jl_genericmemory_to_string(a->ref.mem, len); + else + str = jl_pchar_to_string(jl_array_data(a, char), len); + a->ref.mem = (jl_genericmemory_t*)((jl_datatype_t*)jl_memory_uint8_type)->instance; + a->ref.ptr_or_offset = a->ref.mem->ptr; + a->dimsize[0] = 0; + return str; } -JL_DLLEXPORT jl_value_t *jl_alloc_string(size_t len) +JL_DLLEXPORT jl_array_t *jl_alloc_array_1d(jl_value_t *atype, size_t nr) { - if (len == 0) - return jl_an_empty_string; - size_t sz = sizeof(size_t) + len + 1; // add space for trailing \nul protector and size - if (sz < len) // overflow - jl_throw(jl_memory_exception); - jl_task_t *ct = jl_current_task; - jl_value_t *s; - jl_ptls_t ptls = ct->ptls; - const size_t allocsz = sz + sizeof(jl_taggedvalue_t); - if (sz <= GC_MAX_SZCLASS) { - int pool_id = jl_gc_szclass_align8(allocsz); - jl_gc_pool_t *p = &ptls->heap.norm_pools[pool_id]; - int osize = jl_gc_sizeclasses[pool_id]; - // We call `jl_gc_pool_alloc_noinline` instead of `jl_gc_pool_alloc` to avoid double-counting in - // the Allocations Profiler. (See https://github.com/JuliaLang/julia/pull/43868 for more details.) - s = jl_gc_pool_alloc_noinline(ptls, (char*)p - (char*)ptls, osize); - } - else { - if (allocsz < sz) // overflow in adding offs, size was "negative" - jl_throw(jl_memory_exception); - s = jl_gc_big_alloc_noinline(ptls, allocsz); - } - jl_set_typetagof(s, jl_string_tag, 0); - maybe_record_alloc_to_profile(s, len, jl_string_type); - *(size_t*)s = len; - jl_string_data(s)[len] = 0; - return s; + return new_array(atype, 1, &nr); } -JL_DLLEXPORT jl_value_t *jl_pchar_to_string(const char *str, size_t len) +JL_DLLEXPORT jl_array_t *jl_alloc_array_2d(jl_value_t *atype, size_t nr, size_t nc) { - jl_value_t *s = jl_alloc_string(len); - if (len > 0) - memcpy(jl_string_data(s), str, len); - return s; + size_t dims[2] = {nr, nc}; + return new_array(atype, 2, &dims[0]); } -JL_DLLEXPORT jl_value_t *jl_cstr_to_string(const char *str) +JL_DLLEXPORT jl_array_t *jl_alloc_array_3d(jl_value_t *atype, size_t nr, size_t nc, size_t z) { - return jl_pchar_to_string(str, strlen(str)); + size_t dims[3] = {nr, nc, z}; + return new_array(atype, 3, &dims[0]); +} + +JL_DLLEXPORT jl_array_t *jl_alloc_array_nd(jl_value_t *atype, size_t *dims, size_t ndims) +{ + return new_array(atype, ndims, dims); +} + +JL_DLLEXPORT jl_array_t *jl_pchar_to_array(const char *str, size_t len) +{ + jl_array_t *a = jl_alloc_array_1d(jl_array_uint8_type, len); + assert(jl_array_data(a, char)); + memcpy(jl_array_data(a, char), str, len); + return a; } JL_DLLEXPORT jl_array_t *jl_alloc_vec_any(size_t n) @@ -555,744 +195,164 @@ JL_DLLEXPORT jl_value_t *jl_apply_array_type(jl_value_t *type, size_t dim) return ret; } -// array primitives ----------------------------------------------------------- - -JL_DLLEXPORT jl_value_t *jl_ptrarrayref(jl_array_t *a JL_PROPAGATES_ROOT, size_t i) JL_NOTSAFEPOINT -{ - assert(i < jl_array_len(a)); - assert(a->flags.ptrarray); - jl_value_t *elt = jl_atomic_load_relaxed(((_Atomic(jl_value_t*)*)a->data) + i); - if (elt == NULL) - jl_throw(jl_undefref_exception); - return elt; -} - - -JL_DLLEXPORT jl_value_t *jl_arrayref(jl_array_t *a, size_t i) -{ - if (a->flags.ptrarray) - return jl_ptrarrayref(a, i); - assert(i < jl_array_len(a)); - jl_value_t *eltype = (jl_value_t*)jl_tparam0(jl_typeof(a)); - if (jl_is_uniontype(eltype)) { - // isbits union selector bytes are always stored directly after the last array element - uint8_t sel = jl_array_typetagdata(a)[i]; - eltype = jl_nth_union_component(eltype, sel); - if (jl_is_datatype_singleton((jl_datatype_t*)eltype)) - return ((jl_datatype_t*)eltype)->instance; - } - jl_value_t *r = undefref_check((jl_datatype_t*)eltype, jl_new_bits(eltype, &((char*)a->data)[i * a->elsize])); - if (__unlikely(r == NULL)) - jl_throw(jl_undefref_exception); - return r; -} - -JL_DLLEXPORT int jl_array_isassigned(jl_array_t *a, size_t i) -{ - if (a->flags.ptrarray) { - return jl_atomic_load_relaxed(((_Atomic(jl_value_t*)*)jl_array_data(a)) + i) != NULL; - } - else if (a->flags.hasptr) { - jl_datatype_t *eltype = (jl_datatype_t*)jl_tparam0(jl_typeof(a)); - assert(eltype->layout->first_ptr >= 0); - jl_value_t **elem = (jl_value_t**)((char*)a->data + i * a->elsize); - return elem[eltype->layout->first_ptr] != NULL; - } - return 1; -} - -JL_DLLEXPORT void jl_arrayset(jl_array_t *a JL_ROOTING_ARGUMENT, jl_value_t *rhs JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED, size_t i) -{ - assert(i < jl_array_len(a)); - jl_value_t *eltype = jl_tparam0(jl_typeof(a)); - if (eltype != (jl_value_t*)jl_any_type) { - JL_GC_PUSH1(&rhs); - if (!jl_isa(rhs, eltype)) - jl_type_error("arrayset", eltype, rhs); - JL_GC_POP(); - } - if (!a->flags.ptrarray) { - int hasptr; - if (jl_is_uniontype(eltype)) { - uint8_t *psel = &((uint8_t*)jl_array_typetagdata(a))[i]; - unsigned nth = 0; - if (!jl_find_union_component(eltype, jl_typeof(rhs), &nth)) - assert(0 && "invalid arrayset to isbits union"); - *psel = nth; - if (jl_is_datatype_singleton((jl_datatype_t*)jl_typeof(rhs))) - return; - hasptr = 0; - } - else { - hasptr = a->flags.hasptr; - } - arrayassign_safe(hasptr, jl_array_owner(a), &((char*)a->data)[i * a->elsize], rhs, a->elsize); - } - else { - jl_atomic_store_release(((_Atomic(jl_value_t*)*)a->data) + i, rhs); - jl_gc_wb(jl_array_owner(a), rhs); - } -} - -JL_DLLEXPORT void jl_arrayunset(jl_array_t *a, size_t i) -{ - if (i >= jl_array_len(a)) - jl_bounds_error_int((jl_value_t*)a, i + 1); - if (a->flags.ptrarray) - jl_atomic_store_relaxed(((_Atomic(jl_value_t*)*)a->data) + i, NULL); - else if (a->flags.hasptr) { - size_t elsize = a->elsize; - jl_assume(elsize >= sizeof(void*) && elsize % sizeof(void*) == 0); - memset((char*)a->data + elsize * i, 0, elsize); - } -} - -// at this size and bigger, allocate resized array data with malloc directly -// instead of managing them separately as gc objects -#define MALLOC_THRESH 1048576 - -// Resize the buffer to a max size of `newlen` -// The buffer can either be newly allocated or realloc'd, the return -// value is 1 if a new buffer is allocated and 0 if it is realloc'd. -// the caller needs to take care of moving the data from the old buffer -// to the new one if necessary. -// When this function returns, the `->data` pointer always points to -// the **beginning** of the new buffer. -static int NOINLINE array_resize_buffer(jl_array_t *a, size_t newlen) -{ - jl_task_t *ct = jl_current_task; - assert(!a->flags.isshared || a->flags.how == 3); - size_t elsz = a->elsize; - size_t nbytes = newlen * elsz; - size_t oldnbytes = a->maxsize * elsz; - size_t oldoffsnb = a->offset * elsz; - size_t oldlen = a->nrows; - int isbitsunion = jl_array_isbitsunion(a); - assert(nbytes >= oldnbytes); - if (JL_ARRAY_MEMDEBUG_TERMINATOR && elsz == 1 && !isbitsunion) { - nbytes++; - oldnbytes++; - } - if (isbitsunion) { - nbytes += newlen; - oldnbytes += a->maxsize; - } - int newbuf = 0; - if (a->flags.how == 2) { - // already malloc'd - use realloc - char *olddata = (char*)a->data - oldoffsnb; - a->data = jl_gc_managed_realloc(olddata, nbytes, oldnbytes, - a->flags.isaligned, (jl_value_t*)a); - } - else if (a->flags.how == 3 && jl_is_string(jl_array_data_owner(a)) && !isbitsunion) { - // if data is in a String, keep it that way - jl_value_t *s; - if (a->flags.isshared) { - s = jl_alloc_string(nbytes - (JL_ARRAY_MEMDEBUG_TERMINATOR && elsz == 1)); - newbuf = 1; - } - else { - s = jl_gc_realloc_string(jl_array_data_owner(a), nbytes - (JL_ARRAY_MEMDEBUG_TERMINATOR && elsz == 1)); - } - jl_array_data_owner(a) = s; - jl_gc_wb(a, s); - a->data = jl_string_data(s); - } - else { - newbuf = 1; - if (nbytes >= MALLOC_THRESH) { - a->data = jl_gc_managed_malloc(nbytes); - jl_gc_track_malloced_array(ct->ptls, a); - a->flags.how = 2; - a->flags.isaligned = 1; - } - else { - a->data = jl_gc_alloc_buf(ct->ptls, nbytes); - a->flags.how = 1; - jl_gc_wb_buf(a, a->data, nbytes); - } - } - if (JL_ARRAY_MEMDEBUG_TERMINATOR && elsz == 1 && !isbitsunion) { - memset((char*)a->data + oldnbytes - 1, 0xfe, nbytes - oldnbytes + 1); - msan_allocated_memory((char*)a->data + oldnbytes - 1, nbytes - oldnbytes + 1); - } - (void)oldlen; - assert(oldlen == a->nrows && - "Race condition detected: recursive resizing on the same array."); - a->flags.isshared = 0; - a->maxsize = newlen; - return newbuf; -} - -static void NOINLINE array_try_unshare(jl_array_t *a) -{ - if (a->flags.isshared) { - if (a->flags.how != 3) - jl_error("cannot resize array with shared data"); - // allow resizing when data is shared with a String - if (jl_is_string(jl_array_data_owner(a))) - return; - assert(a->offset == 0); - size_t len = a->maxsize; - size_t nbytes = len * a->elsize; - if (jl_array_isbitsunion(a)) { - nbytes += len; - } - char *olddata = (char*)a->data; - int newbuf = array_resize_buffer(a, len); - assert(newbuf); - (void)newbuf; - memcpy(a->data, olddata, nbytes); - } -} - -size_t overallocation(size_t maxsize) -{ - if (maxsize < 8) - return 8; - // compute maxsize = maxsize + 4*maxsize^(7/8) + maxsize/8 - // for small n, we grow faster than O(n) - // for large n, we grow at O(n/8) - // and as we reach O(memory) for memory>>1MB, - // this means we end by adding about 10% of memory each time - int exp2 = sizeof(maxsize) * 8 - -#ifdef _P64 - __builtin_clzll(maxsize); -#else - __builtin_clz(maxsize); -#endif - maxsize += ((size_t)1 << (exp2 * 7 / 8)) * 4 + maxsize / 8; - return maxsize; -} - -STATIC_INLINE void jl_array_grow_at_beg(jl_array_t *a, size_t idx, size_t inc, - size_t n) +JL_DLLEXPORT void jl_array_grow_end(jl_array_t *a, size_t inc) { - // designed to handle the case of growing and shrinking at both ends - if (__unlikely(a->flags.isshared)) { - if (a->flags.how != 3) - jl_error("cannot resize array with shared data"); - if (inc == 0) { - // If inc > 0, it will always trigger the slow path and unshare the - // buffer - array_try_unshare(a); - return; - } - } + size_t n = jl_array_nrows(a); + size_t elsz = jl_array_elsize(a); + char *data = jl_array_data(a,char); + jl_value_t *mtype = (jl_value_t*)jl_typetagof(a->ref.mem); + int isbitsunion = jl_genericmemory_isbitsunion(a->ref.mem); size_t newnrows = n + inc; - size_t elsz = a->elsize; - size_t nbinc = inc * elsz; - char *data = (char*)a->data; - char *newdata; - char *typetagdata; - char *newtypetagdata = NULL; - int isbitsunion = jl_array_isbitsunion(a); - if (isbitsunion) typetagdata = jl_array_typetagdata(a); - if (a->offset >= inc) { - // already have enough space in a->offset - newdata = data - nbinc; - a->offset -= inc; - if (isbitsunion) newtypetagdata = typetagdata - inc; - if (idx > 0) { - // inserting new elements after 1st element - memmove_safe(a->flags.hasptr, newdata, data, idx * elsz); - if (isbitsunion) { - memmove(newtypetagdata, typetagdata, idx); - memset(newtypetagdata + idx, 0, inc); - } - } - } - else { - // not enough room for requested growth from existing a->offset - size_t oldoffset = a->offset; - size_t oldoffsnb = oldoffset * elsz; - size_t oldmaxsize = a->maxsize; - size_t nb1 = idx * elsz; - if (inc > (a->maxsize - n) / 2 - (a->maxsize - n) / 20) { - // not enough room for requested growth from end of array - size_t newlen = inc * 2; - while (n + 2 * inc > newlen - a->offset) - newlen *= 2; - size_t newmaxsize = overallocation(a->maxsize); - if (newlen < newmaxsize) - newlen = newmaxsize; - size_t newoffset = (newlen - newnrows) / 2; - if (!array_resize_buffer(a, newlen)) { - data = (char*)a->data + oldoffsnb; - } - newdata = (char*)a->data + newoffset * elsz; - if (isbitsunion) { - typetagdata = data + (oldmaxsize - oldoffset) * elsz + oldoffset; - newtypetagdata = newdata + (a->maxsize - newoffset) * elsz + newoffset; - memmove(newtypetagdata, typetagdata, idx); - memset(newtypetagdata + idx, 0, inc); - memmove(newtypetagdata + idx + inc, typetagdata + idx, n - idx); - } - // We could use memcpy if resizing allocates a new buffer, - // hopefully it's not a particularly important optimization. - if (idx > 0 && newdata < data) { - memmove_safe(a->flags.hasptr, newdata, data, nb1); - } - memmove_safe(a->flags.hasptr, newdata + nbinc + nb1, data + nb1, n * elsz - nb1); - if (idx > 0 && newdata > data) { - memmove_safe(a->flags.hasptr, newdata, data, nb1); - } - a->offset = newoffset; - } - else { - // use extra space between a->nrows & a->maxsize - a->offset = (a->maxsize - newnrows) / 2; - newdata = data - oldoffsnb + a->offset * elsz; - if (isbitsunion) newtypetagdata = newdata + (a->maxsize - a->offset) * elsz + a->offset; - if (idx > 0 && newdata < data) { - memmove_safe(a->flags.hasptr, newdata, data, nb1); - if (isbitsunion) { - memmove(newtypetagdata, typetagdata, idx); - memset(newtypetagdata + idx, 0, inc); - } - } - memmove_safe(a->flags.hasptr, newdata + nbinc + nb1, data + nb1, n * elsz - nb1); - if (isbitsunion) memmove(newtypetagdata + idx + inc, typetagdata + idx, n - idx); - if (idx > 0 && newdata > data) { - memmove_safe(a->flags.hasptr, newdata, data, nb1); - if (isbitsunion) { - memmove(newtypetagdata, typetagdata, idx); - memset(newtypetagdata + idx, 0, inc); - } - } - } - } - a->length = newnrows; - a->nrows = newnrows; - a->data = newdata; - if (jl_is_array_zeroinit(a)) { - memset(newdata + idx * elsz, 0, nbinc); - } - if (newtypetagdata) { - memset(newtypetagdata + idx, 0, inc); - } -} - -STATIC_INLINE void jl_array_grow_at_end(jl_array_t *a, size_t idx, - size_t inc, size_t n) -{ - // optimized for the case of only growing and shrinking at the end - if (__unlikely(a->flags.isshared)) { - if (a->flags.how != 3) - jl_error("cannot resize array with shared data"); - if (inc == 0) { - // If inc > 0, it will always trigger the slow path and unshare the - // buffer - array_try_unshare(a); - return; - } + if (!isbitsunion && elsz == 0) { + jl_genericmemory_t *newmem = jl_alloc_genericmemory(mtype, MAXINTVAL - 1); + a->ref.mem = newmem; + jl_gc_wb(a, newmem); + a->dimsize[0] = newnrows; + return; } - size_t elsz = a->elsize; - char *data = (char*)a->data; - char *typetagdata; - char *newtypetagdata; - int isbitsunion = jl_array_isbitsunion(a); - if (isbitsunion) typetagdata = jl_array_typetagdata(a); - int has_gap = n > idx; - size_t reqmaxsize = a->offset + n + inc; - if (__unlikely(reqmaxsize > a->maxsize)) { - size_t nb1 = idx * elsz; - size_t nbinc = inc * elsz; - // grow either by our computed overallocation factor or exactly the requested size, - // whichever is larger - size_t newmaxsize = overallocation(a->maxsize); + size_t oldoffset = isbitsunion ? (size_t)data : (data - (char*)a->ref.mem->ptr) / elsz; + if (isbitsunion) + data = (char*)a->ref.mem->ptr + oldoffset * elsz; + size_t oldmaxsize = a->ref.mem->length; + size_t reqmaxsize = oldoffset + newnrows; + if (__unlikely(reqmaxsize > oldmaxsize)) { + size_t newmaxsize; + if (oldmaxsize < 4) // typical sequence: 0, // 4, // 6, 9, 13, 19, 28, 42, // 50, 60, 72, ... + newmaxsize = 4; + else if (oldmaxsize < 48) + newmaxsize = oldmaxsize*3/2; // grow by 50% + else + newmaxsize = oldmaxsize*6/5; // grow by 20% if (newmaxsize < reqmaxsize) newmaxsize = reqmaxsize; - size_t oldmaxsize = a->maxsize; - int newbuf = array_resize_buffer(a, newmaxsize); - char *newdata = (char*)a->data + a->offset * elsz; - if (isbitsunion) newtypetagdata = newdata + (a->maxsize - a->offset) * elsz + a->offset; - if (newbuf) { - memcpy(newdata, data, nb1); - if (isbitsunion) { - memcpy(newtypetagdata, typetagdata, idx); - if (has_gap) memcpy(newtypetagdata + idx + inc, typetagdata + idx, n - idx); - memset(newtypetagdata + idx, 0, inc); - } - if (has_gap) memcpy(newdata + nb1 + nbinc, data + nb1, n * elsz - nb1); - } - else { - if (isbitsunion) { - typetagdata = newdata + (oldmaxsize - a->offset) * elsz + a->offset; - if (has_gap) memmove(newtypetagdata + idx + inc, typetagdata + idx, n - idx); - memmove(newtypetagdata, typetagdata, idx); - memset(newtypetagdata + idx, 0, inc); - } - if (has_gap) memmove_safe(a->flags.hasptr, newdata + nb1 + nbinc, newdata + nb1, n * elsz - nb1); - } - a->data = data = newdata; - } - else if (has_gap) { + // TODO: round this up to newmaxsize < GC_MAX_SZCLASS ? jl_gc_sizeclasses[jl_gc_szclass(newmaxsize)] : LLT_ALIGN(newmaxsize, 4096), after accounting for the object header (24 bytes) + jl_genericmemory_t *newmem = jl_alloc_genericmemory(mtype, newmaxsize); + char *newdata = (char*)newmem->ptr + oldoffset * elsz; + memcpy(newdata, data, n * elsz); if (isbitsunion) { - memmove(typetagdata + idx + inc, typetagdata + idx, n - idx); - memset(typetagdata + idx, 0, inc); + char *typetagdata = jl_array_typetagdata(a); + char *newtypetagdata = (char*)newmem->ptr + newmaxsize * elsz + oldoffset; + memcpy(newtypetagdata, typetagdata, n); } - size_t nb1 = idx * elsz; - memmove_safe(a->flags.hasptr, data + nb1 + inc * elsz, data + nb1, n * elsz - nb1); - } - else { - // there was enough room for requested growth already in a->maxsize + a->ref.mem = newmem; + jl_gc_wb(a, newmem); if (isbitsunion) - memset(typetagdata + idx, 0, inc); - } - size_t newnrows = n + inc; - a->length = newnrows; - a->nrows = newnrows; - if (jl_is_array_zeroinit(a)) { - memset(data + idx * elsz, 0, inc * elsz); + a->ref.ptr_or_offset = (void*)oldoffset; + else + a->ref.ptr_or_offset = newdata; } + a->dimsize[0] = newnrows; } -JL_DLLEXPORT void jl_array_grow_at(jl_array_t *a, ssize_t idx, size_t inc) -{ - // No need to explicitly unshare. - // Shared arrays are guaranteed to trigger the slow path for growing. - size_t n = jl_array_nrows(a); - if (idx < 0 || idx > n) - jl_bounds_error_int((jl_value_t*)a, idx + 1); - if (idx + 1 < n / 2) { - jl_array_grow_at_beg(a, idx, inc, n); - } - else { - jl_array_grow_at_end(a, idx, inc, n); - } -} - -JL_DLLEXPORT void jl_array_grow_end(jl_array_t *a, size_t inc) -{ - size_t n = jl_array_nrows(a); - jl_array_grow_at_end(a, n, inc, n); -} - -JL_DLLEXPORT void jl_array_grow_beg(jl_array_t *a, size_t inc) -{ - size_t n = jl_array_nrows(a); - jl_array_grow_at_beg(a, 0, inc, n); -} - -STATIC_INLINE void jl_array_shrink(jl_array_t *a, size_t dec) -{ - //if we don't manage this array return - if (a->flags.how == 0) return; - - size_t elsz = a->elsize; - size_t newbytes = (a->maxsize - dec) * a->elsize; - size_t oldnbytes = (a->maxsize) * a->elsize; - int isbitsunion = jl_array_isbitsunion(a); - if (isbitsunion) { - newbytes += a->maxsize - dec; - oldnbytes += a->maxsize; - } - - if (JL_ARRAY_MEMDEBUG_TERMINATOR && elsz == 1 && !isbitsunion) { - newbytes++; - oldnbytes++; - } - char *originalptr = ((char*) a->data) - a->offset * a->elsize; - if (a->flags.how == 1) { - //this is a julia-allocated buffer that needs to be marked - char *typetagdata; - char *newtypetagdata; - if (isbitsunion) { - typetagdata = (char*)malloc_s(a->nrows); - memcpy(typetagdata, jl_array_typetagdata(a), a->nrows); - } - jl_task_t *ct = jl_current_task; - char *originaldata = (char*) a->data - a->offset * a->elsize; - char *newdata = (char*)jl_gc_alloc_buf(ct->ptls, newbytes); - jl_gc_wb_buf(a, newdata, newbytes); - a->maxsize -= dec; - if (isbitsunion) { - newtypetagdata = jl_array_typetagdata(a); - memcpy(newtypetagdata, typetagdata, a->nrows); - free(typetagdata); - } - memcpy(newdata, originaldata, newbytes); - a->data = newdata + a->offset * elsz; - } - else if (a->flags.how == 2) { - //malloc-allocated pointer this array object manages - char *typetagdata; - char *newtypetagdata; - if (isbitsunion) { - typetagdata = (char*)malloc_s(a->nrows); - memcpy(typetagdata, jl_array_typetagdata(a), a->nrows); - } - size_t oldoffsnb = a->offset * elsz; - a->data = ((char*)jl_gc_managed_realloc(originalptr, newbytes, oldnbytes, - a->flags.isaligned, (jl_value_t*) a)) + oldoffsnb; - a->maxsize -= dec; - if (isbitsunion) { - newtypetagdata = jl_array_typetagdata(a); - memcpy(newtypetagdata, typetagdata, a->nrows); - free(typetagdata); - } - } - else if (a->flags.how == 3) { - //this has has a pointer to the object that owns the data - } -} - -static size_t jl_array_limit_offset(jl_array_t *a, size_t offset) -{ - // make sure offset doesn't grow forever due to deleting at beginning - // and growing at end - if (offset >= 13 * a->maxsize / 20) - offset = 17 * (a->maxsize - a->nrows) / 100; -#ifdef _P64 - while (offset > (size_t)UINT32_MAX) { - offset /= 2; - } -#endif - return offset; -} - -STATIC_INLINE void jl_array_del_at_beg(jl_array_t *a, size_t idx, size_t dec, - size_t n) -{ - // no error checking - // assume inbounds, assume unshared - size_t elsz = a->elsize; - size_t offset = a->offset; - int isbitsunion = jl_array_isbitsunion(a); - offset += dec; - a->length = n - dec; - a->nrows = n - dec; - size_t newoffs = jl_array_limit_offset(a, offset); - assert(newoffs <= offset); - size_t nbdec = dec * elsz; - if (__unlikely(newoffs != offset) || idx > 0) { - char *olddata = (char*)a->data; - char *newdata = olddata - (a->offset - newoffs) * elsz; - char *typetagdata = NULL; - char *newtypetagdata = NULL; - if (isbitsunion) { - typetagdata = jl_array_typetagdata(a); - newtypetagdata = typetagdata - (a->offset - newoffs); - } - - size_t nb1 = idx * elsz; // size in bytes of the first block - size_t nbtotal = a->nrows * elsz; // size in bytes of the new array - // Implicit '\0' for byte arrays - if (JL_ARRAY_MEMDEBUG_TERMINATOR && elsz == 1 && !isbitsunion) - nbtotal++; - if (idx > 0) { - memmove_safe(a->flags.hasptr, newdata, olddata, nb1); - if (isbitsunion) memmove(newtypetagdata, typetagdata, idx); - } - // Move the rest of the data if the offset changed - if (newoffs != offset) { - memmove_safe(a->flags.hasptr, newdata + nb1, olddata + nb1 + nbdec, nbtotal - nb1); - if (isbitsunion) memmove(newtypetagdata + idx, typetagdata + idx + dec, a->nrows - idx); - } - a->data = newdata; - } - else { - char *data = (char*)a->data; - a->data = data + nbdec; - } - a->offset = newoffs; -} - -STATIC_INLINE void jl_array_del_at_end(jl_array_t *a, size_t idx, size_t dec, - size_t n) +JL_DLLEXPORT void jl_array_del_end(jl_array_t *a, size_t dec) { - // no error checking // assume inbounds, assume unshared - char *data = (char*)a->data; - size_t elsz = a->elsize; - int isbitsunion = jl_array_isbitsunion(a); - size_t last = idx + dec; - if (n > last) { - memmove_safe(a->flags.hasptr, data + idx * elsz, data + last * elsz, (n - last) * elsz); - if (isbitsunion) { - char *typetagdata = jl_array_typetagdata(a); - memmove(typetagdata + idx, typetagdata + last, n - last); - } - } - n -= dec; - if (JL_ARRAY_MEMDEBUG_TERMINATOR && elsz == 1 && !isbitsunion) { - data[n] = 0xfe; - msan_allocated_memory(&data[n], 1); - } - a->nrows = n; - a->length = n; -} - -JL_DLLEXPORT void jl_array_del_at(jl_array_t *a, ssize_t idx, size_t dec) -{ - size_t n = jl_array_nrows(a); - size_t last = idx + dec; - if (__unlikely(idx < 0)) - jl_bounds_error_int((jl_value_t*)a, idx + 1); - if (__unlikely(last > n)) - jl_bounds_error_int((jl_value_t*)a, last); - // The unsharing needs to happen before we modify the buffer - if (__unlikely(a->flags.isshared)) - array_try_unshare(a); - if (idx < n - last) { - jl_array_del_at_beg(a, idx, dec, n); - } - else { - jl_array_del_at_end(a, idx, dec, n); - } -} - -JL_DLLEXPORT void jl_array_del_beg(jl_array_t *a, size_t dec) -{ size_t n = jl_array_nrows(a); - if (__unlikely(dec > n)) - jl_bounds_error_int((jl_value_t*)a, dec); - if (__unlikely(a->flags.isshared)) - array_try_unshare(a); - if (dec == 0) + if (__unlikely(n < dec)) + jl_bounds_error_int((jl_value_t*)a, 0); + if (__unlikely(dec == 0)) return; - jl_array_del_at_beg(a, 0, dec, n); + n -= dec; + a->dimsize[0] = n; + // don't leave behind deleted data + if (jl_is_genericmemory_zeroinit(a->ref.mem) && !jl_genericmemory_isbitsunion(a->ref.mem)) { + size_t elsz = jl_array_elsize(a); + memset(jl_array_data(a,char) + n * elsz, 0, elsz * dec); + } } -JL_DLLEXPORT void jl_array_del_end(jl_array_t *a, size_t dec) +JL_DLLEXPORT void jl_array_ptr_1d_push(jl_array_t *a, jl_value_t *item) { + assert(jl_typetagis(a, jl_array_any_type)); + jl_array_grow_end(a, 1); size_t n = jl_array_nrows(a); - if (__unlikely(n < dec)) - jl_bounds_error_int((jl_value_t*)a, 0); - if (__unlikely(a->flags.isshared)) - array_try_unshare(a); - if (dec == 0) - return; - jl_array_del_at_end(a, n - dec, dec, n); + jl_array_ptr_set(a, n - 1, item); } -JL_DLLEXPORT void jl_array_sizehint(jl_array_t *a, size_t sz) +JL_DLLEXPORT void jl_array_ptr_1d_append(jl_array_t *a, jl_array_t *a2) { + assert(jl_typetagis(a, jl_array_any_type)); + assert(jl_typetagis(a2, jl_array_any_type)); + size_t i; size_t n = jl_array_nrows(a); - - size_t min = a->offset + a->length; - sz = (sz < min) ? min : sz; - - if (sz <= a->maxsize) { - size_t dec = a->maxsize - sz; - //if we don't save at least an eighth of maxsize then its not worth it to shrink - if (dec <= a->maxsize / 8) return; - jl_array_shrink(a, dec); - } - else { - size_t inc = sz - n; - jl_array_grow_end(a, inc); - - a->nrows = n; - a->length = n; + size_t n2 = jl_array_nrows(a2); + jl_array_grow_end(a, n2); + for (i = 0; i < n2; i++) { + jl_array_ptr_set(a, n + i, jl_array_ptr_ref(a2, i)); } } +JL_DLLEXPORT jl_genericmemory_t *jl_genericmemory_copy_slice(jl_genericmemory_t *mem, void *data, size_t len); + JL_DLLEXPORT jl_array_t *jl_array_copy(jl_array_t *ary) { - size_t elsz = ary->elsize; size_t len = jl_array_len(ary); - int isunion = jl_is_uniontype(jl_tparam0(jl_typeof(ary))); - jl_array_t *new_ary = _new_array_(jl_typeof(ary), jl_array_ndims(ary), - &ary->nrows, !ary->flags.ptrarray, - ary->flags.hasptr, isunion, 0, elsz); - memcpy(new_ary->data, ary->data, len * elsz); - // ensure isbits union arrays copy their selector bytes correctly - if (jl_array_isbitsunion(ary)) - memcpy(jl_array_typetagdata(new_ary), jl_array_typetagdata(ary), len); + jl_genericmemory_t *mem = jl_genericmemory_copy_slice(ary->ref.mem, ary->ref.ptr_or_offset, len); + JL_GC_PUSH1(&mem); + jl_array_t *new_ary = _new_array((jl_value_t*)jl_typetagof(ary), mem, ((jl_datatype_t*)jl_typetagof(ary->ref.mem))->layout, jl_array_ndims(ary), &ary->dimsize[0]); + JL_GC_POP(); return new_ary; } -// Copy element by element until we hit a young object, at which point -// we can finish by using `memmove`. -static NOINLINE ssize_t jl_array_ptr_copy_forward(jl_value_t *owner, - void **src_p, void **dest_p, - ssize_t n) JL_NOTSAFEPOINT +JL_DLLEXPORT jl_value_t *jl_alloc_string(size_t len) { - _Atomic(void*) *src_pa = (_Atomic(void*)*)src_p; - _Atomic(void*) *dest_pa = (_Atomic(void*)*)dest_p; - for (ssize_t i = 0; i < n; i++) { - void *val = jl_atomic_load_relaxed(src_pa + i); - jl_atomic_store_release(dest_pa + i, val); - // `val` is young or old-unmarked - if (val && !(jl_astaggedvalue(val)->bits.gc & GC_MARKED)) { - jl_gc_queue_root(owner); - return i; - } + if (len == 0) + return jl_an_empty_string; + size_t sz = sizeof(size_t) + len + 1; // add space for trailing \nul protector and size + if (sz < len) // overflow + jl_throw(jl_memory_exception); + jl_task_t *ct = jl_current_task; + jl_value_t *s; + jl_ptls_t ptls = ct->ptls; + const size_t allocsz = sz + sizeof(jl_taggedvalue_t); + if (sz <= GC_MAX_SZCLASS) { + int pool_id = jl_gc_szclass_align8(allocsz); + jl_gc_pool_t *p = &ptls->heap.norm_pools[pool_id]; + int osize = jl_gc_sizeclasses[pool_id]; + // We call `jl_gc_pool_alloc_noinline` instead of `jl_gc_pool_alloc` to avoid double-counting in + // the Allocations Profiler. (See https://github.com/JuliaLang/julia/pull/43868 for more details.) + s = jl_gc_pool_alloc_noinline(ptls, (char*)p - (char*)ptls, osize); } - return n; -} - -static NOINLINE ssize_t jl_array_ptr_copy_backward(jl_value_t *owner, - void **src_p, void **dest_p, - ssize_t n) JL_NOTSAFEPOINT -{ - _Atomic(void*) *src_pa = (_Atomic(void*)*)src_p; - _Atomic(void*) *dest_pa = (_Atomic(void*)*)dest_p; - for (ssize_t i = 0; i < n; i++) { - void *val = jl_atomic_load_relaxed(src_pa + n - i - 1); - jl_atomic_store_release(dest_pa + n - i - 1, val); - // `val` is young or old-unmarked - if (val && !(jl_astaggedvalue(val)->bits.gc & GC_MARKED)) { - jl_gc_queue_root(owner); - return i; - } + else { + if (allocsz < sz) // overflow in adding offs, size was "negative" + jl_throw(jl_memory_exception); + s = jl_gc_big_alloc_noinline(ptls, allocsz); } - return n; + jl_set_typetagof(s, jl_string_tag, 0); + maybe_record_alloc_to_profile(s, len, jl_string_type); + *(size_t*)s = len; + jl_string_data(s)[len] = 0; + return s; } -// Unsafe, assume inbounds and that dest and src have the same eltype -JL_DLLEXPORT void jl_array_ptr_copy(jl_array_t *dest, void **dest_p, - jl_array_t *src, void **src_p, ssize_t n) JL_NOTSAFEPOINT +JL_DLLEXPORT jl_value_t *jl_pchar_to_string(const char *str, size_t len) { - assert(dest->flags.ptrarray && src->flags.ptrarray); - jl_value_t *owner = jl_array_owner(dest); - // Destination is old and doesn't refer to any young object - if (__unlikely(jl_astaggedvalue(owner)->bits.gc == GC_OLD_MARKED)) { - jl_value_t *src_owner = jl_array_owner(src); - // Source is young or being promoted or might refer to young objects - // (i.e. source is not an old object that doesn't have wb triggered) - if (jl_astaggedvalue(src_owner)->bits.gc != GC_OLD_MARKED) { - ssize_t done; - if (dest_p < src_p || dest_p > src_p + n) { - done = jl_array_ptr_copy_forward(owner, src_p, dest_p, n); - dest_p += done; - src_p += done; - } - else { - done = jl_array_ptr_copy_backward(owner, src_p, dest_p, n); - } - n -= done; - } - } - memmove_refs(dest_p, src_p, n); + jl_value_t *s = jl_alloc_string(len); + if (len > 0) + memcpy(jl_string_data(s), str, len); + return s; } -JL_DLLEXPORT void jl_array_ptr_1d_push(jl_array_t *a, jl_value_t *item) +JL_DLLEXPORT jl_value_t *jl_cstr_to_string(const char *str) { - assert(jl_typetagis(a, jl_array_any_type)); - jl_array_grow_end(a, 1); - size_t n = jl_array_nrows(a); - jl_array_ptr_set(a, n - 1, item); + return jl_pchar_to_string(str, strlen(str)); } -JL_DLLEXPORT void jl_array_ptr_1d_append(jl_array_t *a, jl_array_t *a2) -{ - assert(jl_typetagis(a, jl_array_any_type)); - assert(jl_typetagis(a2, jl_array_any_type)); - size_t i; - size_t n = jl_array_nrows(a); - size_t n2 = jl_array_nrows(a2); - jl_array_grow_end(a, n2); - for (i = 0; i < n2; i++) { - jl_array_ptr_set(a, n + i, jl_array_ptr_ref(a2, i)); - } -} -JL_DLLEXPORT jl_value_t *(jl_array_data_owner)(jl_array_t *a) JL_NOTSAFEPOINT +// deprecated and unused internally, but some packages (notably OrderedCollections.jl) have not yet started to use the modern Base.unsetindex API +JL_DLLEXPORT void jl_arrayunset(jl_array_t *a, size_t i) { - return jl_array_data_owner(a); + if (i >= jl_array_len(a)) + jl_bounds_error_int((jl_value_t*)a, i + 1); + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(a->ref.mem))->layout; + if (layout->flags.arrayelem_isboxed) { + jl_atomic_store_relaxed(jl_array_data(a,_Atomic(jl_value_t*)) + i, NULL); + } + else if (layout->first_ptr >= 0) { + size_t elsize = layout->size; + jl_assume(elsize >= sizeof(void*) && elsize % sizeof(void*) == 0); + memset(jl_array_data(a,char) + elsize * i, 0, elsize); + } } #ifdef __cplusplus diff --git a/src/ast.c b/src/ast.c index 600b1a229ea80..0971f4ab16e64 100644 --- a/src/ast.c +++ b/src/ast.c @@ -60,6 +60,7 @@ JL_DLLEXPORT jl_sym_t *jl_thunk_sym; JL_DLLEXPORT jl_sym_t *jl_foreigncall_sym; JL_DLLEXPORT jl_sym_t *jl_as_sym; JL_DLLEXPORT jl_sym_t *jl_global_sym; +JL_DLLEXPORT jl_sym_t *jl_local_sym; JL_DLLEXPORT jl_sym_t *jl_list_sym; JL_DLLEXPORT jl_sym_t *jl_dot_sym; JL_DLLEXPORT jl_sym_t *jl_newvar_sym; @@ -98,6 +99,8 @@ JL_DLLEXPORT jl_sym_t *jl_aliasscope_sym; JL_DLLEXPORT jl_sym_t *jl_popaliasscope_sym; JL_DLLEXPORT jl_sym_t *jl_optlevel_sym; JL_DLLEXPORT jl_sym_t *jl_thismodule_sym; +JL_DLLEXPORT jl_sym_t *jl_eval_sym; +JL_DLLEXPORT jl_sym_t *jl_include_sym; JL_DLLEXPORT jl_sym_t *jl_atom_sym; JL_DLLEXPORT jl_sym_t *jl_statement_sym; JL_DLLEXPORT jl_sym_t *jl_all_sym; @@ -150,32 +153,66 @@ static jl_value_t *scm_to_julia(fl_context_t *fl_ctx, value_t e, jl_module_t *mo static value_t julia_to_scm(fl_context_t *fl_ctx, jl_value_t *v); static jl_value_t *jl_expand_macros(jl_value_t *expr, jl_module_t *inmodule, struct macroctx_stack *macroctx, int onelevel, size_t world, int throw_load_error); +static jl_sym_t *scmsym_to_julia(fl_context_t *fl_ctx, value_t s) +{ + assert(issymbol(s)); + if (fl_isgensym(fl_ctx, s)) { + char gsname[16]; + char *n = uint2str(&gsname[1], sizeof(gsname)-1, + ((gensym_t*)ptr(s))->id, 10); + *(--n) = '#'; + return jl_symbol(n); + } + return jl_symbol(symbol_name(fl_ctx, s)); +} + static value_t fl_defined_julia_global(fl_context_t *fl_ctx, value_t *args, uint32_t nargs) { // tells whether a var is defined in and *by* the current module argcount(fl_ctx, "defined-julia-global", nargs, 1); (void)tosymbol(fl_ctx, args[0], "defined-julia-global"); jl_ast_context_t *ctx = jl_ast_ctx(fl_ctx); - jl_sym_t *var = jl_symbol(symbol_name(fl_ctx, args[0])); + jl_sym_t *var = scmsym_to_julia(fl_ctx, args[0]); jl_binding_t *b = jl_get_module_binding(ctx->module, var, 0); return (b != NULL && jl_atomic_load_relaxed(&b->owner) == b) ? fl_ctx->T : fl_ctx->F; } -static value_t fl_current_module_counter(fl_context_t *fl_ctx, value_t *args, uint32_t nargs) JL_NOTSAFEPOINT +static value_t fl_nothrow_julia_global(fl_context_t *fl_ctx, value_t *args, uint32_t nargs) { + // tells whether a var is defined, in the sense that accessing it is nothrow + // can take either a symbol or a module and a symbol jl_ast_context_t *ctx = jl_ast_ctx(fl_ctx); - assert(ctx->module); - return fixnum(jl_module_next_counter(ctx->module)); -} - -static value_t fl_julia_current_file(fl_context_t *fl_ctx, value_t *args, uint32_t nargs) JL_NOTSAFEPOINT -{ - return symbol(fl_ctx, jl_filename); + jl_module_t *mod = ctx->module; + jl_sym_t *var = NULL; + if (nargs == 1) { + (void)tosymbol(fl_ctx, args[0], "nothrow-julia-global"); + var = scmsym_to_julia(fl_ctx, args[0]); + } + else { + argcount(fl_ctx, "nothrow-julia-global", nargs, 2); + value_t argmod = args[0]; + if (iscvalue(argmod) && cv_class((cvalue_t*)ptr(argmod)) == jl_ast_ctx(fl_ctx)->jvtype) { + mod = *(jl_module_t**)cv_data((cvalue_t*)ptr(argmod)); + JL_GC_PROMISE_ROOTED(mod); + } else { + (void)tosymbol(fl_ctx, argmod, "nothrow-julia-global"); + if (scmsym_to_julia(fl_ctx, argmod) != jl_thismodule_sym) { + lerrorf(fl_ctx, fl_ctx->ArgError, "nothrow-julia-global: Unknown globalref module kind"); + } + } + (void)tosymbol(fl_ctx, args[1], "nothrow-julia-global"); + var = scmsym_to_julia(fl_ctx, args[1]); + } + jl_binding_t *b = jl_get_module_binding(mod, var, 0); + b = b ? jl_atomic_load_relaxed(&b->owner) : NULL; + return b != NULL && jl_atomic_load_relaxed(&b->value) != NULL ? fl_ctx->T : fl_ctx->F; } -static value_t fl_julia_current_line(fl_context_t *fl_ctx, value_t *args, uint32_t nargs) JL_NOTSAFEPOINT +static value_t fl_current_module_counter(fl_context_t *fl_ctx, value_t *args, uint32_t nargs) JL_NOTSAFEPOINT { - return fixnum(jl_lineno); + jl_ast_context_t *ctx = jl_ast_ctx(fl_ctx); + assert(ctx->module); + return fixnum(jl_module_next_counter(ctx->module)); } static int jl_is_number(jl_value_t *v) @@ -207,10 +244,9 @@ static jl_value_t *scm_to_julia_(fl_context_t *fl_ctx, value_t e, jl_module_t *m static const builtinspec_t julia_flisp_ast_ext[] = { { "defined-julia-global", fl_defined_julia_global }, // TODO: can we kill this safepoint + { "nothrow-julia-global", fl_nothrow_julia_global }, { "current-julia-module-counter", fl_current_module_counter }, { "julia-scalar?", fl_julia_scalar }, - { "julia-current-file", fl_julia_current_file }, - { "julia-current-line", fl_julia_current_line }, { NULL, NULL } }; @@ -320,6 +356,7 @@ void jl_init_common_symbols(void) jl_opaque_closure_method_sym = jl_symbol("opaque_closure_method"); jl_const_sym = jl_symbol("const"); jl_global_sym = jl_symbol("global"); + jl_local_sym = jl_symbol("local"); jl_thunk_sym = jl_symbol("thunk"); jl_toplevel_sym = jl_symbol("toplevel"); jl_dot_sym = jl_symbol("."); @@ -365,6 +402,8 @@ void jl_init_common_symbols(void) jl_aliasscope_sym = jl_symbol("aliasscope"); jl_popaliasscope_sym = jl_symbol("popaliasscope"); jl_thismodule_sym = jl_symbol("thismodule"); + jl_eval_sym = jl_symbol("eval"); + jl_include_sym = jl_symbol("include"); jl_block_sym = jl_symbol("block"); jl_atom_sym = jl_symbol("atom"); jl_statement_sym = jl_symbol("statement"); @@ -415,20 +454,6 @@ JL_DLLEXPORT void fl_profile(const char *fname) jl_ast_ctx_leave(ctx); } - -static jl_sym_t *scmsym_to_julia(fl_context_t *fl_ctx, value_t s) -{ - assert(issymbol(s)); - if (fl_isgensym(fl_ctx, s)) { - char gsname[16]; - char *n = uint2str(&gsname[1], sizeof(gsname)-1, - ((gensym_t*)ptr(s))->id, 10); - *(--n) = '#'; - return jl_symbol(n); - } - return jl_symbol(symbol_name(fl_ctx, s)); -} - static jl_value_t *scm_to_julia(fl_context_t *fl_ctx, value_t e, jl_module_t *mod) { jl_value_t *v = NULL; @@ -438,7 +463,7 @@ static jl_value_t *scm_to_julia(fl_context_t *fl_ctx, value_t e, jl_module_t *mo } JL_CATCH { // if expression cannot be converted, replace with error expr - //jl_(jl_current_exception()); + //jl_(jl_current_exception(ct)); //jlbacktrace(); jl_expr_t *ex = jl_exprn(jl_error_sym, 1); v = (jl_value_t*)ex; @@ -539,20 +564,16 @@ static jl_value_t *scm_to_julia_(fl_context_t *fl_ctx, value_t e, jl_module_t *m JL_GC_POP(); return temp; } - else if (sym == jl_lineinfo_sym && n == 5) { - jl_value_t *modu=NULL, *name=NULL, *file=NULL, *linenum=NULL, *inlinedat=NULL; - JL_GC_PUSH5(&modu, &name, &file, &linenum, &inlinedat); + else if (sym == jl_lineinfo_sym && n == 3) { + jl_value_t *file=NULL, *linenum=NULL, *inlinedat=NULL; + JL_GC_PUSH3(&file, &linenum, &inlinedat); value_t lst = e; - modu = scm_to_julia_(fl_ctx, car_(lst), mod); - lst = cdr_(lst); - name = scm_to_julia_(fl_ctx, car_(lst), mod); - lst = cdr_(lst); file = scm_to_julia_(fl_ctx, car_(lst), mod); lst = cdr_(lst); linenum = scm_to_julia_(fl_ctx, car_(lst), mod); lst = cdr_(lst); inlinedat = scm_to_julia_(fl_ctx, car_(lst), mod); - temp = jl_new_struct(jl_lineinfonode_type, modu, name, file, linenum, inlinedat); + temp = jl_new_struct(jl_lineinfonode_type, file, linenum, inlinedat); JL_GC_POP(); return temp; } @@ -566,6 +587,15 @@ static jl_value_t *scm_to_julia_(fl_context_t *fl_ctx, value_t e, jl_module_t *m temp = scm_to_julia(fl_ctx, car_(cdr_(e)), mod); temp = jl_new_struct(jl_gotoifnot_type, ex, temp); } + else if (sym == jl_enter_sym) { + ex = scm_to_julia_(fl_ctx, car_(e), mod); + temp = jl_new_struct_uninit(jl_enternode_type); + jl_enternode_scope(temp) = NULL; + jl_enternode_catch_dest(temp) = jl_unbox_long(ex); + if (n == 2) { + jl_enternode_scope(temp) = scm_to_julia(fl_ctx, car_(cdr_(e)), mod); + } + } else if (sym == jl_newvar_sym) { ex = scm_to_julia_(fl_ctx, car_(e), mod); temp = jl_new_struct(jl_newvarnode_type, ex); @@ -650,9 +680,9 @@ static value_t julia_to_scm(fl_context_t *fl_ctx, jl_value_t *v) static void array_to_list(fl_context_t *fl_ctx, jl_array_t *a, value_t *pv, int check_valid) { value_t temp; - for(long i=jl_array_len(a)-1; i >= 0; i--) { + for (long i = jl_array_nrows(a) - 1; i >= 0; i--) { *pv = fl_cons(fl_ctx, fl_ctx->NIL, *pv); - temp = julia_to_scm_(fl_ctx, jl_array_ptr_ref(a,i), check_valid); + temp = julia_to_scm_(fl_ctx, jl_array_ptr_ref(a, i), check_valid); // note: must be separate statement car_(*pv) = temp; } @@ -687,8 +717,20 @@ static int julia_to_scm_noalloc1(fl_context_t *fl_ctx, jl_value_t *v, value_t *r static value_t julia_to_scm_noalloc2(fl_context_t *fl_ctx, jl_value_t *v, int check_valid) JL_NOTSAFEPOINT { - if (jl_is_long(v) && fits_fixnum(jl_unbox_long(v))) - return fixnum(jl_unbox_long(v)); + if (jl_is_long(v)) { + if (fits_fixnum(jl_unbox_long(v))) { + return fixnum(jl_unbox_long(v)); + } else { +#ifdef _P64 + value_t prim = cprim(fl_ctx, fl_ctx->int64type, sizeof(int64_t)); + *((int64_t*)cp_data((cprim_t*)ptr(prim))) = jl_unbox_long(v); +#else + value_t prim = cprim(fl_ctx, fl_ctx->int32type, sizeof(int32_t)); + *((int32_t*)cp_data((cprim_t*)ptr(prim))) = jl_unbox_long(v); +#endif + return prim; + } + } if (check_valid) { if (jl_is_ssavalue(v)) lerror(fl_ctx, symbol(fl_ctx, "error"), "SSAValue objects should not occur in an AST"); @@ -880,7 +922,7 @@ JL_DLLEXPORT jl_value_t *jl_copy_ast(jl_value_t *expr) JL_GC_PUSH2(&new_ci, &new_code); new_ci = jl_copy_code_info(new_ci); new_code = jl_array_copy(new_ci->code); - size_t clen = jl_array_len(new_code); + size_t clen = jl_array_nrows(new_code); for (int i = 0; i < clen; ++i) { jl_array_ptr_set(new_code, i, jl_copy_ast( jl_array_ptr_ref(new_code, i) @@ -892,18 +934,9 @@ JL_DLLEXPORT jl_value_t *jl_copy_ast(jl_value_t *expr) jl_gc_wb(new_ci, new_ci->slotnames); new_ci->slotflags = jl_array_copy(new_ci->slotflags); jl_gc_wb(new_ci, new_ci->slotflags); - new_ci->codelocs = (jl_value_t*)jl_array_copy((jl_array_t*)new_ci->codelocs); - jl_gc_wb(new_ci, new_ci->codelocs); - new_ci->linetable = (jl_value_t*)jl_array_copy((jl_array_t*)new_ci->linetable); - jl_gc_wb(new_ci, new_ci->linetable); new_ci->ssaflags = jl_array_copy(new_ci->ssaflags); jl_gc_wb(new_ci, new_ci->ssaflags); - if (new_ci->edges != jl_nothing) { - new_ci->edges = (jl_value_t*)jl_array_copy((jl_array_t*)new_ci->edges); - jl_gc_wb(new_ci, new_ci->edges); - } - if (jl_is_array(new_ci->ssavaluetypes)) { new_ci->ssavaluetypes = (jl_value_t*)jl_array_copy((jl_array_t*)new_ci->ssavaluetypes); jl_gc_wb(new_ci, new_ci->ssavaluetypes); @@ -913,7 +946,7 @@ JL_DLLEXPORT jl_value_t *jl_copy_ast(jl_value_t *expr) } if (jl_is_expr(expr)) { jl_expr_t *e = (jl_expr_t*)expr; - size_t i, l = jl_array_len(e->args); + size_t i, l = jl_array_nrows(e->args); jl_expr_t *ne = jl_exprn(e->head, l); JL_GC_PUSH2(&ne, &expr); for (i = 0; i < l; i++) { @@ -944,7 +977,7 @@ JL_DLLEXPORT jl_value_t *jl_copy_ast(jl_value_t *expr) return expr; } -JL_DLLEXPORT int jl_is_operator(char *sym) +JL_DLLEXPORT int jl_is_operator(const char *sym) { jl_ast_context_t *ctx = jl_ast_ctx_enter(NULL); fl_context_t *fl_ctx = &ctx->fl; @@ -953,7 +986,7 @@ JL_DLLEXPORT int jl_is_operator(char *sym) return res; } -JL_DLLEXPORT int jl_is_unary_operator(char *sym) +JL_DLLEXPORT int jl_is_unary_operator(const char *sym) { jl_ast_context_t *ctx = jl_ast_ctx_enter(NULL); fl_context_t *fl_ctx = &ctx->fl; @@ -962,7 +995,7 @@ JL_DLLEXPORT int jl_is_unary_operator(char *sym) return res; } -JL_DLLEXPORT int jl_is_unary_and_binary_operator(char *sym) +JL_DLLEXPORT int jl_is_unary_and_binary_operator(const char *sym) { jl_ast_context_t *ctx = jl_ast_ctx_enter(NULL); fl_context_t *fl_ctx = &ctx->fl; @@ -971,7 +1004,7 @@ JL_DLLEXPORT int jl_is_unary_and_binary_operator(char *sym) return res; } -JL_DLLEXPORT int jl_is_syntactic_operator(char *sym) +JL_DLLEXPORT int jl_is_syntactic_operator(const char *sym) { jl_ast_context_t *ctx = jl_ast_ctx_enter(NULL); fl_context_t *fl_ctx = &ctx->fl; @@ -980,7 +1013,7 @@ JL_DLLEXPORT int jl_is_syntactic_operator(char *sym) return res; } -JL_DLLEXPORT int jl_operator_precedence(char *sym) +JL_DLLEXPORT int jl_operator_precedence(const char *sym) { jl_ast_context_t *ctx = jl_ast_ctx_enter(NULL); fl_context_t *fl_ctx = &ctx->fl; @@ -991,11 +1024,11 @@ JL_DLLEXPORT int jl_operator_precedence(char *sym) int jl_has_meta(jl_array_t *body, jl_sym_t *sym) JL_NOTSAFEPOINT { - size_t i, l = jl_array_len(body); + size_t i, l = jl_array_nrows(body); for (i = 0; i < l; i++) { jl_expr_t *stmt = (jl_expr_t*)jl_array_ptr_ref(body, i); if (jl_is_expr((jl_value_t*)stmt) && stmt->head == jl_meta_sym) { - size_t i, l = jl_array_len(stmt->args); + size_t i, l = jl_array_nrows(stmt->args); for (i = 0; i < l; i++) if (jl_array_ptr_ref(stmt->args, i) == (jl_value_t*)sym) return 1; @@ -1060,7 +1093,7 @@ static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule { jl_task_t *ct = jl_current_task; JL_TIMING(MACRO_INVOCATION, MACRO_INVOCATION); - size_t nargs = jl_array_len(args) + 1; + size_t nargs = jl_array_nrows(args) + 1; JL_NARGSV("macrocall", 3); // macro name, location, and module jl_value_t **margs; JL_GC_PUSHARGS(margs, nargs); @@ -1105,7 +1138,7 @@ static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule margs[0] = jl_cstr_to_string(""); margs[1] = jl_fieldref(lno, 0); // extract and allocate line number jl_rethrow_other(jl_new_struct(jl_loaderror_type, margs[0], margs[1], - jl_current_exception())); + jl_current_exception(ct))); } } ct->world_age = last_age; @@ -1121,7 +1154,7 @@ static jl_value_t *jl_expand_macros(jl_value_t *expr, jl_module_t *inmodule, str jl_expr_t *e = (jl_expr_t*)expr; if (e->head == jl_inert_sym || e->head == jl_module_sym || - //e->head == jl_toplevel_sym || // TODO: enable this once julia-expand-macroscope is fixed / removed + e->head == jl_toplevel_sym || e->head == jl_meta_sym) { return expr; } @@ -1199,7 +1232,7 @@ static jl_value_t *jl_expand_macros(jl_value_t *expr, jl_module_t *inmodule, str } size_t i; - for (i = 0; i < jl_array_len(e->args); i++) { + for (i = 0; i < jl_array_nrows(e->args); i++) { jl_value_t *a = jl_array_ptr_ref(e->args, i); jl_value_t *a2 = jl_expand_macros(a, inmodule, macroctx, onelevel, world, throw_load_error); if (a != a2) diff --git a/src/ast.scm b/src/ast.scm index 87db8449b3992..abfce314fc569 100644 --- a/src/ast.scm +++ b/src/ast.scm @@ -249,7 +249,7 @@ ;; misc syntax forms ((import using) (string (car e) " " (string.join (map deparse-import-path (cdr e)) ", "))) - ((global local export) (string (car e) " " (string.join (map deparse (cdr e)) ", "))) + ((global local export public) (string (car e) " " (string.join (map deparse (cdr e)) ", "))) ((const) (string "const " (deparse (cadr e)))) ((top) (deparse (cadr e))) ((core) (string "Core." (deparse (cadr e)))) diff --git a/src/builtin_proto.h b/src/builtin_proto.h index 64e3fbd1af366..b0536bef24e27 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -21,46 +21,57 @@ extern "C" { JL_DLLEXPORT extern jl_fptr_args_t jl_f_##name##_addr #endif -DECLARE_BUILTIN(applicable); DECLARE_BUILTIN(_apply_iterate); DECLARE_BUILTIN(_apply_pure); -DECLARE_BUILTIN(apply_type); -DECLARE_BUILTIN(arrayref); -DECLARE_BUILTIN(arrayset); -DECLARE_BUILTIN(arraysize); DECLARE_BUILTIN(_call_in_world); DECLARE_BUILTIN(_call_in_world_total); DECLARE_BUILTIN(_call_latest); -DECLARE_BUILTIN(replacefield); -DECLARE_BUILTIN(const_arrayref); +DECLARE_BUILTIN(_compute_sparams); DECLARE_BUILTIN(_expr); +DECLARE_BUILTIN(_svec_ref); +DECLARE_BUILTIN(_typebody); +DECLARE_BUILTIN(_typevar); +DECLARE_BUILTIN(applicable); +DECLARE_BUILTIN(apply_type); +DECLARE_BUILTIN(compilerbarrier); +DECLARE_BUILTIN(current_scope); +DECLARE_BUILTIN(donotdelete); DECLARE_BUILTIN(fieldtype); +DECLARE_BUILTIN(finalizer); DECLARE_BUILTIN(getfield); +DECLARE_BUILTIN(getglobal); DECLARE_BUILTIN(ifelse); DECLARE_BUILTIN(invoke); DECLARE_BUILTIN(is); DECLARE_BUILTIN(isa); DECLARE_BUILTIN(isdefined); DECLARE_BUILTIN(issubtype); +DECLARE_BUILTIN(memoryref); +DECLARE_BUILTIN(memoryref_isassigned); +DECLARE_BUILTIN(memoryrefget); +DECLARE_BUILTIN(memoryrefmodify); +DECLARE_BUILTIN(memoryrefoffset); +DECLARE_BUILTIN(memoryrefreplace); +DECLARE_BUILTIN(memoryrefset); +DECLARE_BUILTIN(memoryrefsetonce); +DECLARE_BUILTIN(memoryrefswap); DECLARE_BUILTIN(modifyfield); +DECLARE_BUILTIN(modifyglobal); DECLARE_BUILTIN(nfields); +DECLARE_BUILTIN(replacefield); +DECLARE_BUILTIN(replaceglobal); DECLARE_BUILTIN(setfield); +DECLARE_BUILTIN(setfieldonce); +DECLARE_BUILTIN(setglobal); +DECLARE_BUILTIN(setglobalonce); DECLARE_BUILTIN(sizeof); DECLARE_BUILTIN(svec); DECLARE_BUILTIN(swapfield); +DECLARE_BUILTIN(swapglobal); DECLARE_BUILTIN(throw); DECLARE_BUILTIN(tuple); DECLARE_BUILTIN(typeassert); -DECLARE_BUILTIN(_typebody); DECLARE_BUILTIN(typeof); -DECLARE_BUILTIN(_typevar); -DECLARE_BUILTIN(donotdelete); -DECLARE_BUILTIN(compilerbarrier); -DECLARE_BUILTIN(getglobal); -DECLARE_BUILTIN(setglobal); -DECLARE_BUILTIN(finalizer); -DECLARE_BUILTIN(_compute_sparams); -DECLARE_BUILTIN(_svec_ref); JL_CALLABLE(jl_f__structtype); JL_CALLABLE(jl_f__abstracttype); diff --git a/src/builtins.c b/src/builtins.c index 2defeb4b3d833..eb9839bcc20f3 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -115,7 +115,7 @@ static int NOINLINE compare_fields(const jl_value_t *a, const jl_value_t *b, jl_ continue; // skip this field (it is #undef) } } - if (!ft->layout->haspadding) { + if (!ft->layout->flags.haspadding) { if (!bits_equal(ao, bo, ft->layout->size)) return 0; } @@ -284,7 +284,7 @@ inline int jl_egal__bits(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t if (sz == 0) return 1; size_t nf = jl_datatype_nfields(dt); - if (nf == 0 || !dt->layout->haspadding) + if (nf == 0 || !dt->layout->flags.haspadding) return bits_equal(a, b, sz); return compare_fields(a, b, dt); } @@ -344,6 +344,9 @@ static uintptr_t type_object_id_(jl_value_t *v, jl_varidx_t *env) JL_NOTSAFEPOIN i++; pe = pe->prev; } + uintptr_t bits = jl_astaggedvalue(v)->header; + if (bits & GC_IN_IMAGE) + return ((uintptr_t*)v)[-2]; return inthash((uintptr_t)v); } if (tv == jl_uniontype_type) { @@ -391,7 +394,7 @@ static uintptr_t immut_id_(jl_datatype_t *dt, jl_value_t *v, uintptr_t h) JL_NOT if (sz == 0) return ~h; size_t f, nf = jl_datatype_nfields(dt); - if (nf == 0 || (!dt->layout->haspadding && dt->layout->npointers == 0)) { + if (nf == 0 || (!dt->layout->flags.haspadding && dt->layout->npointers == 0)) { // operate element-wise if there are unused bits inside, // otherwise just take the whole data block at once // a few select pointers (notably symbol) also have special hash values @@ -432,55 +435,62 @@ static uintptr_t immut_id_(jl_datatype_t *dt, jl_value_t *v, uintptr_t h) JL_NOT return h; } -static uintptr_t NOINLINE jl_object_id__cold(jl_datatype_t *dt, jl_value_t *v) JL_NOTSAFEPOINT +static uintptr_t NOINLINE jl_object_id__cold(uintptr_t tv, jl_value_t *v) JL_NOTSAFEPOINT { - if (dt == jl_simplevector_type) - return hash_svec((jl_svec_t*)v); - if (dt == jl_datatype_type) { - jl_datatype_t *dtv = (jl_datatype_t*)v; - uintptr_t h = ~dtv->name->hash; - return bitmix(h, hash_svec(dtv->parameters)); - } - if (dt == jl_string_type) { + jl_datatype_t *dt = (jl_datatype_t*)jl_to_typeof(tv); + if (dt->name->mutabl) { + if (dt == jl_string_type) { #ifdef _P64 - return memhash_seed(jl_string_data(v), jl_string_len(v), 0xedc3b677); + return memhash_seed(jl_string_data(v), jl_string_len(v), 0xedc3b677); #else - return memhash32_seed(jl_string_data(v), jl_string_len(v), 0xedc3b677); + return memhash32_seed(jl_string_data(v), jl_string_len(v), 0xedc3b677); #endif - } - if (dt == jl_module_type) { - jl_module_t *m = (jl_module_t*)v; - return m->hash; - } - if (dt->name->mutabl) + } + if (dt == jl_simplevector_type) + return hash_svec((jl_svec_t*)v); + if (dt == jl_datatype_type) { + jl_datatype_t *dtv = (jl_datatype_t*)v; + uintptr_t h = ~dtv->name->hash; + return bitmix(h, hash_svec(dtv->parameters)); + } + if (dt == jl_module_type) { + jl_module_t *m = (jl_module_t*)v; + return m->hash; + } + uintptr_t bits = jl_astaggedvalue(v)->header; + if (bits & GC_IN_IMAGE) + return ((uintptr_t*)v)[-2]; return inthash((uintptr_t)v); + } return immut_id_(dt, v, dt->hash); } -JL_DLLEXPORT inline uintptr_t jl_object_id_(jl_value_t *tv, jl_value_t *v) JL_NOTSAFEPOINT +JL_DLLEXPORT inline uintptr_t jl_object_id_(uintptr_t tv, jl_value_t *v) JL_NOTSAFEPOINT { - jl_datatype_t *dt = (jl_datatype_t*)tv; - if (dt == jl_symbol_type) + if (tv == jl_symbol_tag << 4) { return ((jl_sym_t*)v)->hash; - if (dt == jl_typename_type) - return ((jl_typename_t*)v)->hash; - if (dt == jl_datatype_type) { + } + else if (tv == jl_datatype_tag << 4) { jl_datatype_t *dtv = (jl_datatype_t*)v; if (dtv->isconcretetype) return dtv->hash; } - return jl_object_id__cold(dt, v); + else if (tv == (uintptr_t)jl_typename_type) { + return ((jl_typename_t*)v)->hash; + } + return jl_object_id__cold(tv, v); } JL_DLLEXPORT uintptr_t jl_object_id(jl_value_t *v) JL_NOTSAFEPOINT { - return jl_object_id_(jl_typeof(v), v); + return jl_object_id_(jl_typetagof(v), v); } // eq hash table -------------------------------------------------------------- #include "iddict.c" +#include "idset.c" // object model and type primitives ------------------------------------------- @@ -511,21 +521,18 @@ JL_CALLABLE(jl_f_sizeof) } if (jl_is_datatype(x)) { jl_datatype_t *dx = (jl_datatype_t*)x; - if (dx->layout == NULL) { + if (!jl_struct_try_layout(dx)) { if (dx->name->abstract) jl_errorf("Abstract type %s does not have a definite size.", jl_symbol_name(dx->name->name)); else jl_errorf("Argument is an incomplete %s type and does not have a definite size.", jl_symbol_name(dx->name->name)); } - if (jl_is_layout_opaque(dx->layout)) + if (jl_is_layout_opaque(dx->layout)) // includes all GenericMemory{kind,T} jl_errorf("Type %s does not have a definite size.", jl_symbol_name(dx->name->name)); return jl_box_long(jl_datatype_size(x)); } if (x == jl_bottom_type) jl_error("The empty type does not have a definite size since it does not have instances."); - if (jl_is_array(x)) { - return jl_box_long(jl_array_len(x) * ((jl_array_t*)x)->elsize); - } if (jl_is_string(x)) return jl_box_long(jl_string_len(x)); if (jl_is_symbol(x)) @@ -535,7 +542,10 @@ JL_CALLABLE(jl_f_sizeof) jl_datatype_t *dt = (jl_datatype_t*)jl_typeof(x); assert(jl_is_datatype(dt)); assert(!dt->name->abstract); - return jl_box_long(jl_datatype_size(dt)); + size_t sz = dt->layout->size; + if (jl_is_genericmemory(x)) + sz = (sz + (dt->layout->flags.arrayelem_isunion ? 1 : 0)) * ((jl_genericmemory_t*)x)->length; + return jl_box_long(sz); } JL_CALLABLE(jl_f_issubtype) @@ -577,6 +587,12 @@ JL_CALLABLE(jl_f_ifelse) return (args[0] == jl_false ? args[2] : args[1]); } +JL_CALLABLE(jl_f_current_scope) +{ + JL_NARGS(current_scope, 0, 0); + return jl_current_task->scope; +} + // apply ---------------------------------------------------------------------- static NOINLINE jl_svec_t *_copy_to(size_t newalloc, jl_value_t **oldargs, size_t oldalloc) @@ -607,6 +623,12 @@ STATIC_INLINE void _grow_to(jl_value_t **root, jl_value_t ***oldargs, jl_svec_t *n_alloc = newalloc; } + +static jl_value_t *jl_arrayref(jl_array_t *a, size_t i) +{ + return jl_memoryrefget(jl_memoryrefindex(a->ref, i), 0); +} + static jl_value_t *do_apply(jl_value_t **args, uint32_t nargs, jl_value_t *iterate) { jl_function_t *f = args[0]; @@ -615,6 +637,17 @@ static jl_value_t *do_apply(jl_value_t **args, uint32_t nargs, jl_value_t *itera if (f == jl_builtin_svec) { if (jl_is_svec(args[1])) return args[1]; + if (jl_is_genericmemory(args[1])) { + jl_genericmemory_t *mem = (jl_genericmemory_t*)args[1]; + size_t n = mem->length; + jl_svec_t *t = jl_alloc_svec(n); + JL_GC_PUSH1(&t); + for (size_t i = 0; i < n; i++) { + jl_svecset(t, i, jl_genericmemoryref(mem, i)); + } + JL_GC_POP(); + return (jl_value_t*)t; + } if (jl_is_array(args[1])) { size_t n = jl_array_len(args[1]); jl_svec_t *t = jl_alloc_svec(n); @@ -641,6 +674,9 @@ static jl_value_t *do_apply(jl_value_t **args, uint32_t nargs, jl_value_t *itera else if (jl_is_tuple(args[i]) || jl_is_namedtuple(args[i])) { precount += jl_nfields(args[i]); } + else if (jl_is_genericmemory(args[i])) { + precount += ((jl_genericmemory_t*)args[i])->length; + } else if (jl_is_array(args[i])) { precount += jl_array_len(args[i]); } @@ -649,7 +685,7 @@ static jl_value_t *do_apply(jl_value_t **args, uint32_t nargs, jl_value_t *itera } } if (extra && iterate == NULL) { - jl_undefined_var_error(jl_symbol("iterate")); + jl_undefined_var_error(jl_symbol("iterate"), NULL); } // allocate space for the argument array and gc roots for it // based on our previous estimates @@ -709,13 +745,40 @@ static jl_value_t *do_apply(jl_value_t **args, uint32_t nargs, jl_value_t *itera jl_gc_wb(arg_heap, newargs[n - 1]); } } + else if (jl_is_genericmemory(ai)) { + jl_genericmemory_t *mem = (jl_genericmemory_t*)ai; + size_t j, al = mem->length; + precount = (precount > al) ? precount - al : 0; + _grow_to(&roots[0], &newargs, &arg_heap, &n_alloc, n + precount + al, extra); + assert(newargs != NULL); // inform GCChecker that we didn't write a NULL here + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(mem))->layout; + if (layout->flags.arrayelem_isboxed) { + for (j = 0; j < al; j++) { + jl_value_t *arg = jl_genericmemory_ptr_ref(mem, j); + // apply with array splatting may have embedded NULL value (#11772) + if (__unlikely(arg == NULL)) + jl_throw(jl_undefref_exception); + newargs[n++] = arg; + if (arg_heap) + jl_gc_wb(arg_heap, arg); + } + } + else { + for (j = 0; j < al; j++) { + newargs[n++] = jl_genericmemoryref(mem, j); + if (arg_heap) + jl_gc_wb(arg_heap, newargs[n - 1]); + } + } + } else if (jl_is_array(ai)) { jl_array_t *aai = (jl_array_t*)ai; size_t j, al = jl_array_len(aai); precount = (precount > al) ? precount - al : 0; _grow_to(&roots[0], &newargs, &arg_heap, &n_alloc, n + precount + al, extra); assert(newargs != NULL); // inform GCChecker that we didn't write a NULL here - if (aai->flags.ptrarray) { + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(aai->ref.mem))->layout; + if (layout->flags.arrayelem_isboxed) { for (j = 0; j < al; j++) { jl_value_t *arg = jl_array_ptr_ref(aai, j); // apply with array splatting may have embedded NULL value (#11772) @@ -941,7 +1004,7 @@ static inline size_t get_checked_fieldindex(const char *name, jl_datatype_t *st, } if (mutabl && jl_field_isconst(st, idx)) { jl_errorf("%s: const field .%s of type %s cannot be changed", name, - jl_symbol_name((jl_sym_t*)jl_svec_ref(jl_field_names(st), idx)), jl_symbol_name(st->name->name)); + jl_symbol_name((jl_sym_t*)jl_svecref(jl_field_names(st), idx)), jl_symbol_name(st->name->name)); } return idx; } @@ -972,9 +1035,11 @@ JL_CALLABLE(jl_f_getfield) jl_atomic_error("getfield: non-atomic field cannot be accessed atomically"); if (isatomic && order == jl_memory_order_notatomic) jl_atomic_error("getfield: atomic field cannot be accessed non-atomically"); - v = jl_get_nth_field_checked(v, idx); - if (order >= jl_memory_order_acq_rel || order == jl_memory_order_acquire) - jl_fence(); // `v` already had at least consume ordering + if (order >= jl_memory_order_seq_cst) + jl_fence(); + v = jl_get_nth_field_checked(v, idx); // `v` already had at least consume ordering + if (order >= jl_memory_order_acquire) + jl_fence(); return v; } @@ -996,7 +1061,7 @@ JL_CALLABLE(jl_f_setfield) jl_value_t *ft = jl_field_type_concrete(st, idx); if (!jl_isa(args[2], ft)) jl_type_error("setfield!", ft, args[2]); - if (order >= jl_memory_order_acq_rel || order == jl_memory_order_release) + if (order >= jl_memory_order_release) jl_fence(); // `st->[idx]` will have at least relaxed ordering set_nth_field(st, v, idx, args[2], isatomic); return args[2]; @@ -1070,6 +1135,35 @@ JL_CALLABLE(jl_f_replacefield) return v; } +JL_CALLABLE(jl_f_setfieldonce) +{ + enum jl_memory_order success_order = jl_memory_order_notatomic; + JL_NARGS(setfieldonce!, 3, 5); + if (nargs >= 4) { + JL_TYPECHK(setfieldonce!, symbol, args[3]); + success_order = jl_get_atomic_order_checked((jl_sym_t*)args[3], 1, 1); + } + enum jl_memory_order failure_order = success_order; + if (nargs == 5) { + JL_TYPECHK(setfieldonce!, symbol, args[4]); + failure_order = jl_get_atomic_order_checked((jl_sym_t*)args[4], 1, 0); + } + if (failure_order > success_order) + jl_atomic_error("invalid atomic ordering"); + // TODO: filter more invalid ordering combinations? + jl_value_t *v = args[0]; + jl_datatype_t *st = (jl_datatype_t*)jl_typeof(v); + size_t idx = get_checked_fieldindex("setfieldonce!", st, v, args[1], 1); + int isatomic = !!jl_field_isatomic(st, idx); + if (isatomic == (success_order == jl_memory_order_notatomic)) + jl_atomic_error(isatomic ? "setfieldonce!: atomic field cannot be written non-atomically" + : "setfieldonce!: non-atomic field cannot be written atomically"); + if (isatomic == (failure_order == jl_memory_order_notatomic)) + jl_atomic_error(isatomic ? "setfieldonce!: atomic field cannot be accessed non-atomically" + : "setfieldonce!: non-atomic field cannot be accessed atomically"); + int success = set_nth_fieldonce(st, v, idx, args[2], isatomic); // always seq_cst, if isatomic needed at all + return success ? jl_true : jl_false; +} static jl_value_t *get_fieldtype(jl_value_t *t, jl_value_t *f, int dothrow) { @@ -1125,6 +1219,8 @@ static jl_value_t *get_fieldtype(jl_value_t *t, jl_value_t *f, int dothrow) tt = ((jl_tvar_t*)tt)->ub; if (tt == (jl_value_t*)jl_any_type) return (jl_value_t*)jl_any_type; + if (tt == (jl_value_t*)jl_bottom_type) + return (jl_value_t*)jl_bottom_type; JL_GC_PUSH1(&f); if (jl_is_symbol(f)) f = jl_box_long(field_index+1); @@ -1178,7 +1274,12 @@ JL_CALLABLE(jl_f_isdefined) JL_TYPECHK(isdefined, symbol, args[1]); m = (jl_module_t*)args[0]; s = (jl_sym_t*)args[1]; - return jl_boundp(m, s) ? jl_true : jl_false; // is seq_cst already + if (order == jl_memory_order_unspecified) + order = jl_memory_order_unordered; + if (order < jl_memory_order_unordered) + jl_atomic_error("isdefined: module binding cannot be accessed non-atomically"); + int bound = jl_boundp(m, s); // seq_cst always + return bound ? jl_true : jl_false; } jl_datatype_t *vt = (jl_datatype_t*)jl_typeof(args[0]); assert(jl_is_datatype(vt)); @@ -1205,15 +1306,11 @@ JL_CALLABLE(jl_f_isdefined) jl_atomic_error("isdefined: non-atomic field cannot be accessed atomically"); if (isatomic && order == jl_memory_order_notatomic) jl_atomic_error("isdefined: atomic field cannot be accessed non-atomically"); - int v = jl_field_isdefined(args[0], idx); - if (v == 2) { - if (order > jl_memory_order_notatomic) - jl_fence(); // isbits case has no ordering already - } - else { - if (order >= jl_memory_order_acq_rel || order == jl_memory_order_acquire) - jl_fence(); // `v` already gave at least consume ordering - } + if (order >= jl_memory_order_seq_cst) + jl_fence(); + int v = jl_field_isdefined(args[0], idx); // relaxed ordering + if (order >= jl_memory_order_acquire) + jl_fence(); return v ? jl_true : jl_false; } @@ -1234,8 +1331,11 @@ JL_CALLABLE(jl_f_getglobal) JL_TYPECHK(getglobal, symbol, (jl_value_t*)sym); if (order == jl_memory_order_notatomic) jl_atomic_error("getglobal: module binding cannot be read non-atomically"); - jl_value_t *v = jl_eval_global_var(mod, sym); - // is seq_cst already, no fence needed + else if (order >= jl_memory_order_seq_cst) + jl_fence(); + jl_value_t *v = jl_eval_global_var(mod, sym); // relaxed load + if (order >= jl_memory_order_acquire) + jl_fence(); return v; } @@ -1253,9 +1353,12 @@ JL_CALLABLE(jl_f_setglobal) JL_TYPECHK(setglobal!, symbol, (jl_value_t*)var); if (order == jl_memory_order_notatomic) jl_atomic_error("setglobal!: module binding cannot be written non-atomically"); - // is seq_cst already, no fence needed + else if (order >= jl_memory_order_seq_cst) + jl_fence(); jl_binding_t *b = jl_get_binding_wr(mod, var); - jl_checked_assignment(b, mod, var, args[2]); + jl_checked_assignment(b, mod, var, args[2]); // release store + if (order >= jl_memory_order_seq_cst) + jl_fence(); return args[2]; } @@ -1292,16 +1395,114 @@ JL_CALLABLE(jl_f_set_binding_type) JL_TYPECHK(set_binding_type!, type, ty); jl_binding_t *b = jl_get_binding_wr(m, s); jl_value_t *old_ty = NULL; - if (!jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, ty) && ty != old_ty) { - if (nargs == 2) - return jl_nothing; + if (jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, ty)) { + jl_gc_wb(b, ty); + } + else if (nargs != 2 && !jl_types_equal(ty, old_ty)) { jl_errorf("cannot set type for global %s.%s. It already has a value or is already set to a different type.", jl_symbol_name(m->name), jl_symbol_name(s)); } - jl_gc_wb_binding(b, ty); return jl_nothing; } +JL_CALLABLE(jl_f_swapglobal) +{ + enum jl_memory_order order = jl_memory_order_release; + JL_NARGS(swapglobal!, 3, 4); + if (nargs == 4) { + JL_TYPECHK(swapglobal!, symbol, args[3]); + order = jl_get_atomic_order_checked((jl_sym_t*)args[3], 1, 1); + } + jl_module_t *mod = (jl_module_t*)args[0]; + jl_sym_t *var = (jl_sym_t*)args[1]; + JL_TYPECHK(swapglobal!, module, (jl_value_t*)mod); + JL_TYPECHK(swapglobal!, symbol, (jl_value_t*)var); + if (order == jl_memory_order_notatomic) + jl_atomic_error("swapglobal!: module binding cannot be written non-atomically"); + // is seq_cst already, no fence needed + jl_binding_t *b = jl_get_binding_wr(mod, var); + return jl_checked_swap(b, mod, var, args[2]); +} + +JL_CALLABLE(jl_f_modifyglobal) +{ + enum jl_memory_order order = jl_memory_order_release; + JL_NARGS(modifyglobal!, 4, 5); + if (nargs == 5) { + JL_TYPECHK(modifyglobal!, symbol, args[4]); + order = jl_get_atomic_order_checked((jl_sym_t*)args[4], 1, 1); + } + jl_module_t *mod = (jl_module_t*)args[0]; + jl_sym_t *var = (jl_sym_t*)args[1]; + JL_TYPECHK(modifyglobal!, module, (jl_value_t*)mod); + JL_TYPECHK(modifyglobal!, symbol, (jl_value_t*)var); + if (order == jl_memory_order_notatomic) + jl_atomic_error("modifyglobal!: module binding cannot be written non-atomically"); + jl_binding_t *b = jl_get_binding_wr(mod, var); + // is seq_cst already, no fence needed + return jl_checked_modify(b, mod, var, args[2], args[3]); +} + +JL_CALLABLE(jl_f_replaceglobal) +{ + enum jl_memory_order success_order = jl_memory_order_release; + JL_NARGS(replaceglobal!, 4, 6); + if (nargs >= 5) { + JL_TYPECHK(replaceglobal!, symbol, args[4]); + success_order = jl_get_atomic_order_checked((jl_sym_t*)args[4], 1, 1); + } + enum jl_memory_order failure_order = success_order; + if (nargs == 6) { + JL_TYPECHK(replaceglobal!, symbol, args[5]); + failure_order = jl_get_atomic_order_checked((jl_sym_t*)args[5], 1, 0); + } + if (failure_order > success_order) + jl_atomic_error("invalid atomic ordering"); + // TODO: filter more invalid ordering combinations? + jl_module_t *mod = (jl_module_t*)args[0]; + jl_sym_t *var = (jl_sym_t*)args[1]; + JL_TYPECHK(replaceglobal!, module, (jl_value_t*)mod); + JL_TYPECHK(replaceglobal!, symbol, (jl_value_t*)var); + if (success_order == jl_memory_order_notatomic) + jl_atomic_error("replaceglobal!: module binding cannot be written non-atomically"); + if (failure_order == jl_memory_order_notatomic) + jl_atomic_error("replaceglobal!: module binding cannot be accessed non-atomically"); + jl_binding_t *b = jl_get_binding_wr(mod, var); + // is seq_cst already, no fence needed + return jl_checked_replace(b, mod, var, args[2], args[3]); +} + +JL_CALLABLE(jl_f_setglobalonce) +{ + enum jl_memory_order success_order = jl_memory_order_release; + JL_NARGS(setglobalonce!, 3, 5); + if (nargs >= 4) { + JL_TYPECHK(setglobalonce!, symbol, args[3]); + success_order = jl_get_atomic_order_checked((jl_sym_t*)args[3], 1, 1); + } + enum jl_memory_order failure_order = success_order; + if (nargs == 5) { + JL_TYPECHK(setglobalonce!, symbol, args[4]); + failure_order = jl_get_atomic_order_checked((jl_sym_t*)args[4], 1, 0); + } + if (failure_order > success_order) + jl_atomic_error("invalid atomic ordering"); + // TODO: filter more invalid ordering combinations? + jl_module_t *mod = (jl_module_t*)args[0]; + jl_sym_t *var = (jl_sym_t*)args[1]; + JL_TYPECHK(setglobalonce!, module, (jl_value_t*)mod); + JL_TYPECHK(setglobalonce!, symbol, (jl_value_t*)var); + if (success_order == jl_memory_order_notatomic) + jl_atomic_error("setglobalonce!: module binding cannot be written non-atomically"); + if (failure_order == jl_memory_order_notatomic) + jl_atomic_error("setglobalonce!: module binding cannot be accessed non-atomically"); + jl_binding_t *b = jl_get_binding_wr(mod, var); + // is seq_cst already, no fence needed + jl_value_t *old = jl_checked_assignonce(b, mod, var, args[2]); + return old == NULL ? jl_true : jl_false; +} + + // apply_type ----------------------------------------------------------------- @@ -1402,7 +1603,7 @@ JL_CALLABLE(jl_f_invoke) if (!jl_is_tuple_type(jl_unwrap_unionall(args[1]))) jl_type_error("invoke", (jl_value_t*)jl_anytuple_type_type, args[1]); if (!jl_tuple_isa(&args[2], nargs - 2, (jl_datatype_t*)argtypes)) - jl_error("invoke: argument type error"); + jl_type_error("invoke: argument type error", argtypes, jl_f_tuple(NULL, &args[2], nargs - 2)); jl_value_t *res = jl_gf_invoke(argtypes, args[0], &args[2], nargs - 1); JL_GC_POP(); return res; @@ -1463,70 +1664,261 @@ JL_CALLABLE(jl_f__typevar) return (jl_value_t *)jl_new_typevar((jl_sym_t*)args[0], args[1], args[2]); } -// arrays --------------------------------------------------------------------- +// genericmemory --------------------------------------------------------------------- -JL_CALLABLE(jl_f_arraysize) +JL_CALLABLE(jl_f_memoryref) { - JL_NARGS(arraysize, 2, 2); - JL_TYPECHK(arraysize, array, args[0]); - jl_array_t *a = (jl_array_t*)args[0]; - size_t nd = jl_array_ndims(a); - JL_TYPECHK(arraysize, long, args[1]); - int dno = jl_unbox_long(args[1]); - if (dno < 1) - jl_error("arraysize: dimension out of range"); - if (dno > nd) - return jl_box_long(1); - return jl_box_long((&a->nrows)[dno-1]); + JL_NARGS(memoryref, 1, 3); + if (nargs == 1) { + JL_TYPECHK(memoryref, genericmemory, args[0]); + jl_genericmemory_t *m = (jl_genericmemory_t*)args[0]; + jl_value_t *typ = jl_apply_type((jl_value_t*)jl_genericmemoryref_type, jl_svec_data(((jl_datatype_t*)jl_typetagof(m))->parameters), 3); + JL_GC_PROMISE_ROOTED(typ); // it is a concrete type + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(m))->layout; + if (layout->flags.arrayelem_isunion || layout->size == 0) + return (jl_value_t*)jl_new_memoryref(typ, m, 0); + return (jl_value_t*)jl_new_memoryref(typ, m, m->ptr); + } + else { + JL_TYPECHK(memoryref, genericmemoryref, args[0]); + JL_TYPECHK(memoryref, long, args[1]); + if (nargs == 3) + JL_TYPECHK(memoryref, bool, args[2]); + jl_genericmemoryref_t *m = (jl_genericmemoryref_t*)args[0]; + size_t i = jl_unbox_long(args[1]) - 1; + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(m->mem))->layout; + char *data = (char*)m->ptr_or_offset; + if (layout->flags.arrayelem_isboxed) { + if (((data - (char*)m->mem->ptr) / sizeof(jl_value_t*)) + i >= m->mem->length) + jl_bounds_error((jl_value_t*)m, args[1]); + data += sizeof(jl_value_t*) * i; + } + else if (layout->flags.arrayelem_isunion || layout->size == 0) { + if ((size_t)data + i >= m->mem->length) + jl_bounds_error((jl_value_t*)m, args[1]); + data += i; + } + else { + if (((data - (char*)m->mem->ptr) / layout->size) + i >= m->mem->length) + jl_bounds_error((jl_value_t*)m, args[1]); + data += layout->size * i; + } + return (jl_value_t*)jl_new_memoryref((jl_value_t*)jl_typetagof(m), m->mem, data); + } } -static size_t array_nd_index(jl_array_t *a, jl_value_t **args, size_t nidxs, - const char *fname) +JL_CALLABLE(jl_f_memoryrefoffset) { - size_t i = 0; - size_t k, stride = 1; - size_t nd = jl_array_ndims(a); - for (k = 0; k < nidxs; k++) { - if (!jl_is_long(args[k])) - jl_type_error(fname, (jl_value_t*)jl_long_type, args[k]); - size_t ii = jl_unbox_long(args[k]) - 1; - i += ii * stride; - size_t d = (k >= nd) ? 1 : jl_array_dim(a, k); - if (k < nidxs - 1 && ii >= d) - jl_bounds_error_v((jl_value_t*)a, args, nidxs); - stride *= d; + JL_NARGS(memoryrefoffset, 1, 1); + JL_TYPECHK(memoryref, genericmemoryref, args[0]); + jl_genericmemoryref_t m = *(jl_genericmemoryref_t*)args[0]; + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(m.mem))->layout; + size_t offset; + if (layout->flags.arrayelem_isboxed) { + offset = (((char*)m.ptr_or_offset - (char*)m.mem->ptr) / sizeof(jl_value_t*)); + } + else if (layout->flags.arrayelem_isunion || layout->size == 0) { + offset = (size_t)m.ptr_or_offset; } - for (; k < nd; k++) - stride *= jl_array_dim(a, k); - if (i >= stride) - jl_bounds_error_v((jl_value_t*)a, args, nidxs); - return i; + else { + offset = ((char*)m.ptr_or_offset - (char*)m.mem->ptr) / layout->size; + } + return (jl_value_t*)jl_box_long(offset + 1); } -JL_CALLABLE(jl_f_arrayref) +JL_CALLABLE(jl_f_memoryrefget) { - JL_NARGSV(arrayref, 3); - JL_TYPECHK(arrayref, bool, args[0]); - JL_TYPECHK(arrayref, array, args[1]); - jl_array_t *a = (jl_array_t*)args[1]; - size_t i = array_nd_index(a, &args[2], nargs - 2, "arrayref"); - return jl_arrayref(a, i); + enum jl_memory_order order = jl_memory_order_notatomic; + JL_NARGS(memoryrefget, 3, 3); + JL_TYPECHK(memoryrefget, genericmemoryref, args[0]); + JL_TYPECHK(memoryrefget, symbol, args[1]); + JL_TYPECHK(memoryrefget, bool, args[2]); + jl_genericmemoryref_t m = *(jl_genericmemoryref_t*)args[0]; + jl_value_t *kind = jl_tparam0(jl_typetagof(m.mem)); + if (kind == (jl_value_t*)jl_not_atomic_sym) { + if (args[1] != kind) { + order = jl_get_atomic_order_checked((jl_sym_t*)args[1], 1, 0); + jl_atomic_error("memoryrefget: non-atomic memory cannot be accessed atomically"); + } + } + else if (kind == (jl_value_t*)jl_atomic_sym) { + order = jl_get_atomic_order_checked((jl_sym_t*)args[1], 1, 0); + if (order == jl_memory_order_notatomic) + jl_atomic_error("memoryrefget: atomic memory cannot be accessed non-atomically"); + } + if (m.mem->length == 0) + jl_bounds_error_int((jl_value_t*)m.mem, 1); + return jl_memoryrefget(m, kind == (jl_value_t*)jl_atomic_sym); } -JL_CALLABLE(jl_f_const_arrayref) +JL_CALLABLE(jl_f_memoryrefset) { - return jl_f_arrayref(F, args, nargs); + enum jl_memory_order order = jl_memory_order_notatomic; + JL_NARGS(memoryrefset!, 4, 4); + JL_TYPECHK(memoryrefset!, genericmemoryref, args[0]); + JL_TYPECHK(memoryrefset!, symbol, args[2]); + JL_TYPECHK(memoryrefset!, bool, args[3]); + jl_genericmemoryref_t m = *(jl_genericmemoryref_t*)args[0]; + jl_value_t *kind = jl_tparam0(jl_typetagof(m.mem)); + if (kind == (jl_value_t*)jl_not_atomic_sym) { + if (args[2] != kind) { + order = jl_get_atomic_order_checked((jl_sym_t*)args[2], 0, 1); + jl_atomic_error("memoryrefset!: non-atomic memory cannot be written atomically"); + } + } + else if (kind == (jl_value_t*)jl_atomic_sym) { + order = jl_get_atomic_order_checked((jl_sym_t*)args[2], 0, 1); + if (order == jl_memory_order_notatomic) + jl_atomic_error("memoryrefset!: atomic memory cannot be written non-atomically"); + } + if (m.mem->length == 0) + jl_bounds_error_int((jl_value_t*)m.mem, 1); + jl_memoryrefset(m, args[1], kind == (jl_value_t*)jl_atomic_sym); + return args[1]; } -JL_CALLABLE(jl_f_arrayset) +JL_CALLABLE(jl_f_memoryref_isassigned) { - JL_NARGSV(arrayset, 4); - JL_TYPECHK(arrayset, bool, args[0]); - JL_TYPECHK(arrayset, array, args[1]); - jl_array_t *a = (jl_array_t*)args[1]; - size_t i = array_nd_index(a, &args[3], nargs - 3, "arrayset"); - jl_arrayset(a, args[2], i); - return args[1]; + enum jl_memory_order order = jl_memory_order_notatomic; + JL_NARGS(memoryref_isassigned, 3, 3); + JL_TYPECHK(memoryref_isassigned, genericmemoryref, args[0]); + JL_TYPECHK(memoryref_isassigned, symbol, args[1]); + JL_TYPECHK(memoryref_isassigned, bool, args[2]); + jl_genericmemoryref_t m = *(jl_genericmemoryref_t*)args[0]; + jl_value_t *kind = jl_tparam0(jl_typetagof(m.mem)); + if (kind == (jl_value_t*)jl_not_atomic_sym) { + if (args[1] != kind) { + order = jl_get_atomic_order_checked((jl_sym_t*)args[1], 1, 0); + jl_atomic_error("memoryref_isassigned: non-atomic memory cannot be accessed atomically"); + } + } + else if (kind == (jl_value_t*)jl_atomic_sym) { + order = jl_get_atomic_order_checked((jl_sym_t*)args[1], 1, 0); + if (order == jl_memory_order_notatomic) + jl_atomic_error("memoryref_isassigned: atomic memory cannot be accessed non-atomically"); + } + if (m.mem->length == 0) + // TODO(jwn): decide on the fences required for ordering here + return jl_false; + return jl_memoryref_isassigned(m, kind == (jl_value_t*)jl_atomic_sym); +} + +JL_CALLABLE(jl_f_memoryrefswap) +{ + enum jl_memory_order order = jl_memory_order_notatomic; + JL_NARGS(memoryrefswap!, 4, 4); + JL_TYPECHK(memoryrefswap!, genericmemoryref, args[0]); + JL_TYPECHK(memoryrefswap!, symbol, args[2]); + JL_TYPECHK(memoryrefswap!, bool, args[3]); + jl_genericmemoryref_t m = *(jl_genericmemoryref_t*)args[0]; + jl_value_t *kind = jl_tparam0(jl_typetagof(m.mem)); + if (kind == (jl_value_t*)jl_not_atomic_sym) { + if (args[2] != kind) { + order = jl_get_atomic_order_checked((jl_sym_t*)args[2], 1, 1); + jl_atomic_error("memoryrefswap!: non-atomic memory cannot be written atomically"); + } + } + else if (kind == (jl_value_t*)jl_atomic_sym) { + order = jl_get_atomic_order_checked((jl_sym_t*)args[2], 1, 1); + if (order == jl_memory_order_notatomic) + jl_atomic_error("memoryrefswap!: atomic memory cannot be written non-atomically"); + } + if (m.mem->length == 0) + jl_bounds_error_int((jl_value_t*)m.mem, 1); + return jl_memoryrefswap(m, args[1], kind == (jl_value_t*)jl_atomic_sym); +} + +JL_CALLABLE(jl_f_memoryrefmodify) +{ + enum jl_memory_order order = jl_memory_order_notatomic; + JL_NARGS(memoryrefmodify!, 5, 5); + JL_TYPECHK(memoryrefmodify!, genericmemoryref, args[0]); + JL_TYPECHK(memoryrefmodify!, symbol, args[3]); + JL_TYPECHK(memoryrefmodify!, bool, args[4]); + jl_genericmemoryref_t m = *(jl_genericmemoryref_t*)args[0]; + jl_value_t *kind = jl_tparam0(jl_typetagof(m.mem)); + if (kind == (jl_value_t*)jl_not_atomic_sym) { + if (args[3] != kind) { + order = jl_get_atomic_order_checked((jl_sym_t*)args[3], 1, 1); + jl_atomic_error("memoryrefmodify!: non-atomic memory cannot be written atomically"); + } + } + else if (kind == (jl_value_t*)jl_atomic_sym) { + order = jl_get_atomic_order_checked((jl_sym_t*)args[3], 1, 1); + if (order == jl_memory_order_notatomic) + jl_atomic_error("memoryrefmodify!: atomic memory cannot be written non-atomically"); + } + if (m.mem->length == 0) + jl_bounds_error_int((jl_value_t*)m.mem, 1); + return jl_memoryrefmodify(m, args[1], args[2], kind == (jl_value_t*)jl_atomic_sym); +} + +JL_CALLABLE(jl_f_memoryrefreplace) +{ + enum jl_memory_order success_order = jl_memory_order_notatomic; + enum jl_memory_order failure_order = jl_memory_order_notatomic; + JL_NARGS(memoryrefreplace!, 6, 6); + JL_TYPECHK(memoryrefreplace!, genericmemoryref, args[0]); + JL_TYPECHK(memoryrefreplace!, symbol, args[3]); + JL_TYPECHK(memoryrefreplace!, symbol, args[4]); + JL_TYPECHK(memoryrefreplace!, bool, args[5]); + jl_genericmemoryref_t m = *(jl_genericmemoryref_t*)args[0]; + jl_value_t *kind = jl_tparam0(jl_typetagof(m.mem)); + if (kind == (jl_value_t*)jl_not_atomic_sym) { + if (args[4] != kind) + jl_atomic_error("invalid atomic ordering"); // because either it is invalid, or failure_order > success_order + if (args[3] != kind) { + success_order = jl_get_atomic_order_checked((jl_sym_t*)args[3], 1, 1); + jl_atomic_error("memoryrefreplace!: non-atomic memory cannot be written atomically"); + } + } + else if (kind == (jl_value_t*)jl_atomic_sym) { + success_order = jl_get_atomic_order_checked((jl_sym_t*)args[3], 1, 1); + failure_order = jl_get_atomic_order_checked((jl_sym_t*)args[4], 1, 0); + if (failure_order > success_order) + jl_atomic_error("invalid atomic ordering"); // because either it is invalid, or failure_order > success_order + if (success_order == jl_memory_order_notatomic) + jl_atomic_error("memoryrefreplace!: atomic memory cannot be written non-atomically"); + if (failure_order == jl_memory_order_notatomic) + jl_atomic_error("memoryrefreplace!: atomic memory cannot be accessed non-atomically"); + } + if (m.mem->length == 0) + jl_bounds_error_int((jl_value_t*)m.mem, 1); + return jl_memoryrefreplace(m, args[1], args[2], kind == (jl_value_t*)jl_atomic_sym); +} + +JL_CALLABLE(jl_f_memoryrefsetonce) +{ + enum jl_memory_order success_order = jl_memory_order_notatomic; + enum jl_memory_order failure_order = jl_memory_order_notatomic; + JL_NARGS(memoryrefsetonce!, 5, 5); + JL_TYPECHK(memoryrefsetonce!, genericmemoryref, args[0]); + JL_TYPECHK(memoryrefsetonce!, symbol, args[2]); + JL_TYPECHK(memoryrefsetonce!, symbol, args[3]); + JL_TYPECHK(memoryrefsetonce!, bool, args[4]); + jl_genericmemoryref_t m = *(jl_genericmemoryref_t*)args[0]; + jl_value_t *kind = jl_tparam0(jl_typetagof(m.mem)); + if (kind == (jl_value_t*)jl_not_atomic_sym) { + if (args[3] != kind) + jl_atomic_error("invalid atomic ordering"); // because either it is invalid, or failure_order > success_order + if (args[2] != kind) { + success_order = jl_get_atomic_order_checked((jl_sym_t*)args[2], 1, 1); + jl_atomic_error("memoryrefsetonce!: non-atomic memory cannot be written atomically"); + } + } + else if (kind == (jl_value_t*)jl_atomic_sym) { + success_order = jl_get_atomic_order_checked((jl_sym_t*)args[2], 1, 1); + failure_order = jl_get_atomic_order_checked((jl_sym_t*)args[3], 1, 0); + if (failure_order > success_order) + jl_atomic_error("invalid atomic ordering"); // because either it is invalid, or failure_order > success_order + if (success_order == jl_memory_order_notatomic) + jl_atomic_error("memoryrefsetonce!: atomic memory cannot be written non-atomically"); + if (failure_order == jl_memory_order_notatomic) + jl_atomic_error("memoryrefsetonce!: atomic memory cannot be accessed non-atomically"); + } + if (m.mem->length == 0) + jl_bounds_error_int((jl_value_t*)m.mem, 1); + return jl_memoryrefsetonce(m, args[1], kind == (jl_value_t*)jl_atomic_sym); } // type definition ------------------------------------------------------------ @@ -1665,7 +2057,7 @@ JL_CALLABLE(jl_f__svec_ref) if (idx < 1 || idx > len) { jl_bounds_error_int((jl_value_t*)s, idx); } - return jl_svec_ref(s, idx-1); + return jl_svecref(s, idx-1); } static int equiv_field_types(jl_value_t *old, jl_value_t *ft) @@ -1678,7 +2070,7 @@ static int equiv_field_types(jl_value_t *old, jl_value_t *ft) jl_value_t *ta = jl_svecref(old, i); jl_value_t *tb = jl_svecref(ft, i); if (jl_has_free_typevars(ta)) { - if (!jl_has_free_typevars(tb) || !jl_egal(ta, tb)) + if (!jl_has_free_typevars(tb) || !jl_types_egal(ta, tb)) return 0; } else if (jl_has_free_typevars(tb) || jl_typetagof(ta) != jl_typetagof(tb) || @@ -1719,7 +2111,7 @@ static int references_name(jl_value_t *p, jl_typename_t *name, int affects_layou jl_datatype_t *dp = (jl_datatype_t*)p; if (affects_layout && dp->name == name) return 1; - affects_layout = ((jl_datatype_t*)jl_unwrap_unionall(dp->name->wrapper))->layout == NULL; + affects_layout = jl_is_genericmemory_type(dp) || ((jl_datatype_t*)jl_unwrap_unionall(dp->name->wrapper))->layout == NULL; // and even if it has a layout, the fields themselves might trigger layouts if they use tparam i // rather than checking this for each field, we just assume it applies if (!affects_layout && freevars && jl_field_names(dp) != jl_emptysvec) { @@ -1871,13 +2263,12 @@ static unsigned intrinsic_nargs[num_intrinsics]; JL_CALLABLE(jl_f_intrinsic_call) { - JL_TYPECHK(intrinsic_call, intrinsic, F); enum intrinsic f = (enum intrinsic)*(uint32_t*)jl_data_ptr(F); if (f == cglobal && nargs == 1) f = cglobal_auto; unsigned fargs = intrinsic_nargs[f]; if (!fargs) - jl_errorf("`%s` must be compiled to be called", jl_intrinsic_name(f)); + jl_errorf("`%s` requires the compiler", jl_intrinsic_name(f)); JL_NARGS(intrinsic_call, fargs, fargs); union { @@ -1930,6 +2321,7 @@ unsigned jl_intrinsic_nargs(int f) static void add_intrinsic_properties(enum intrinsic f, unsigned nargs, void (*pfunc)(void)) { + assert(nargs <= 5 && "jl_f_intrinsic_call only implements up to 5 args"); intrinsic_nargs[f] = nargs; runtime_fp[f] = pfunc; } @@ -1982,12 +2374,13 @@ static void add_builtin(const char *name, jl_value_t *v) jl_set_const(jl_core_module, jl_symbol(name), v); } -jl_fptr_args_t jl_get_builtin_fptr(jl_value_t *b) +jl_fptr_args_t jl_get_builtin_fptr(jl_datatype_t *dt) { - assert(jl_isa(b, (jl_value_t*)jl_builtin_type)); - jl_typemap_entry_t *entry = (jl_typemap_entry_t*)jl_atomic_load_relaxed(&jl_gf_mtable(b)->defs); + assert(jl_subtype((jl_value_t*)dt, (jl_value_t*)jl_builtin_type)); + jl_typemap_entry_t *entry = (jl_typemap_entry_t*)jl_atomic_load_relaxed(&dt->name->mt->defs); jl_method_instance_t *mi = jl_atomic_load_relaxed(&entry->func.method->unspecialized); jl_code_instance_t *ci = jl_atomic_load_relaxed(&mi->cache); + assert(ci->owner == jl_nothing); return jl_atomic_load_relaxed(&ci->specptr.fptr1); } @@ -2011,6 +2404,7 @@ void jl_init_primitives(void) JL_GC_DISABLED // field access jl_builtin_getfield = add_builtin_func("getfield", jl_f_getfield); jl_builtin_setfield = add_builtin_func("setfield!", jl_f_setfield); + jl_builtin_setfieldonce = add_builtin_func("setfieldonce!", jl_f_setfieldonce); jl_builtin_swapfield = add_builtin_func("swapfield!", jl_f_swapfield); jl_builtin_modifyfield = add_builtin_func("modifyfield!", jl_f_modifyfield); jl_builtin_replacefield = add_builtin_func("replacefield!", jl_f_replacefield); @@ -2023,12 +2417,21 @@ void jl_init_primitives(void) JL_GC_DISABLED jl_builtin_setglobal = add_builtin_func("setglobal!", jl_f_setglobal); add_builtin_func("get_binding_type", jl_f_get_binding_type); add_builtin_func("set_binding_type!", jl_f_set_binding_type); - - // array primitives - jl_builtin_arrayref = add_builtin_func("arrayref", jl_f_arrayref); - jl_builtin_const_arrayref = add_builtin_func("const_arrayref", jl_f_arrayref); - jl_builtin_arrayset = add_builtin_func("arrayset", jl_f_arrayset); - jl_builtin_arraysize = add_builtin_func("arraysize", jl_f_arraysize); + jl_builtin_swapglobal = add_builtin_func("swapglobal!", jl_f_swapglobal); + jl_builtin_replaceglobal = add_builtin_func("replaceglobal!", jl_f_replaceglobal); + jl_builtin_modifyglobal = add_builtin_func("modifyglobal!", jl_f_modifyglobal); + jl_builtin_setglobalonce = add_builtin_func("setglobalonce!", jl_f_setglobalonce); + + // memory primitives + jl_builtin_memoryref = add_builtin_func("memoryref", jl_f_memoryref); + jl_builtin_memoryrefoffset = add_builtin_func("memoryrefoffset", jl_f_memoryrefoffset); + jl_builtin_memoryrefget = add_builtin_func("memoryrefget", jl_f_memoryrefget); + jl_builtin_memoryrefset = add_builtin_func("memoryrefset!", jl_f_memoryrefset); + jl_builtin_memoryref_isassigned = add_builtin_func("memoryref_isassigned", jl_f_memoryref_isassigned); + jl_builtin_memoryrefswap = add_builtin_func("memoryrefswap!", jl_f_memoryrefswap); + jl_builtin_memoryrefreplace = add_builtin_func("memoryrefreplace!", jl_f_memoryrefreplace); + jl_builtin_memoryrefmodify = add_builtin_func("memoryrefmodify!", jl_f_memoryrefmodify); + jl_builtin_memoryrefsetonce = add_builtin_func("memoryrefsetonce!", jl_f_memoryrefsetonce); // method table utils jl_builtin_applicable = add_builtin_func("applicable", jl_f_applicable); @@ -2055,6 +2458,7 @@ void jl_init_primitives(void) JL_GC_DISABLED add_builtin_func("finalizer", jl_f_finalizer); add_builtin_func("_compute_sparams", jl_f__compute_sparams); add_builtin_func("_svec_ref", jl_f__svec_ref); + add_builtin_func("current_scope", jl_f_current_scope); // builtin types add_builtin("Any", (jl_value_t*)jl_any_type); @@ -2092,21 +2496,27 @@ void jl_init_primitives(void) JL_GC_DISABLED add_builtin("Builtin", (jl_value_t*)jl_builtin_type); add_builtin("MethodInstance", (jl_value_t*)jl_method_instance_type); add_builtin("CodeInfo", (jl_value_t*)jl_code_info_type); - add_builtin("Ref", (jl_value_t*)jl_ref_type); - add_builtin("Ptr", (jl_value_t*)jl_pointer_type); add_builtin("LLVMPtr", (jl_value_t*)jl_llvmpointer_type); add_builtin("Task", (jl_value_t*)jl_task_type); add_builtin("OpaqueClosure", (jl_value_t*)jl_opaque_closure_type); + add_builtin("AddrSpace", (jl_value_t*)jl_addrspace_type); + add_builtin("Ref", (jl_value_t*)jl_ref_type); + add_builtin("Ptr", (jl_value_t*)jl_pointer_type); + //add_builtin("GenericPtr", (jl_value_t*)jl_genericpointer_type); add_builtin("AbstractArray", (jl_value_t*)jl_abstractarray_type); add_builtin("DenseArray", (jl_value_t*)jl_densearray_type); add_builtin("Array", (jl_value_t*)jl_array_type); + add_builtin("GenericMemory", (jl_value_t*)jl_genericmemory_type); + add_builtin("GenericMemoryRef", (jl_value_t*)jl_genericmemoryref_type); add_builtin("Expr", (jl_value_t*)jl_expr_type); add_builtin("LineNumberNode", (jl_value_t*)jl_linenumbernode_type); - add_builtin("LineInfoNode", (jl_value_t*)jl_lineinfonode_type); + add_builtin("LegacyLineInfoNode", (jl_value_t*)jl_lineinfonode_type); + add_builtin("DebugInfo", (jl_value_t*)jl_debuginfo_type); add_builtin("GotoNode", (jl_value_t*)jl_gotonode_type); add_builtin("GotoIfNot", (jl_value_t*)jl_gotoifnot_type); + add_builtin("EnterNode", (jl_value_t*)jl_enternode_type); add_builtin("ReturnNode", (jl_value_t*)jl_returnnode_type); add_builtin("PiNode", (jl_value_t*)jl_pinode_type); add_builtin("PhiNode", (jl_value_t*)jl_phinode_type); diff --git a/src/ccall.cpp b/src/ccall.cpp index fafb71efaf52d..2d4343a00a2a5 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -27,16 +27,16 @@ TRANSFORMED_CCALL_STAT(jl_get_current_task); TRANSFORMED_CCALL_STAT(jl_set_next_task); TRANSFORMED_CCALL_STAT(jl_sigatomic_begin); TRANSFORMED_CCALL_STAT(jl_sigatomic_end); -TRANSFORMED_CCALL_STAT(jl_svec_len); -TRANSFORMED_CCALL_STAT(jl_svec_ref); -TRANSFORMED_CCALL_STAT(jl_array_isassigned); TRANSFORMED_CCALL_STAT(jl_string_ptr); TRANSFORMED_CCALL_STAT(jl_symbol_name); +TRANSFORMED_CCALL_STAT(jl_genericmemory_owner); +TRANSFORMED_CCALL_STAT(jl_alloc_genericmemory); TRANSFORMED_CCALL_STAT(memcpy); TRANSFORMED_CCALL_STAT(memset); TRANSFORMED_CCALL_STAT(memmove); TRANSFORMED_CCALL_STAT(jl_object_id); #undef TRANSFORMED_CCALL_STAT +extern "C" JL_DLLEXPORT jl_value_t *ijl_genericmemory_owner(jl_genericmemory_t *m JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; STATISTIC(EmittedCCalls, "Number of ccalls emitted"); STATISTIC(DeferredCCallLookups, "Number of ccalls looked up at runtime"); @@ -80,7 +80,7 @@ static bool runtime_sym_gvs(jl_codectx_t &ctx, const char *f_lib, const char *f_ else { std::string name = "ccalllib_"; name += llvm::sys::path::filename(f_lib); - name += std::to_string(jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1)); + name += std::to_string(jl_atomic_fetch_add_relaxed(&globalUniqueGeneratedNames, 1)); runtime_lib = true; auto &libgv = ctx.emission_context.libMapGV[f_lib]; if (libgv.first == NULL) { @@ -100,7 +100,7 @@ static bool runtime_sym_gvs(jl_codectx_t &ctx, const char *f_lib, const char *f_ std::string name = "ccall_"; name += f_name; name += "_"; - name += std::to_string(jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1)); + name += std::to_string(jl_atomic_fetch_add_relaxed(&globalUniqueGeneratedNames, 1)); auto T_pvoidfunc = JuliaType::get_pvoidfunc_ty(M->getContext()); llvmgv = new GlobalVariable(*M, T_pvoidfunc, false, GlobalVariable::ExternalLinkage, @@ -152,7 +152,7 @@ static Value *runtime_sym_lookup( dlsym_lookup); assert(f->getParent() != NULL); - f->getBasicBlockList().push_back(dlsym_lookup); + dlsym_lookup->insertInto(f); irbuilder.SetInsertPoint(dlsym_lookup); Instruction *llvmf; Value *nameval = stringConstPtr(emission_context, irbuilder, f_name); @@ -179,7 +179,7 @@ static Value *runtime_sym_lookup( store->setAtomic(AtomicOrdering::Release); irbuilder.CreateBr(ccall_bb); - f->getBasicBlockList().push_back(ccall_bb); + ccall_bb->insertInto(f); irbuilder.SetInsertPoint(ccall_bb); PHINode *p = irbuilder.CreatePHI(T_pvoidfunc, 2); p->addIncoming(llvmf_orig, enter_bb); @@ -205,7 +205,7 @@ static Value *runtime_sym_lookup( std::string gvname = "libname_"; gvname += f_name; gvname += "_"; - gvname += std::to_string(jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1)); + gvname += std::to_string(jl_atomic_fetch_add_relaxed(&globalUniqueGeneratedNames, 1)); llvmgv = new GlobalVariable(*jl_Module, T_pvoidfunc, false, GlobalVariable::ExternalLinkage, Constant::getNullValue(T_pvoidfunc), gvname); @@ -233,7 +233,7 @@ static GlobalVariable *emit_plt_thunk( libptrgv = prepare_global_in(M, libptrgv); llvmgv = prepare_global_in(M, llvmgv); std::string fname; - raw_string_ostream(fname) << "jlplt_" << f_name << "_" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); + raw_string_ostream(fname) << "jlplt_" << f_name << "_" << jl_atomic_fetch_add_relaxed(&globalUniqueGeneratedNames, 1); Function *plt = Function::Create(functype, GlobalVariable::PrivateLinkage, fname, M); @@ -439,21 +439,21 @@ static Value *llvm_type_rewrite( Value *from; Value *to; const DataLayout &DL = ctx.builder.GetInsertBlock()->getModule()->getDataLayout(); - unsigned align = std::max(DL.getPrefTypeAlignment(target_type), DL.getPrefTypeAlignment(from_type)); + Align align = std::max(DL.getPrefTypeAlign(target_type), DL.getPrefTypeAlign(from_type)); if (DL.getTypeAllocSize(target_type) >= DL.getTypeAllocSize(from_type)) { to = emit_static_alloca(ctx, target_type); setName(ctx.emission_context, to, "type_rewrite_buffer"); - cast(to)->setAlignment(Align(align)); + cast(to)->setAlignment(align); from = emit_bitcast(ctx, to, from_type->getPointerTo()); } else { from = emit_static_alloca(ctx, from_type); setName(ctx.emission_context, from, "type_rewrite_buffer"); - cast(from)->setAlignment(Align(align)); + cast(from)->setAlignment(align); to = emit_bitcast(ctx, from, target_type->getPointerTo()); } - ctx.builder.CreateAlignedStore(v, from, Align(align)); - auto pun = ctx.builder.CreateAlignedLoad(target_type, to, Align(align)); + ctx.builder.CreateAlignedStore(v, from, align); + auto pun = ctx.builder.CreateAlignedLoad(target_type, to, align); setName(ctx.emission_context, pun, "type_rewrite"); return pun; } @@ -472,7 +472,7 @@ static Value *runtime_apply_type_env(jl_codectx_t &ctx, jl_value_t *ty) ctx.spvals_ptr, ConstantInt::get(ctx.types().T_size, sizeof(jl_svec_t) / sizeof(jl_value_t*))) }; - auto call = ctx.builder.CreateCall(prepare_call(jlapplytype_func), makeArrayRef(args)); + auto call = ctx.builder.CreateCall(prepare_call(jlapplytype_func), ArrayRef(args)); addRetAttr(call, Attribute::getWithAlignment(ctx.builder.getContext(), Align(16))); return call; } @@ -482,9 +482,10 @@ static const std::string make_errmsg(const char *fname, int n, const char *err) std::string _msg; raw_string_ostream msg(_msg); msg << fname; - if (n > 0) - msg << " argument " << n; - else + if (n > 0) { + msg << " argument "; + msg << n; + } else msg << " return"; msg << err; return msg.str(); @@ -615,7 +616,7 @@ static void interpret_symbol_arg(jl_codectx_t &ctx, native_sym_arg_t &out, jl_va emit_cpointercheck(ctx, arg1, errmsg); } arg1 = update_julia_type(ctx, arg1, (jl_value_t*)jl_voidpointer_type); - jl_ptr = emit_unbox(ctx, ctx.types().T_size, arg1, (jl_value_t*)jl_voidpointer_type); + jl_ptr = emit_unbox(ctx, ctx.types().T_ptr, arg1, (jl_value_t*)jl_voidpointer_type); } else { out.gcroot = ptr; @@ -668,7 +669,7 @@ static void interpret_symbol_arg(jl_codectx_t &ctx, native_sym_arg_t &out, jl_va // --- code generator for cglobal --- -static jl_cgval_t emit_runtime_call(jl_codectx_t &ctx, JL_I::intrinsic f, const jl_cgval_t *argv, size_t nargs); +static jl_cgval_t emit_runtime_call(jl_codectx_t &ctx, JL_I::intrinsic f, ArrayRef argv, size_t nargs); static jl_cgval_t emit_cglobal(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) { @@ -683,7 +684,7 @@ static jl_cgval_t emit_cglobal(jl_codectx_t &ctx, jl_value_t **args, size_t narg rt = static_eval(ctx, args[2]); if (rt == NULL) { JL_GC_POP(); - jl_cgval_t argv[2] = {jl_cgval_t(), jl_cgval_t()}; + jl_cgval_t argv[2]; argv[0] = emit_expr(ctx, args[1]); argv[1] = emit_expr(ctx, args[2]); return emit_runtime_call(ctx, JL_I::cglobal, argv, nargs); @@ -695,7 +696,7 @@ static jl_cgval_t emit_cglobal(jl_codectx_t &ctx, jl_value_t **args, size_t narg else { rt = (jl_value_t*)jl_voidpointer_type; } - Type *lrt = ctx.types().T_size; + Type *lrt = ctx.types().T_ptr; assert(lrt == julia_type_to_llvm(ctx, rt)); interpret_symbol_arg(ctx, sym, args[1], /*ccall=*/false, false); @@ -722,7 +723,6 @@ static jl_cgval_t emit_cglobal(jl_codectx_t &ctx, jl_value_t **args, size_t narg } else /*if (ctx.emission_context.imaging) */{ res = runtime_sym_lookup(ctx, cast(getInt8PtrTy(ctx.builder.getContext())), sym.f_lib, NULL, sym.f_name, ctx.f); - res = ctx.builder.CreatePtrToInt(res, lrt); } } @@ -748,7 +748,7 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar jl_value_t *ir_arg = args[1]; JL_GC_PUSH4(&ir, &rt, &at, &entry); if (jl_is_ssavalue(ir_arg)) - ir_arg = jl_arrayref((jl_array_t*)ctx.source->code, ((jl_ssavalue_t*)ir_arg)->id - 1); + ir_arg = jl_array_ptr_ref((jl_array_t*)ctx.source->code, ((jl_ssavalue_t*)ir_arg)->id - 1); ir = static_eval(ctx, ir_arg); if (!ir) { emit_error(ctx, "error statically evaluating llvm IR argument"); @@ -756,7 +756,7 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar return jl_cgval_t(); } if (jl_is_ssavalue(args[2]) && !jl_is_long(ctx.source->ssavaluetypes)) { - jl_value_t *rtt = jl_arrayref((jl_array_t*)ctx.source->ssavaluetypes, ((jl_ssavalue_t*)args[2])->id - 1); + jl_value_t *rtt = jl_array_ptr_ref((jl_array_t*)ctx.source->ssavaluetypes, ((jl_ssavalue_t*)args[2])->id - 1); if (jl_is_type_type(rtt)) rt = jl_tparam0(rtt); } @@ -769,7 +769,7 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar } } if (jl_is_ssavalue(args[3]) && !jl_is_long(ctx.source->ssavaluetypes)) { - jl_value_t *att = jl_arrayref((jl_array_t*)ctx.source->ssavaluetypes, ((jl_ssavalue_t*)args[3])->id - 1); + jl_value_t *att = jl_array_ptr_ref((jl_array_t*)ctx.source->ssavaluetypes, ((jl_ssavalue_t*)args[3])->id - 1); if (jl_is_type_type(att)) at = jl_tparam0(att); } @@ -813,18 +813,13 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar JL_TYPECHK(llvmcall, type, rt); JL_TYPECHK(llvmcall, type, at); - // Generate arguments - std::string arguments; - raw_string_ostream argstream(arguments); - jl_svec_t *tt = ((jl_datatype_t*)at)->parameters; - jl_value_t *rtt = rt; + // Determine argument types + // + // Semantics for arguments are as follows: + // If the argument type is immutable (including bitstype), we pass the loaded llvm value + // type. Otherwise we pass a pointer to a jl_value_t. + jl_svec_t *tt = ((jl_datatype_t *)at)->parameters; size_t nargt = jl_svec_len(tt); - - /* - * Semantics for arguments are as follows: - * If the argument type is immutable (including bitstype), we pass the loaded llvm value - * type. Otherwise we pass a pointer to a jl_value_t. - */ SmallVector argtypes; SmallVector argvals(nargt); for (size_t i = 0; i < nargt; ++i) { @@ -845,45 +840,87 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar argvals[i] = llvm_type_rewrite(ctx, v, t, issigned); } + // Determine return type + jl_value_t *rtt = rt; bool retboxed; Type *rettype = julia_type_to_llvm(ctx, rtt, &retboxed); // Make sure to find a unique name std::string ir_name; while (true) { - raw_string_ostream(ir_name) << (ctx.f->getName().str()) << "u" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); + raw_string_ostream(ir_name) + << (ctx.f->getName().str()) << "u" + << jl_atomic_fetch_add_relaxed(&globalUniqueGeneratedNames, 1); if (jl_Module->getFunction(ir_name) == NULL) break; } // generate a temporary module that contains our IR std::unique_ptr Mod; + Function *f; if (entry == NULL) { // we only have function IR, which we should put in a function - bool first = true; + // stringify arguments + std::string arguments; + raw_string_ostream argstream(arguments); for (SmallVector::iterator it = argtypes.begin(); it != argtypes.end(); ++it) { - if (!first) + if (it != argtypes.begin()) argstream << ","; - else - first = false; (*it)->print(argstream); argstream << " "; } + // stringify return type std::string rstring; raw_string_ostream rtypename(rstring); rettype->print(rtypename); - std::map localDecls; + // generate IR function definition std::string ir_string; raw_string_ostream ir_stream(ir_string); - ir_stream << "; Number of arguments: " << nargt << "\n" - << "define "< 0) + compat_argstream << ","; + jl_value_t *tti = jl_svecref(tt, i); + Type *t; + if (jl_is_cpointer_type(tti)) + t = ctx.types().T_size; + else + t = argtypes[i]; + t->print(compat_argstream); + compat_argstream << " "; + } + + std::string compat_rstring; + raw_string_ostream compat_rtypename(compat_rstring); + if (jl_is_cpointer_type(rtt)) + ctx.types().T_size->print(compat_rtypename); + else + rettype->print(compat_rtypename); + + std::string compat_ir_string; + raw_string_ostream compat_ir_stream(compat_ir_string); + compat_ir_stream << "define " << compat_rtypename.str() << " @\"" << ir_name + << "\"(" << compat_argstream.str() << ") {\n" + << jl_string_data(ir) << "\n}"; + + SMDiagnostic Err = SMDiagnostic(); + Mod = + parseAssemblyString(compat_ir_stream.str(), Err, ctx.builder.getContext()); + } + if (!Mod) { std::string message = "Failed to parse LLVM assembly: \n"; raw_string_ostream stream(message); @@ -893,7 +930,7 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar return jl_cgval_t(); } - Function *f = Mod->getFunction(ir_name); + f = Mod->getFunction(ir_name); f->addFnAttr(Attribute::AlwaysInline); } else { @@ -913,7 +950,7 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar } else { auto Buf = MemoryBuffer::getMemBuffer( - StringRef((char *)jl_array_data(ir), jl_array_len(ir)), "llvmcall", + StringRef(jl_array_data(ir, char), jl_array_nrows(ir)), "llvmcall", /*RequiresNullTerminator*/ false); Expected> ModuleOrErr = parseBitcodeFile(*Buf, ctx.builder.getContext()); @@ -931,21 +968,96 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar Mod = std::move(ModuleOrErr.get()); } - Function *f = Mod->getFunction(jl_string_data(entry)); + f = Mod->getFunction(jl_string_data(entry)); if (!f) { emit_error(ctx, "Module IR does not contain specified entry function"); JL_GC_POP(); return jl_cgval_t(); } + assert(!f->isDeclaration()); f->setName(ir_name); + } - // verify the function type - assert(!f->isDeclaration()); - assert(f->getReturnType() == rettype); - int i = 0; - for (SmallVector::iterator it = argtypes.begin(); - it != argtypes.end(); ++it, ++i) - assert(*it == f->getFunctionType()->getParamType(i)); + // backwards compatibility: support for IR with integer pointers + bool mismatched_pointers = false; + for (size_t i = 0; i < nargt; ++i) { + jl_value_t *tti = jl_svecref(tt, i); + if (jl_is_cpointer_type(tti) && + !f->getFunctionType()->getParamType(i)->isPointerTy()) { + mismatched_pointers = true; + break; + } + } + if (mismatched_pointers) { + if (jl_options.depwarn) { + if (jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR) + jl_error("llvmcall with integer pointers is deprecated, " + "use an actual pointer type instead."); + + // ensure we only depwarn once per method + // TODO: lift this into a reusable codegen-level depwarn utility + static std::set llvmcall_depwarns; + jl_method_t *m = ctx.linfo->def.method; + if (llvmcall_depwarns.find(m) == llvmcall_depwarns.end()) { + llvmcall_depwarns.insert(m); + jl_printf(JL_STDERR, + "WARNING: llvmcall with integer pointers is deprecated.\n" + "Use actual pointers instead, replacing i32 or i64 with i8* or ptr\n" + "in "); + jl_static_show(JL_STDERR, (jl_value_t*) ctx.linfo->def.method); + jl_printf(JL_STDERR, " at %s\n", ctx.file.str().c_str()); + } + } + + // wrap the function, performing the necessary pointer conversion + + Function *inner = f; + inner->setName(ir_name + ".inner"); + + FunctionType *wrapper_ft = FunctionType::get(rettype, argtypes, false); + Function *wrapper = + Function::Create(wrapper_ft, inner->getLinkage(), ir_name, *Mod); + + wrapper->copyAttributesFrom(inner); + inner->addFnAttr(Attribute::AlwaysInline); + + BasicBlock *entry = BasicBlock::Create(ctx.builder.getContext(), "", wrapper); + IRBuilder<> irbuilder(entry); + SmallVector wrapper_args; + for (size_t i = 0; i < nargt; ++i) { + jl_value_t *tti = jl_svecref(tt, i); + Value *v = wrapper->getArg(i); + if (jl_is_cpointer_type(tti)) + v = irbuilder.CreatePtrToInt(v, ctx.types().T_size); + wrapper_args.push_back(v); + } + Value *call = irbuilder.CreateCall(inner, wrapper_args); + // check if void + if (rettype->isVoidTy()) + irbuilder.CreateRetVoid(); + else { + if (jl_is_cpointer_type(rtt)) + call = irbuilder.CreateIntToPtr(call, ctx.types().T_ptr); + irbuilder.CreateRet(call); + } + + f = wrapper; + } + + // verify the function type + assert(f->getReturnType() == rettype); + int i = 0; + for (SmallVector::iterator it = argtypes.begin(); it != argtypes.end(); + ++it, ++i) { + if (*it != f->getFunctionType()->getParamType(i)) { + std::string message; + raw_string_ostream stream(message); + stream << "Malformed llvmcall: argument " << i + 1 << " type " + << *f->getFunctionType()->getParamType(i) + << " does not match expected argument type " << **it; + emit_error(ctx, stream.str()); + return jl_cgval_t(); + } } // copy module properties that should always match @@ -983,7 +1095,7 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar if (inst->getType() != rettype) { std::string message; raw_string_ostream stream(message); - stream << "llvmcall return type " << *inst->getType() + stream << "Malformed llvmcall: return type " << *inst->getType() << " does not match declared return type" << *rettype; emit_error(ctx, stream.str()); return jl_cgval_t(); @@ -1000,8 +1112,9 @@ static Value *box_ccall_result(jl_codectx_t &ctx, Value *result, Value *runtime_ // XXX: need to handle parameterized zero-byte types (singleton) const DataLayout &DL = ctx.builder.GetInsertBlock()->getModule()->getDataLayout(); unsigned nb = DL.getTypeStoreSize(result->getType()); + unsigned align = sizeof(void*); // Allocations are at least pointer aligned MDNode *tbaa = jl_is_mutable(rt) ? ctx.tbaa().tbaa_mutab : ctx.tbaa().tbaa_immut; - Value *strct = emit_allocobj(ctx, nb, runtime_dt); + Value *strct = emit_allocobj(ctx, nb, runtime_dt, true, align); setName(ctx.emission_context, strct, "ccall_result_box"); init_bits_value(ctx, strct, result, tbaa); return strct; @@ -1054,7 +1167,7 @@ class function_sig_t { FunctionType *functype(LLVMContext &ctxt) const { assert(err_msg.empty()); if (nreqargs > 0) - return FunctionType::get(sret ? getVoidTy(ctxt) : prt, makeArrayRef(fargt_sig).slice(0, nreqargs), true); + return FunctionType::get(sret ? getVoidTy(ctxt) : prt, ArrayRef(fargt_sig).slice(0, nreqargs), true); else return FunctionType::get(sret ? getVoidTy(ctxt) : prt, fargt_sig, false); } @@ -1063,7 +1176,7 @@ class function_sig_t { jl_codectx_t &ctx, const native_sym_arg_t &symarg, jl_cgval_t *argv, - SmallVector &gc_uses, + SmallVectorImpl &gc_uses, bool static_rt) const; private: @@ -1141,7 +1254,8 @@ std::string generate_func_sig(const char *fname) } Type *pat; - if (!jl_is_datatype(tti) || ((jl_datatype_t*)tti)->layout == NULL || jl_is_layout_opaque(((jl_datatype_t*)tti)->layout)) { + // n.b. `Array` used as argument type just passes a julia object reference + if (!jl_is_datatype(tti) || ((jl_datatype_t*)tti)->layout == NULL || jl_is_array_type(tti) || jl_is_layout_opaque(((jl_datatype_t*)tti)->layout)) { tti = (jl_value_t*)jl_voidpointer_type; // passed as pointer } @@ -1272,7 +1386,7 @@ static const std::string verify_ccall_sig(jl_value_t *&rt, jl_value_t *at, JL_TYPECHK(ccall, type, rt); JL_TYPECHK(ccall, simplevector, at); - if (rt == (jl_value_t*)jl_any_type || jl_is_array_type(rt) || + if (rt == (jl_value_t*)jl_any_type || jl_is_array_type(rt) || jl_is_genericmemory_type(rt) || (jl_is_datatype(rt) && ((jl_datatype_t*)rt)->layout != NULL && jl_is_layout_opaque(((jl_datatype_t*)rt)->layout))) { // n.b. `Array` used as return type just returns a julia object reference @@ -1350,11 +1464,6 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) return jl_cgval_t(); } - auto ccallarg = [=] (size_t i) { - assert(i < nccallargs && i + fc_args_start <= nargs); - return args[fc_args_start + i]; - }; - auto _is_libjulia_func = [&] (uintptr_t ptr, StringRef name) { if ((uintptr_t)fptr == ptr) return true; @@ -1381,21 +1490,24 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) SmallVector argv(nccallargs); for (size_t i = 0; i < nccallargs; i++) { // Julia (expression) value of current parameter - jl_value_t *argi = ccallarg(i); + assert(i < nccallargs && i + fc_args_start <= nargs); + jl_value_t *argi = args[fc_args_start + i]; argv[i] = emit_expr(ctx, argi); + if (argv[i].typ == jl_bottom_type) { + JL_GC_POP(); + return jl_cgval_t(); + } } // emit roots - SmallVector gc_uses; + SmallVector gc_uses; for (size_t i = nccallargs + fc_args_start; i <= nargs; i++) { // Julia (expression) value of current parameter gcroot jl_value_t *argi_root = args[i]; if (jl_is_long(argi_root)) continue; jl_cgval_t arg_root = emit_expr(ctx, argi_root); - Value *gc_root = get_gc_root_for(arg_root); - if (gc_root) - gc_uses.push_back(gc_root); + gc_uses.append(get_gc_roots_for(ctx, arg_root)); } jl_unionall_t *unionall = (jl_is_method(ctx.linfo->def.method) && jl_is_unionall(ctx.linfo->def.method->sig)) @@ -1459,25 +1571,16 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) // some special functions bool isVa = nreqargs > 0; (void)isVa; // prevent compiler warning - if (is_libjulia_func(jl_array_ptr)) { - ++CCALL_STAT(jl_array_ptr); - assert(lrt == ctx.types().T_size); - assert(!isVa && !llvmcall && nccallargs == 1); - const jl_cgval_t &ary = argv[0]; - JL_GC_POP(); - return mark_or_box_ccall_result(ctx, ctx.builder.CreatePtrToInt(emit_unsafe_arrayptr(ctx, ary), lrt), - retboxed, rt, unionall, static_rt); - } - else if (is_libjulia_func(jl_value_ptr)) { + if (is_libjulia_func(jl_value_ptr)) { ++CCALL_STAT(jl_value_ptr); - assert(retboxed ? lrt == ctx.types().T_prjlvalue : lrt == ctx.types().T_size); + assert(retboxed ? lrt == ctx.types().T_prjlvalue : lrt == ctx.types().T_ptr); assert(!isVa && !llvmcall && nccallargs == 1); jl_value_t *tti = jl_svecref(at, 0); Type *largty; bool isboxed; if (jl_is_abstract_ref_type(tti)) { tti = (jl_value_t*)jl_voidpointer_type; - largty = ctx.types().T_size; + largty = ctx.types().T_ptr; isboxed = false; } else { @@ -1490,7 +1593,6 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) } else { retval = emit_unbox(ctx, largty, argv[0], tti); - retval = emit_inttoptr(ctx, retval, ctx.types().T_pjlvalue); } // retval is now an untracked jl_value_t* if (retboxed) @@ -1569,9 +1671,7 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) assert(lrt == ctx.types().T_size); assert(!isVa && !llvmcall && nccallargs == 0); JL_GC_POP(); - return mark_or_box_ccall_result(ctx, - ctx.builder.CreatePtrToInt(get_current_ptls(ctx), lrt), - retboxed, rt, unionall, static_rt); + return mark_or_box_ccall_result(ctx, get_current_ptls(ctx), retboxed, rt, unionall, static_rt); } else if (is_libjulia_func(jl_threadid)) { ++CCALL_STAT(jl_threadid); @@ -1619,7 +1719,7 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) assert(lrt == ctx.types().T_prjlvalue); assert(!isVa && !llvmcall && nccallargs == 0); JL_GC_POP(); - auto ct = track_pjlvalue(ctx, emit_bitcast(ctx, get_current_task(ctx), ctx.types().T_pjlvalue)); + auto ct = track_pjlvalue(ctx, get_current_task(ctx)); return mark_or_box_ccall_result(ctx, ct, retboxed, rt, unionall, static_rt); } else if (is_libjulia_func(jl_set_next_task)) { @@ -1685,123 +1785,26 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) true); setName(ctx.emission_context, signal_page_load, "signal_page_load"); ctx.builder.CreateBr(contBB); - ctx.f->getBasicBlockList().push_back(contBB); + contBB->insertInto(ctx.f); ctx.builder.SetInsertPoint(contBB); return ghostValue(ctx, jl_nothing_type); } - else if (is_libjulia_func(jl_svec_len)) { - ++CCALL_STAT(jl_svec_len); - assert(!isVa && !llvmcall && nccallargs == 1); - const jl_cgval_t &svecv = argv[0]; - Value *len; - if (svecv.constant && svecv.typ == (jl_value_t*)jl_simplevector_type) { - // Check the type as well before we call - len = ConstantInt::get(ctx.types().T_size, jl_svec_len(svecv.constant)); - } - else { - auto ptr = emit_bitcast(ctx, boxed(ctx, svecv), ctx.types().T_size->getPointerTo()); - setName(ctx.emission_context, ptr, "svec_len_ptr"); - len = ctx.builder.CreateAlignedLoad(ctx.types().T_size, ptr, ctx.types().alignof_ptr); - setName(ctx.emission_context, len, "svec_len"); - // Only mark with TBAA if we are sure about the type. - // This could otherwise be in a dead branch - if (svecv.typ == (jl_value_t*)jl_simplevector_type) { - jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); - ai.decorateInst(cast(len)); - } - MDBuilder MDB(ctx.builder.getContext()); - auto rng = MDB.createRange( - Constant::getNullValue(ctx.types().T_size), ConstantInt::get(ctx.types().T_size, INTPTR_MAX / sizeof(void*) - 1)); - cast(len)->setMetadata(LLVMContext::MD_range, rng); - } - JL_GC_POP(); - return mark_or_box_ccall_result(ctx, len, retboxed, rt, unionall, static_rt); - } - else if (is_libjulia_func(jl_svec_ref) && argv[1].typ == (jl_value_t*)jl_long_type) { - ++CCALL_STAT(jl_svec_ref); - assert(lrt == ctx.types().T_prjlvalue); - assert(!isVa && !llvmcall && nccallargs == 2); - const jl_cgval_t &svecv = argv[0]; - const jl_cgval_t &idxv = argv[1]; - Value *idx = emit_unbox(ctx, ctx.types().T_size, idxv, (jl_value_t*)jl_long_type); - idx = ctx.builder.CreateAdd(idx, ConstantInt::get(ctx.types().T_size, 1)); - setName(ctx.emission_context, idx, "svec_idx"); - auto ptr = emit_bitcast(ctx, boxed(ctx, svecv), ctx.types().T_pprjlvalue); - setName(ctx.emission_context, ptr, "svec_data_ptr"); - Value *slot_addr = ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, - decay_derived(ctx, ptr), idx); - setName(ctx.emission_context, slot_addr, "svec_slot_addr"); - LoadInst *load = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, slot_addr, - Align(sizeof(void*))); - setName(ctx.emission_context, load, "svec_slot"); - load->setAtomic(AtomicOrdering::Unordered); - // Only mark with TBAA if we are sure about the type. - // This could otherwise be in a dead branch - if (svecv.typ == (jl_value_t*)jl_simplevector_type) { - jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); - ai.decorateInst(load); - } - JL_GC_POP(); - return mark_or_box_ccall_result(ctx, load, retboxed, rt, unionall, static_rt); - } - else if (is_libjulia_func(jl_array_isassigned) && - argv[1].typ == (jl_value_t*)jl_ulong_type) { - ++CCALL_STAT(jl_array_isassigned); - assert(!isVa && !llvmcall && nccallargs == 2); - jl_value_t *aryex = ccallarg(0); - const jl_cgval_t &aryv = argv[0]; - const jl_cgval_t &idxv = argv[1]; - jl_datatype_t *arydt = (jl_datatype_t*)jl_unwrap_unionall(aryv.typ); - if (jl_is_array_type(arydt)) { - jl_value_t *ety = jl_tparam0(arydt); - bool ptrarray = !jl_stored_inline(ety); - if (!ptrarray && !jl_type_hasptr(ety)) { - JL_GC_POP(); - return mark_or_box_ccall_result(ctx, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 1), - false, rt, unionall, static_rt); - } - else if (!jl_has_free_typevars(ety)) { - Value *idx = emit_unbox(ctx, ctx.types().T_size, idxv, (jl_value_t*)jl_ulong_type); - Value *arrayptr = emit_bitcast(ctx, emit_arrayptr(ctx, aryv, aryex), ctx.types().T_pprjlvalue); - if (!ptrarray) { - size_t elsz = jl_datatype_size(ety); - unsigned align = jl_datatype_align(ety); - size_t stride = LLT_ALIGN(elsz, align) / sizeof(jl_value_t*); - if (stride != 1) - idx = ctx.builder.CreateMul(idx, ConstantInt::get(ctx.types().T_size, stride)); - idx = ctx.builder.CreateAdd(idx, ConstantInt::get(ctx.types().T_size, ((jl_datatype_t*)ety)->layout->first_ptr)); - setName(ctx.emission_context, idx, "array_idx"); - } - Value *slot_addr = ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, arrayptr, idx); - setName(ctx.emission_context, slot_addr, "array_slot_addr"); - LoadInst *load = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, slot_addr, Align(sizeof(void*))); - setName(ctx.emission_context, load, "array_slot"); - load->setAtomic(AtomicOrdering::Unordered); - jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_ptrarraybuf); - ai.decorateInst(load); - Value *res = ctx.builder.CreateZExt(ctx.builder.CreateICmpNE(load, Constant::getNullValue(ctx.types().T_prjlvalue)), getInt32Ty(ctx.builder.getContext())); - JL_GC_POP(); - return mark_or_box_ccall_result(ctx, res, retboxed, rt, unionall, static_rt); - } - } - } else if (is_libjulia_func(jl_string_ptr)) { ++CCALL_STAT(jl_string_ptr); - assert(lrt == ctx.types().T_size); + assert(lrt == ctx.types().T_ptr); assert(!isVa && !llvmcall && nccallargs == 1); auto obj = emit_bitcast(ctx, emit_pointer_from_objref(ctx, boxed(ctx, argv[0])), ctx.types().T_pprjlvalue); // The inbounds gep makes it more clear to LLVM that the resulting value is not // a null pointer. auto strp = ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_prjlvalue, obj, 1); - strp = ctx.builder.CreatePtrToInt(strp, ctx.types().T_size); setName(ctx.emission_context, strp, "string_ptr"); JL_GC_POP(); return mark_or_box_ccall_result(ctx, strp, retboxed, rt, unionall, static_rt); } else if (is_libjulia_func(jl_symbol_name)) { ++CCALL_STAT(jl_symbol_name); - assert(lrt == ctx.types().T_size); + assert(lrt == ctx.types().T_ptr); assert(!isVa && !llvmcall && nccallargs == 1); auto obj = emit_bitcast(ctx, emit_pointer_from_objref(ctx, boxed(ctx, argv[0])), ctx.types().T_pprjlvalue); @@ -1809,24 +1812,56 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) // a null pointer. auto strp = ctx.builder.CreateConstInBoundsGEP1_32( ctx.types().T_prjlvalue, obj, (sizeof(jl_sym_t) + sizeof(void*) - 1) / sizeof(void*)); - strp = ctx.builder.CreatePtrToInt(strp, ctx.types().T_size); setName(ctx.emission_context, strp, "symbol_name"); JL_GC_POP(); return mark_or_box_ccall_result(ctx, strp, retboxed, rt, unionall, static_rt); } + else if (is_libjulia_func(jl_genericmemory_owner) || is_libjulia_func(ijl_genericmemory_owner)) { + ++CCALL_STAT(jl_genericmemory_owner); + assert(lrt == ctx.types().T_prjlvalue); + assert(!isVa && !llvmcall && nccallargs == 1); + Value *obj = emit_genericmemoryowner(ctx, boxed(ctx, argv[0])); + JL_GC_POP(); + return mark_julia_type(ctx, obj, true, jl_any_type); + } + else if (is_libjulia_func(jl_alloc_genericmemory)) { + ++CCALL_STAT(jl_alloc_genericmemory); + assert(lrt == ctx.types().T_prjlvalue); + assert(!isVa && !llvmcall && nccallargs == 2); + const jl_cgval_t &typ = argv[0]; + const jl_cgval_t &nel = argv[1]; + auto arg_typename = [&] JL_NOTSAFEPOINT { + auto istyp = argv[0].constant; + std::string type_str; + if (istyp && jl_is_datatype(istyp) && jl_is_genericmemory_type(istyp)){ + auto eltype = jl_tparam1(istyp); + if (jl_is_datatype(eltype)) + type_str = jl_symbol_name(((jl_datatype_t*)eltype)->name->name); + else if (jl_is_uniontype(eltype)) + type_str = "Union"; + else + type_str = ""; + } + else + type_str = ""; + return "Memory{" + type_str + "}[]"; + }; + auto alloc = ctx.builder.CreateCall(prepare_call(jl_allocgenericmemory), { boxed(ctx,typ), emit_unbox(ctx, ctx.types().T_size, nel, (jl_value_t*)jl_ulong_type)}); + setName(ctx.emission_context, alloc, arg_typename); + JL_GC_POP(); + return mark_julia_type(ctx, alloc, true, jl_any_type); + } else if (is_libjulia_func(memcpy) && (rt == (jl_value_t*)jl_nothing_type || jl_is_cpointer_type(rt))) { ++CCALL_STAT(memcpy); const jl_cgval_t &dst = argv[0]; const jl_cgval_t &src = argv[1]; const jl_cgval_t &n = argv[2]; - Value *destp = emit_unbox(ctx, ctx.types().T_size, dst, (jl_value_t*)jl_voidpointer_type); + Value *destp = emit_unbox(ctx, ctx.types().T_ptr, dst, (jl_value_t*)jl_voidpointer_type); ctx.builder.CreateMemCpy( - emit_inttoptr(ctx, destp, getInt8PtrTy(ctx.builder.getContext())), + destp, MaybeAlign(1), - emit_inttoptr(ctx, - emit_unbox(ctx, ctx.types().T_size, src, (jl_value_t*)jl_voidpointer_type), - getInt8PtrTy(ctx.builder.getContext())), + emit_unbox(ctx, ctx.types().T_ptr, src, (jl_value_t*)jl_voidpointer_type), MaybeAlign(1), emit_unbox(ctx, ctx.types().T_size, n, (jl_value_t*)jl_ulong_type), false); @@ -1839,11 +1874,11 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) const jl_cgval_t &dst = argv[0]; const jl_cgval_t &val = argv[1]; const jl_cgval_t &n = argv[2]; - Value *destp = emit_unbox(ctx, ctx.types().T_size, dst, (jl_value_t*)jl_voidpointer_type); + Value *destp = emit_unbox(ctx, ctx.types().T_ptr, dst, (jl_value_t*)jl_voidpointer_type); Value *val32 = emit_unbox(ctx, getInt32Ty(ctx.builder.getContext()), val, (jl_value_t*)jl_uint32_type); Value *val8 = ctx.builder.CreateTrunc(val32, getInt8Ty(ctx.builder.getContext()), "memset_val"); ctx.builder.CreateMemSet( - emit_inttoptr(ctx, destp, getInt8PtrTy(ctx.builder.getContext())), + destp, val8, emit_unbox(ctx, ctx.types().T_size, n, (jl_value_t*)jl_ulong_type), MaybeAlign(1) @@ -1857,14 +1892,12 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) const jl_cgval_t &dst = argv[0]; const jl_cgval_t &src = argv[1]; const jl_cgval_t &n = argv[2]; - Value *destp = emit_unbox(ctx, ctx.types().T_size, dst, (jl_value_t*)jl_voidpointer_type); + Value *destp = emit_unbox(ctx, ctx.types().T_ptr, dst, (jl_value_t*)jl_voidpointer_type); ctx.builder.CreateMemMove( - emit_inttoptr(ctx, destp, getInt8PtrTy(ctx.builder.getContext())), + destp, MaybeAlign(0), - emit_inttoptr(ctx, - emit_unbox(ctx, ctx.types().T_size, src, (jl_value_t*)jl_voidpointer_type), - getInt8PtrTy(ctx.builder.getContext())), + emit_unbox(ctx, ctx.types().T_ptr, src, (jl_value_t*)jl_voidpointer_type), MaybeAlign(0), emit_unbox(ctx, ctx.types().T_size, n, (jl_value_t*)jl_ulong_type), false); @@ -1895,13 +1928,13 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) if (!val.isghost && !val.ispointer()) val = value_to_pointer(ctx, val); Value *args[] = { - emit_typeof(ctx, val), + emit_typeof(ctx, val, false, true), val.isghost ? ConstantPointerNull::get(T_pint8_derived) : ctx.builder.CreateBitCast( decay_derived(ctx, data_pointer(ctx, val)), T_pint8_derived) }; - Value *ret = ctx.builder.CreateCall(prepare_call(jl_object_id__func), makeArrayRef(args)); + Value *ret = ctx.builder.CreateCall(prepare_call(jl_object_id__func), ArrayRef(args)); setName(ctx.emission_context, ret, "object_id"); JL_GC_POP(); return mark_or_box_ccall_result(ctx, ret, retboxed, rt, unionall, static_rt); @@ -1922,7 +1955,7 @@ jl_cgval_t function_sig_t::emit_a_ccall( jl_codectx_t &ctx, const native_sym_arg_t &symarg, jl_cgval_t *argv, - SmallVector &gc_uses, + SmallVectorImpl &gc_uses, bool static_rt) const { ++EmittedCCalls; @@ -1999,7 +2032,7 @@ jl_cgval_t function_sig_t::emit_a_ccall( // XXX: result needs to be zero'd and given a GC root here // and has incorrect write barriers. // instead this code path should behave like `unsafe_load` - result = emit_allocobj(ctx, (jl_datatype_t*)rt); + result = emit_allocobj(ctx, (jl_datatype_t*)rt, true); setName(ctx.emission_context, result, "ccall_sret_box"); sretty = ctx.types().T_jlvalue; sretboxed = true; @@ -2066,9 +2099,9 @@ jl_cgval_t function_sig_t::emit_a_ccall( } else if (symarg.jl_ptr != NULL) { ++LiteralCCalls; - null_pointer_check(ctx, symarg.jl_ptr); + null_pointer_check(ctx, symarg.jl_ptr, nullptr); Type *funcptype = PointerType::get(functype, 0); - llvmf = emit_inttoptr(ctx, symarg.jl_ptr, funcptype); + llvmf = ctx.builder.CreateBitCast(symarg.jl_ptr, funcptype); } else if (symarg.fptr != NULL) { ++LiteralCCalls; @@ -2156,7 +2189,7 @@ jl_cgval_t function_sig_t::emit_a_ccall( else if (jlretboxed && !retboxed) { assert(jl_is_datatype(rt)); if (static_rt) { - Value *strct = emit_allocobj(ctx, (jl_datatype_t*)rt); + Value *strct = emit_allocobj(ctx, (jl_datatype_t*)rt, true); setName(ctx.emission_context, strct, "ccall_ret_box"); MDNode *tbaa = jl_is_mutable(rt) ? ctx.tbaa().tbaa_mutab : ctx.tbaa().tbaa_immut; int boxalign = julia_alignment(rt); diff --git a/src/cgmemmgr.cpp b/src/cgmemmgr.cpp index b627224e027a9..c78e6092ca5db 100644 --- a/src/cgmemmgr.cpp +++ b/src/cgmemmgr.cpp @@ -25,6 +25,9 @@ # include # include #endif +#ifdef _OS_OPENBSD_ +# include +#endif #include "julia_assert.h" namespace { diff --git a/src/cgutils.cpp b/src/cgutils.cpp index 63f5e2be5ddbb..344bc7c5be5b2 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -28,13 +28,8 @@ STATISTIC(EmittedGetfieldKnowns, "Number of known getfield calls emitted"); STATISTIC(EmittedSetfield, "Number of setfield calls emitted"); STATISTIC(EmittedUnionLoads, "Number of union loads emitted"); STATISTIC(EmittedVarargsLength, "Number of varargs length calls emitted"); -STATISTIC(EmittedArraysize, "Number of arraysize calls emitted"); -STATISTIC(EmittedArraylen, "Number of array length calls emitted"); -STATISTIC(EmittedArrayptr, "Number of array data pointer loads emitted"); -STATISTIC(EmittedArrayflags, "Number of arrayflags calls emitted"); -STATISTIC(EmittedArrayNDims, "Number of array ndims calls emitted"); +STATISTIC(EmittedArrayptr, "Number of array ptr calls emitted"); STATISTIC(EmittedArrayElsize, "Number of array elsize calls emitted"); -STATISTIC(EmittedArrayOffset, "Number of array offset calls emitted"); STATISTIC(EmittedArrayNdIndex, "Number of array nd index calls emitted"); STATISTIC(EmittedBoxes, "Number of box operations emitted"); STATISTIC(EmittedCPointerChecks, "Number of C pointer checks emitted"); @@ -62,7 +57,7 @@ static Value *maybe_decay_untracked(jl_codectx_t &ctx, Value *V) static Value *decay_derived(jl_codectx_t &ctx, Value *V) { Type *T = V->getType(); - if (cast(T)->getAddressSpace() == AddressSpace::Derived) + if (T->getPointerAddressSpace() == AddressSpace::Derived) return V; // Once llvm deletes pointer element types, we won't need it here any more either. Type *NewT = PointerType::getWithSamePointeeType(cast(T), AddressSpace::Derived); @@ -73,7 +68,7 @@ static Value *decay_derived(jl_codectx_t &ctx, Value *V) static Value *maybe_decay_tracked(jl_codectx_t &ctx, Value *V) { Type *T = V->getType(); - if (cast(T)->getAddressSpace() != AddressSpace::Tracked) + if (T->getPointerAddressSpace() != AddressSpace::Tracked) return V; Type *NewT = PointerType::getWithSamePointeeType(cast(T), AddressSpace::Derived); return ctx.builder.CreateAddrSpaceCast(V, NewT); @@ -130,7 +125,7 @@ static Value *stringConstPtr( Value *zero = ConstantInt::get(Type::getInt32Ty(irbuilder.getContext()), 0); Value *Args[] = { zero, zero }; auto gep = irbuilder.CreateInBoundsGEP(gv->getValueType(), - // Addrspacecast in case globals are in non-0 AS + // AddrSpaceCast in case globals are in non-0 AS irbuilder.CreateAddrSpaceCast(gv, gv->getValueType()->getPointerTo(0)), Args); setName(emission_context, gep, "string_const_ptr"); @@ -213,7 +208,7 @@ static DIType *_julia_type_to_di(jl_codegen_params_t *ctx, jl_debugcache_t &debu uint64_t SizeInBits = jl_datatype_nbits(jdt); ditype = dbuilder->createBasicType(tname, SizeInBits, llvm::dwarf::DW_ATE_unsigned); } - else if (jl_is_structtype(jt) && !jl_is_layout_opaque(jdt->layout)) { + else if (jl_is_structtype(jt) && !jl_is_layout_opaque(jdt->layout) && !jl_is_array_type(jdt)) { size_t ntypes = jl_datatype_nfields(jdt); SmallVector Elements(ntypes); for (unsigned i = 0; i < ntypes; i++) { @@ -300,7 +295,7 @@ void jl_debugcache_t::initialize(Module *m) { static Value *emit_pointer_from_objref(jl_codectx_t &ctx, Value *V) { - unsigned AS = cast(V->getType())->getAddressSpace(); + unsigned AS = V->getType()->getPointerAddressSpace(); if (AS != AddressSpace::Tracked && AS != AddressSpace::Derived) return V; V = decay_derived(ctx, V); @@ -314,20 +309,67 @@ static Value *emit_pointer_from_objref(jl_codectx_t &ctx, Value *V) return Call; } -static Value *get_gc_root_for(const jl_cgval_t &x) +static Value *emit_unbox(jl_codectx_t &ctx, Type *to, const jl_cgval_t &x, jl_value_t *jt); +static void emit_unbox_store(jl_codectx_t &ctx, const jl_cgval_t &x, Value* dest, MDNode *tbaa_dest, unsigned alignment, bool isVolatile=false); + +static bool type_is_permalloc(jl_value_t *typ) { - if (x.Vboxed) - return x.Vboxed; - if (x.ispointer() && !x.constant) { - assert(x.V); - if (PointerType *T = dyn_cast(x.V->getType())) { - if (T->getAddressSpace() == AddressSpace::Tracked || - T->getAddressSpace() == AddressSpace::Derived) { - return x.V; - } + // Singleton should almost always be handled by the later optimization passes. + // Also do it here since it is cheap and save some effort in LLVM passes. + if (jl_is_datatype(typ) && jl_is_datatype_singleton((jl_datatype_t*)typ)) + return true; + return typ == (jl_value_t*)jl_symbol_type || + typ == (jl_value_t*)jl_int8_type || + typ == (jl_value_t*)jl_uint8_type; +} + + +static void find_perm_offsets(jl_datatype_t *typ, SmallVectorImpl &res, unsigned offset) +{ + // This is a inlined field at `offset`. + if (!typ->layout || typ->layout->npointers == 0) + return; + jl_svec_t *types = jl_get_fieldtypes(typ); + size_t nf = jl_svec_len(types); + for (size_t i = 0; i < nf; i++) { + jl_value_t *_fld = jl_svecref(types, i); + if (!jl_is_datatype(_fld)) + continue; + jl_datatype_t *fld = (jl_datatype_t*)_fld; + if (jl_field_isptr(typ, i)) { + // pointer field, check if field is perm-alloc + if (type_is_permalloc((jl_value_t*)fld)) + res.push_back(offset + jl_field_offset(typ, i)); + continue; } + // inline field + find_perm_offsets(fld, res, offset + jl_field_offset(typ, i)); } - return nullptr; +} + +static llvm::SmallVector get_gc_roots_for(jl_codectx_t &ctx, const jl_cgval_t &x) +{ + if (x.constant || x.typ == jl_bottom_type) + return {}; + if (x.Vboxed) // superset of x.isboxed + return {x.Vboxed}; + assert(!x.isboxed); + if (x.ispointer()) { + assert(x.V); + assert(x.V->getType()->getPointerAddressSpace() != AddressSpace::Tracked); + return {x.V}; + } + else if (jl_is_concrete_immutable(x.typ) && !jl_is_pointerfree(x.typ)) { + jl_value_t *jltype = x.typ; + Type *T = julia_type_to_llvm(ctx, jltype); + Value *agg = emit_unbox(ctx, T, x, jltype); + SmallVector perm_offsets; + if (jltype && jl_is_datatype(jltype) && ((jl_datatype_t*)jltype)->layout) + find_perm_offsets((jl_datatype_t*)jltype, perm_offsets, 0); + return ExtractTrackedValues(agg, agg->getType(), false, ctx.builder, perm_offsets); + } + // nothing here to root, move along + return {}; } // --- emitting pointers directly into code --- @@ -345,7 +387,7 @@ static Constant *julia_pgv(jl_codectx_t &ctx, const char *cname, void *addr) StringRef localname; std::string gvname; if (!gv) { - uint64_t id = jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); // TODO: use ctx.emission_context.global_targets.size() + uint64_t id = jl_atomic_fetch_add_relaxed(&globalUniqueGeneratedNames, 1); // TODO: use ctx.emission_context.global_targets.size() raw_string_ostream(gvname) << cname << id; localname = StringRef(gvname); } @@ -444,11 +486,7 @@ static Constant *literal_pointer_val_slot(jl_codectx_t &ctx, jl_value_t *p) static size_t dereferenceable_size(jl_value_t *jt) { - if (jl_is_array_type(jt)) { - // Array has at least this much data - return sizeof(jl_array_t); - } - else if (jl_is_datatype(jt) && jl_struct_try_layout((jl_datatype_t*)jt)) { + if (jl_is_datatype(jt) && jl_struct_try_layout((jl_datatype_t*)jt)) { return jl_datatype_size(jt); } return 0; @@ -457,10 +495,6 @@ static size_t dereferenceable_size(jl_value_t *jt) // Return the min required / expected alignment of jltype (on the stack or heap) static unsigned julia_alignment(jl_value_t *jt) { - if (jl_is_array_type(jt)) { - // Array always has this alignment - return JL_SMALL_BYTE_ALIGNMENT; - } if (jt == (jl_value_t*)jl_datatype_type) { // types are never allocated in julia code/on the stack // and this is the guarantee we have for the GC bits @@ -588,17 +622,6 @@ static Value *julia_binding_gv(jl_codectx_t &ctx, jl_binding_t *b) // --- mapping between julia and llvm types --- -static bool type_is_permalloc(jl_value_t *typ) -{ - // Singleton should almost always be handled by the later optimization passes. - // Also do it here since it is cheap and save some effort in LLVM passes. - if (jl_is_datatype(typ) && jl_is_datatype_singleton((jl_datatype_t*)typ)) - return true; - return typ == (jl_value_t*)jl_symbol_type || - typ == (jl_value_t*)jl_int8_type || - typ == (jl_value_t*)jl_uint8_type; -} - static unsigned convert_struct_offset(const llvm::DataLayout &DL, Type *lty, unsigned byte_offset) { const StructLayout *SL = DL.getStructLayout(cast(lty)); @@ -667,6 +690,8 @@ static Type *bitstype_to_llvm(jl_value_t *bt, LLVMContext &ctxt, bool llvmcall = return getDoubleTy(ctxt); if (bt == (jl_value_t*)jl_bfloat16_type) return getBFloatTy(ctxt); + if (jl_is_cpointer_type(bt)) + return PointerType::get(getInt8Ty(ctxt), 0); if (jl_is_llvmpointer_type(bt)) { jl_value_t *as_param = jl_tparam1(bt); int as; @@ -695,6 +720,37 @@ static unsigned jl_field_align(jl_datatype_t *dt, size_t i) return std::min({al, (unsigned)jl_datatype_align(dt), (unsigned)JL_HEAP_ALIGNMENT}); } +static llvm::StructType* get_jlmemoryref(llvm::LLVMContext &C, unsigned AS) { + return llvm::StructType::get(C, { + llvm::PointerType::get(llvm::Type::getInt8Ty(C), AS), + JuliaType::get_prjlvalue_ty(C), + }); +} +static llvm::StructType* get_jlmemoryboxedref(llvm::LLVMContext &C, unsigned AS) { + return llvm::StructType::get(C, { + llvm::PointerType::get(JuliaType::get_prjlvalue_ty(C), AS), + JuliaType::get_prjlvalue_ty(C), + }); +} +static llvm::StructType* get_jlmemoryunionref(llvm::LLVMContext &C, llvm::Type *T_size) { + return llvm::StructType::get(C, { + T_size, // offset + JuliaType::get_prjlvalue_ty(C), + }); +} +static StructType *get_memoryref_type(LLVMContext &ctxt, Type *T_size, const jl_datatype_layout_t *layout, unsigned AS) +{ + // TODO: try to remove this slightly odd special case + bool isboxed = layout->flags.arrayelem_isboxed; + bool isunion = layout->flags.arrayelem_isunion; + bool isghost = layout->size == 0; + if (isboxed) + return get_jlmemoryboxedref(ctxt, AS); + if (isunion || isghost) + return get_jlmemoryunionref(ctxt, T_size); + return get_jlmemoryref(ctxt, AS); +} + static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, LLVMContext &ctxt, jl_value_t *jt, bool *isboxed, bool llvmcall) { // this function converts a Julia Type into the equivalent LLVM struct @@ -706,7 +762,13 @@ static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, LLVMContext &ctxt, if (jl_is_primitivetype(jt)) return bitstype_to_llvm(jt, ctxt, llvmcall); jl_datatype_t *jst = (jl_datatype_t*)jt; - if (jl_is_structtype(jt) && !(jst->layout && jl_is_layout_opaque(jst->layout))) { + if (jl_is_structtype(jt) && !(jst->layout && jl_is_layout_opaque(jst->layout)) && !jl_is_array_type(jst) && !jl_is_genericmemory_type(jst)) { + if (jl_is_genericmemoryref_type(jst)) { + jl_value_t *mty_dt = jl_field_type_concrete(jst, 1); + const jl_datatype_layout_t *layout = ((jl_datatype_t*)mty_dt)->layout; + Type *T_size = bitstype_to_llvm((jl_value_t*)jl_long_type, ctxt); + return get_memoryref_type(ctxt, T_size, layout, 0); + } bool isTuple = jl_is_tuple_type(jt); jl_svec_t *ftypes = jl_get_fieldtypes(jst); size_t i, ntypes = jl_svec_len(ftypes); @@ -744,17 +806,15 @@ static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, LLVMContext &ctxt, lty = JuliaType::get_prjlvalue_ty(ctxt); isvector = false; } - else if (ty == (jl_value_t*)jl_bool_type) { - lty = getInt8Ty(ctxt); - } else if (jl_is_uniontype(ty)) { // pick an Integer type size such that alignment will generally be correct, // and always end with an Int8 (selector byte). // We may need to insert padding first to get to the right offset size_t fsz = 0, al = 0; bool isptr = !jl_islayout_inline(ty, &fsz, &al); - assert(!isptr && fsz == jl_field_size(jst, i) - 1); (void)isptr; - if (fsz > 0) { + assert(!isptr && fsz < jl_field_size(jst, i)); (void)isptr; + size_t fsz1 = jl_field_size(jst, i) - 1; + if (fsz1 > 0) { if (al > MAX_ALIGN) { Type *AlignmentType; AlignmentType = ArrayType::get(FixedVectorType::get(getInt8Ty(ctxt), al), 0); @@ -762,8 +822,8 @@ static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, LLVMContext &ctxt, al = MAX_ALIGN; } Type *AlignmentType = IntegerType::get(ctxt, 8 * al); - unsigned NumATy = fsz / al; - unsigned remainder = fsz % al; + unsigned NumATy = fsz1 / al; + unsigned remainder = fsz1 % al; assert(al == 1 || NumATy > 0); while (NumATy--) latypes.push_back(AlignmentType); @@ -827,7 +887,7 @@ static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, LLVMContext &ctxt, // unsigned remainder = fsz % al; // while (remainder--) // Elements.push_back(getInt8Ty(ctxt)); - // lty = StructType::get(lty->getContext(), makeArrayRef(Elements)); + // lty = StructType::get(lty->getContext(),ArrayRef(Elements)); // } if (isboxed) *isboxed = true; return JuliaType::get_prjlvalue_ty(ctxt); @@ -874,7 +934,7 @@ static bool is_tupletype_homogeneous(jl_svec_t *t, bool allow_va = false) } static bool for_each_uniontype_small( - std::function f, + llvm::function_ref f, jl_value_t *ty, unsigned &counter) { @@ -1040,20 +1100,6 @@ static void emit_memcpy(jl_codectx_t &ctx, Value *dst, jl_aliasinfo_t const &dst emit_memcpy_llvm(ctx, dst, dst_ai, data_pointer(ctx, src), src_ai, sz, align_dst, align_src, is_volatile); } -static LoadInst *emit_nthptr_recast(jl_codectx_t &ctx, Value *v, Value *idx, MDNode *tbaa, Type *type) -{ - // p = (jl_value_t**)v; *(type*)&p[n] - Value *vptr = ctx.builder.CreateInBoundsGEP( - ctx.types().T_prjlvalue, - emit_bitcast(ctx, maybe_decay_tracked(ctx, v), ctx.types().T_pprjlvalue), - idx); - setName(ctx.emission_context, vptr, "arraysize_ptr"); - LoadInst *load = ctx.builder.CreateLoad(type, emit_bitcast(ctx, vptr, PointerType::get(type, 0))); - jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); - ai.decorateInst(load); - return load; -} - static Value *emit_tagfrom(jl_codectx_t &ctx, jl_datatype_t *dt) { if (dt->smalltag) @@ -1181,7 +1227,8 @@ static Value *emit_datatype_nfields(jl_codectx_t &ctx, Value *dt) return nfields; } -static Value *emit_datatype_size(jl_codectx_t &ctx, Value *dt) +// emit the size field from the layout of a dt +static Value *emit_datatype_size(jl_codectx_t &ctx, Value *dt, bool add_isunion=false) { jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); Value *Ptr = emit_bitcast(ctx, decay_derived(ctx, dt), getInt32PtrTy(ctx.builder.getContext())->getPointerTo()); @@ -1189,9 +1236,18 @@ static Value *emit_datatype_size(jl_codectx_t &ctx, Value *dt) Ptr = ctx.builder.CreateInBoundsGEP(getInt32PtrTy(ctx.builder.getContext()), Ptr, Idx); Ptr = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt32PtrTy(ctx.builder.getContext()), Ptr, Align(sizeof(int32_t*)))); Idx = ConstantInt::get(ctx.types().T_size, offsetof(jl_datatype_layout_t, size) / sizeof(int32_t)); - Ptr = ctx.builder.CreateInBoundsGEP(getInt32Ty(ctx.builder.getContext()), Ptr, Idx); - auto Size = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt32Ty(ctx.builder.getContext()), Ptr, Align(sizeof(int32_t)))); + Value *SizePtr = ctx.builder.CreateInBoundsGEP(getInt32Ty(ctx.builder.getContext()), Ptr, Idx); + Value *Size = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt32Ty(ctx.builder.getContext()), SizePtr, Align(sizeof(int32_t)))); setName(ctx.emission_context, Size, "datatype_size"); + if (add_isunion) { + Idx = ConstantInt::get(ctx.types().T_size, offsetof(jl_datatype_layout_t, flags) / sizeof(int16_t)); + Value *FlagPtr = ctx.builder.CreateInBoundsGEP(getInt16Ty(ctx.builder.getContext()), emit_bitcast(ctx, Ptr, getInt16PtrTy(ctx.builder.getContext())), Idx); + Value *Flag = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt16Ty(ctx.builder.getContext()), FlagPtr, Align(sizeof(int16_t)))); + Flag = ctx.builder.CreateLShr(Flag, 4); + Flag = ctx.builder.CreateAnd(Flag, ConstantInt::get(Flag->getType(), 1)); + Flag = ctx.builder.CreateZExt(Flag, Size->getType()); + Size = ctx.builder.CreateAdd(Size, Flag); + } return Size; } @@ -1214,7 +1270,7 @@ static Value *emit_sizeof(jl_codectx_t &ctx, const jl_cgval_t &p) BasicBlock *dynloadBB = BasicBlock::Create(ctx.builder.getContext(), "dyn_sizeof", ctx.f); BasicBlock *postBB = BasicBlock::Create(ctx.builder.getContext(), "post_sizeof", ctx.f); Value *isboxed = ctx.builder.CreateICmpNE( - ctx.builder.CreateAnd(p.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)), + ctx.builder.CreateAnd(p.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER)), ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0)); ctx.builder.CreateCondBr(isboxed, dynloadBB, postBB); ctx.builder.SetInsertPoint(dynloadBB); @@ -1318,7 +1374,7 @@ static void error_unless(jl_codectx_t &ctx, Value *cond, const Twine &msg) ctx.builder.SetInsertPoint(failBB); just_emit_error(ctx, prepare_call(jlerror_func), msg); ctx.builder.CreateUnreachable(); - ctx.f->getBasicBlockList().push_back(passBB); + passBB->insertInto(ctx.f); ctx.builder.SetInsertPoint(passBB); } @@ -1332,7 +1388,7 @@ static void raise_exception(jl_codectx_t &ctx, Value *exc, contBB = BasicBlock::Create(ctx.builder.getContext(), "after_throw", ctx.f); } else { - ctx.f->getBasicBlockList().push_back(contBB); + contBB->insertInto(ctx.f); } ctx.builder.SetInsertPoint(contBB); } @@ -1348,16 +1404,31 @@ static void raise_exception_unless(jl_codectx_t &ctx, Value *cond, Value *exc) raise_exception(ctx, exc, passBB); } +static void undef_var_error_ifnot(jl_codectx_t &ctx, Value *ok, jl_sym_t *name, jl_value_t *scope) +{ + ++EmittedUndefVarErrors; + BasicBlock *err = BasicBlock::Create(ctx.builder.getContext(), "err", ctx.f); + BasicBlock *ifok = BasicBlock::Create(ctx.builder.getContext(), "ok"); + ctx.builder.CreateCondBr(ok, ifok, err); + ctx.builder.SetInsertPoint(err); + ctx.builder.CreateCall(prepare_call(jlundefvarerror_func), { + mark_callee_rooted(ctx, literal_pointer_val(ctx, (jl_value_t*)name)), + mark_callee_rooted(ctx, literal_pointer_val(ctx, scope))}); + ctx.builder.CreateUnreachable(); + ifok->insertInto(ctx.f); + ctx.builder.SetInsertPoint(ifok); +} + static Value *null_pointer_cmp(jl_codectx_t &ctx, Value *v) { ++EmittedNullchecks; - return ctx.builder.CreateICmpNE(v, Constant::getNullValue(v->getType())); + return ctx.builder.CreateIsNotNull(v); } // If `nullcheck` is not NULL and a pointer NULL check is necessary // store the pointer to be checked in `*nullcheck` instead of checking it -static void null_pointer_check(jl_codectx_t &ctx, Value *v, Value **nullcheck = nullptr) +static void null_pointer_check(jl_codectx_t &ctx, Value *v, Value **nullcheck) { if (nullcheck) { *nullcheck = v; @@ -1367,9 +1438,22 @@ static void null_pointer_check(jl_codectx_t &ctx, Value *v, Value **nullcheck = literal_pointer_val(ctx, jl_undefref_exception)); } + +static void null_load_check(jl_codectx_t &ctx, Value *v, jl_module_t *scope, jl_sym_t *name) +{ + Value *notnull = null_pointer_cmp(ctx, v); + if (name && scope) + undef_var_error_ifnot(ctx, notnull, name, (jl_value_t*)scope); + else + raise_exception_unless(ctx, notnull, literal_pointer_val(ctx, jl_undefref_exception)); +} + template static Value *emit_guarded_test(jl_codectx_t &ctx, Value *ifnot, Value *defval, Func &&func) { + if (!ifnot) { + return func(); + } if (auto Cond = dyn_cast(ifnot)) { if (Cond->isZero()) return defval; @@ -1508,21 +1592,25 @@ static bool can_optimize_isa_union(jl_uniontype_t *type) } // a simple case of emit_isa that is obvious not to include a safe-point -static Value *emit_exactly_isa(jl_codectx_t &ctx, const jl_cgval_t &arg, jl_datatype_t *dt) +static Value *emit_exactly_isa(jl_codectx_t &ctx, const jl_cgval_t &arg, jl_datatype_t *dt, bool could_be_null=false) { assert(jl_is_concrete_type((jl_value_t*)dt)); if (arg.TIndex) { unsigned tindex = get_box_tindex(dt, arg.typ); if (tindex > 0) { // optimize more when we know that this is a split union-type where tindex = 0 is invalid - Value *xtindex = ctx.builder.CreateAnd(arg.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x7f)); + Value *xtindex = ctx.builder.CreateAnd(arg.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), ~UNION_BOX_MARKER)); auto isa = ctx.builder.CreateICmpEQ(xtindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), tindex)); setName(ctx.emission_context, isa, "exactly_isa"); return isa; } else if (arg.Vboxed) { - // test for (arg.TIndex == 0x80 && typeof(arg.V) == type) - Value *isboxed = ctx.builder.CreateICmpEQ(arg.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)); + // test for (arg.TIndex == UNION_BOX_MARKER && typeof(arg.V) == type) + Value *isboxed = ctx.builder.CreateICmpEQ(arg.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER)); + if (could_be_null) { + isboxed = ctx.builder.CreateAnd(isboxed, + ctx.builder.CreateNot(null_pointer_cmp(ctx, arg.Vboxed))); + } setName(ctx.emission_context, isboxed, "isboxed"); BasicBlock *currBB = ctx.builder.GetInsertBlock(); BasicBlock *isaBB = BasicBlock::Create(ctx.builder.getContext(), "isa", ctx.f); @@ -1543,9 +1631,16 @@ static Value *emit_exactly_isa(jl_codectx_t &ctx, const jl_cgval_t &arg, jl_data return ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0); } } - auto isa = ctx.builder.CreateICmpEQ(emit_typeof(ctx, arg, false, true), emit_tagfrom(ctx, dt)); - setName(ctx.emission_context, isa, "exactly_isa"); - return isa; + Value *isnull = NULL; + if (could_be_null && arg.isboxed) { + isnull = null_pointer_cmp(ctx, arg.Vboxed); + } + Constant *Vfalse = ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0); + return emit_guarded_test(ctx, isnull, Vfalse, [&]{ + auto isa = ctx.builder.CreateICmpEQ(emit_typeof(ctx, arg, false, true), emit_tagfrom(ctx, dt)); + setName(ctx.emission_context, isa, "exactly_isa"); + return isa; + }); } static std::pair emit_isa(jl_codectx_t &ctx, const jl_cgval_t &x, @@ -1703,7 +1798,7 @@ static void emit_typecheck(jl_codectx_t &ctx, const jl_cgval_t &x, jl_value_t *t just_emit_type_error(ctx, x, literal_pointer_val(ctx, type), msg); ctx.builder.CreateUnreachable(); - ctx.f->getBasicBlockList().push_back(passBB); + passBB->insertInto(ctx.f); ctx.builder.SetInsertPoint(passBB); } } @@ -1746,7 +1841,6 @@ static bool bounds_check_enabled(jl_codectx_t &ctx, jl_value_t *inbounds) { static Value *emit_bounds_check(jl_codectx_t &ctx, const jl_cgval_t &ainfo, jl_value_t *ty, Value *i, Value *len, jl_value_t *boundscheck) { Value *im1 = ctx.builder.CreateSub(i, ConstantInt::get(ctx.types().T_size, 1)); -#if CHECK_BOUNDS==1 if (bounds_check_enabled(ctx, boundscheck)) { ++EmittedBoundschecks; Value *ok = ctx.builder.CreateICmpULT(im1, len); @@ -1779,15 +1873,20 @@ static Value *emit_bounds_check(jl_codectx_t &ctx, const jl_cgval_t &ainfo, jl_v i }); } ctx.builder.CreateUnreachable(); - ctx.f->getBasicBlockList().push_back(passBB); + passBB->insertInto(ctx.f); ctx.builder.SetInsertPoint(passBB); } -#endif return im1; } -static Value *emit_unbox(jl_codectx_t &ctx, Type *to, const jl_cgval_t &x, jl_value_t *jt); -static void emit_unbox_store(jl_codectx_t &ctx, const jl_cgval_t &x, Value* dest, MDNode *tbaa_dest, unsigned alignment, bool isVolatile=false); +static Value *CreateSimplifiedExtractValue(jl_codectx_t &ctx, Value *Agg, ArrayRef Idxs) +{ + // aka IRBuilder + SimplifyQuery SQ(jl_Module->getDataLayout()); // not actually used, but required by API + if (Value *Inst = simplifyExtractValueInst(Agg, Idxs, SQ)) + return Inst; + return ctx.builder.CreateExtractValue(Agg, Idxs); +} static void emit_write_barrier(jl_codectx_t&, Value*, ArrayRef); static void emit_write_barrier(jl_codectx_t&, Value*, Value*); @@ -1829,23 +1928,23 @@ Value *extract_first_ptr(jl_codectx_t &ctx, Value *V) if (path.empty()) return NULL; std::reverse(std::begin(path), std::end(path)); - return ctx.builder.CreateExtractValue(V, path); + return CreateSimplifiedExtractValue(ctx, V, path); } static void emit_lockstate_value(jl_codectx_t &ctx, Value *strct, bool newstate) { ++EmittedLockstates; - Value *v = mark_callee_rooted(ctx, strct); - ctx.builder.CreateCall(prepare_call(newstate ? jllockvalue_func : jlunlockvalue_func), v); -} -static void emit_lockstate_value(jl_codectx_t &ctx, const jl_cgval_t &strct, bool newstate) -{ - assert(strct.isboxed); - emit_lockstate_value(ctx, boxed(ctx, strct), newstate); + if (strct->getType()->getPointerAddressSpace() == AddressSpace::Loaded) { + Value *v = emit_bitcast(ctx, strct, PointerType::get(ctx.types().T_jlvalue, AddressSpace::Loaded)); + ctx.builder.CreateCall(prepare_call(newstate ? jllockfield_func : jlunlockfield_func), v); + } + else { + Value *v = mark_callee_rooted(ctx, strct); + ctx.builder.CreateCall(prepare_call(newstate ? jllockvalue_func : jlunlockvalue_func), v); + } } - // If `nullcheck` is not NULL and a pointer NULL check is necessary // store the pointer to be checked in `*nullcheck` instead of checking it static jl_cgval_t typed_load(jl_codectx_t &ctx, Value *ptr, Value *idx_0based, jl_value_t *jltype, @@ -1855,8 +1954,11 @@ static jl_cgval_t typed_load(jl_codectx_t &ctx, Value *ptr, Value *idx_0based, j { // TODO: we should use unordered loads for anything with CountTrackedPointers(elty).count > 0 (if not otherwise locked) Type *elty = isboxed ? ctx.types().T_prjlvalue : julia_type_to_llvm(ctx, jltype); - if (type_is_ghost(elty)) + if (type_is_ghost(elty)) { + if (isStrongerThanMonotonic(Order)) + ctx.builder.CreateFence(Order); return ghostValue(ctx, jltype); + } unsigned nb = isboxed ? sizeof(void*) : jl_datatype_size(jltype); // note that nb == jl_Module->getDataLayout().getTypeAllocSize(elty) or getTypeStoreSize, depending on whether it is a struct or primitive type AllocaInst *intcast = NULL; @@ -1896,14 +1998,34 @@ static jl_cgval_t typed_load(jl_codectx_t &ctx, Value *ptr, Value *idx_0based, j emit_memcpy(ctx, intcast, jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_stack), data, jl_aliasinfo_t::fromTBAA(ctx, tbaa), nb, alignment, intcast->getAlign().value()); } else { - LoadInst *load = ctx.builder.CreateAlignedLoad(elty, data, Align(alignment), false); - load->setOrdering(Order); - if (isboxed) - maybe_mark_load_dereferenceable(load, true, jltype); - jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); - ai.scope = MDNode::concatenate(aliasscope, ai.scope); - ai.decorateInst(load); - instr = load; + if (!isboxed && jl_is_genericmemoryref_type(jltype)) { + // load these FCA as individual fields, so LLVM does not need to split them later + Value *fld0 = ctx.builder.CreateStructGEP(elty, data, 0); + LoadInst *load0 = ctx.builder.CreateAlignedLoad(elty->getStructElementType(0), fld0, Align(alignment), false); + load0->setOrdering(Order); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.scope = MDNode::concatenate(aliasscope, ai.scope); + ai.decorateInst(load0); + Value *fld1 = ctx.builder.CreateStructGEP(elty, data, 1); + LoadInst *load1 = ctx.builder.CreateAlignedLoad(elty->getStructElementType(1), fld1, Align(alignment), false); + static_assert(offsetof(jl_genericmemoryref_t, ptr_or_offset) == 0, "wrong field order"); + maybe_mark_load_dereferenceable(load1, true, sizeof(void*)*2, alignof(void*)); + load1->setOrdering(Order); + ai.decorateInst(load1); + instr = Constant::getNullValue(elty); + instr = ctx.builder.CreateInsertValue(instr, load0, 0); + instr = ctx.builder.CreateInsertValue(instr, load1, 1); + } + else { + LoadInst *load = ctx.builder.CreateAlignedLoad(elty, data, Align(alignment), false); + load->setOrdering(Order); + if (isboxed) + maybe_mark_load_dereferenceable(load, true, jltype); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.scope = MDNode::concatenate(aliasscope, ai.scope); + ai.decorateInst(load); + instr = load; + } if (elty != realelty) instr = ctx.builder.CreateTrunc(instr, realelty); if (intcast) { @@ -1936,12 +2058,13 @@ 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, jl_cgval_t rhs, jl_cgval_t cmp, + Value *ptr, 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 isswapfield, bool ismodifyfield, - bool maybe_null_if_boxed, const jl_cgval_t *modifyop, const Twine &fname) + Value *needlock, bool issetfield, bool isreplacefield, bool isswapfield, bool ismodifyfield, bool issetfieldonce, + bool maybe_null_if_boxed, const jl_cgval_t *modifyop, const Twine &fname, + jl_module_t *mod, jl_sym_t *var) { auto newval = [&](const jl_cgval_t &lhs) { const jl_cgval_t argv[3] = { cmp, lhs, rhs }; @@ -1957,9 +2080,10 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, ret = update_julia_type(ctx, ret, jltype); return ret; }; - assert(!needlock || parent != nullptr); Type *elty = isboxed ? ctx.types().T_prjlvalue : julia_type_to_llvm(ctx, jltype); - if (type_is_ghost(elty)) { + if (type_is_ghost(elty) || + (issetfieldonce && !maybe_null_if_boxed) || + (issetfieldonce && !isboxed && !jl_type_hasptr(jltype))) { if (isStrongerThanMonotonic(Order)) ctx.builder.CreateFence(Order); if (issetfield) { @@ -1975,13 +2099,21 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, else if (isswapfield) { return ghostValue(ctx, jltype); } - else { // modifyfield + else if (ismodifyfield) { jl_cgval_t oldval = ghostValue(ctx, 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); } + else { // issetfieldonce + return mark_julia_const(ctx, jl_false); + } } + // if FailOrder was inherited from Order, may need to remove Load-only effects now + if (FailOrder == AtomicOrdering::AcquireRelease) + FailOrder = AtomicOrdering::Acquire; + if (FailOrder == AtomicOrdering::Release) + FailOrder = AtomicOrdering::Monotonic; unsigned nb = isboxed ? sizeof(void*) : jl_datatype_size(jltype); AllocaInst *intcast = nullptr; if (!isboxed && Order != AtomicOrdering::NotAtomic && !elty->isIntOrPtrTy()) { @@ -1998,7 +2130,7 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, elty = Type::getIntNTy(ctx.builder.getContext(), 8 * nb2); } Value *r = nullptr; - if (issetfield || isswapfield || isreplacefield) { + if (issetfield || isswapfield || isreplacefield || issetfieldonce) { if (isboxed) r = boxed(ctx, rhs); else if (aliasscope || Order != AtomicOrdering::NotAtomic || CountTrackedPointers(realelty).count) { @@ -2010,8 +2142,6 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, Type *ptrty = PointerType::get(elty, ptr->getType()->getPointerAddressSpace()); if (ptr->getType() != ptrty) ptr = ctx.builder.CreateBitCast(ptr, ptrty); - if (idx_0based) - ptr = ctx.builder.CreateInBoundsGEP(elty, ptr, idx_0based); if (isboxed) alignment = sizeof(void*); else if (!alignment) @@ -2021,12 +2151,13 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, Value *Success = nullptr; BasicBlock *DoneBB = nullptr; if (needlock) - emit_lockstate_value(ctx, parent, true); + emit_lockstate_value(ctx, needlock, true); jl_cgval_t oldval = rhs; + // TODO: we should do Release ordering for anything with CountTrackedPointers(elty).count > 0, instead of just isboxed if (issetfield || (Order == AtomicOrdering::NotAtomic && isswapfield)) { if (isswapfield) { auto *load = ctx.builder.CreateAlignedLoad(elty, ptr, Align(alignment)); - setName(ctx.emission_context, load, "swapfield_load"); + setName(ctx.emission_context, load, "swap_load"); if (isboxed) load->setOrdering(AtomicOrdering::Unordered); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); @@ -2047,17 +2178,19 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, emit_unbox_store(ctx, rhs, ptr, tbaa, alignment); } } - else if (isswapfield && isStrongerThanMonotonic(Order)) { + else if (isswapfield) { + if (Order == AtomicOrdering::Unordered) + Order = AtomicOrdering::Monotonic; assert(Order != AtomicOrdering::NotAtomic && r); auto *store = ctx.builder.CreateAtomicRMW(AtomicRMWInst::Xchg, ptr, r, Align(alignment), Order); - setName(ctx.emission_context, store, "swapfield_atomicrmw"); + setName(ctx.emission_context, store, "swap_atomicrmw"); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); ai.decorateInst(store); instr = store; } else { - // replacefield, modifyfield, or swapfield (isboxed && atomic) + // replacefield, modifyfield, swapfield, setfieldonce (isboxed && atomic) DoneBB = BasicBlock::Create(ctx.builder.getContext(), "done_xchg", ctx.f); bool needloop; PHINode *Succ = nullptr, *Current = nullptr; @@ -2067,7 +2200,7 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, } else if (!isboxed) { assert(jl_is_concrete_type(jltype)); - needloop = ((jl_datatype_t*)jltype)->layout->haspadding; + needloop = ((jl_datatype_t*)jltype)->layout->flags.haspadding; Value *SameType = emit_isa(ctx, cmp, jltype, Twine()).first; if (SameType != ConstantInt::getTrue(ctx.builder.getContext())) { BasicBlock *SkipBB = BasicBlock::Create(ctx.builder.getContext(), "skip_xchg", ctx.f); @@ -2075,7 +2208,7 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, ctx.builder.CreateCondBr(SameType, BB, SkipBB); ctx.builder.SetInsertPoint(SkipBB); LoadInst *load = ctx.builder.CreateAlignedLoad(elty, ptr, Align(alignment)); - setName(ctx.emission_context, load, "atomic_replacefield_initial"); + setName(ctx.emission_context, load, "atomic_replace_initial"); load->setOrdering(FailOrder == AtomicOrdering::NotAtomic && isboxed ? AtomicOrdering::Monotonic : FailOrder); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); @@ -2103,6 +2236,11 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, needloop = true; } } + else if (issetfieldonce) { + needloop = !isboxed && Order != AtomicOrdering::NotAtomic && nb > sizeof(void*); + if (Order != AtomicOrdering::NotAtomic) + Compare = Constant::getNullValue(elty); + } else { // swap or modify LoadInst *Current = ctx.builder.CreateAlignedLoad(elty, ptr, Align(alignment)); Current->setOrdering(Order == AtomicOrdering::NotAtomic && !isboxed ? Order : AtomicOrdering::Monotonic); @@ -2125,7 +2263,7 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, } if (ismodifyfield) { if (needlock) - emit_lockstate_value(ctx, parent, false); + emit_lockstate_value(ctx, needlock, false); Value *realCompare = Compare; if (realelty != elty) realCompare = ctx.builder.CreateTrunc(realCompare, realelty); @@ -2137,7 +2275,7 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, 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); + null_load_check(ctx, first_ptr, mod, var); } if (intcast) oldval = mark_julia_slot(intcast, jltype, NULL, ctx.tbaa().tbaa_stack); @@ -2153,12 +2291,12 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, r = ctx.builder.CreateZExt(r, elty); } if (needlock) - emit_lockstate_value(ctx, parent, true); + emit_lockstate_value(ctx, needlock, true); cmp = oldval; } Value *Done; if (Order == AtomicOrdering::NotAtomic) { - // modifyfield or replacefield + // modifyfield or replacefield or setfieldonce assert(elty == realelty && !intcast); auto *load = ctx.builder.CreateAlignedLoad(elty, ptr, Align(alignment)); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); @@ -2170,9 +2308,11 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, 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); - }); + assert(!issetfieldonce || first_ptr != nullptr); + if (issetfieldonce) + Success = ctx.builder.CreateIsNull(first_ptr); + else + Success = emit_f_is(ctx, oldval, cmp, first_ptr, nullptr); if (needloop && ismodifyfield) CmpPhi->addIncoming(load, ctx.builder.GetInsertBlock()); assert(Succ == nullptr); @@ -2192,13 +2332,13 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, ctx.builder.CreateBr(DoneBB); instr = load; } - else { + else { // something atomic assert(r); if (Order == AtomicOrdering::Unordered) Order = AtomicOrdering::Monotonic; if (Order == AtomicOrdering::Monotonic && isboxed) Order = AtomicOrdering::Release; - if (!isreplacefield) + if (!isreplacefield && !issetfieldonce) FailOrder = AtomicOrdering::Monotonic; else if (FailOrder == AtomicOrdering::Unordered) FailOrder = AtomicOrdering::Monotonic; @@ -2209,7 +2349,7 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, instr = ctx.builder.Insert(ExtractValueInst::Create(store, 0)); Success = ctx.builder.Insert(ExtractValueInst::Create(store, 1)); Done = Success; - if (isreplacefield && needloop) { + if ((isreplacefield || issetfieldonce) && needloop) { Value *realinstr = instr; if (realelty != elty) realinstr = ctx.builder.CreateTrunc(realinstr, realelty); @@ -2222,15 +2362,22 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, else { oldval = mark_julia_type(ctx, realinstr, isboxed, jltype); } - Done = emit_guarded_test(ctx, ctx.builder.CreateNot(Success), false, [&] { - Value *first_ptr = nullptr; - if (maybe_null_if_boxed) - first_ptr = isboxed ? realinstr : extract_first_ptr(ctx, realinstr); - return emit_nullcheck_guard(ctx, first_ptr, [&] { - return emit_f_is(ctx, oldval, cmp); + if (issetfieldonce) { + assert(!isboxed && maybe_null_if_boxed); + Value *first_ptr = extract_first_ptr(ctx, realinstr); + assert(first_ptr != nullptr); + Done = ctx.builder.CreateIsNotNull(first_ptr); + } + else { + // Done = !(!Success && (first_ptr != NULL && oldval == cmp)) + Done = emit_guarded_test(ctx, ctx.builder.CreateNot(Success), false, [&] { + Value *first_ptr = nullptr; + if (maybe_null_if_boxed) + first_ptr = isboxed ? realinstr : extract_first_ptr(ctx, realinstr); + return emit_f_is(ctx, oldval, cmp, first_ptr, nullptr); }); - }); - Done = ctx.builder.CreateNot(Done); + Done = ctx.builder.CreateNot(Done); + } } if (needloop) ctx.builder.CreateCondBr(Done, DoneBB, BB); @@ -2249,9 +2396,9 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, if (DoneBB) ctx.builder.SetInsertPoint(DoneBB); if (needlock) - emit_lockstate_value(ctx, parent, false); + emit_lockstate_value(ctx, needlock, false); if (parent != NULL) { - if (isreplacefield) { + if (isreplacefield || issetfieldonce) { // TODO: avoid this branch if we aren't making a write barrier BasicBlock *BB = BasicBlock::Create(ctx.builder.getContext(), "xchg_wb", ctx.f); DoneBB = BasicBlock::Create(ctx.builder.getContext(), "done_xchg_wb", ctx.f); @@ -2264,7 +2411,7 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, else if (!type_is_permalloc(rhs.typ)) emit_write_barrier(ctx, parent, r); } - if (isreplacefield) { + if (isreplacefield || issetfieldonce) { ctx.builder.CreateBr(DoneBB); ctx.builder.SetInsertPoint(DoneBB); } @@ -2274,6 +2421,9 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, jl_datatype_t *rettyp = jl_apply_modify_type(jltype); oldval = emit_new_struct(ctx, (jl_value_t*)rettyp, 2, argv); } + else if (issetfieldonce) { + oldval = mark_julia_type(ctx, Success, false, jl_bool_type); + } else if (!issetfield) { // swapfield or replacefield if (realelty != elty) instr = ctx.builder.Insert(CastInst::Create(Instruction::Trunc, instr, realelty)); @@ -2286,7 +2436,7 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, instr = ctx.builder.CreateLoad(intcast->getAllocatedType(), intcast); Value *first_ptr = isboxed ? instr : extract_first_ptr(ctx, instr); if (first_ptr) - null_pointer_check(ctx, first_ptr, nullptr); + null_load_check(ctx, first_ptr, mod, var); if (intcast && !first_ptr) instr = nullptr; } @@ -2391,17 +2541,17 @@ static bool emit_getfield_unknownidx(jl_codectx_t &ctx, assert((cast(strct.V->getType())->getElementType() == ctx.types().T_prjlvalue) == isboxed); Value *idx = idx0(); unsigned i = 0; - Value *fld = ctx.builder.CreateExtractValue(strct.V, makeArrayRef(i)); + Value *fld = ctx.builder.CreateExtractValue(strct.V, ArrayRef(i)); for (i = 1; i < nfields; i++) { fld = ctx.builder.CreateSelect( ctx.builder.CreateICmpEQ(idx, ConstantInt::get(idx->getType(), i)), - ctx.builder.CreateExtractValue(strct.V, makeArrayRef(i)), + ctx.builder.CreateExtractValue(strct.V, ArrayRef(i)), fld); } setName(ctx.emission_context, fld, "getfield"); jl_value_t *jft = issame ? jl_svecref(types, 0) : (jl_value_t*)jl_any_type; if (isboxed && maybe_null) - null_pointer_check(ctx, fld); + null_pointer_check(ctx, fld, nullptr); *ret = mark_julia_type(ctx, fld, isboxed, jft); return true; } @@ -2440,7 +2590,7 @@ static bool emit_getfield_unknownidx(jl_codectx_t &ctx, ai.decorateInst(fld); maybe_mark_load_dereferenceable(fld, maybe_null, minimum_field_size, minimum_align); if (maybe_null) - null_pointer_check(ctx, fld); + null_pointer_check(ctx, fld, nullptr); *ret = mark_julia_type(ctx, fld, true, jl_any_type); return true; } @@ -2496,6 +2646,129 @@ static jl_cgval_t emit_unionload(jl_codectx_t &ctx, Value *addr, Value *ptindex, return mark_julia_slot(fsz > 0 ? addr : nullptr, jfty, tindex, tbaa); } +static bool isTBAA(MDNode *TBAA, std::initializer_list const strset) +{ + if (!TBAA) + return false; + while (TBAA->getNumOperands() > 1) { + TBAA = cast(TBAA->getOperand(1).get()); + auto str = cast(TBAA->getOperand(0))->getString(); + for (auto str2 : strset) { + if (str == str2) { + return true; + } + } + } + return false; +} + +// Check if this is a load from an immutable value. The easiest +// way to do so is to look at the tbaa and see if it derives from +// jtbaa_immut. +static bool isLoadFromImmut(LoadInst *LI) +{ + if (LI->getMetadata(LLVMContext::MD_invariant_load)) + return true; + MDNode *TBAA = LI->getMetadata(LLVMContext::MD_tbaa); + if (isTBAA(TBAA, {"jtbaa_immut", "jtbaa_const", "jtbaa_datatype", "jtbaa_memoryptr", "jtbaa_memorylen", "jtbaa_memoryown"})) + return true; + return false; +} + +static bool isConstGV(GlobalVariable *gv) +{ + return gv->isConstant() || gv->getMetadata("julia.constgv"); +} + +// Check if this is can be traced through constant loads to an constant global +// or otherwise globally rooted value. +// Almost all `tbaa_const` loads satisfies this with the exception of +// task local constants which are constant as far as the code is concerned but aren't +// global constants. For task local constant `task_local` will be true when this function +// returns. +// Unlike this function in llvm-late-gc-lowering, we do not examine PhiNode, as those are not emitted yet +static bool isLoadFromConstGV(LoadInst *LI); +static bool isLoadFromConstGV(Value *v) +{ + v = v->stripInBoundsOffsets(); + if (auto LI = dyn_cast(v)) + return isLoadFromConstGV(LI); + if (auto gv = dyn_cast(v)) + return isConstGV(gv); + // null pointer + if (isa(v)) + return true; + // literal pointers + if (auto CE = dyn_cast(v)) + return (CE->getOpcode() == Instruction::IntToPtr && + isa(CE->getOperand(0))); + if (auto SL = dyn_cast(v)) + return (isLoadFromConstGV(SL->getTrueValue()) && + isLoadFromConstGV(SL->getFalseValue())); + if (auto call = dyn_cast(v)) { + auto callee = call->getCalledFunction(); + if (callee && callee->getName() == "julia.typeof") { + return true; + } + if (callee && callee->getName() == "julia.get_pgcstack") { + return true; + } + if (callee && callee->getName() == "julia.gc_loaded") { + return isLoadFromConstGV(call->getArgOperand(0)) && + isLoadFromConstGV(call->getArgOperand(1)); + } + } + if (isa(v)) { + return true; + } + return false; +} + +// The white list implemented here and above in `isLoadFromConstGV(Value*)` should +// cover all the cases we and LLVM generates. +static bool isLoadFromConstGV(LoadInst *LI) +{ + // We only emit single slot GV in codegen + // but LLVM global merging can change the pointer operands to GEPs/bitcasts + auto load_base = LI->getPointerOperand()->stripInBoundsOffsets(); + assert(load_base); // Static analyzer + auto gv = dyn_cast(load_base); + if (isLoadFromImmut(LI)) { + if (gv) + return true; + return isLoadFromConstGV(load_base); + } + if (gv) + return isConstGV(gv); + return false; +} + + +static MDNode *best_field_tbaa(jl_codectx_t &ctx, const jl_cgval_t &strct, jl_datatype_t *jt, unsigned idx, size_t byte_offset) +{ + auto tbaa = strct.tbaa; + if (tbaa == ctx.tbaa().tbaa_datatype) + if (byte_offset != offsetof(jl_datatype_t, types)) + return ctx.tbaa().tbaa_const; + if (tbaa == ctx.tbaa().tbaa_array) { + if (jl_is_genericmemory_type(jt)) { + if (idx == 0) + return ctx.tbaa().tbaa_memorylen; + if (idx == 1) + return ctx.tbaa().tbaa_memoryptr; + } + else if (jl_is_array_type(jt)) { + if (idx == 0) + return ctx.tbaa().tbaa_arrayptr; + if (idx == 1) + return ctx.tbaa().tbaa_arraysize; + } + } + if (strct.V && jl_field_isconst(jt, idx) && isLoadFromConstGV(strct.V)) + return ctx.tbaa().tbaa_const; + return tbaa; +} + // If `nullcheck` is not NULL and a pointer NULL check is necessary // store the pointer to be checked in `*nullcheck` instead of checking it static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &strct, @@ -2507,7 +2780,6 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st }; jl_value_t *jfty = jl_field_type(jt, idx); bool isatomic = jl_field_isatomic(jt, idx); - bool needlock = isatomic && !jl_field_isptr(jt, idx) && jl_datatype_size(jfty) > MAX_ATOMIC_SIZE; if (!isatomic && order != jl_memory_order_notatomic && order != jl_memory_order_unspecified) { emit_atomic_error(ctx, "getfield: non-atomic field cannot be accessed atomically"); return jl_cgval_t(); // unreachable @@ -2525,13 +2797,16 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st } if (type_is_ghost(julia_type_to_llvm(ctx, jfty))) return ghostValue(ctx, jfty); + Value *needlock = nullptr; + if (isatomic && !jl_field_isptr(jt, idx) && jl_datatype_size(jfty) > MAX_ATOMIC_SIZE) { + assert(strct.isboxed); + needlock = boxed(ctx, strct); + } size_t nfields = jl_datatype_nfields(jt); bool maybe_null = idx >= nfields - (unsigned)jt->name->n_uninitialized; size_t byte_offset = jl_field_offset(jt, idx); - auto tbaa = strct.tbaa; - if (tbaa == ctx.tbaa().tbaa_datatype && byte_offset != offsetof(jl_datatype_t, types)) - tbaa = ctx.tbaa().tbaa_const; if (strct.ispointer()) { + auto tbaa = best_field_tbaa(ctx, strct, jt, idx, byte_offset); Value *staddr = data_pointer(ctx, strct); bool isboxed; Type *lt = julia_type_to_llvm(ctx, (jl_value_t*)jt, &isboxed); @@ -2578,14 +2853,15 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st size_t fsz = 0, al = 0; int union_max = jl_islayout_inline(jfty, &fsz, &al); bool isptr = (union_max == 0); - assert(!isptr && fsz == jl_field_size(jt, idx) - 1); (void)isptr; + assert(!isptr && fsz < jl_field_size(jt, idx)); (void)isptr; + size_t fsz1 = jl_field_size(jt, idx) - 1; Value *ptindex; if (isboxed) { ptindex = ctx.builder.CreateConstInBoundsGEP1_32( - getInt8Ty(ctx.builder.getContext()), emit_bitcast(ctx, staddr, getInt8PtrTy(ctx.builder.getContext())), byte_offset + fsz); + getInt8Ty(ctx.builder.getContext()), emit_bitcast(ctx, staddr, getInt8PtrTy(ctx.builder.getContext())), byte_offset + fsz1); } else { - ptindex = emit_struct_gep(ctx, cast(lt), staddr, byte_offset + fsz); + ptindex = emit_struct_gep(ctx, cast(lt), staddr, byte_offset + fsz1); } auto val = emit_unionload(ctx, addr, ptindex, jfty, fsz, al, tbaa, !jl_field_isconst(jt, idx), union_max, ctx.tbaa().tbaa_unionselbyte); if (val.V && val.V != addr) { @@ -2601,7 +2877,7 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st } unsigned align = jl_field_align(jt, idx); if (needlock) - emit_lockstate_value(ctx, strct, true); + emit_lockstate_value(ctx, needlock, true); jl_cgval_t ret = typed_load(ctx, addr, NULL, jfty, tbaa, nullptr, false, needlock ? AtomicOrdering::NotAtomic : get_llvm_atomic_order(order), maybe_null, align, nullcheck); @@ -2609,7 +2885,7 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st setNameWithField(ctx.emission_context, ret.V, get_objname, jt, idx, Twine()); } if (needlock) - emit_lockstate_value(ctx, strct, false); + emit_lockstate_value(ctx, needlock, false); return ret; } else if (isa(strct.V)) { @@ -2641,7 +2917,7 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st unsigned i = 0; for (; i < fsz / align; i++) { unsigned fld = st_idx + i; - Value *fldv = ctx.builder.CreateExtractValue(obj, makeArrayRef(fld)); + Value *fldv = ctx.builder.CreateExtractValue(obj, ArrayRef(fld)); Value *fldp = ctx.builder.CreateConstInBoundsGEP1_32(ET, lv, i); ctx.builder.CreateAlignedStore(fldv, fldp, Align(align)); } @@ -2650,14 +2926,14 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st Value *staddr = ctx.builder.CreateConstInBoundsGEP1_32(ET, lv, i); staddr = ctx.builder.CreateBitCast(staddr, getInt8PtrTy(ctx.builder.getContext())); for (; i < ptindex - st_idx; i++) { - Value *fldv = ctx.builder.CreateExtractValue(obj, makeArrayRef(st_idx + i)); + Value *fldv = ctx.builder.CreateExtractValue(obj, ArrayRef(st_idx + i)); Value *fldp = ctx.builder.CreateConstInBoundsGEP1_32(getInt8Ty(ctx.builder.getContext()), staddr, i); ctx.builder.CreateAlignedStore(fldv, fldp, Align(1)); } } setNameWithField(ctx.emission_context, lv, get_objname, jt, idx, Twine()); } - Value *tindex0 = ctx.builder.CreateExtractValue(obj, makeArrayRef(ptindex)); + Value *tindex0 = ctx.builder.CreateExtractValue(obj, ArrayRef(ptindex)); Value *tindex = ctx.builder.CreateNUWAdd(ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 1), tindex0); setNameWithField(ctx.emission_context, tindex, get_objname, jt, idx, Twine(".tindex")); return mark_julia_slot(lv, jfty, tindex, ctx.tbaa().tbaa_stack); @@ -2670,7 +2946,7 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st st_idx = convert_struct_offset(ctx, T, byte_offset); else llvm_unreachable("encountered incompatible type for a struct"); - fldv = ctx.builder.CreateExtractValue(obj, makeArrayRef(st_idx)); + fldv = ctx.builder.CreateExtractValue(obj, ArrayRef(st_idx)); setNameWithField(ctx.emission_context, fldv, get_objname, jt, idx, Twine()); } if (maybe_null) { @@ -2702,409 +2978,108 @@ static Value *emit_n_varargs(jl_codectx_t &ctx) #endif } -static bool arraytype_constdim(jl_value_t *ty, size_t *dim) +static Value *emit_genericmemoryelsize(jl_codectx_t &ctx, Value *v, jl_value_t *typ, bool add_isunion) { - if (jl_is_array_type(ty) && jl_is_long(jl_tparam1(ty))) { - *dim = jl_unbox_long(jl_tparam1(ty)); - return true; + ++EmittedArrayElsize; + jl_datatype_t *sty = (jl_datatype_t*)jl_unwrap_unionall(typ); + if (jl_is_datatype(sty) && !jl_has_free_typevars((jl_value_t*)sty) && sty->layout) { + if (jl_is_genericmemoryref_type(sty)) + sty = (jl_datatype_t*)jl_field_type_concrete(sty, 1); + size_t sz = sty->layout->size; + if (sty->layout->flags.arrayelem_isunion) + sz++; + return ConstantInt::get(ctx.types().T_size, sz); + } + else { + v = emit_bitcast(ctx, v, ctx.types().T_prjlvalue); + Value *t = emit_typeof(ctx, v, false, false, true); + Value *elsize = emit_datatype_size(ctx, t, add_isunion); + return ctx.builder.CreateZExt(elsize, ctx.types().T_size); } - return false; -} - -static bool arraytype_constshape(jl_value_t *ty) -{ - size_t dim; - if (!arraytype_constdim(ty, &dim)) - return false; - return dim != 1; } -static bool arraytype_constelsize(jl_datatype_t *ty, size_t *elsz) +static ssize_t genericmemoryype_constelsize(jl_value_t *typ) { - assert(jl_is_array_type(ty)); - jl_value_t *ety = jl_tparam0(ty); - if (jl_has_free_typevars(ety)) - return false; - // `jl_islayout_inline` requires `*elsz` and `al` to be initialized. - size_t al = 0; - *elsz = 0; - int union_max = jl_islayout_inline(ety, elsz, &al); - bool isboxed = (union_max == 0); - if (isboxed) { - *elsz = sizeof(void*); - } - else if (jl_is_primitivetype(ety)) { - // Primitive types should use the array element size, but - // this can be different from the type's size - *elsz = LLT_ALIGN(*elsz, al); + jl_datatype_t *sty = (jl_datatype_t*)jl_unwrap_unionall(typ); + if (jl_is_datatype(sty) && !jl_has_free_typevars((jl_value_t*)sty) && sty->layout) { + if (jl_is_array_type(sty)) + sty = (jl_datatype_t*)jl_field_type_concrete(sty, 0); + if (jl_is_genericmemoryref_type(sty)) + sty = (jl_datatype_t*)jl_field_type_concrete(sty, 1); + return sty->layout->size; } - return true; + return -1; } -static intptr_t arraytype_maxsize(jl_value_t *ty) +static intptr_t genericmemoryype_maxsize(jl_value_t *ty) // the maxsize is strictly less than the return value { - if (!jl_is_array_type(ty)) - return INTPTR_MAX; - size_t elsz; - if (arraytype_constelsize((jl_datatype_t*)ty, &elsz) || elsz == 0) + ssize_t elsz = genericmemoryype_constelsize(ty); + if (elsz <= 1) return INTPTR_MAX; return INTPTR_MAX / elsz; } -static Value *emit_arraylen(jl_codectx_t &ctx, const jl_cgval_t &tinfo); - -static Value *emit_arraysize(jl_codectx_t &ctx, const jl_cgval_t &tinfo, Value *dim) -{ - size_t ndim; - MDNode *tbaa = ctx.tbaa().tbaa_arraysize; - if (arraytype_constdim(tinfo.typ, &ndim)) { - if (ndim == 0) - return ConstantInt::get(ctx.types().T_size, 1); - if (ndim == 1) { - if (auto d = dyn_cast(dim)) { - if (d->getZExtValue() == 1) { - return emit_arraylen(ctx, tinfo); - } - } - } - if (ndim > 1) { - if (tinfo.constant && isa(dim)) { - auto n = cast(dim)->getZExtValue() - 1; - return ConstantInt::get(ctx.types().T_size, jl_array_dim(tinfo.constant, n)); - } - tbaa = ctx.tbaa().tbaa_const; - } - } - ++EmittedArraysize; - Value *t = boxed(ctx, tinfo); - int o = offsetof(jl_array_t, nrows) / sizeof(void*) - 1; - auto load = emit_nthptr_recast(ctx, - t, - ctx.builder.CreateAdd(dim, ConstantInt::get(dim->getType(), o)), - tbaa, ctx.types().T_size); - setName(ctx.emission_context, load, "arraysize"); - MDBuilder MDB(ctx.builder.getContext()); - auto rng = MDB.createRange(Constant::getNullValue(ctx.types().T_size), ConstantInt::get(ctx.types().T_size, arraytype_maxsize(tinfo.typ))); - load->setMetadata(LLVMContext::MD_range, rng); - return load; -} - -static Value *emit_arraysize(jl_codectx_t &ctx, const jl_cgval_t &tinfo, int dim) +static Value *emit_genericmemorylen(jl_codectx_t &ctx, Value *addr, jl_value_t *typ) { - return emit_arraysize(ctx, tinfo, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), dim)); -} - -static Value *emit_vectormaxsize(jl_codectx_t &ctx, const jl_cgval_t &ary) -{ - return emit_arraysize(ctx, ary, 2); // maxsize aliases ncols in memory layout for vector -} - -static Value *emit_arraylen_prim(jl_codectx_t &ctx, const jl_cgval_t &tinfo) -{ - size_t ndim; - jl_value_t *ty = tinfo.typ; - MDNode *tbaa = ctx.tbaa().tbaa_arraylen; - if (arraytype_constdim(ty, &ndim)) { - if (ndim == 0) - return ConstantInt::get(ctx.types().T_size, 1); - if (ndim != 1) { - if (tinfo.constant) - return ConstantInt::get(ctx.types().T_size, jl_array_len(tinfo.constant)); - tbaa = ctx.tbaa().tbaa_const; - } - } - ++EmittedArraylen; - Value *t = boxed(ctx, tinfo); - Value *addr = ctx.builder.CreateStructGEP(ctx.types().T_jlarray, - emit_bitcast(ctx, decay_derived(ctx, t), ctx.types().T_pjlarray), - 1); //index (not offset) of length field in ctx.types().T_pjlarray - if (tinfo.V) - setName(ctx.emission_context, addr, tinfo.V->getName() + ".length_ptr"); - else - setName(ctx.emission_context, addr, ".length_ptr"); - LoadInst *len = ctx.builder.CreateAlignedLoad(ctx.types().T_size, addr, ctx.types().alignof_ptr); - if (tinfo.V) - setName(ctx.emission_context, len, tinfo.V->getName() + ".length"); - else - setName(ctx.emission_context, len, ".length"); - len->setOrdering(AtomicOrdering::NotAtomic); + addr = emit_bitcast(ctx, decay_derived(ctx, addr), ctx.types().T_jlgenericmemory->getPointerTo()), + addr = ctx.builder.CreateStructGEP(ctx.types().T_jlgenericmemory, addr, 0); + LoadInst *LI = ctx.builder.CreateAlignedLoad(ctx.types().T_jlgenericmemory->getElementType(0), addr, Align(sizeof(size_t))); + jl_aliasinfo_t aliasinfo_mem = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_memorylen); + aliasinfo_mem.decorateInst(LI); MDBuilder MDB(ctx.builder.getContext()); - auto rng = MDB.createRange(Constant::getNullValue(ctx.types().T_size), ConstantInt::get(ctx.types().T_size, arraytype_maxsize(tinfo.typ))); - len->setMetadata(LLVMContext::MD_range, rng); - jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); - return ai.decorateInst(len); -} - -static Value *emit_arraylen(jl_codectx_t &ctx, const jl_cgval_t &tinfo) -{ - return emit_arraylen_prim(ctx, tinfo); + auto rng = MDB.createRange(Constant::getNullValue(ctx.types().T_size), ConstantInt::get(ctx.types().T_size, genericmemoryype_maxsize(typ))); + LI->setMetadata(LLVMContext::MD_range, rng); + return LI; } -static Value *emit_arrayptr_internal(jl_codectx_t &ctx, const jl_cgval_t &tinfo, Value *t, unsigned AS, bool isboxed) +static Value *emit_genericmemoryptr(jl_codectx_t &ctx, Value *mem, const jl_datatype_layout_t *layout, unsigned AS) { ++EmittedArrayptr; - auto asarray = emit_bitcast(ctx, t, ctx.types().T_pjlarray); - Value *addr = ctx.builder.CreateStructGEP(ctx.types().T_jlarray, - asarray, 0); - if (addr != asarray) { - if (tinfo.V) - setName(ctx.emission_context, addr, tinfo.V->getName() + ".data_ptr"); - else - setName(ctx.emission_context, addr, ".data_ptr"); - } - // Normally allocated array of 0 dimension always have a inline pointer. - // However, we can't rely on that here since arrays can also be constructed from C pointers. - PointerType *PT = cast(addr->getType()); - PointerType *PPT = cast(ctx.types().T_jlarray->getElementType(0)); - PointerType *LoadT = PPT; - - if (isboxed) { - LoadT = PointerType::get(ctx.types().T_prjlvalue, AS); - } - else if (AS != PPT->getAddressSpace()) { - LoadT = PointerType::getWithSamePointeeType(PPT, AS); - } - if (LoadT != PPT) { - const auto Ty = PointerType::get(LoadT, PT->getAddressSpace()); - addr = ctx.builder.CreateBitCast(addr, Ty); - } - - LoadInst *LI = ctx.builder.CreateAlignedLoad(LoadT, addr, Align(sizeof(char *))); - if (tinfo.V) - setName(ctx.emission_context, LI, tinfo.V->getName() + ".data"); - else - setName(ctx.emission_context, LI, ".data"); + PointerType *PT = cast(mem->getType()); + assert(PT == ctx.types().T_prjlvalue); + Value *addr = emit_bitcast(ctx, mem, ctx.types().T_jlgenericmemory->getPointerTo(PT->getAddressSpace())); + addr = ctx.builder.CreateStructGEP(ctx.types().T_jlgenericmemory, addr, 1); + setName(ctx.emission_context, addr, ".data_ptr"); + PointerType *PPT = cast(ctx.types().T_jlgenericmemory->getElementType(1)); + LoadInst *LI = ctx.builder.CreateAlignedLoad(PPT, addr, Align(sizeof(char*))); LI->setOrdering(AtomicOrdering::NotAtomic); LI->setMetadata(LLVMContext::MD_nonnull, MDNode::get(ctx.builder.getContext(), None)); - jl_aliasinfo_t aliasinfo = jl_aliasinfo_t::fromTBAA(ctx, arraytype_constshape(tinfo.typ) ? ctx.tbaa().tbaa_const : ctx.tbaa().tbaa_arrayptr); + jl_aliasinfo_t aliasinfo = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); aliasinfo.decorateInst(LI); - - return LI; -} - -static Value *emit_arrayptr(jl_codectx_t &ctx, const jl_cgval_t &tinfo, bool isboxed = false) -{ - Value *t = boxed(ctx, tinfo); - return emit_arrayptr_internal(ctx, tinfo, decay_derived(ctx, t), AddressSpace::Loaded, isboxed); -} - -static Value *emit_unsafe_arrayptr(jl_codectx_t &ctx, const jl_cgval_t &tinfo, bool isboxed = false) -{ - Value *t = boxed(ctx, tinfo); - t = emit_pointer_from_objref(ctx, decay_derived(ctx, t)); - return emit_arrayptr_internal(ctx, tinfo, t, 0, isboxed); -} - -static Value *emit_arrayptr(jl_codectx_t &ctx, const jl_cgval_t &tinfo, jl_value_t *ex, bool isboxed = false) -{ - return emit_arrayptr(ctx, tinfo, isboxed); -} - -static Value *emit_arraysize(jl_codectx_t &ctx, const jl_cgval_t &tinfo, jl_value_t *ex, int dim) -{ - return emit_arraysize(ctx, tinfo, dim); -} - -static Value *emit_arrayflags(jl_codectx_t &ctx, const jl_cgval_t &tinfo) -{ - ++EmittedArrayflags; - Value *t = boxed(ctx, tinfo); - int arrayflag_field = 2; - Value *addr = ctx.builder.CreateStructGEP( - ctx.types().T_jlarray, - emit_bitcast(ctx, decay_derived(ctx, t), ctx.types().T_pjlarray), - arrayflag_field); - if (tinfo.V) - setName(ctx.emission_context, addr, tinfo.V->getName() + ".flags_ptr"); - else - setName(ctx.emission_context, addr, ".flags_ptr"); - jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_arrayflags); - auto flags = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt16Ty(ctx.builder.getContext()), addr, Align(sizeof(int16_t)))); - if (tinfo.V) - setName(ctx.emission_context, flags, tinfo.V->getName() + ".flags"); - else - setName(ctx.emission_context, flags, ".flags"); - return flags; -} - -static Value *emit_arrayndims(jl_codectx_t &ctx, const jl_cgval_t &ary) -{ - ++EmittedArrayNDims; - Value *flags = emit_arrayflags(ctx, ary); - auto name = flags->getName(); - cast(flags)->setMetadata(LLVMContext::MD_invariant_load, MDNode::get(ctx.builder.getContext(), None)); - flags = ctx.builder.CreateLShr(flags, 2); - flags = ctx.builder.CreateAnd(flags, 0x1FF); // (1<<9) - 1 - setName(ctx.emission_context, flags, name + ".ndims"); - return flags; -} - -static Value *emit_arrayelsize(jl_codectx_t &ctx, const jl_cgval_t &tinfo) -{ - ++EmittedArrayElsize; - Value *t = boxed(ctx, tinfo); - int elsize_field = 3; - Value *addr = ctx.builder.CreateStructGEP(ctx.types().T_jlarray, - emit_bitcast(ctx, decay_derived(ctx, t), ctx.types().T_pjlarray), - elsize_field); - if (tinfo.V) - setName(ctx.emission_context, addr, tinfo.V->getName() + ".elsize_ptr"); - else - setName(ctx.emission_context, addr, ".elsize_ptr"); - jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); - auto elsize = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt16Ty(ctx.builder.getContext()), addr, Align(sizeof(int16_t)))); - if (tinfo.V) - setName(ctx.emission_context, elsize, tinfo.V->getName() + ".elsize"); - else - setName(ctx.emission_context, elsize, ".elsize"); - return elsize; -} - -static Value *emit_arrayoffset(jl_codectx_t &ctx, const jl_cgval_t &tinfo, int nd) -{ - ++EmittedArrayOffset; - if (nd != -1 && nd != 1) // only Vector can have an offset - return ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 0); - Value *t = boxed(ctx, tinfo); - int offset_field = 4; - - Value *addr = ctx.builder.CreateStructGEP( - ctx.types().T_jlarray, - emit_bitcast(ctx, decay_derived(ctx, t), ctx.types().T_pjlarray), - offset_field); - if (tinfo.V) - setName(ctx.emission_context, addr, tinfo.V->getName() + ".offset_ptr"); - else - setName(ctx.emission_context, addr, ".offset_ptr"); - jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_arrayoffset); - auto offset = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt32Ty(ctx.builder.getContext()), addr, Align(sizeof(int32_t)))); - if (tinfo.V) - setName(ctx.emission_context, offset, tinfo.V->getName() + ".offset"); - else - setName(ctx.emission_context, offset, ".offset"); - return offset; -} - -// Returns the size of the array represented by `tinfo` for the given dimension `dim` if -// `dim` is a valid dimension, otherwise returns constant one. -static Value *emit_arraysize_for_unsafe_dim(jl_codectx_t &ctx, - const jl_cgval_t &tinfo, jl_value_t *ex, size_t dim, size_t nd) -{ - return dim > nd ? ConstantInt::get(ctx.types().T_size, 1) : emit_arraysize(ctx, tinfo, ex, dim); + Value *ptr = LI; + if (AS) { + assert(AS == AddressSpace::Loaded); + ptr = ctx.builder.CreateCall(prepare_call(gc_loaded_func), { mem, ptr }); + } + if (!layout->flags.arrayelem_isboxed) + ptr = ctx.builder.CreateBitCast(ptr, PointerType::get(getInt8Ty(ctx.builder.getContext()), AS)); + return ptr; } -// `nd == -1` means the dimension is unknown. -static Value *emit_array_nd_index( - jl_codectx_t &ctx, const jl_cgval_t &ainfo, jl_value_t *ex, ssize_t nd, - const jl_cgval_t *argv, size_t nidxs, jl_value_t *inbounds) +static Value *emit_genericmemoryowner(jl_codectx_t &ctx, Value *t) { - ++EmittedArrayNdIndex; - Value *a = boxed(ctx, ainfo); - Value *i = Constant::getNullValue(ctx.types().T_size); - Value *stride = ConstantInt::get(ctx.types().T_size, 1); -#if CHECK_BOUNDS==1 - bool bc = bounds_check_enabled(ctx, inbounds); - BasicBlock *failBB = NULL, *endBB = NULL; - if (bc) { - failBB = BasicBlock::Create(ctx.builder.getContext(), "oob"); - endBB = BasicBlock::Create(ctx.builder.getContext(), "idxend"); - } -#endif - SmallVector idxs(nidxs); - for (size_t k = 0; k < nidxs; k++) { - idxs[k] = emit_unbox(ctx, ctx.types().T_size, argv[k], (jl_value_t*)jl_long_type); // type asserted by caller - } - Value *ii = NULL; - for (size_t k = 0; k < nidxs; k++) { - ii = ctx.builder.CreateSub(idxs[k], ConstantInt::get(ctx.types().T_size, 1)); - i = ctx.builder.CreateAdd(i, ctx.builder.CreateMul(ii, stride)); - if (k < nidxs - 1) { - assert(nd >= 0); - Value *d = emit_arraysize_for_unsafe_dim(ctx, ainfo, ex, k + 1, nd); -#if CHECK_BOUNDS==1 - if (bc) { - BasicBlock *okBB = BasicBlock::Create(ctx.builder.getContext(), "ib"); - // if !(i < d) goto error - auto bc = ctx.builder.CreateICmpULT(ii, d); - setName(ctx.emission_context, bc, "inbounds"); - ctx.builder.CreateCondBr(bc, okBB, failBB); - ctx.f->getBasicBlockList().push_back(okBB); - ctx.builder.SetInsertPoint(okBB); - } -#endif - stride = ctx.builder.CreateMul(stride, d); - setName(ctx.emission_context, stride, "stride"); - } - } -#if CHECK_BOUNDS==1 - if (bc) { - // We have already emitted a bounds check for each index except for - // the last one which we therefore have to do here. - if (nidxs == 1) { - // Linear indexing: Check against the entire linear span of the array - Value *alen = emit_arraylen(ctx, ainfo); - auto bc = ctx.builder.CreateICmpULT(i, alen); - setName(ctx.emission_context, bc, "inbounds"); - ctx.builder.CreateCondBr(bc, endBB, failBB); - } else if (nidxs >= (size_t)nd){ - // No dimensions were omitted; just check the last remaining index - assert(nd >= 0); - Value *last_index = ii; - Value *last_dimension = emit_arraysize_for_unsafe_dim(ctx, ainfo, ex, nidxs, nd); - auto bc = ctx.builder.CreateICmpULT(last_index, last_dimension); - setName(ctx.emission_context, bc, "inbounds"); - ctx.builder.CreateCondBr(bc, endBB, failBB); - } else { - // There were fewer indices than dimensions; check the last remaining index - BasicBlock *checktrailingdimsBB = BasicBlock::Create(ctx.builder.getContext(), "dimsib"); - assert(nd >= 0); - Value *last_index = ii; - Value *last_dimension = emit_arraysize_for_unsafe_dim(ctx, ainfo, ex, nidxs, nd); - auto bc = ctx.builder.CreateICmpULT(last_index, last_dimension); - setName(ctx.emission_context, bc, "inbounds"); - ctx.builder.CreateCondBr(bc, checktrailingdimsBB, failBB); - ctx.f->getBasicBlockList().push_back(checktrailingdimsBB); - ctx.builder.SetInsertPoint(checktrailingdimsBB); - // And then also make sure that all dimensions that weren't explicitly - // indexed into have size 1 - for (size_t k = nidxs+1; k < (size_t)nd; k++) { - BasicBlock *dimsokBB = BasicBlock::Create(ctx.builder.getContext(), "dimsok"); - Value *dim = emit_arraysize_for_unsafe_dim(ctx, ainfo, ex, k, nd); - auto bc = ctx.builder.CreateICmpEQ(dim, ConstantInt::get(ctx.types().T_size, 1)); - setName(ctx.emission_context, bc, "inbounds"); - ctx.builder.CreateCondBr(bc, dimsokBB, failBB); - ctx.f->getBasicBlockList().push_back(dimsokBB); - ctx.builder.SetInsertPoint(dimsokBB); - } - Value *dim = emit_arraysize_for_unsafe_dim(ctx, ainfo, ex, nd, nd); - auto bc2 = ctx.builder.CreateICmpEQ(dim, ConstantInt::get(ctx.types().T_size, 1)); - setName(ctx.emission_context, bc2, "inbounds"); - ctx.builder.CreateCondBr(bc2, endBB, failBB); - } - - ctx.f->getBasicBlockList().push_back(failBB); - ctx.builder.SetInsertPoint(failBB); - // CreateAlloca is OK here since we are on an error branch - Value *tmp = ctx.builder.CreateAlloca(ctx.types().T_size, ConstantInt::get(ctx.types().T_size, nidxs)); - setName(ctx.emission_context, tmp, "errorbox"); - for (size_t k = 0; k < nidxs; k++) { - ctx.builder.CreateAlignedStore(idxs[k], ctx.builder.CreateInBoundsGEP(ctx.types().T_size, tmp, ConstantInt::get(ctx.types().T_size, k)), ctx.types().alignof_ptr); - } - ctx.builder.CreateCall(prepare_call(jlboundserrorv_func), - { mark_callee_rooted(ctx, a), tmp, ConstantInt::get(ctx.types().T_size, nidxs) }); - ctx.builder.CreateUnreachable(); - - ctx.f->getBasicBlockList().push_back(endBB); - ctx.builder.SetInsertPoint(endBB); - } -#endif - - return i; + Value *m = emit_bitcast(ctx, decay_derived(ctx, t), ctx.types().T_jlgenericmemory->getPointerTo(0)); + Value *addr = ctx.builder.CreateStructGEP(ctx.types().T_jlgenericmemory, m, 1); + Type *T_data = ctx.types().T_jlgenericmemory->getElementType(1); + LoadInst *LI = ctx.builder.CreateAlignedLoad(T_data, addr, Align(sizeof(char*))); + LI->setOrdering(AtomicOrdering::NotAtomic); + LI->setMetadata(LLVMContext::MD_nonnull, MDNode::get(ctx.builder.getContext(), None)); + jl_aliasinfo_t aliasinfo_mem = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_memoryown); + aliasinfo_mem.decorateInst(LI); + addr = ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_prjlvalue, emit_bitcast(ctx, m, LI->getType()), JL_SMALL_BYTE_ALIGNMENT / sizeof(void*)); + Value *foreign = ctx.builder.CreateICmpNE(addr, decay_derived(ctx, LI)); + return emit_guarded_test(ctx, foreign, t, [&] { + addr = ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_jlgenericmemory, m, 1); + LoadInst *owner = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, emit_bitcast(ctx, addr, ctx.types().T_pprjlvalue), Align(sizeof(void*))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + ai.decorateInst(owner); + return ctx.builder.CreateSelect(ctx.builder.CreateIsNull(owner), t, owner); + }); } // --- boxing --- -static Value *emit_allocobj(jl_codectx_t &ctx, jl_datatype_t *jt); +static Value *emit_allocobj(jl_codectx_t &ctx, jl_datatype_t *jt, bool fully_initialized); static void init_bits_value(jl_codectx_t &ctx, Value *newv, Value *v, MDNode *tbaa, unsigned alignment = sizeof(void*)) // min alignment in julia's gc is pointer-aligned @@ -3119,7 +3094,8 @@ static void init_bits_cgval(jl_codectx_t &ctx, Value *newv, const jl_cgval_t& v, { // newv should already be tagged if (v.ispointer()) { - emit_memcpy(ctx, newv, jl_aliasinfo_t::fromTBAA(ctx, tbaa), v, jl_datatype_size(v.typ), sizeof(void*), julia_alignment(v.typ)); + unsigned align = std::max(julia_alignment(v.typ), (unsigned)sizeof(void*)); + emit_memcpy(ctx, newv, jl_aliasinfo_t::fromTBAA(ctx, tbaa), v, jl_datatype_size(v.typ), align, julia_alignment(v.typ)); } else { init_bits_value(ctx, newv, v.V, tbaa); @@ -3167,14 +3143,14 @@ static jl_value_t *static_constant_instance(const llvm::DataLayout &DL, Constant if (const auto *CC = dyn_cast(constant)) nargs = CC->getNumOperands(); else if (const auto *CAZ = dyn_cast(constant)) { - // SVE: Elsewhere we use `getMinKownValue` + // SVE: Elsewhere we use `getMinKnownValue` nargs = CAZ->getElementCount().getFixedValue(); } else if (const auto *CDS = dyn_cast(constant)) nargs = CDS->getNumElements(); else return NULL; - assert(nargs > 0 && jst->instance == NULL); + assert(nargs > 0 && !jl_is_datatype_singleton(jst)); if (nargs != jl_datatype_nfields(jst)) return NULL; @@ -3277,15 +3253,14 @@ static Value *_boxed_special(jl_codectx_t &ctx, const jl_cgval_t &vinfo, Type *t else if (jb == jl_char_type) box = call_with_attrs(ctx, box_char_func, as_value(ctx, t, vinfo)); else if (jb == jl_ssavalue_type) { - unsigned zero = 0; Value *v = as_value(ctx, t, vinfo); assert(v->getType() == ctx.emission_context.llvmtypes[jl_ssavalue_type]); - v = ctx.builder.CreateExtractValue(v, makeArrayRef(&zero, 1)); + v = ctx.builder.CreateExtractValue(v, 0); box = call_with_attrs(ctx, box_ssavalue_func, v); } else if (!jb->name->abstract && jl_datatype_nbits(jb) == 0) { // singleton - assert(jb->instance != NULL); + assert(jl_is_datatype_singleton(jb)); return track_pjlvalue(ctx, literal_pointer_val(ctx, jb->instance)); } if (box) { @@ -3372,7 +3347,7 @@ static AllocaInst *try_emit_union_alloca(jl_codectx_t &ctx, jl_uniontype_t *ut, * returning `Constant::getNullValue(ctx.types().T_pjlvalue)` in one of the skipped cases. If `skip` is not empty, * skip[0] (corresponding to unknown boxed) must always be set. In that * case, the calling code must separately deal with the case where - * `vinfo` is already an unknown boxed union (union tag 0x80). + * `vinfo` is already an unknown boxed union (union tag UNION_BOX_MARKER). */ // Returns ctx.types().T_prjlvalue static Value *box_union(jl_codectx_t &ctx, const jl_cgval_t &vinfo, const SmallBitVector &skip) @@ -3415,7 +3390,7 @@ static Value *box_union(jl_codectx_t &ctx, const jl_cgval_t &vinfo, const SmallB jl_cgval_t vinfo_r = jl_cgval_t(vinfo, (jl_value_t*)jt, NULL); box = _boxed_special(ctx, vinfo_r, t); if (!box) { - box = emit_allocobj(ctx, jt); + box = emit_allocobj(ctx, jt, true); setName(ctx.emission_context, box, "unionbox"); init_bits_cgval(ctx, box, vinfo_r, jl_is_mutable(jt) ? ctx.tbaa().tbaa_mutab : ctx.tbaa().tbaa_immut); } @@ -3460,7 +3435,7 @@ static Function *mangleIntrinsic(IntrinsicInst *call) //mangling based on replac auto newfType = FunctionType::get( oldfType->getReturnType(), - makeArrayRef(argTys).slice(0, oldfType->getNumParams()), + ArrayRef(argTys).slice(0, oldfType->getNumParams()), oldfType->isVarArg()); // Accumulate an array of overloaded types for the given intrinsic @@ -3542,7 +3517,7 @@ static Value *boxed(jl_codectx_t &ctx, const jl_cgval_t &vinfo, bool is_promotab if (do_promote && is_promotable) { auto IP = ctx.builder.saveIP(); ctx.builder.SetInsertPoint(vinfo.promotion_point); - box = emit_allocobj(ctx, (jl_datatype_t*)jt); + box = emit_allocobj(ctx, (jl_datatype_t*)jt, true); Value *decayed = decay_derived(ctx, box); AllocaInst *originalAlloca = cast(vinfo.V); box->takeName(originalAlloca); @@ -3556,10 +3531,10 @@ static Value *boxed(jl_codectx_t &ctx, const jl_cgval_t &vinfo, bool is_promotab ctx.builder.restoreIP(IP); } else { auto arg_typename = [&] JL_NOTSAFEPOINT { - return jl_symbol_name(((jl_datatype_t*)(jt))->name->name); + return "box::" + std::string(jl_symbol_name(((jl_datatype_t*)(jt))->name->name)); }; - box = emit_allocobj(ctx, (jl_datatype_t*)jt); - setName(ctx.emission_context, box, "box" + StringRef("::") + arg_typename()); + box = emit_allocobj(ctx, (jl_datatype_t*)jt, true); + setName(ctx.emission_context, box, arg_typename); init_bits_cgval(ctx, box, vinfo, jl_is_mutable(jt) ? ctx.tbaa().tbaa_mutab : ctx.tbaa().tbaa_immut); } } @@ -3680,13 +3655,14 @@ static void emit_cpointercheck(jl_codectx_t &ctx, const jl_cgval_t &x, const Twi just_emit_type_error(ctx, x, literal_pointer_val(ctx, (jl_value_t*)jl_pointer_type), msg); ctx.builder.CreateUnreachable(); - ctx.f->getBasicBlockList().push_back(passBB); + passBB->insertInto(ctx.f); ctx.builder.SetInsertPoint(passBB); } // allocation for known size object // returns a prjlvalue -static Value *emit_allocobj(jl_codectx_t &ctx, size_t static_size, Value *jt) +static Value *emit_allocobj(jl_codectx_t &ctx, size_t static_size, Value *jt, + bool fully_initialized, unsigned align) { ++EmittedAllocObjs; Value *current_task = get_current_task(ctx); @@ -3694,13 +3670,17 @@ static Value *emit_allocobj(jl_codectx_t &ctx, size_t static_size, Value *jt) auto call = ctx.builder.CreateCall(F, {current_task, ConstantInt::get(ctx.types().T_size, static_size), maybe_decay_untracked(ctx, jt)}); call->setAttributes(F->getAttributes()); if (static_size > 0) - call->addRetAttr(Attribute::getWithDereferenceableBytes(ctx.builder.getContext(), static_size)); + call->addRetAttr(Attribute::getWithDereferenceableBytes(call->getContext(), static_size)); + call->addRetAttr(Attribute::getWithAlignment(call->getContext(), Align(align))); + if (fully_initialized) + call->addFnAttr(Attribute::get(call->getContext(), Attribute::AllocKind, uint64_t(AllocFnKind::Alloc | AllocFnKind::Uninitialized))); return call; } -static Value *emit_allocobj(jl_codectx_t &ctx, jl_datatype_t *jt) +static Value *emit_allocobj(jl_codectx_t &ctx, jl_datatype_t *jt, bool fully_initialized) { - return emit_allocobj(ctx, jl_datatype_size(jt), ctx.builder.CreateIntToPtr(emit_tagfrom(ctx, jt), ctx.types().T_pjlvalue)); + return emit_allocobj(ctx, jl_datatype_size(jt), ctx.builder.CreateIntToPtr(emit_tagfrom(ctx, jt), ctx.types().T_pjlvalue), + fully_initialized, julia_alignment((jl_value_t*)jt)); } // allocation for unknown object from an untracked pointer @@ -3716,7 +3696,7 @@ static Value *emit_new_bits(jl_codectx_t &ctx, Value *jt, Value *pval) // if ptr is NULL this emits a write barrier _back_ static void emit_write_barrier(jl_codectx_t &ctx, Value *parent, Value *ptr) { - emit_write_barrier(ctx, parent, makeArrayRef(ptr)); + emit_write_barrier(ctx, parent, ArrayRef(ptr)); } static void emit_write_barrier(jl_codectx_t &ctx, Value *parent, ArrayRef ptrs) @@ -3733,29 +3713,6 @@ static void emit_write_barrier(jl_codectx_t &ctx, Value *parent, ArrayRef &res, unsigned offset) -{ - // This is a inlined field at `offset`. - if (!typ->layout || typ->layout->npointers == 0) - return; - jl_svec_t *types = jl_get_fieldtypes(typ); - size_t nf = jl_svec_len(types); - for (size_t i = 0; i < nf; i++) { - jl_value_t *_fld = jl_svecref(types, i); - if (!jl_is_datatype(_fld)) - continue; - jl_datatype_t *fld = (jl_datatype_t*)_fld; - if (jl_field_isptr(typ, i)) { - // pointer field, check if field is perm-alloc - if (type_is_permalloc((jl_value_t*)fld)) - res.push_back(offset + jl_field_offset(typ, i)); - continue; - } - // inline field - find_perm_offsets(fld, res, offset + jl_field_offset(typ, i)); - } -} - static void emit_write_multibarrier(jl_codectx_t &ctx, Value *parent, Value *agg, jl_value_t *jltype) { @@ -3766,11 +3723,97 @@ static void emit_write_multibarrier(jl_codectx_t &ctx, Value *parent, Value *agg emit_write_barrier(ctx, parent, ptrs); } +static jl_cgval_t union_store(jl_codectx_t &ctx, + Value *ptr, Value *ptindex, jl_cgval_t rhs, jl_cgval_t cmp, + jl_value_t *jltype, MDNode *tbaa, MDNode *aliasscope, MDNode *tbaa_tindex, + AtomicOrdering Order, AtomicOrdering FailOrder, + Value *needlock, bool issetfield, bool isreplacefield, bool isswapfield, bool ismodifyfield, bool issetfieldonce, + const jl_cgval_t *modifyop, const Twine &fname) +{ + assert(Order == AtomicOrdering::NotAtomic); + if (issetfieldonce) + return mark_julia_const(ctx, jl_false); + size_t fsz = 0, al = 0; + int union_max = jl_islayout_inline(jltype, &fsz, &al); + assert(union_max > 0); + // compute tindex from rhs + jl_cgval_t rhs_union = convert_julia_type(ctx, rhs, jltype); + if (rhs_union.typ == jl_bottom_type) + return jl_cgval_t(); + if (needlock) + emit_lockstate_value(ctx, needlock, true); + BasicBlock *ModifyBB = NULL; + if (ismodifyfield) { + ModifyBB = BasicBlock::Create(ctx.builder.getContext(), "modify_xchg", ctx.f); + ctx.builder.CreateBr(ModifyBB); + ctx.builder.SetInsertPoint(ModifyBB); + } + jl_cgval_t oldval = rhs; + if (!issetfield) + oldval = emit_unionload(ctx, ptr, ptindex, jltype, fsz, al, tbaa, true, union_max, tbaa_tindex); + Value *Success = NULL; + BasicBlock *DoneBB = NULL; + if (isreplacefield || ismodifyfield) { + if (ismodifyfield) { + if (needlock) + emit_lockstate_value(ctx, needlock, 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, julia_call); + rhs = mark_julia_type(ctx, callval, true, jl_any_type); + } + emit_typecheck(ctx, rhs, jltype, fname); + rhs = update_julia_type(ctx, rhs, jltype); + rhs_union = convert_julia_type(ctx, rhs, jltype); + if (rhs_union.typ == jl_bottom_type) + return jl_cgval_t(); + if (needlock) + emit_lockstate_value(ctx, needlock, true); + cmp = oldval; + oldval = emit_unionload(ctx, ptr, ptindex, jltype, fsz, al, tbaa, true, union_max, tbaa_tindex); + } + BasicBlock *XchgBB = BasicBlock::Create(ctx.builder.getContext(), "xchg", ctx.f); + DoneBB = BasicBlock::Create(ctx.builder.getContext(), "done_xchg", ctx.f); + Success = emit_f_is(ctx, oldval, cmp); + ctx.builder.CreateCondBr(Success, XchgBB, ismodifyfield ? ModifyBB : DoneBB); + ctx.builder.SetInsertPoint(XchgBB); + } + Value *tindex = compute_tindex_unboxed(ctx, rhs_union, jltype); + tindex = ctx.builder.CreateNUWSub(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 1)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_unionselbyte); + ai.decorateInst(ctx.builder.CreateAlignedStore(tindex, ptindex, Align(1))); + // copy data + if (!rhs.isghost) { + emit_unionmove(ctx, ptr, tbaa, rhs, nullptr); + } + if (isreplacefield || ismodifyfield) { + ctx.builder.CreateBr(DoneBB); + ctx.builder.SetInsertPoint(DoneBB); + } + if (needlock) + emit_lockstate_value(ctx, needlock, false); + if (isreplacefield) { + Success = ctx.builder.CreateZExt(Success, getInt8Ty(ctx.builder.getContext())); + 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); + } + else if (ismodifyfield) { + 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); + } + return oldval; +} + static jl_cgval_t emit_setfield(jl_codectx_t &ctx, jl_datatype_t *sty, const jl_cgval_t &strct, size_t idx0, jl_cgval_t rhs, jl_cgval_t cmp, bool wb, AtomicOrdering Order, AtomicOrdering FailOrder, - bool needlock, bool issetfield, bool isreplacefield, bool isswapfield, bool ismodifyfield, + Value *needlock, bool issetfield, bool isreplacefield, bool isswapfield, bool ismodifyfield, bool issetfieldonce, const jl_cgval_t *modifyop, const Twine &fname) { auto get_objname = [&]() { @@ -3779,122 +3822,46 @@ static jl_cgval_t emit_setfield(jl_codectx_t &ctx, ++EmittedSetfield; assert(strct.ispointer()); size_t byte_offset = jl_field_offset(sty, idx0); + auto tbaa = best_field_tbaa(ctx, strct, sty, idx0, byte_offset); Value *addr = data_pointer(ctx, strct); if (byte_offset > 0) { addr = ctx.builder.CreateInBoundsGEP( getInt8Ty(ctx.builder.getContext()), emit_bitcast(ctx, addr, getInt8PtrTy(ctx.builder.getContext())), - ConstantInt::get(ctx.types().T_size, byte_offset)); // TODO: use emit_struct_gep + ConstantInt::get(ctx.types().T_size, byte_offset)); setNameWithField(ctx.emission_context, addr, get_objname, sty, idx0, Twine("_ptr")); } jl_value_t *jfty = jl_field_type(sty, idx0); - if (!jl_field_isptr(sty, idx0) && jl_is_uniontype(jfty)) { - size_t fsz = 0, al = 0; - int union_max = jl_islayout_inline(jfty, &fsz, &al); - bool isptr = (union_max == 0); - assert(!isptr && fsz == jl_field_size(sty, idx0) - 1); (void)isptr; - // compute tindex from rhs - jl_cgval_t rhs_union = convert_julia_type(ctx, rhs, jfty); - if (rhs_union.typ == jl_bottom_type) - return jl_cgval_t(); + bool isboxed = jl_field_isptr(sty, idx0); + if (!isboxed && jl_is_uniontype(jfty)) { + size_t fsz1 = jl_field_size(sty, idx0) - 1; Value *ptindex = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), emit_bitcast(ctx, addr, getInt8PtrTy(ctx.builder.getContext())), - ConstantInt::get(ctx.types().T_size, fsz)); + ConstantInt::get(ctx.types().T_size, fsz1)); setNameWithField(ctx.emission_context, ptindex, get_objname, sty, idx0, Twine(".tindex_ptr")); - if (needlock) - emit_lockstate_value(ctx, strct, true); - BasicBlock *ModifyBB = NULL; - if (ismodifyfield) { - ModifyBB = BasicBlock::Create(ctx.builder.getContext(), "modify_xchg", ctx.f); - ctx.builder.CreateBr(ModifyBB); - ctx.builder.SetInsertPoint(ModifyBB); - } - jl_cgval_t oldval = rhs; - if (!issetfield) - oldval = emit_unionload(ctx, addr, ptindex, jfty, fsz, al, strct.tbaa, true, union_max, ctx.tbaa().tbaa_unionselbyte); - Value *Success = NULL; - BasicBlock *DoneBB = NULL; - 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, julia_call); - rhs = mark_julia_type(ctx, callval, true, jl_any_type); - } - 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, union_max, ctx.tbaa().tbaa_unionselbyte); - } - BasicBlock *XchgBB = BasicBlock::Create(ctx.builder.getContext(), "xchg", ctx.f); - DoneBB = BasicBlock::Create(ctx.builder.getContext(), "done_xchg", ctx.f); - Success = emit_f_is(ctx, oldval, cmp); - ctx.builder.CreateCondBr(Success, XchgBB, ismodifyfield ? ModifyBB : DoneBB); - ctx.builder.SetInsertPoint(XchgBB); - } - Value *tindex = compute_tindex_unboxed(ctx, rhs_union, jfty); - tindex = ctx.builder.CreateNUWSub(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 1)); - jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_unionselbyte); - ai.decorateInst(ctx.builder.CreateAlignedStore(tindex, ptindex, Align(1))); - // copy data - if (!rhs.isghost) { - emit_unionmove(ctx, addr, strct.tbaa, rhs, nullptr); - } - 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, getInt8Ty(ctx.builder.getContext())); - jl_cgval_t argv[2] = {oldval, mark_julia_type(ctx, Success, false, jl_bool_type)}; - jl_datatype_t *rettyp = jl_apply_cmpswap_type(jfty); - oldval = emit_new_struct(ctx, (jl_value_t*)rettyp, 2, argv); - if (oldval.V) { - setNameWithField(ctx.emission_context, oldval.V, get_objname, sty, idx0, Twine()); - } - } - 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); - if (oldval.V) { - setNameWithField(ctx.emission_context, oldval.V, get_objname, sty, idx0, Twine()); - } - } - return oldval; - } - else { - unsigned align = jl_field_align(sty, idx0); - bool isboxed = jl_field_isptr(sty, idx0); - size_t nfields = jl_datatype_nfields(sty); - bool maybe_null = idx0 >= nfields - (unsigned)sty->name->n_uninitialized; - return typed_store(ctx, addr, NULL, rhs, cmp, jfty, strct.tbaa, nullptr, - wb ? boxed(ctx, strct) : nullptr, - isboxed, Order, FailOrder, align, - needlock, issetfield, isreplacefield, isswapfield, ismodifyfield, maybe_null, modifyop, fname); + return union_store(ctx, addr, ptindex, rhs, cmp, jfty, tbaa, nullptr, ctx.tbaa().tbaa_unionselbyte, + Order, FailOrder, + needlock, issetfield, isreplacefield, isswapfield, ismodifyfield, issetfieldonce, + modifyop, fname); } + unsigned align = jl_field_align(sty, idx0); + size_t nfields = jl_datatype_nfields(sty); + bool maybe_null = idx0 >= nfields - (unsigned)sty->name->n_uninitialized; + return typed_store(ctx, addr, rhs, cmp, jfty, tbaa, nullptr, + wb ? boxed(ctx, strct) : nullptr, + isboxed, Order, FailOrder, align, + needlock, issetfield, isreplacefield, isswapfield, ismodifyfield, issetfieldonce, + maybe_null, modifyop, fname, nullptr, nullptr); } -static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t nargs, const jl_cgval_t *argv, bool is_promotable) +static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t nargs, ArrayRef argv, bool is_promotable) { ++EmittedNewStructs; assert(jl_is_datatype(ty)); assert(jl_is_concrete_type(ty)); jl_datatype_t *sty = (jl_datatype_t*)ty; auto arg_typename = [&] JL_NOTSAFEPOINT { - return jl_symbol_name((sty)->name->name); + return "new::" + std::string(jl_symbol_name((sty)->name->name)); }; size_t nf = jl_datatype_nfields(sty); if (nf > 0 || sty->name->mutabl) { @@ -3920,14 +3887,20 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg strct = NULL; } else if (init_as_value) { - if (tracked.count) + if (tracked.count) { strct = Constant::getNullValue(lt); - else + } + else { strct = UndefValue::get(lt); + if (nargs < nf) + strct = ctx.builder.CreateFreeze(strct); + } } else { strct = emit_static_alloca(ctx, lt); - setName(ctx.emission_context, strct, "new" + StringRef("::") + arg_typename()); + setName(ctx.emission_context, strct, arg_typename); + if (nargs < nf) + promotion_point = ctx.builder.CreateStore(ctx.builder.CreateFreeze(UndefValue::get(lt)), strct); if (tracked.count) undef_derived_strct(ctx, strct, sty, ctx.tbaa().tbaa_stack); } @@ -3995,27 +3968,28 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg tindex = ctx.builder.CreateNUWSub(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 1)); size_t fsz = 0, al = 0; bool isptr = !jl_islayout_inline(jtype, &fsz, &al); - assert(!isptr && fsz == jl_field_size(sty, i) - 1); (void)isptr; + assert(!isptr && fsz < jl_field_size(sty, i)); (void)isptr; + size_t fsz1 = jl_field_size(sty, i) - 1; if (init_as_value) { // If you wanted to implement init_as_value, // would need to emit the union-move into temporary memory, // then load it and combine with the tindex. // But more efficient to just store it directly. - unsigned ptindex = convert_struct_offset(ctx, lt, offs + fsz); - if (fsz > 0 && !fval_info.isghost) { + unsigned ptindex = convert_struct_offset(ctx, lt, offs + fsz1); + if (fsz1 > 0 && !fval_info.isghost) { Type *ET = IntegerType::get(ctx.builder.getContext(), 8 * al); assert(lt->getStructElementType(llvm_idx) == ET); AllocaInst *lv = emit_static_alloca(ctx, ET); setName(ctx.emission_context, lv, "unioninit"); - lv->setOperand(0, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), (fsz + al - 1) / al)); + lv->setOperand(0, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), (fsz1 + al - 1) / al)); emit_unionmove(ctx, lv, ctx.tbaa().tbaa_stack, fval_info, nullptr); // emit all of the align-sized words unsigned i = 0; - for (; i < fsz / al; i++) { + for (; i < fsz1 / al; i++) { Value *fldp = ctx.builder.CreateConstInBoundsGEP1_32(ET, lv, i); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_stack); Value *fldv = ai.decorateInst(ctx.builder.CreateAlignedLoad(ET, fldp, Align(al))); - strct = ctx.builder.CreateInsertValue(strct, fldv, makeArrayRef(llvm_idx + i)); + strct = ctx.builder.CreateInsertValue(strct, fldv, ArrayRef(llvm_idx + i)); } // emit remaining bytes up to tindex if (i < ptindex - llvm_idx) { @@ -4025,17 +3999,17 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg Value *fldp = ctx.builder.CreateConstInBoundsGEP1_32(getInt8Ty(ctx.builder.getContext()), staddr, i); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_stack); Value *fldv = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), fldp, Align(1))); - strct = ctx.builder.CreateInsertValue(strct, fldv, makeArrayRef(llvm_idx + i)); + strct = ctx.builder.CreateInsertValue(strct, fldv, ArrayRef(llvm_idx + i)); } } } llvm_idx = ptindex; fval = tindex; if (jl_is_vecelement_type(ty)) - fval = ctx.builder.CreateInsertValue(strct, fval, makeArrayRef(llvm_idx)); + fval = ctx.builder.CreateInsertValue(strct, fval, ArrayRef(llvm_idx)); } else { - Value *ptindex = emit_struct_gep(ctx, lt, strct, offs + fsz); + Value *ptindex = emit_struct_gep(ctx, lt, strct, offs + fsz1); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_unionselbyte); ai.decorateInst(ctx.builder.CreateAlignedStore(tindex, ptindex, Align(1))); if (!rhs_union.isghost) @@ -4059,7 +4033,7 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg else if (lt->isVectorTy()) strct = ctx.builder.CreateInsertElement(strct, fval, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), llvm_idx)); else if (lt->isAggregateType()) - strct = ctx.builder.CreateInsertValue(strct, fval, makeArrayRef(llvm_idx)); + strct = ctx.builder.CreateInsertValue(strct, fval, ArrayRef(llvm_idx)); else assert(false); } @@ -4073,7 +4047,7 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg int fsz = jl_field_size(sty, i) - 1; unsigned llvm_idx = convert_struct_offset(ctx, cast(lt), offs + fsz); if (init_as_value) - strct = ctx.builder.CreateInsertValue(strct, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0), makeArrayRef(llvm_idx)); + strct = ctx.builder.CreateInsertValue(strct, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0), ArrayRef(llvm_idx)); else { jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_unionselbyte); ai.decorateInst(ctx.builder.CreateAlignedStore( @@ -4096,8 +4070,8 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg return ret; } } - Value *strct = emit_allocobj(ctx, sty); - setName(ctx.emission_context, strct, "new" + StringRef("::") + arg_typename()); + Value *strct = emit_allocobj(ctx, sty, nargs >= nf); + setName(ctx.emission_context, strct, arg_typename); jl_cgval_t strctinfo = mark_julia_type(ctx, strct, true, ty); strct = decay_derived(ctx, strct); undef_derived_strct(ctx, strct, sty, strctinfo.tbaa); @@ -4124,18 +4098,19 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg rhs = update_julia_type(ctx, rhs, ft); if (rhs.typ == jl_bottom_type) return jl_cgval_t(); - emit_setfield(ctx, sty, strctinfo, i, rhs, jl_cgval_t(), need_wb, AtomicOrdering::NotAtomic, AtomicOrdering::NotAtomic, false, true, false, false, false, nullptr, ""); + emit_setfield(ctx, sty, strctinfo, i, rhs, jl_cgval_t(), need_wb, AtomicOrdering::NotAtomic, AtomicOrdering::NotAtomic, nullptr, true, false, false, false, false, nullptr, "new"); } return strctinfo; } else { - // 0 fields, ghost or bitstype + // 0 fields, ghost or primitive type if (jl_datatype_nbits(sty) == 0) return ghostValue(ctx, sty); + // n.b. this is not valid IR form to construct a primitive type (use bitcast for example) bool isboxed; Type *lt = julia_type_to_llvm(ctx, ty, &isboxed); assert(!isboxed); - return mark_julia_type(ctx, UndefValue::get(lt), false, ty); + return mark_julia_type(ctx, ctx.builder.CreateFreeze(UndefValue::get(lt)), false, ty); } } @@ -4170,6 +4145,234 @@ static int compare_cgparams(const jl_cgparams_t *a, const jl_cgparams_t *b) } #endif +static jl_cgval_t _emit_memoryref(jl_codectx_t &ctx, Value *mem, Value *data, const jl_datatype_layout_t *layout, jl_value_t *typ) +{ + //jl_cgval_t argv[] = { + // mark_julia_type(ctx, mem, true, jl_any_type), + // mark_julia_type(ctx, data, false, jl_voidpointer_type) + //}; + //return emit_new_struct(ctx, typ, 3, argv); + Value *ref = Constant::getNullValue(get_memoryref_type(ctx.builder.getContext(), ctx.types().T_size, layout, 0)); + ref = ctx.builder.CreateInsertValue(ref, data, 0); + ref = ctx.builder.CreateInsertValue(ref, mem, 1); + return mark_julia_type(ctx, ref, false, typ); +} + +static jl_cgval_t _emit_memoryref(jl_codectx_t &ctx, const jl_cgval_t &mem, const jl_datatype_layout_t *layout, jl_value_t *typ) +{ + bool isboxed = layout->flags.arrayelem_isboxed; + bool isunion = layout->flags.arrayelem_isunion; + bool isghost = layout->size == 0; + Value *data = (!isboxed && isunion) || isghost ? ConstantInt::get(ctx.types().T_size, 0) : emit_genericmemoryptr(ctx, boxed(ctx, mem), layout, 0); + return _emit_memoryref(ctx, boxed(ctx, mem), data, layout, typ); +} + +static Value *emit_memoryref_FCA(jl_codectx_t &ctx, const jl_cgval_t &ref, const jl_datatype_layout_t *layout) +{ + if (ref.ispointer()) { + LLVMContext &C = ctx.builder.getContext(); + Type *type = get_memoryref_type(C, ctx.types().T_size, layout, 0); + LoadInst *load = ctx.builder.CreateLoad(type, emit_bitcast(ctx, data_pointer(ctx, ref), PointerType::get(type, 0))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ref.tbaa); + ai.decorateInst(load); + return load; + } + else { + return ref.V; + } +} + +static jl_cgval_t emit_memoryref(jl_codectx_t &ctx, const jl_cgval_t &ref, jl_cgval_t idx, jl_value_t *inbounds, const jl_datatype_layout_t *layout) +{ + ++EmittedArrayNdIndex; + emit_typecheck(ctx, idx, (jl_value_t*)jl_long_type, "memoryref"); + idx = update_julia_type(ctx, idx, (jl_value_t*)jl_long_type); + if (idx.typ == jl_bottom_type) + return jl_cgval_t(); + Value *V = emit_memoryref_FCA(ctx, ref, layout); + Value *data = CreateSimplifiedExtractValue(ctx, V, 0); + Value *mem = CreateSimplifiedExtractValue(ctx, V, 1); + Value *i = emit_unbox(ctx, ctx.types().T_size, idx, (jl_value_t*)jl_long_type); + Value *offset = ctx.builder.CreateSub(i, ConstantInt::get(ctx.types().T_size, 1)); + Value *elsz = emit_genericmemoryelsize(ctx, mem, ref.typ, false); + bool bc = bounds_check_enabled(ctx, inbounds); +#if 1 + Value *ovflw = nullptr; +#endif + Value *newdata; + bool isboxed = layout->flags.arrayelem_isboxed; + bool isunion = layout->flags.arrayelem_isunion; + bool isghost = layout->size == 0; + if ((!isboxed && isunion) || isghost) { + newdata = ctx.builder.CreateAdd(data, offset); + if (bc) { + BasicBlock *failBB, *endBB; + failBB = BasicBlock::Create(ctx.builder.getContext(), "oob"); + endBB = BasicBlock::Create(ctx.builder.getContext(), "idxend"); + Value *mlen = emit_genericmemorylen(ctx, mem, ref.typ); + Value *inbound = ctx.builder.CreateICmpULT(newdata, mlen); + ctx.builder.CreateCondBr(inbound, endBB, failBB); + failBB->insertInto(ctx.f); + ctx.builder.SetInsertPoint(failBB); + ctx.builder.CreateCall(prepare_call(jlboundserror_func), + { mark_callee_rooted(ctx, boxed(ctx, ref)), i }); + ctx.builder.CreateUnreachable(); + endBB->insertInto(ctx.f); + ctx.builder.SetInsertPoint(endBB); + } + } + else { + Value *boffset; +#if 0 + if (bc) { + auto *MulF = Intrinsic::getDeclaration(jl_Module, Intrinsic::smul_with_overflow, offset->getType()); + CallInst *Mul = ctx.builder.CreateCall(MulF, {offset, elsz}); + boffset = ctx.builder.CreateExtractValue(Mul, 0); + ovflw = ctx.builder.CreateExtractValue(Mul, 1); + } + else +#else + if (bc) { + // n.b. we could boundscheck that -len<=offset<=len instead of using smul.ovflw, + // since we know that len*elsz does not overflow, + // and we can further rearrange that as ovflw = !( offset+len < len+len ) as unsigned math + Value *mlen = emit_genericmemorylen(ctx, mem, ref.typ); + ovflw = ctx.builder.CreateICmpUGE(ctx.builder.CreateAdd(offset, mlen), ctx.builder.CreateNUWAdd(mlen, mlen)); + } +#endif + boffset = ctx.builder.CreateMul(offset, elsz); +#if 0 // TODO: if opaque-pointers? + newdata = emit_bitcast(ctx, data, getInt8PtrTy(ctx.builder.getContext())); + newdata = ctx.builder.CreateGEP(getInt8Ty(ctx.builder.getContext()), newdata, boffset); +#else + Type *elty = isboxed ? ctx.types().T_prjlvalue : julia_type_to_llvm(ctx, jl_tparam1(ref.typ)); + newdata = emit_bitcast(ctx, data, elty->getPointerTo(0)); + newdata = ctx.builder.CreateInBoundsGEP(elty, newdata, offset); + (void)boffset; // LLVM is very bad at handling GEP with types different from the load +#endif + newdata = emit_bitcast(ctx, newdata, data->getType()); + if (bc) { + BasicBlock *failBB, *endBB; + failBB = BasicBlock::Create(ctx.builder.getContext(), "oob"); + endBB = BasicBlock::Create(ctx.builder.getContext(), "idxend"); + Value *mlen = emit_genericmemorylen(ctx, mem, ref.typ); + Value *mptr = emit_genericmemoryptr(ctx, mem, layout, 0); + mptr = emit_bitcast(ctx, mptr, newdata->getType()); +#if 0 + Value *mend = emit_bitcast(ctx, mptr, getInt8PtrTy(ctx.builder.getContext())); + Value *blen = ctx.builder.CreateMul(mlen, elsz, "", true, true); + mend = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), mend, blen); + mend = emit_bitcast(ctx, mend, newdata->getType()); + Value *inbound = ctx.builder.CreateAnd( + ctx.builder.CreateICmpULE(mptr, newdata), + ctx.builder.CreateICmpULT(newdata, mend)); + inbound = ctx.builder.CreateAnd( + ctx.builder.CreateNot(ovflw), + inbound); +#elif 1 + Value *bidx0 = ctx.builder.CreateSub( + ctx.builder.CreatePtrToInt(newdata, ctx.types().T_size), + ctx.builder.CreatePtrToInt(mptr, ctx.types().T_size)); + Value *blen = ctx.builder.CreateMul(mlen, elsz, "", true, true); + Value *inbound = ctx.builder.CreateICmpULT(bidx0, blen); + inbound = ctx.builder.CreateAnd(ctx.builder.CreateNot(ovflw), inbound); +#else + Value *idx0; // (newdata - mptr) / elsz + idx0 = ctx.builder.CreateSub( + ctx.builder.CreatePtrToInt(newdata, ctx.types().T_size), + ctx.builder.CreatePtrToInt(mptr, ctx.types().T_size)); + idx0 = ctx.builder.CreateExactUDiv(idx0, elsz); + Value *inbound = ctx.builder.CreateICmpULT(idx0, mlen); +#endif + ctx.builder.CreateCondBr(inbound, endBB, failBB); + failBB->insertInto(ctx.f); + ctx.builder.SetInsertPoint(failBB); + ctx.builder.CreateCall(prepare_call(jlboundserror_func), + { mark_callee_rooted(ctx, boxed(ctx, ref)), i }); + ctx.builder.CreateUnreachable(); + endBB->insertInto(ctx.f); + ctx.builder.SetInsertPoint(endBB); + } + } + return _emit_memoryref(ctx, mem, newdata, layout, ref.typ); +} + +static jl_cgval_t emit_memoryref_offset(jl_codectx_t &ctx, const jl_cgval_t &ref, const jl_datatype_layout_t *layout) +{ + Value *offset; + Value *V = emit_memoryref_FCA(ctx, ref, layout); + Value *data = CreateSimplifiedExtractValue(ctx, V, 0); + if (layout->flags.arrayelem_isunion || layout->size == 0) { + offset = data; + } + else { + Value *mem = CreateSimplifiedExtractValue(ctx, V, 1); + Value *mptr = emit_genericmemoryptr(ctx, mem, layout, 0); + mptr = emit_bitcast(ctx, mptr, mem->getType()); + // (data - mptr) / elsz + offset = ctx.builder.CreateSub( + ctx.builder.CreatePtrToInt(data, ctx.types().T_size), + ctx.builder.CreatePtrToInt(mptr, ctx.types().T_size)); + Value *elsz = emit_genericmemoryelsize(ctx, mem, ref.typ, false); + offset = ctx.builder.CreateExactUDiv(offset, elsz); + } + offset = ctx.builder.CreateAdd(offset, ConstantInt::get(ctx.types().T_size, 1)); + return mark_julia_type(ctx, offset, false, jl_long_type); +} + +static Value *emit_memoryref_mem(jl_codectx_t &ctx, const jl_cgval_t &ref, const jl_datatype_layout_t *layout) +{ + Value *V = emit_memoryref_FCA(ctx, ref, layout); + return CreateSimplifiedExtractValue(ctx, V, 1); +} + +static Value *emit_memoryref_ptr(jl_codectx_t &ctx, const jl_cgval_t &ref, const jl_datatype_layout_t *layout) +{ + assert(!layout->flags.arrayelem_isunion && layout->size != 0); + Value *newref = emit_memoryref_FCA(ctx, ref, layout); + Value *data = CreateSimplifiedExtractValue(ctx, newref, 0); + unsigned AS = AddressSpace::Loaded; + Value *mem = CreateSimplifiedExtractValue(ctx, newref, 1); + // rebuild GEP on data, so that we manually hoist this gc_loaded_func call over it, back to the original load + // we should add this to llvm-julia-licm too, so we can attempt hoisting over PhiNodes too (which aren't defined yet here) + IRBuilder<>::InsertPointGuard resetIP(ctx.builder); + SmallVector GEPlist; + data = data->stripPointerCastsSameRepresentation(); + while (GetElementPtrInst *GEP = dyn_cast(data)) { // ignoring bitcast will not be required with opaque pointers + GEPlist.push_back(GEP); + data = GEP->getPointerOperand()->stripPointerCastsSameRepresentation(); + } + data = ctx.builder.CreateBitCast(data, ctx.types().T_pprjlvalue); + data = ctx.builder.CreateCall(prepare_call(gc_loaded_func), { mem, data }); + if (!GEPlist.empty()) { + for (auto &GEP : make_range(GEPlist.rbegin(), GEPlist.rend())) { + data = ctx.builder.CreateBitCast(data, PointerType::get(GEP->getSourceElementType(), AS)); + Instruction *GEP2 = GEP->clone(); + GEP2->mutateType(PointerType::get(GEP->getResultElementType(), AS)); + GEP2->setOperand(GetElementPtrInst::getPointerOperandIndex(), data); + ctx.builder.Insert(GEP2); + data = GEP2; + } + } + return data; +} + +jl_value_t *jl_fptr_wait_for_compiled(jl_value_t *f, jl_value_t **args, uint32_t nargs, jl_code_instance_t *m) +{ + // This relies on the invariant that the JIT will have set the invoke ptr + // by the time it releases the codegen lock. If the code is refactored to make the + // codegen lock more fine-grained, this will have to be replaced by a per-codeinstance futex. + size_t nthreads = jl_atomic_load_acquire(&jl_n_threads); + // This should only be possible if there's more than one thread. If not, either there's a bug somewhere + // that resulted in this not getting cleared, or we're about to deadlock. Either way, that's bad. + if (nthreads == 1) { + jl_error("Internal error: Reached jl_fptr_wait_for_compiled in single-threaded execution."); + } + JL_LOCK(&jl_codegen_lock); + JL_UNLOCK(&jl_codegen_lock); + return jl_atomic_load_acquire(&m->invoke)(f, args, nargs, m); +} + // Reset us back to codegen debug type #undef DEBUG_TYPE #define DEBUG_TYPE "julia_irgen_codegen" diff --git a/src/clangsa/GCChecker.cpp b/src/clangsa/GCChecker.cpp index 1dcb7f5a754d7..60471f1909f02 100644 --- a/src/clangsa/GCChecker.cpp +++ b/src/clangsa/GCChecker.cpp @@ -1,5 +1,6 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license +#include "clang/AST/Type.h" #include "clang/Frontend/FrontendActions.h" #include "clang/StaticAnalyzer/Checkers/SValExplainer.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" @@ -14,6 +15,7 @@ #include "clang/Tooling/Tooling.h" #include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h" +#include "llvm/Support/Debug.h" #include #include @@ -806,8 +808,11 @@ bool GCChecker::isGCTrackedType(QualType QT) { Name.endswith_lower("jl_expr_t") || Name.endswith_lower("jl_code_info_t") || Name.endswith_lower("jl_array_t") || + Name.endswith_lower("jl_genericmemory_t") || + //Name.endswith_lower("jl_genericmemoryref_t") || Name.endswith_lower("jl_method_t") || Name.endswith_lower("jl_method_instance_t") || + Name.endswith_lower("jl_debuginfo_t") || Name.endswith_lower("jl_tupletype_t") || Name.endswith_lower("jl_datatype_t") || Name.endswith_lower("jl_typemap_entry_t") || @@ -892,9 +897,11 @@ bool GCChecker::isSafepoint(const CallEvent &Call, CheckerContext &C) const { if (!Decl || !FD) { if (Callee == nullptr) { isCalleeSafepoint = true; - } else if (const TypedefType *TDT = dyn_cast(Callee->getType())) { - isCalleeSafepoint = - !declHasAnnotation(TDT->getDecl(), "julia_not_safepoint"); + } else if (const ElaboratedType *ET = dyn_cast(Callee->getType())){ + if (const TypedefType *TDT = dyn_cast(ET->getNamedType())) { + isCalleeSafepoint = + !declHasAnnotation(TDT->getDecl(), "julia_not_safepoint"); + } } else if (const CXXPseudoDestructorExpr *PDE = dyn_cast(Callee)) { // A pseudo-destructor is an expression that looks like a member @@ -939,7 +946,7 @@ bool GCChecker::processPotentialSafepoint(const CallEvent &Call, isGCTrackedType(ParmType->getPointeeType())) { // This is probably an out parameter. Find the value it refers to now. SVal Loaded = - State->getSVal(Call.getArgSVal(i).getAs().getValue()); + State->getSVal(*(Call.getArgSVal(i).getAs())); SpeciallyRootedSymbol = Loaded.getAsSymbol(); continue; } @@ -1438,7 +1445,8 @@ bool GCChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { } else if (name == "JL_GC_PUSH1" || name == "JL_GC_PUSH2" || name == "JL_GC_PUSH3" || name == "JL_GC_PUSH4" || name == "JL_GC_PUSH5" || name == "JL_GC_PUSH6" || - name == "JL_GC_PUSH7" || name == "JL_GC_PUSH8") { + name == "JL_GC_PUSH7" || name == "JL_GC_PUSH8" || + name == "JL_GC_PUSH9") { ProgramStateRef State = C.getState(); // Transform slots to roots, transform values to rooted unsigned NumArgs = CE->getNumArgs(); @@ -1518,7 +1526,7 @@ bool GCChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { } } if (FD) { - Loc ItemsLoc = State->getLValue(FD, ArrayList).getAs().getValue(); + Loc ItemsLoc = *(State->getLValue(FD, ArrayList).getAs()); SVal Items = State->getSVal(ItemsLoc); if (Items.isUnknown()) { Items = C.getSValBuilder().conjureSymbolVal( @@ -1686,7 +1694,7 @@ void GCChecker::checkLocation(SVal SLoc, bool IsLoad, const Stmt *S, // better than this. if (IsLoad && (RS = State->get(SLoc.getAsRegion()))) { SymbolRef LoadedSym = - State->getSVal(SLoc.getAs().getValue()).getAsSymbol(); + State->getSVal(*SLoc.getAs()).getAsSymbol(); if (LoadedSym) { const ValueState *ValS = State->get(LoadedSym); if (!ValS || !ValS->isRooted() || ValS->RootDepth > RS->RootedAtDepth) { diff --git a/src/codegen-stubs.c b/src/codegen-stubs.c index 0ce3a9b188df2..e67ae3b75ea76 100644 --- a/src/codegen-stubs.c +++ b/src/codegen-stubs.c @@ -38,17 +38,17 @@ JL_DLLEXPORT void jl_register_fptrs_fallback(uint64_t image_base, const struct _ (void)image_base; (void)fptrs; (void)linfos; (void)n; } -JL_DLLEXPORT jl_code_instance_t *jl_generate_fptr_fallback(jl_method_instance_t *mi JL_PROPAGATES_ROOT, size_t world) -{ - return NULL; -} - JL_DLLEXPORT void jl_generate_fptr_for_unspecialized_fallback(jl_code_instance_t *unspec) { jl_atomic_store_release(&unspec->invoke, &jl_fptr_interpret_call); } -JL_DLLEXPORT void jl_generate_fptr_for_oc_wrapper_fallback(jl_code_instance_t *unspec) UNAVAILABLE +JL_DLLEXPORT int jl_compile_codeinst_fallback(jl_code_instance_t *unspec) +{ + // Do nothing. The caller will notice that we failed to provide a an ->invoke and trigger + // appropriate fallbacks. + return 0; +} JL_DLLEXPORT uint32_t jl_get_LLVM_VERSION_fallback(void) { @@ -107,8 +107,7 @@ JL_DLLEXPORT uint64_t jl_getUnwindInfo_fallback(uint64_t dwAddr) return 0; } -JL_DLLEXPORT void jl_build_newpm_pipeline_fallback(void *MPM, void *PB, int Speedup, int Size, - int lower_intrinsics, int dump_native, int external_use, int llvm_only) UNAVAILABLE +JL_DLLEXPORT void jl_build_newpm_pipeline_fallback(void *MPM, void *PB, void *config) UNAVAILABLE JL_DLLEXPORT void jl_register_passbuilder_callbacks_fallback(void *PB) { } diff --git a/src/codegen.cpp b/src/codegen.cpp index 940f235bf59fc..b6e948e9fcaff 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -40,10 +40,10 @@ #include #include #include +#include // support #include -#include #include #include #include @@ -86,7 +86,7 @@ static bool jl_fpo_disabled(const Triple &TT) { // MSAN doesn't support FPO return true; #endif - if (TT.isOSLinux() || TT.isOSWindows() || TT.isOSFreeBSD()) { + if (TT.isOSLinux() || TT.isOSWindows() || TT.isOSFreeBSD() || TT.isOSOpenBSD()) { return true; } return false; @@ -163,7 +163,6 @@ typedef Instruction TerminatorInst; #endif #include "jitlayers.h" -#include "llvm-codegen-shared.h" #include "processor.h" #include "julia_assert.h" @@ -212,7 +211,7 @@ void setNameWithField(jl_codegen_params_t ¶ms, Value *V, std::functionsetName(Twine(GetObjName()) + "." + Twine(jl_symbol_name((jl_sym_t*)name)) + suffix); return; @@ -284,12 +283,14 @@ extern void _chkstk(void); // types struct jl_typecache_t { + Type *T_ptr; Type *T_size; Type *T_jlvalue; Type *T_pjlvalue; Type *T_prjlvalue; Type *T_ppjlvalue; Type *T_pprjlvalue; + StructType *T_jlgenericmemory; StructType *T_jlarray; Type *T_pjlarray; FunctionType *T_jlfunc; @@ -304,16 +305,18 @@ struct jl_typecache_t { bool initialized; jl_typecache_t() : - T_jlvalue(nullptr), T_pjlvalue(nullptr), T_prjlvalue(nullptr), - T_ppjlvalue(nullptr), T_pprjlvalue(nullptr), T_jlarray(nullptr), - T_pjlarray(nullptr), T_jlfunc(nullptr), T_jlfuncparams(nullptr), - T_sigatomic(nullptr), T_ppint8(nullptr), initialized(false) {} + T_ptr(nullptr), T_jlvalue(nullptr), T_pjlvalue(nullptr), T_prjlvalue(nullptr), + T_ppjlvalue(nullptr), T_pprjlvalue(nullptr), + T_jlgenericmemory(nullptr), T_jlarray(nullptr), T_pjlarray(nullptr), + T_jlfunc(nullptr), T_jlfuncparams(nullptr), T_sigatomic(nullptr), T_ppint8(nullptr), + initialized(false) {} void initialize(LLVMContext &context, const DataLayout &DL) { if (initialized) { return; } initialized = true; + T_ptr = getInt8PtrTy(context); T_ppint8 = PointerType::get(getInt8PtrTy(context), 0); T_sigatomic = Type::getIntNTy(context, sizeof(sig_atomic_t) * 8); T_size = DL.getIntPtrType(context); @@ -331,15 +334,12 @@ struct jl_typecache_t { T_jlfuncparams = JuliaType::get_jlfuncparams_ty(context); assert(T_jlfuncparams != NULL); - Type *vaelts[] = {PointerType::get(getInt8Ty(context), AddressSpace::Loaded) - , T_size - , getInt16Ty(context) - , getInt16Ty(context) - , getInt32Ty(context) + T_jlgenericmemory = StructType::get(context, { T_size, T_pprjlvalue /* [, real-owner] */ }); + Type *vaelts[] = { PointerType::get(getInt8Ty(context), AddressSpace::Loaded), + PointerType::get(T_jlgenericmemory, AddressSpace::Tracked), + // dimsize[ndims] }; - static_assert(sizeof(jl_array_flags_t) == sizeof(int16_t), - "Size of jl_array_flags_t is not the same as int16_t"); - T_jlarray = StructType::get(context, makeArrayRef(vaelts)); + T_jlarray = StructType::get(context, ArrayRef(vaelts)); T_pjlarray = PointerType::get(T_jlarray, 0); } }; @@ -354,19 +354,19 @@ struct jl_tbaacache_t { MDNode *tbaa_unionselbyte; // a selector byte in isbits Union struct fields MDNode *tbaa_data; // Any user data that `pointerset/ref` are allowed to alias MDNode *tbaa_binding; // jl_binding_t::value - MDNode *tbaa_value; // jl_value_t, that is not jl_array_t + MDNode *tbaa_value; // jl_value_t, that is not jl_array_t or jl_genericmemory_t MDNode *tbaa_mutab; // mutable type MDNode *tbaa_datatype; // datatype MDNode *tbaa_immut; // immutable type MDNode *tbaa_ptrarraybuf; // Data in an array of boxed values MDNode *tbaa_arraybuf; // Data in an array of POD - MDNode *tbaa_array; // jl_array_t - MDNode *tbaa_arrayptr; // The pointer inside a jl_array_t + MDNode *tbaa_array; // jl_array_t or jl_genericmemory_t + MDNode *tbaa_arrayptr; // The pointer inside a jl_array_t (to memoryref) MDNode *tbaa_arraysize; // A size in a jl_array_t - MDNode *tbaa_arraylen; // The len in a jl_array_t - MDNode *tbaa_arrayflags; // The flags in a jl_array_t - MDNode *tbaa_arrayoffset; // The offset in a jl_array_t - MDNode *tbaa_arrayselbyte; // a selector byte in a isbits Union jl_array_t + MDNode *tbaa_arrayselbyte; // a selector byte in a isbits Union jl_genericmemory_t + MDNode *tbaa_memoryptr; // The pointer inside a jl_genericmemory_t + MDNode *tbaa_memorylen; // The length in a jl_genericmemory_t + MDNode *tbaa_memoryown; // The owner in a foreign jl_genericmemory_t MDNode *tbaa_const; // Memory that is immutable by the time LLVM can see it bool initialized; @@ -375,8 +375,8 @@ struct jl_tbaacache_t { tbaa_value(nullptr), tbaa_mutab(nullptr), tbaa_datatype(nullptr), tbaa_immut(nullptr), tbaa_ptrarraybuf(nullptr), tbaa_arraybuf(nullptr), tbaa_array(nullptr), tbaa_arrayptr(nullptr), tbaa_arraysize(nullptr), - tbaa_arraylen(nullptr), tbaa_arrayflags(nullptr), tbaa_arrayoffset(nullptr), - tbaa_arrayselbyte(nullptr), tbaa_const(nullptr), initialized(false) {} + tbaa_arrayselbyte(nullptr), tbaa_memoryptr(nullptr), tbaa_memorylen(nullptr), tbaa_memoryown(nullptr), + tbaa_const(nullptr), initialized(false) {} auto tbaa_make_child(MDBuilder &mbuilder, const char *name, MDNode *parent = nullptr, bool isConstant = false) { MDNode *scalar = mbuilder.createTBAAScalarTypeNode(name, parent ? parent : tbaa_root); @@ -414,11 +414,11 @@ struct jl_tbaacache_t { std::tie(tbaa_array, tbaa_array_scalar) = tbaa_make_child(mbuilder, "jtbaa_array"); tbaa_arrayptr = tbaa_make_child(mbuilder, "jtbaa_arrayptr", tbaa_array_scalar).first; tbaa_arraysize = tbaa_make_child(mbuilder, "jtbaa_arraysize", tbaa_array_scalar).first; - tbaa_arraylen = tbaa_make_child(mbuilder, "jtbaa_arraylen", tbaa_array_scalar).first; - tbaa_arrayflags = tbaa_make_child(mbuilder, "jtbaa_arrayflags", tbaa_array_scalar).first; - tbaa_arrayoffset = tbaa_make_child(mbuilder, "jtbaa_arrayoffset", tbaa_array_scalar).first; - tbaa_const = tbaa_make_child(mbuilder, "jtbaa_const", nullptr, true).first; tbaa_arrayselbyte = tbaa_make_child(mbuilder, "jtbaa_arrayselbyte", tbaa_array_scalar).first; + tbaa_memoryptr = tbaa_make_child(mbuilder, "jtbaa_memoryptr", tbaa_array_scalar, true).first; + tbaa_memorylen = tbaa_make_child(mbuilder, "jtbaa_memorylen", tbaa_array_scalar, true).first; + tbaa_memoryown = tbaa_make_child(mbuilder, "jtbaa_memoryown", tbaa_array_scalar, true).first; + tbaa_const = tbaa_make_child(mbuilder, "jtbaa_const", nullptr, true).first; } }; @@ -431,7 +431,7 @@ struct jl_noaliascache_t { MDNode *gcframe; // GC frame MDNode *stack; // Stack slot MDNode *data; // Any user data that `pointerset/ref` are allowed to alias - MDNode *type_metadata; // Non-user-accessible type metadata incl. size, union selectors, etc. + MDNode *type_metadata; // Non-user-accessible type metadata incl. union selectors, etc. MDNode *constant; // Memory that is immutable by the time LLVM can see it jl_regions_t(): gcframe(nullptr), stack(nullptr), data(nullptr), type_metadata(nullptr), constant(nullptr) {} @@ -605,18 +605,21 @@ static inline void add_named_global(StringRef name, T *addr) add_named_global(name, (void*)(uintptr_t)addr); } -AttributeSet Attributes(LLVMContext &C, std::initializer_list attrkinds) +AttributeSet Attributes(LLVMContext &C, std::initializer_list attrkinds, std::initializer_list extra={}) { - SmallVector attrs(attrkinds.size()); + SmallVector attrs(attrkinds.size() + extra.size()); for (size_t i = 0; i < attrkinds.size(); i++) attrs[i] = Attribute::get(C, attrkinds.begin()[i]); - return AttributeSet::get(C, makeArrayRef(attrs)); + for (size_t i = 0; i < extra.size(); i++) + attrs[attrkinds.size() + i] = extra.begin()[i]; + return AttributeSet::get(C, ArrayRef(attrs)); } static Type *get_pjlvalue(LLVMContext &C) { return JuliaType::get_pjlvalue_ty(C); } static FunctionType *get_func_sig(LLVMContext &C) { return JuliaType::get_jlfunc_ty(C); } static FunctionType *get_func2_sig(LLVMContext &C) { return JuliaType::get_jlfunc2_ty(C); } +static FunctionType *get_func3_sig(LLVMContext &C) { return JuliaType::get_jlfunc3_ty(C); } static FunctionType *get_donotdelete_sig(LLVMContext &C) { return FunctionType::get(getVoidTy(C), true); @@ -633,9 +636,16 @@ static AttributeList get_func_attrs(LLVMContext &C) static AttributeList get_donotdelete_func_attrs(LLVMContext &C) { - AttributeSet FnAttrs = Attributes(C, {Attribute::InaccessibleMemOnly, Attribute::WillReturn, Attribute::NoUnwind}); + AttrBuilder FnAttrs(C); +#if JL_LLVM_VERSION >= 160000 + FnAttrs.addMemoryAttr(MemoryEffects::inaccessibleMemOnly()); +#else + FnAttrs.addAttribute(Attribute::InaccessibleMemOnly); +#endif + FnAttrs.addAttribute(Attribute::WillReturn); + FnAttrs.addAttribute(Attribute::NoUnwind); return AttributeList::get(C, - FnAttrs, + AttributeSet::get(C, FnAttrs), Attributes(C, {}), None); } @@ -656,20 +666,65 @@ static AttributeList get_attrs_basic(LLVMContext &C) None); } -static AttributeList get_attrs_sext(LLVMContext &C) +static AttributeList get_attrs_box_float(LLVMContext &C, unsigned nbytes) { + auto FnAttrs = AttrBuilder(C); + FnAttrs.addAttribute(Attribute::WillReturn); + FnAttrs.addAttribute(Attribute::NoUnwind); +#if JL_LLVM_VERSION >= 160000 + FnAttrs.addMemoryAttr(MemoryEffects::inaccessibleMemOnly()); +#else + FnAttrs.addAttribute(Attribute::InaccessibleMemOnly); +#endif + auto RetAttrs = AttrBuilder(C); + RetAttrs.addAttribute(Attribute::NonNull); + RetAttrs.addDereferenceableAttr(nbytes); + RetAttrs.addAlignmentAttr(Align(alignof(void*))); return AttributeList::get(C, - AttributeSet(), - Attributes(C, {Attribute::NonNull}), - {Attributes(C, {Attribute::SExt})}); + AttributeSet::get(C, FnAttrs), + AttributeSet::get(C, RetAttrs), + None); } -static AttributeList get_attrs_zext(LLVMContext &C) +static AttributeList get_attrs_box_sext(LLVMContext &C, unsigned nbytes) { + auto FnAttrs = AttrBuilder(C); + FnAttrs.addAttribute(Attribute::WillReturn); + FnAttrs.addAttribute(Attribute::NoUnwind); +#if JL_LLVM_VERSION >= 160000 + FnAttrs.addMemoryAttr(MemoryEffects::inaccessibleMemOnly()); +#else + FnAttrs.addAttribute(Attribute::InaccessibleMemOnly); +#endif + auto RetAttrs = AttrBuilder(C); + RetAttrs.addAttribute(Attribute::NonNull); + RetAttrs.addAttribute(Attribute::getWithDereferenceableBytes(C, nbytes)); + RetAttrs.addDereferenceableAttr(nbytes); + RetAttrs.addAlignmentAttr(Align(alignof(void*))); return AttributeList::get(C, - AttributeSet(), - Attributes(C, {Attribute::NonNull}), - {Attributes(C, {Attribute::ZExt})}); + AttributeSet::get(C, FnAttrs), + AttributeSet::get(C, RetAttrs), + AttributeSet::get(C, {Attribute::get(C, Attribute::SExt)})); +} + +static AttributeList get_attrs_box_zext(LLVMContext &C, unsigned nbytes) +{ + auto FnAttrs = AttrBuilder(C); + FnAttrs.addAttribute(Attribute::WillReturn); + FnAttrs.addAttribute(Attribute::NoUnwind); +#if JL_LLVM_VERSION >= 160000 + FnAttrs.addMemoryAttr(MemoryEffects::inaccessibleMemOnly()); +#else + FnAttrs.addAttribute(Attribute::InaccessibleMemOnly); +#endif + auto RetAttrs = AttrBuilder(C); + RetAttrs.addAttribute(Attribute::NonNull); + RetAttrs.addDereferenceableAttr(nbytes); + RetAttrs.addAlignmentAttr(Align(alignof(void*))); + return AttributeList::get(C, + AttributeSet::get(C, FnAttrs), + AttributeSet::get(C, RetAttrs), + AttributeSet::get(C, {Attribute::get(C, Attribute::ZExt)})); } @@ -780,8 +835,10 @@ static const auto jltypeerror_func = new JuliaFunction<>{ }; static const auto jlundefvarerror_func = new JuliaFunction<>{ XSTR(jl_undefined_var_error), - [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), - {PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted)}, false); }, + [](LLVMContext &C) { + Type *T = PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted); + return FunctionType::get(getVoidTy(C), {T, T}, false); + }, get_attrs_noreturn, }; static const auto jlhasnofield_func = new JuliaFunction<>{ @@ -824,6 +881,42 @@ static const auto jlcheckassign_func = new JuliaFunction<>{ {T_pjlvalue, T_pjlvalue, T_pjlvalue, PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted)}, false); }, nullptr, }; +static const auto jlcheckreplace_func = new JuliaFunction<>{ + XSTR(jl_checked_replace), + [](LLVMContext &C) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); + return FunctionType::get(T_prjlvalue, + {T_pjlvalue, T_pjlvalue, T_pjlvalue, T_prjlvalue, T_prjlvalue}, false); }, + nullptr, +}; +static const auto jlcheckmodify_func = new JuliaFunction<>{ + XSTR(jl_checked_modify), + [](LLVMContext &C) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); + return FunctionType::get(T_prjlvalue, + {T_pjlvalue, T_pjlvalue, T_pjlvalue, T_prjlvalue, T_prjlvalue}, false); }, + nullptr, +}; +static const auto jlcheckswap_func = new JuliaFunction<>{ + XSTR(jl_checked_swap), + [](LLVMContext &C) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); + return FunctionType::get(T_prjlvalue, + {T_pjlvalue, T_pjlvalue, T_pjlvalue, PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted)}, false); }, + nullptr, +}; +static const auto jlcheckassignonce_func = new JuliaFunction<>{ + XSTR(jl_checked_assignonce), + [](LLVMContext &C) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); + return FunctionType::get(T_prjlvalue, + {T_pjlvalue, T_pjlvalue, T_pjlvalue, PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted)}, false); }, + nullptr, +}; static const auto jldeclareconst_func = new JuliaFunction<>{ XSTR(jl_declare_constant), [](LLVMContext &C) { @@ -937,32 +1030,84 @@ static const auto jlunlockvalue_func = new JuliaFunction<>{ AttributeSet(), {Attributes(C, {Attribute::NoCapture})}); }, }; +static const auto jllockfield_func = new JuliaFunction<>{ + XSTR(jl_lock_field), + [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), + {PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::Loaded)}, false); }, + [](LLVMContext &C) { return AttributeList::get(C, + AttributeSet(), + AttributeSet(), + {Attributes(C, {Attribute::NoCapture})}); }, +}; +static const auto jlunlockfield_func = new JuliaFunction<>{ + XSTR(jl_unlock_field), + [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), + {PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::Loaded)}, false); }, + [](LLVMContext &C) { return AttributeList::get(C, + AttributeSet(), + AttributeSet(), + {Attributes(C, {Attribute::NoCapture})}); }, +}; static const auto jlenter_func = new JuliaFunction<>{ XSTR(jl_enter_handler), - [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), - {getInt8PtrTy(C)}, false); }, + [](LLVMContext &C) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + return FunctionType::get(getVoidTy(C), + {T_pjlvalue, getInt8PtrTy(C)}, false); }, nullptr, }; static const auto jl_current_exception_func = new JuliaFunction<>{ XSTR(jl_current_exception), - [](LLVMContext &C) { return FunctionType::get(JuliaType::get_prjlvalue_ty(C), false); }, + [](LLVMContext &C) { return FunctionType::get(JuliaType::get_prjlvalue_ty(C), {JuliaType::get_pjlvalue_ty(C)}, false); }, nullptr, }; static const auto jlleave_func = new JuliaFunction<>{ XSTR(jl_pop_handler), - [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), - {getInt32Ty(C)}, false); }, - nullptr, + [](LLVMContext &C) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + return FunctionType::get(getVoidTy(C), + {T_pjlvalue, getInt32Ty(C)}, false); }, + [](LLVMContext &C) { + auto FnAttrs = AttrBuilder(C); + FnAttrs.addAttribute(Attribute::WillReturn); + FnAttrs.addAttribute(Attribute::NoUnwind); + auto RetAttrs = AttrBuilder(C); + return AttributeList::get(C, + AttributeSet::get(C, FnAttrs), + AttributeSet(), + None); + }, +}; +static const auto jlleave_noexcept_func = new JuliaFunction<>{ + XSTR(jl_pop_handler_noexcept), + [](LLVMContext &C) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + return FunctionType::get(getVoidTy(C), + {T_pjlvalue, getInt32Ty(C)}, false); }, + [](LLVMContext &C) { + auto FnAttrs = AttrBuilder(C); + FnAttrs.addAttribute(Attribute::WillReturn); + FnAttrs.addAttribute(Attribute::NoUnwind); + auto RetAttrs = AttrBuilder(C); + return AttributeList::get(C, + AttributeSet::get(C, FnAttrs), + AttributeSet(), + None); + }, }; static const auto jl_restore_excstack_func = new JuliaFunction{ XSTR(jl_restore_excstack), - [](LLVMContext &C, Type *T_size) { return FunctionType::get(getVoidTy(C), - {T_size}, false); }, + [](LLVMContext &C, Type *T_size) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + return FunctionType::get(getVoidTy(C), + {T_pjlvalue, T_size}, false); }, nullptr, }; static const auto jl_excstack_state_func = new JuliaFunction{ XSTR(jl_excstack_state), - [](LLVMContext &C, Type *T_size) { return FunctionType::get(T_size, false); }, + [](LLVMContext &C, Type *T_size) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + return FunctionType::get(T_size, {T_pjlvalue}, false); }, nullptr, }; static const auto jlegalx_func = new JuliaFunction{ @@ -970,26 +1115,38 @@ static const auto jlegalx_func = new JuliaFunction{ [](LLVMContext &C, Type *T_size) { Type *T = PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::Derived); return FunctionType::get(getInt32Ty(C), {T, T, T_size}, false); }, - [](LLVMContext &C) { return AttributeList::get(C, - Attributes(C, {Attribute::ReadOnly, Attribute::NoUnwind, Attribute::ArgMemOnly}), - AttributeSet(), - None); }, + [](LLVMContext &C) { + AttrBuilder FnAttrs(C); +#if JL_LLVM_VERSION >= 160000 + FnAttrs.addMemoryAttr(MemoryEffects::inaccessibleOrArgMemOnly()); +#else + FnAttrs.addAttribute(Attribute::ReadOnly); + FnAttrs.addAttribute(Attribute::ArgMemOnly); +#endif + FnAttrs.addAttribute(Attribute::NoUnwind); + return AttributeList::get(C, + AttributeSet::get(C, FnAttrs), + AttributeSet(), + None); }, }; static const auto jl_alloc_obj_func = new JuliaFunction{ "julia.gc_alloc_obj", [](LLVMContext &C, Type *T_size) { auto T_jlvalue = JuliaType::get_jlvalue_ty(C); auto T_prjlvalue = PointerType::get(T_jlvalue, AddressSpace::Tracked); - auto T_ppjlvalue = PointerType::get(PointerType::get(T_jlvalue, 0), 0); + auto T_pjlvalue = PointerType::get(T_jlvalue, 0); return FunctionType::get(T_prjlvalue, - {T_ppjlvalue, T_size, T_prjlvalue}, false); + {T_pjlvalue, T_size, T_prjlvalue}, false); }, [](LLVMContext &C) { auto FnAttrs = AttrBuilder(C); FnAttrs.addAllocSizeAttr(1, None); // returns %1 bytes -#if JL_LLVM_VERSION >= 150000 - FnAttrs.addAllocKindAttr(AllocFnKind::Alloc | AllocFnKind::Uninitialized | AllocFnKind::Aligned); + FnAttrs.addAllocKindAttr(AllocFnKind::Alloc); +#if JL_LLVM_VERSION >= 160000 + FnAttrs.addMemoryAttr(MemoryEffects::argMemOnly(ModRefInfo::Ref) | MemoryEffects::inaccessibleMemOnly(ModRefInfo::ModRef)); #endif + FnAttrs.addAttribute(Attribute::WillReturn); + FnAttrs.addAttribute(Attribute::NoUnwind); auto RetAttrs = AttrBuilder(C); RetAttrs.addAttribute(Attribute::NoAlias); RetAttrs.addAttribute(Attribute::NonNull); @@ -1021,8 +1178,17 @@ static const auto jl_typeof_func = new JuliaFunction<>{ return FunctionType::get(T_prjlvalue, {T_prjlvalue}, false); }, - [](LLVMContext &C) { return AttributeList::get(C, - Attributes(C, {Attribute::ReadNone, Attribute::NoUnwind, Attribute::NoRecurse}), + [](LLVMContext &C) { + AttrBuilder FnAttrs(C); +#if JL_LLVM_VERSION >= 160000 + FnAttrs.addMemoryAttr(MemoryEffects::none()); +#else + FnAttrs.addAttribute(Attribute::ReadNone); +#endif + FnAttrs.addAttribute(Attribute::NoUnwind); + FnAttrs.addAttribute(Attribute::NoRecurse); + return AttributeList::get(C, + AttributeSet::get(C, FnAttrs), Attributes(C, {Attribute::NonNull}), None); }, }; @@ -1031,10 +1197,20 @@ static const auto jl_write_barrier_func = new JuliaFunction<>{ "julia.write_barrier", [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), {JuliaType::get_prjlvalue_ty(C)}, true); }, - [](LLVMContext &C) { return AttributeList::get(C, - Attributes(C, {Attribute::NoUnwind, Attribute::NoRecurse, Attribute::InaccessibleMemOnly}), + [](LLVMContext &C) { + AttrBuilder FnAttrs(C); +#if JL_LLVM_VERSION >= 160000 + FnAttrs.addMemoryAttr(MemoryEffects::inaccessibleMemOnly()); +#else + FnAttrs.addAttribute(Attribute::InaccessibleMemOnly); +#endif + FnAttrs.addAttribute(Attribute::NoUnwind); + FnAttrs.addAttribute(Attribute::NoRecurse); + return AttributeList::get(C, + AttributeSet::get(C, FnAttrs), AttributeSet(), - {Attributes(C, {Attribute::ReadOnly})}); }, + {Attributes(C, {Attribute::ReadOnly})}); + }, }; static const auto jlisa_func = new JuliaFunction<>{ @@ -1069,7 +1245,7 @@ static const auto jlapplytype_func = new JuliaFunction<>{ [](LLVMContext &C) { return AttributeList::get(C, AttributeSet(), - AttributeSet::get(C, makeArrayRef({Attribute::get(C, Attribute::NonNull), + AttributeSet::get(C, ArrayRef({Attribute::get(C, Attribute::NonNull), Attribute::getWithAlignment(C, Align(16))})), None); }, @@ -1077,7 +1253,7 @@ static const auto jlapplytype_func = new JuliaFunction<>{ static const auto jl_object_id__func = new JuliaFunction{ XSTR(jl_object_id_), [](LLVMContext &C, Type *T_size) { return FunctionType::get(T_size, - {JuliaType::get_prjlvalue_ty(C), PointerType::get(getInt8Ty(C), AddressSpace::Derived)}, false); }, + {T_size, PointerType::get(getInt8Ty(C), AddressSpace::Derived)}, false); }, nullptr, }; static const auto setjmp_func = new JuliaFunction{ @@ -1098,8 +1274,17 @@ static const auto memcmp_func = new JuliaFunction{ XSTR(memcmp), [](LLVMContext &C, Type *T_size) { return FunctionType::get(getInt32Ty(C), {getInt8PtrTy(C), getInt8PtrTy(C), T_size}, false); }, - [](LLVMContext &C) { return AttributeList::get(C, - Attributes(C, {Attribute::ReadOnly, Attribute::NoUnwind, Attribute::ArgMemOnly}), + [](LLVMContext &C) { + AttrBuilder FnAttrs(C); +#if JL_LLVM_VERSION >= 160000 + FnAttrs.addMemoryAttr(MemoryEffects::argMemOnly(ModRefInfo::Ref)); +#else + FnAttrs.addAttribute(Attribute::ArgMemOnly); + FnAttrs.addAttribute(Attribute::ReadOnly); +#endif + FnAttrs.addAttribute(Attribute::NoUnwind); + return AttributeList::get(C, + AttributeSet::get(C, FnAttrs), AttributeSet(), None); }, // TODO: inferLibFuncAttributes(*memcmp_func, TLI); @@ -1144,8 +1329,17 @@ static const auto jlfieldindex_func = new JuliaFunction<>{ return FunctionType::get(getInt32Ty(C), {T_prjlvalue, T_prjlvalue, getInt32Ty(C)}, false); }, - [](LLVMContext &C) { return AttributeList::get(C, - Attributes(C, {Attribute::NoUnwind, Attribute::ReadOnly, Attribute::WillReturn}), + [](LLVMContext &C) { + AttrBuilder FnAttrs(C); +#if JL_LLVM_VERSION >= 160000 + FnAttrs.addMemoryAttr(MemoryEffects::readOnly()); +#else + FnAttrs.addAttribute(Attribute::ReadOnly); +#endif + FnAttrs.addAttribute(Attribute::NoUnwind); + FnAttrs.addAttribute(Attribute::WillReturn); + return AttributeList::get(C, + AttributeSet::get(C, FnAttrs), AttributeSet(), None); }, // This function can error if the third argument is 1 so don't do that. }; @@ -1196,6 +1390,29 @@ static const auto sync_gc_total_bytes_func = new JuliaFunction<>{ {getInt64Ty(C)}, false); }, nullptr, }; +static const auto jl_allocgenericmemory = new JuliaFunction{ + XSTR(jl_alloc_genericmemory), + [](LLVMContext &C, Type *T_Size) { + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); + return FunctionType::get(T_prjlvalue, // new Memory + {T_prjlvalue, // type + T_Size // nelements + }, false); }, + [](LLVMContext &C) { + AttrBuilder FnAttrs(C); + AttrBuilder RetAttrs(C); +#if JL_LLVM_VERSION >= 160000 + FnAttrs.addMemoryAttr(MemoryEffects::inaccessibleMemOnly(ModRefInfo::ModRef) | MemoryEffects::argMemOnly(ModRefInfo::Ref)); +#endif + FnAttrs.addAttribute(Attribute::WillReturn); + RetAttrs.addAlignmentAttr(Align(16)); + RetAttrs.addAttribute(Attribute::NonNull); + RetAttrs.addDereferenceableAttr(16); + return AttributeList::get(C, + AttributeSet::get(C, FnAttrs), + AttributeSet::get(C, RetAttrs), + None); }, +}; static const auto jlarray_data_owner_func = new JuliaFunction<>{ XSTR(jl_array_data_owner), [](LLVMContext &C) { @@ -1208,22 +1425,22 @@ static const auto jlarray_data_owner_func = new JuliaFunction<>{ Attributes(C, {Attribute::NonNull}), None); }, }; -#define BOX_FUNC(ct,at,attrs) \ +#define BOX_FUNC(ct,at,attrs,nbytes) \ static const auto box_##ct##_func = new JuliaFunction<>{ \ XSTR(jl_box_##ct), \ [](LLVMContext &C) { return FunctionType::get(JuliaType::get_prjlvalue_ty(C),\ {at}, false); }, \ - attrs, \ -} -BOX_FUNC(int16, getInt16Ty(C), get_attrs_sext); -BOX_FUNC(uint16, getInt16Ty(C), get_attrs_zext); -BOX_FUNC(int32, getInt32Ty(C), get_attrs_sext); -BOX_FUNC(uint32, getInt32Ty(C), get_attrs_zext); -BOX_FUNC(int64, getInt64Ty(C), get_attrs_sext); -BOX_FUNC(uint64, getInt64Ty(C), get_attrs_zext); -BOX_FUNC(char, getCharTy(C), get_attrs_zext); -BOX_FUNC(float32, getFloatTy(C), get_attrs_basic); -BOX_FUNC(float64, getDoubleTy(C), get_attrs_basic); + [](LLVMContext &C) { return attrs(C,nbytes); }, \ +} +BOX_FUNC(int16, getInt16Ty(C), get_attrs_box_sext, 2); +BOX_FUNC(uint16, getInt16Ty(C), get_attrs_box_zext, 2); +BOX_FUNC(int32, getInt32Ty(C), get_attrs_box_sext, 4); +BOX_FUNC(uint32, getInt32Ty(C), get_attrs_box_zext, 4); +BOX_FUNC(int64, getInt64Ty(C), get_attrs_box_sext, 8); +BOX_FUNC(uint64, getInt64Ty(C), get_attrs_box_zext, 8); +BOX_FUNC(char, getCharTy(C), get_attrs_box_zext, 1); +BOX_FUNC(float32, getFloatTy(C), get_attrs_box_float, 4); +BOX_FUNC(float64, getDoubleTy(C), get_attrs_box_float, 8); #undef BOX_FUNC static const auto box_ssavalue_func = new JuliaFunction{ @@ -1235,6 +1452,12 @@ static const auto box_ssavalue_func = new JuliaFunction{ }, get_attrs_basic, }; +static const auto jlgetbuiltinfptr_func = new JuliaFunction<>{ + XSTR(jl_get_builtin_fptr), + [](LLVMContext &C) { return FunctionType::get(get_func_sig(C)->getPointerTo(), + {JuliaType::get_prjlvalue_ty(C)}, false); }, + nullptr, +}; // placeholder functions @@ -1255,9 +1478,11 @@ static const auto gc_preserve_end_func = new JuliaFunction<> { }; static const auto except_enter_func = new JuliaFunction<>{ "julia.except_enter", - [](LLVMContext &C) { return FunctionType::get(getInt32Ty(C), false); }, + [](LLVMContext &C) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + return FunctionType::get(getInt32Ty(C), {T_pjlvalue}, false); }, [](LLVMContext &C) { return AttributeList::get(C, - AttributeSet::get(C, makeArrayRef({Attribute::get(C, Attribute::ReturnsTwice)})), + Attributes(C, {Attribute::ReturnsTwice}), AttributeSet(), None); }, }; @@ -1265,11 +1490,48 @@ static const auto pointer_from_objref_func = new JuliaFunction<>{ "julia.pointer_from_objref", [](LLVMContext &C) { return FunctionType::get(JuliaType::get_pjlvalue_ty(C), {PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::Derived)}, false); }, - [](LLVMContext &C) { return AttributeList::get(C, - AttributeSet::get(C, makeArrayRef({Attribute::get(C, Attribute::ReadNone), Attribute::get(C, Attribute::NoUnwind)})), + [](LLVMContext &C) { + AttrBuilder FnAttrs(C); +#if JL_LLVM_VERSION >= 160000 + FnAttrs.addMemoryAttr(MemoryEffects::none()); +#else + FnAttrs.addAttribute(Attribute::ReadNone); +#endif + FnAttrs.addAttribute(Attribute::NoUnwind); + return AttributeList::get(C, + AttributeSet::get(C, FnAttrs), Attributes(C, {Attribute::NonNull}), None); }, }; +static const auto gc_loaded_func = new JuliaFunction<>{ + "julia.gc_loaded", + // # memory(none) nosync nounwind speculatable willreturn norecurse + // declare nonnull noundef ptr(Loaded) @"julia.gc_loaded"(ptr(Tracked) nocapture nonnull noundef readnone, ptr nonnull noundef readnone) + // top: + // %metadata GC base pointer is ptr(Tracked) + // ret addrspacecast ptr to ptr(Loaded) + [](LLVMContext &C) { return FunctionType::get(PointerType::get(JuliaType::get_prjlvalue_ty(C), AddressSpace::Loaded), + {JuliaType::get_prjlvalue_ty(C), PointerType::get(JuliaType::get_prjlvalue_ty(C), 0)}, false); }, + [](LLVMContext &C) { + AttrBuilder FnAttrs(C); + FnAttrs.addAttribute(Attribute::NoSync); + FnAttrs.addAttribute(Attribute::NoUnwind); + FnAttrs.addAttribute(Attribute::Speculatable); + FnAttrs.addAttribute(Attribute::WillReturn); + FnAttrs.addAttribute(Attribute::NoRecurse); +#if JL_LLVM_VERSION >= 160000 + FnAttrs.addMemoryAttr(MemoryEffects::none()); +#else + FnAttrs.addAttribute(Attribute::ReadNone); +#endif + AttrBuilder RetAttrs(C); + RetAttrs.addAttribute(Attribute::NonNull); + RetAttrs.addAttribute(Attribute::NoUndef); + return AttributeList::get(C, AttributeSet::get(C,FnAttrs), AttributeSet::get(C,RetAttrs), + { Attributes(C, {Attribute::NonNull, Attribute::NoUndef, Attribute::ReadNone, Attribute::NoCapture}), + Attributes(C, {Attribute::NonNull, Attribute::NoUndef, Attribute::ReadNone}) }); + }, +}; // julia.call represents a call with julia calling convention, it is used as // @@ -1307,7 +1569,23 @@ static const auto julia_call2 = new JuliaFunction<>{ get_attrs_basic, }; +// julia.call3 is like julia.call, except that %fptr is derived rather than tracked +static const auto julia_call3 = new JuliaFunction<>{ + "julia.call3", + [](LLVMContext &C) { + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); + Type *T = PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::Derived); + return FunctionType::get(T_prjlvalue, + {get_func3_sig(C)->getPointerTo(), + T}, // %f + true); }, // %args + get_attrs_basic, +}; + + static const auto jltuple_func = new JuliaFunction<>{XSTR(jl_f_tuple), get_func_sig, get_func_attrs}; +static const auto jlintrinsic_func = new JuliaFunction<>{XSTR(jl_f_intrinsic_call), get_func3_sig, get_func_attrs}; + static const auto &builtin_func_map() { static std::map*> builtins = { { jl_f_is_addr, new JuliaFunction<>{XSTR(jl_f_is), get_func_sig, get_func_attrs} }, @@ -1336,10 +1614,14 @@ static const auto &builtin_func_map() { { jl_f_nfields_addr, new JuliaFunction<>{XSTR(jl_f_nfields), get_func_sig, get_func_attrs} }, { jl_f__expr_addr, new JuliaFunction<>{XSTR(jl_f__expr), get_func_sig, get_func_attrs} }, { jl_f__typevar_addr, new JuliaFunction<>{XSTR(jl_f__typevar), get_func_sig, get_func_attrs} }, - { jl_f_arrayref_addr, new JuliaFunction<>{XSTR(jl_f_arrayref), get_func_sig, get_func_attrs} }, - { jl_f_const_arrayref_addr, new JuliaFunction<>{XSTR(jl_f_const_arrayref), get_func_sig, get_func_attrs} }, - { jl_f_arrayset_addr, new JuliaFunction<>{XSTR(jl_f_arrayset), get_func_sig, get_func_attrs} }, - { jl_f_arraysize_addr, new JuliaFunction<>{XSTR(jl_f_arraysize), get_func_sig, get_func_attrs} }, + { jl_f_memoryref_addr, new JuliaFunction<>{XSTR(jl_f_memoryref), get_func_sig, get_func_attrs} }, + { jl_f_memoryrefoffset_addr, new JuliaFunction<>{XSTR(jl_f_memoryrefoffset), get_func_sig, get_func_attrs} }, + { jl_f_memoryrefset_addr, new JuliaFunction<>{XSTR(jl_f_memoryrefset), get_func_sig, get_func_attrs} }, + { jl_f_memoryrefswap_addr, new JuliaFunction<>{XSTR(jl_f_memoryrefswap), get_func_sig, get_func_attrs} }, + { jl_f_memoryrefreplace_addr, new JuliaFunction<>{XSTR(jl_f_memoryrefreplace), get_func_sig, get_func_attrs} }, + { jl_f_memoryrefmodify_addr, new JuliaFunction<>{XSTR(jl_f_memoryrefmodify), get_func_sig, get_func_attrs} }, + { jl_f_memoryrefsetonce_addr, new JuliaFunction<>{XSTR(jl_f_memoryrefsetonce), get_func_sig, get_func_attrs} }, + { jl_f_memoryref_isassigned_addr,new JuliaFunction<>{XSTR(jl_f_memoryref_isassigned), get_func_sig, get_func_attrs} }, { jl_f_apply_type_addr, new JuliaFunction<>{XSTR(jl_f_apply_type), get_func_sig, get_func_attrs} }, { jl_f_donotdelete_addr, new JuliaFunction<>{XSTR(jl_f_donotdelete), get_donotdelete_sig, get_donotdelete_func_attrs} }, { jl_f_compilerbarrier_addr, new JuliaFunction<>{XSTR(jl_f_compilerbarrier), get_func_sig, get_func_attrs} }, @@ -1383,6 +1665,8 @@ static MDNode *best_tbaa(jl_tbaacache_t &tbaa_cache, jl_value_t *jt) { return tbaa_cache.tbaa_value; if (jl_is_abstracttype(jt)) return tbaa_cache.tbaa_value; + if (jl_is_genericmemory_type(jt) || jl_is_array_type(jt)) + return tbaa_cache.tbaa_array; // If we're here, we know all subtypes are (im)mutable, even if we // don't know what the exact type is return jl_is_mutable(jt) ? tbaa_cache.tbaa_mutab : tbaa_cache.tbaa_immut; @@ -1492,15 +1776,25 @@ struct jl_aliasinfo_t { }; // metadata tracking for a llvm Value* during codegen +const uint8_t UNION_BOX_MARKER = 0x80; struct jl_cgval_t { Value *V; // may be of type T* or T, or set to NULL if ghost (or if the value has not been initialized yet, for a variable definition) // For unions, we may need to keep a reference to the boxed part individually. // If this is non-NULL, then, at runtime, we satisfy the invariant that (for the corresponding - // runtime values) if `(TIndex | 0x80) != 0`, then `Vboxed == V` (by value). + // runtime values) if `(TIndex | UNION_BOX_MARKER) != 0`, then `Vboxed == V` (by value). // For convenience, we also set this value of isboxed values, in which case // it is equal (at compile time) to V. - // If this is non-NULL, it is always of type `T_prjlvalue` + + // If this is non-NULL (at compile time), it is always of type `T_prjlvalue`. + // N.B.: In general we expect this to always be a dereferenceable pointer at runtime. + // However, there are situations where this value may be a runtime NULL + // (PhiNodes with undef predecessors or PhiC with undef UpsilonNode). + // The middle-end arranges appropriate error checks before any use + // of this value that may read a non-dereferenceable Vboxed, with two + // exceptions: PhiNode and UpsilonNode arguments which need special + // handling to account for the possibility that this may be NULL. Value *Vboxed; + Value *TIndex; // if `V` is an unboxed (tagged) Union described by `typ`, this gives the DataType index (1-based, small int) as an i8 jl_value_t *constant; // constant value (rooted in linfo.def.roots) jl_value_t *typ; // the original type of V, never NULL @@ -1646,6 +1940,7 @@ class jl_codectx_t { // local var info. globals are not in here. SmallVector slots; std::map phic_slots; + std::map > scope_restore; SmallVector SAvalues; SmallVector, 0> PhiNodes; SmallVector ssavalue_assigned; @@ -1658,7 +1953,8 @@ class jl_codectx_t { jl_value_t *rettype = NULL; jl_code_info_t *source = NULL; jl_array_t *code = NULL; - size_t world = 0; + size_t min_world = 0; + size_t max_world = -1; const char *name = NULL; StringRef file{}; ssize_t *line = NULL; @@ -1681,14 +1977,19 @@ class jl_codectx_t { SmallVector, 0> llvmcall_modules; - jl_codectx_t(LLVMContext &llvmctx, jl_codegen_params_t ¶ms) + jl_codectx_t(LLVMContext &llvmctx, jl_codegen_params_t ¶ms, size_t min_world, size_t max_world) : builder(llvmctx), emission_context(params), call_targets(), - world(params.world), + min_world(min_world), + max_world(max_world), use_cache(params.cache), external_linkage(params.external_linkage), - params(params.params) { } + params(params.params) { + } + + jl_codectx_t(LLVMContext &llvmctx, jl_codegen_params_t ¶ms, jl_code_instance_t *ci) : + jl_codectx_t(llvmctx, params, jl_atomic_load_relaxed(&ci->min_world), jl_atomic_load_relaxed(&ci->max_world)) {} jl_typecache_t &types() { type_cache.initialize(builder.getContext(), emission_context.DL); @@ -1792,7 +2093,7 @@ static jl_returninfo_t get_specsig_function(jl_codectx_t &ctx, Module *M, Value static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval = -1); static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t *s, jl_binding_t **pbnd, bool assign); -static jl_cgval_t emit_checked_var(jl_codectx_t &ctx, Value *bp, jl_sym_t *name, bool isvol, MDNode *tbaa); +static jl_cgval_t emit_checked_var(jl_codectx_t &ctx, Value *bp, jl_sym_t *name, jl_value_t *scope, bool isvol, MDNode *tbaa); static jl_cgval_t emit_sparam(jl_codectx_t &ctx, size_t i); static Value *emit_condition(jl_codectx_t &ctx, const jl_cgval_t &condV, const Twine &msg); static Value *get_current_task(jl_codectx_t &ctx); @@ -1800,13 +2101,13 @@ static Value *get_current_ptls(jl_codectx_t &ctx); static Value *get_last_age_field(jl_codectx_t &ctx); static void CreateTrap(IRBuilder<> &irbuilder, bool create_new_block = true); static CallInst *emit_jlcall(jl_codectx_t &ctx, FunctionCallee theFptr, Value *theF, - const jl_cgval_t *args, size_t nargs, JuliaFunction<> *trampoline); + ArrayRef args, size_t nargs, JuliaFunction<> *trampoline); static CallInst *emit_jlcall(jl_codectx_t &ctx, JuliaFunction<> *theFptr, Value *theF, - const jl_cgval_t *args, size_t nargs, JuliaFunction<> *trampoline); + ArrayRef args, size_t nargs, JuliaFunction<> *trampoline); 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, bool is_promotable=false); -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 jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t nargs, ArrayRef argv, bool is_promotable=false); +static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, ArrayRef argv, size_t nargs, jl_value_t *rt); static Value *literal_pointer_val(jl_codectx_t &ctx, jl_value_t *p); static unsigned julia_alignment(jl_value_t *jt); @@ -1889,9 +2190,9 @@ static void undef_derived_strct(jl_codectx_t &ctx, Value *ptr, jl_datatype_t *st size_t first_offset = sty->layout->nfields ? jl_field_offset(sty, 0) : 0; if (first_offset != 0) ctx.builder.CreateMemSet(ptr, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0), first_offset, MaybeAlign(0)); - size_t i, np = sty->layout->npointers; - if (np == 0) + if (sty->layout->first_ptr < 0) return; + size_t i, np = sty->layout->npointers; auto T_prjlvalue = JuliaType::get_prjlvalue_ty(ctx.builder.getContext()); ptr = ctx.builder.CreateBitCast(ptr, T_prjlvalue->getPointerTo(ptr->getType()->getPointerAddressSpace())); for (i = 0; i < np; i++) { @@ -1976,9 +2277,12 @@ static bool valid_as_globalinit(const Value *v) { return isa(v); } +static Value *zext_struct(jl_codectx_t &ctx, Value *V); + static inline jl_cgval_t value_to_pointer(jl_codectx_t &ctx, Value *v, jl_value_t *typ, Value *tindex) { Value *loc; + v = zext_struct(ctx, v); if (valid_as_globalinit(v)) { // llvm can't handle all the things that could be inside a ConstantExpr assert(jl_is_concrete_type(typ)); // not legal to have an unboxed abstract type loc = get_pointer_to_constant(ctx.emission_context, cast(v), Align(julia_alignment(typ)), "_j_const", *jl_Module); @@ -2104,17 +2408,6 @@ static void alloc_def_flag(jl_codectx_t &ctx, jl_varinfo_t& vi) // --- utilities --- -static Constant *undef_value_for_type(Type *T) { - auto tracked = CountTrackedPointers(T); - Constant *undef; - if (tracked.count) - // make sure gc pointers (including ptr_phi of union-split) are initialized to NULL - undef = Constant::getNullValue(T); - else - undef = UndefValue::get(T); - return undef; -} - static void CreateTrap(IRBuilder<> &irbuilder, bool create_new_block) { Function *f = irbuilder.GetInsertBlock()->getParent(); @@ -2157,7 +2450,7 @@ static void CreateConditionalAbort(IRBuilder<> &irbuilder, Value *test) static jl_cgval_t convert_julia_type_union(jl_codectx_t &ctx, const jl_cgval_t &v, jl_value_t *typ, Value **skip) { // previous value was a split union, compute new index, or box - Value *new_tindex = ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80); + Value *new_tindex = ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER); SmallBitVector skip_box(1, true); Value *tindex = ctx.builder.CreateAnd(v.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x7f)); if (jl_is_uniontype(typ)) { @@ -2200,14 +2493,14 @@ static jl_cgval_t convert_julia_type_union(jl_codectx_t &ctx, const jl_cgval_t & // some of the values are still unboxed if (!isa(new_tindex)) { Value *wasboxed = NULL; - // If the old value was boxed and unknown (type tag 0x80), + // If the old value was boxed and unknown (type tag UNION_BOX_MARKER), // it is possible that the tag was actually one of the types // that are now explicitly represented. To find out, we need // to compare typeof(v.Vboxed) (i.e. the type of the unknown // value) against all the types that are now explicitly // selected and select the appropriate one as our new tindex. if (v.Vboxed) { - wasboxed = ctx.builder.CreateAnd(v.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)); + wasboxed = ctx.builder.CreateAnd(v.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER)); new_tindex = ctx.builder.CreateOr(wasboxed, new_tindex); wasboxed = ctx.builder.CreateICmpNE(wasboxed, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0)); setName(ctx.emission_context, wasboxed, "wasboxed"); @@ -2229,10 +2522,10 @@ static jl_cgval_t convert_julia_type_union(jl_codectx_t &ctx, const jl_cgval_t & }; // If we don't find a match. The type remains unknown - // (0x80). We could use `v.Tindex`, here, since we know - // it has to be 0x80, but it seems likely the backend + // (UNION_BOX_MARKER). We could use `v.Tindex`, here, since we know + // it has to be UNION_BOX_MARKER, but it seems likely the backend // will like the explicit constant better. - Value *union_box_tindex = ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80); + Value *union_box_tindex = ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER); unsigned counter = 0; for_each_uniontype_small( // for each new union-split value @@ -2242,7 +2535,7 @@ static jl_cgval_t convert_julia_type_union(jl_codectx_t &ctx, const jl_cgval_t & // didn't handle this item before, select its new union index maybe_setup_union_isa(); Value *cmp = ctx.builder.CreateICmpEQ(emit_tagfrom(ctx, jt), union_box_dt); - union_box_tindex = ctx.builder.CreateSelect(cmp, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80 | idx), union_box_tindex); + union_box_tindex = ctx.builder.CreateSelect(cmp, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER | idx), union_box_tindex); } }, typ, @@ -2252,7 +2545,7 @@ static jl_cgval_t convert_julia_type_union(jl_codectx_t &ctx, const jl_cgval_t & BasicBlock *postBB = BasicBlock::Create(ctx.builder.getContext(), "post_union_isa", ctx.f); ctx.builder.CreateBr(postBB); ctx.builder.SetInsertPoint(currBB); - Value *wasunknown = ctx.builder.CreateICmpEQ(v.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)); + Value *wasunknown = ctx.builder.CreateICmpEQ(v.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER)); ctx.builder.CreateCondBr(wasunknown, union_isaBB, postBB); ctx.builder.SetInsertPoint(postBB); PHINode *tindex_phi = ctx.builder.CreatePHI(getInt8Ty(ctx.builder.getContext()), 2); @@ -2264,14 +2557,14 @@ static jl_cgval_t convert_julia_type_union(jl_codectx_t &ctx, const jl_cgval_t & } if (!skip_box.all()) { // some values weren't unboxed in the new union - // box them now (tindex above already selected 0x80 = box for them) + // box them now (tindex above already selected UNION_BOX_MARKER = box for them) Value *boxv = box_union(ctx, v, skip_box); if (v.Vboxed) { // If the value is boxed both before and after, we don't need // to touch it at all. Otherwise we're either transitioning // unboxed->boxed, or leaving an unboxed value in place. Value *isboxed = ctx.builder.CreateICmpNE( - ctx.builder.CreateAnd(new_tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)), + ctx.builder.CreateAnd(new_tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER)), ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0)); boxv = ctx.builder.CreateSelect( ctx.builder.CreateAnd(wasboxed, isboxed), v.Vboxed, boxv); @@ -2305,7 +2598,7 @@ static jl_cgval_t convert_julia_type_union(jl_codectx_t &ctx, const jl_cgval_t & } // given a value marked with type `v.typ`, compute the mapping and/or boxing to return a value of type `typ` -// TODO: should this set TIndex when trivial (such as 0x80 or concrete types) ? +// TODO: should this set TIndex when trivial (such as UNION_BOX_MARKER or concrete types) ? static jl_cgval_t convert_julia_type(jl_codectx_t &ctx, const jl_cgval_t &v, jl_value_t *typ, Value **skip) { if (typ == (jl_value_t*)jl_typeofbottom_type) @@ -2317,29 +2610,28 @@ static jl_cgval_t convert_julia_type(jl_codectx_t &ctx, const jl_cgval_t &v, jl_ return ghostValue(ctx, typ); Value *new_tindex = NULL; if (jl_is_concrete_type(typ)) { - if (v.TIndex && !jl_is_pointerfree(typ)) { - // discovered that this union-split type must actually be isboxed - if (v.Vboxed) { - return jl_cgval_t(v.Vboxed, true, typ, NULL, best_tbaa(ctx.tbaa(), typ)); - } - else { - // type mismatch: there weren't any boxed values in the union - if (skip) - *skip = ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1); - else - CreateTrap(ctx.builder); - return jl_cgval_t(); - } - } if (jl_is_concrete_type(v.typ)) { - if (jl_is_concrete_type(typ)) { - // type mismatch: changing from one leaftype to another - if (skip) - *skip = ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1); - else - CreateTrap(ctx.builder); - return jl_cgval_t(); + // type mismatch: changing from one leaftype to another + if (skip) + *skip = ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1); + else + CreateTrap(ctx.builder); + return jl_cgval_t(); + } + bool mustbox_union = v.TIndex && !jl_is_pointerfree(typ); + if (v.Vboxed && (v.isboxed || mustbox_union)) { + if (skip) { + *skip = ctx.builder.CreateNot(emit_exactly_isa(ctx, v, (jl_datatype_t*)typ, true)); } + return jl_cgval_t(v.Vboxed, true, typ, NULL, best_tbaa(ctx.tbaa(), typ)); + } + if (mustbox_union) { + // type mismatch: there weren't any boxed values in the union + if (skip) + *skip = ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1); + else + CreateTrap(ctx.builder); + return jl_cgval_t(); } } else { @@ -2436,11 +2728,7 @@ static void jl_init_function(Function *F, const Triple &TT) attr.addStackAlignmentAttr(16); } if (TT.isOSWindows() && TT.getArch() == Triple::x86_64) { -#if JL_LLVM_VERSION < 150000 - attr.addAttribute(Attribute::UWTable); // force NeedsWinEH -#else attr.addUWTableAttr(llvm::UWTableKind::Default); // force NeedsWinEH -#endif } if (jl_fpo_disabled(TT)) attr.addAttribute("frame-pointer", "all"); @@ -2580,7 +2868,7 @@ static void cg_bdw(jl_codectx_t &ctx, jl_sym_t *var, jl_binding_t *b) } } -static jl_value_t *static_apply_type(jl_codectx_t &ctx, const jl_cgval_t *args, size_t nargs) +static jl_value_t *static_apply_type(jl_codectx_t &ctx, ArrayRef args, size_t nargs) { assert(nargs > 1); SmallVector v(nargs); @@ -2746,7 +3034,7 @@ static bool local_var_occurs(jl_value_t *e, int sl) static std::set assigned_in_try(jl_array_t *stmts, int s, long l) { std::set av; - for(int i=s; i <= l; i++) { + for(int i=s; i < l; i++) { jl_value_t *st = jl_array_ptr_ref(stmts,i); if (jl_is_expr(st)) { if (((jl_expr_t*)st)->head == jl_assign_sym) { @@ -2765,18 +3053,18 @@ static void mark_volatile_vars(jl_array_t *stmts, SmallVectorImpl size_t slength = jl_array_dim0(stmts); for (int i = 0; i < (int)slength; i++) { jl_value_t *st = jl_array_ptr_ref(stmts, i); - if (jl_is_expr(st)) { - if (((jl_expr_t*)st)->head == jl_enter_sym) { - int last = jl_unbox_long(jl_exprarg(st, 0)); - std::set as = assigned_in_try(stmts, i + 1, last); - for (int j = 0; j < (int)slength; j++) { - if (j < i || j > last) { - std::set::iterator it = as.begin(); - for (; it != as.end(); it++) { - if (local_var_occurs(jl_array_ptr_ref(stmts, j), *it)) { - jl_varinfo_t &vi = slots[*it]; - vi.isVolatile = true; - } + if (jl_is_enternode(st)) { + int last = jl_enternode_catch_dest(st); + if (last == 0) + continue; + std::set as = assigned_in_try(stmts, i + 1, last - 1); + for (int j = 0; j < (int)slength; j++) { + if (j < i || j > last) { + std::set::iterator it = as.begin(); + for (; it != as.end(); it++) { + if (local_var_occurs(jl_array_ptr_ref(stmts, j), *it)) { + jl_varinfo_t &vi = slots[*it]; + vi.isVolatile = true; } } } @@ -2834,7 +3122,7 @@ static void general_use_analysis(jl_codectx_t &ctx, jl_value_t *expr, callback & } else if (jl_is_phicnode(expr)) { jl_array_t *values = (jl_array_t*)jl_fieldref_noalloc(expr, 0); - size_t i, elen = jl_array_len(values); + size_t i, elen = jl_array_nrows(values); for (i = 0; i < elen; i++) { jl_value_t *v = jl_array_ptr_ref(values, i); general_use_analysis(ctx, v, f); @@ -2842,7 +3130,7 @@ static void general_use_analysis(jl_codectx_t &ctx, jl_value_t *expr, callback & } else if (jl_is_phinode(expr)) { jl_array_t *values = (jl_array_t*)jl_fieldref_noalloc(expr, 1); - size_t i, elen = jl_array_len(values); + size_t i, elen = jl_array_nrows(values); for (i = 0; i < elen; i++) { jl_value_t *v = jl_array_ptr_ref(values, i); if (v) @@ -2888,7 +3176,7 @@ static jl_value_t *jl_ensure_rooted(jl_codectx_t &ctx, jl_value_t *val) } JL_UNLOCK(&m->writelock); } - return jl_as_global_root(val); + return jl_as_global_root(val, 1); } // --- generating function calls --- @@ -2900,45 +3188,92 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t * if (bp == NULL) return jl_cgval_t(); bp = julia_binding_pvalue(ctx, bp); + jl_value_t *ty = nullptr; if (bnd) { jl_value_t *v = jl_atomic_load_acquire(&bnd->value); // acquire value for ty - if (v != NULL) { - if (bnd->constp) - return mark_julia_const(ctx, v); - LoadInst *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*))); - setName(ctx.emission_context, v, jl_symbol_name(name)); - v->setOrdering(order); - jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_binding); - ai.decorateInst(v); - jl_value_t *ty = jl_atomic_load_relaxed(&bnd->ty); - return mark_julia_type(ctx, v, true, ty); - } + if (v != NULL && bnd->constp) + return mark_julia_const(ctx, v); + ty = jl_atomic_load_relaxed(&bnd->ty); } - // todo: use type info to avoid undef check - return emit_checked_var(ctx, bp, name, false, ctx.tbaa().tbaa_binding); + if (ty == nullptr) + ty = (jl_value_t*)jl_any_type; + return update_julia_type(ctx, emit_checked_var(ctx, bp, name, (jl_value_t*)mod, false, ctx.tbaa().tbaa_binding), ty); } -static bool emit_globalset(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *sym, const jl_cgval_t &rval_info, AtomicOrdering Order) +static jl_cgval_t emit_globalop(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *sym, jl_cgval_t rval, const jl_cgval_t &cmp, + AtomicOrdering Order, AtomicOrdering FailOrder, + bool issetglobal, bool isreplaceglobal, bool isswapglobal, bool ismodifyglobal, bool issetglobalonce, + const jl_cgval_t *modifyop) { jl_binding_t *bnd = NULL; Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true); if (bp == NULL) - return false; - Value *rval = boxed(ctx, rval_info); + return jl_cgval_t(); if (bnd && !bnd->constp) { jl_value_t *ty = jl_atomic_load_relaxed(&bnd->ty); - if (ty && jl_subtype(rval_info.typ, ty)) { // TODO: use typeassert here instead - StoreInst *v = ctx.builder.CreateAlignedStore(rval, julia_binding_pvalue(ctx, bp), Align(sizeof(void*))); - v->setOrdering(Order); - jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_binding); - ai.decorateInst(v); - emit_write_barrier(ctx, bp, rval); - return true; - } - } - ctx.builder.CreateCall(prepare_call(jlcheckassign_func), - { bp, literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym), mark_callee_rooted(ctx, rval) }); - return true; + if (ty != nullptr) { + const std::string fname = issetglobal ? "setglobal!" : isreplaceglobal ? "replaceglobal!" : isswapglobal ? "swapglobal!" : ismodifyglobal ? "modifyglobal!" : "setglobalonce!"; + if (!ismodifyglobal) { + // TODO: use typeassert in jl_check_binding_wr too + emit_typecheck(ctx, rval, ty, "typeassert"); + rval = update_julia_type(ctx, rval, ty); + if (rval.typ == jl_bottom_type) + return jl_cgval_t(); + } + bool isboxed = true; + bool maybe_null = jl_atomic_load_relaxed(&bnd->value) == NULL; + return typed_store(ctx, + julia_binding_pvalue(ctx, bp), + rval, cmp, ty, + ctx.tbaa().tbaa_binding, + nullptr, + bp, + isboxed, + Order, + FailOrder, + 0, + nullptr, + issetglobal, + isreplaceglobal, + isswapglobal, + ismodifyglobal, + issetglobalonce, + maybe_null, + modifyop, + fname, + mod, + sym); + + } + } + Value *m = literal_pointer_val(ctx, (jl_value_t*)mod); + Value *s = literal_pointer_val(ctx, (jl_value_t*)sym); + if (issetglobal) { + ctx.builder.CreateCall(prepare_call(jlcheckassign_func), + { bp, m, s, mark_callee_rooted(ctx, boxed(ctx, rval)) }); + return rval; + } + else if (isreplaceglobal) { + Value *r = ctx.builder.CreateCall(prepare_call(jlcheckreplace_func), + { bp, m, s, boxed(ctx, cmp), boxed(ctx, rval) }); + return mark_julia_type(ctx, r, true, jl_any_type); + } + else if (isswapglobal) { + Value *r = ctx.builder.CreateCall(prepare_call(jlcheckswap_func), + { bp, m, s, mark_callee_rooted(ctx, boxed(ctx, rval)) }); + return mark_julia_type(ctx, r, true, jl_any_type); + } + else if (ismodifyglobal) { + Value *r = ctx.builder.CreateCall(prepare_call(jlcheckmodify_func), + { bp, m, s, boxed(ctx, cmp), boxed(ctx, rval) }); + return mark_julia_type(ctx, r, true, jl_any_type); + } + else if (issetglobalonce) { + Value *r = ctx.builder.CreateCall(prepare_call(jlcheckassignonce_func), + { bp, m, s, mark_callee_rooted(ctx, boxed(ctx, rval)) }); + return mark_julia_type(ctx, r, true, jl_bool_type); + } + abort(); // unreachable } static Value *emit_box_compare(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgval_t &arg2, @@ -3055,25 +3390,25 @@ static Value *emit_bits_compare(jl_codectx_t &ctx, jl_cgval_t arg1, jl_cgval_t a if (at->isAggregateType()) { // Struct or Array jl_datatype_t *sty = (jl_datatype_t*)arg1.typ; size_t sz = jl_datatype_size(sty); - if (sz > 512 && !sty->layout->haspadding) { + if (sz > 512 && !sty->layout->flags.haspadding) { Value *varg1 = arg1.ispointer() ? data_pointer(ctx, arg1) : value_to_pointer(ctx, arg1).V; Value *varg2 = arg2.ispointer() ? data_pointer(ctx, arg2) : value_to_pointer(ctx, arg2).V; varg1 = emit_pointer_from_objref(ctx, varg1); varg2 = emit_pointer_from_objref(ctx, varg2); - Value *gc_uses[2]; - int nroots = 0; - if ((gc_uses[nroots] = get_gc_root_for(arg1))) - nroots++; - if ((gc_uses[nroots] = get_gc_root_for(arg2))) - nroots++; - OperandBundleDef OpBundle("jl_roots", makeArrayRef(gc_uses, nroots)); + SmallVector gc_uses; + // these roots may seem a bit overkill, but we want to make sure + // that a!=b implies (a,)!=(b,) even if a and b are unused and + // therefore could be freed and then the memory for a reused for b + gc_uses.append(get_gc_roots_for(ctx, arg1)); + gc_uses.append(get_gc_roots_for(ctx, arg2)); + OperandBundleDef OpBundle("jl_roots", gc_uses); auto answer = ctx.builder.CreateCall(prepare_call(memcmp_func), { ctx.builder.CreateBitCast(varg1, getInt8PtrTy(ctx.builder.getContext())), ctx.builder.CreateBitCast(varg2, getInt8PtrTy(ctx.builder.getContext())), ConstantInt::get(ctx.types().T_size, sz) }, - ArrayRef(&OpBundle, nroots ? 1 : 0)); + ArrayRef(&OpBundle, gc_uses.empty() ? 0 : 1)); if (arg1.tbaa || arg2.tbaa) { jl_aliasinfo_t ai; @@ -3216,37 +3551,69 @@ static Value *emit_f_is(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgva } static bool emit_f_opglobal(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, - const jl_cgval_t *argv, size_t nargs, const jl_cgval_t *modifyop) + ArrayRef argv, size_t nargs, const jl_cgval_t *modifyop) { + bool issetglobal = f == jl_builtin_setglobal; + bool isreplaceglobal = f == jl_builtin_replaceglobal; + bool isswapglobal = f == jl_builtin_swapglobal; + bool ismodifyglobal = f == jl_builtin_modifyglobal; + bool issetglobalonce = f == jl_builtin_setglobalonce; + const jl_cgval_t undefval; const jl_cgval_t &mod = argv[1]; const jl_cgval_t &sym = argv[2]; - const jl_cgval_t &val = argv[3]; - enum jl_memory_order order = jl_memory_order_unspecified; - assert(f == jl_builtin_setglobal && modifyop == nullptr && "unimplemented"); - - if (nargs == 4) { - const jl_cgval_t &arg4 = argv[4]; - if (arg4.constant && jl_is_symbol(arg4.constant)) - order = jl_get_atomic_order((jl_sym_t*)arg4.constant, false, true); - else + jl_cgval_t val = argv[isreplaceglobal || ismodifyglobal ? 4 : 3]; + const jl_cgval_t &cmp = isreplaceglobal || ismodifyglobal ? argv[3] : undefval; + enum jl_memory_order order = jl_memory_order_release; + const std::string fname = issetglobal ? "setglobal!" : isreplaceglobal ? "replaceglobal!" : isswapglobal ? "swapglobal!" : ismodifyglobal ? "modifyglobal!" : "setglobalonce!"; + if (nargs >= (isreplaceglobal || ismodifyglobal ? 5 : 4)) { + const jl_cgval_t &ord = argv[isreplaceglobal || ismodifyglobal ? 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, !issetglobal, true); + } + enum jl_memory_order fail_order = order; + if ((isreplaceglobal || issetglobalonce) && nargs == (isreplaceglobal ? 6 : 5)) { + const jl_cgval_t &ord = argv[isreplaceglobal ? 6 : 5]; + 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; } - else - order = jl_memory_order_release; - if (order == jl_memory_order_invalid || order == jl_memory_order_notatomic) { - emit_atomic_error(ctx, order == jl_memory_order_invalid ? "invalid atomic ordering" : "setglobal!: module binding cannot be written non-atomically"); + if (order == jl_memory_order_notatomic) { + emit_atomic_error(ctx, + issetglobal ? "setglobal!: module binding cannot be written non-atomically" : + isreplaceglobal ? "replaceglobal!: module binding cannot be written non-atomically" : + isswapglobal ? "swapglobal!: module binding cannot be written non-atomically" : + ismodifyglobal ? "modifyglobal!: module binding cannot be written non-atomically" : + "setglobalonce!: module binding cannot be written non-atomically"); + *ret = jl_cgval_t(); // unreachable + return true; + } + else if (fail_order == jl_memory_order_notatomic) { + emit_atomic_error(ctx, + isreplaceglobal ? "replaceglobal!: module binding cannot be accessed non-atomically" : + "setglobalonce!: module binding cannot be accessed non-atomically"); *ret = jl_cgval_t(); // unreachable return true; } if (sym.constant && jl_is_symbol(sym.constant)) { - jl_sym_t *name = (jl_sym_t*)sym.constant; if (mod.constant && jl_is_module(mod.constant)) { - if (emit_globalset(ctx, (jl_module_t*)mod.constant, name, val, get_llvm_atomic_order(order))) - *ret = val; - else - *ret = jl_cgval_t(); // unreachable + *ret = emit_globalop(ctx, (jl_module_t*)mod.constant, (jl_sym_t*)sym.constant, val, cmp, + get_llvm_atomic_order(order), get_llvm_atomic_order(fail_order), + issetglobal, + isreplaceglobal, + isswapglobal, + ismodifyglobal, + issetglobalonce, + modifyop); return true; } } @@ -3255,20 +3622,21 @@ static bool emit_f_opglobal(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } 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) + ArrayRef argv, size_t nargs, const jl_cgval_t *modifyop) { ++EmittedOpfields; bool issetfield = f == jl_builtin_setfield; bool isreplacefield = f == jl_builtin_replacefield; bool isswapfield = f == jl_builtin_swapfield; bool ismodifyfield = f == jl_builtin_modifyfield; + bool issetfieldonce = f == jl_builtin_setfieldonce; 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!"; + const std::string fname = issetfield ? "setfield!" : isreplacefield ? "replacefield!" : isswapfield ? "swapfield!" : ismodifyfield ? "modifyfield!" : "setfieldonce!"; 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); @@ -3277,8 +3645,8 @@ static bool emit_f_opfield(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, 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]; + if ((isreplacefield || issetfieldonce) && nargs == (isreplacefield ? 6 : 5)) { + const jl_cgval_t &ord = argv[isreplacefield ? 6 : 5]; emit_typecheck(ctx, ord, (jl_value_t*)jl_symbol_type, fname); if (!ord.constant) return false; @@ -3326,13 +3694,19 @@ static bool emit_f_opfield(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, isswapfield ? (isatomic ? "swapfield!: atomic field cannot be written non-atomically" : "swapfield!: non-atomic field cannot be written atomically") : + ismodifyfield ? (isatomic ? "modifyfield!: atomic field cannot be written non-atomically" - : "modifyfield!: non-atomic field cannot be written atomically")); + : "modifyfield!: non-atomic field cannot be written atomically") : + (isatomic ? "setfieldonce!: atomic field cannot be written non-atomically" + : "setfieldonce!: non-atomic field cannot be written atomically")); } else if (isatomic == (fail_order == jl_memory_order_notatomic)) { emit_atomic_error(ctx, + isreplacefield ? (isatomic ? "replacefield!: atomic field cannot be accessed non-atomically" - : "replacefield!: non-atomic field cannot be accessed atomically")); + : "replacefield!: non-atomic field cannot be accessed atomically") : + (isatomic ? "setfieldonce!: atomic field cannot be accessed non-atomically" + : "setfieldonce!: non-atomic field cannot be accessed atomically")); } else if (!uty->name->mutabl) { std::string msg = fname + ": immutable struct of type " @@ -3342,13 +3716,14 @@ static bool emit_f_opfield(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } else if (jl_field_isconst(uty, idx)) { std::string msg = fname + ": const field ." - + std::string(jl_symbol_name((jl_sym_t*)jl_svec_ref(jl_field_names(uty), idx))) + + std::string(jl_symbol_name((jl_sym_t*)jl_svecref(jl_field_names(uty), idx))) + " of type " + std::string(jl_symbol_name(uty->name->name)) + " cannot be changed"; emit_error(ctx, msg); } else { + assert(obj.isboxed); *ret = emit_setfield(ctx, uty, obj, idx, val, cmp, true, (needlock || order <= jl_memory_order_notatomic) ? AtomicOrdering::NotAtomic @@ -3356,7 +3731,8 @@ static bool emit_f_opfield(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, (needlock || fail_order <= jl_memory_order_notatomic) ? AtomicOrdering::NotAtomic : get_llvm_atomic_order(fail_order), - needlock, issetfield, isreplacefield, isswapfield, ismodifyfield, + needlock ? boxed(ctx, obj) : nullptr, + issetfield, isreplacefield, isswapfield, ismodifyfield, issetfieldonce, modifyop, fname); } return true; @@ -3366,18 +3742,198 @@ static bool emit_f_opfield(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, return false; } +static bool emit_f_opmemory(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, + ArrayRef argv, size_t nargs, const jl_cgval_t *modifyop) +{ + bool issetmemory = f == jl_builtin_memoryrefset; + bool isreplacememory = f == jl_builtin_memoryrefreplace; + bool isswapmemory = f == jl_builtin_memoryrefswap; + bool ismodifymemory = f == jl_builtin_memoryrefmodify; + bool issetmemoryonce = f == jl_builtin_memoryrefsetonce; + + const jl_cgval_t undefval; + const jl_cgval_t &ref = argv[1]; + jl_cgval_t val = argv[isreplacememory || ismodifymemory ? 3 : 2]; + jl_value_t *mty_dt = jl_unwrap_unionall(ref.typ); + if (!jl_is_genericmemoryref_type(mty_dt) || !jl_is_concrete_type(mty_dt)) + return false; + + jl_value_t *kind = jl_tparam0(mty_dt); + jl_value_t *ety = jl_tparam1(mty_dt); + jl_value_t *addrspace = jl_tparam2(mty_dt); (void)addrspace; // TODO + mty_dt = jl_field_type_concrete((jl_datatype_t*)mty_dt, 1); + if (kind != (jl_value_t*)jl_not_atomic_sym && kind != (jl_value_t*)jl_atomic_sym) + return false; + + const jl_cgval_t &cmp = isreplacememory || ismodifymemory ? argv[2] : undefval; + enum jl_memory_order order = jl_memory_order_notatomic; + const std::string fname = issetmemory ? "memoryrefset!" : isreplacememory ? "memoryrefreplace!" : isswapmemory ? "memoryrefswap!" : ismodifymemory ? "memoryrefmodify!" : "memoryrefsetonce!"; + { + const jl_cgval_t &ord = argv[isreplacememory || ismodifymemory ? 4 : 3]; + 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, !issetmemory, true); + } + enum jl_memory_order fail_order = order; + if (isreplacememory || issetmemoryonce) { + const jl_cgval_t &ord = argv[isreplacememory ? 5 : 4]; + 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_value_t *boundscheck = argv[nargs].constant; + emit_typecheck(ctx, argv[nargs], (jl_value_t*)jl_bool_type, fname); + const jl_datatype_layout_t *layout = ((jl_datatype_t*)mty_dt)->layout; + bool isboxed = layout->flags.arrayelem_isboxed; + bool isunion = layout->flags.arrayelem_isunion; + bool isatomic = kind == (jl_value_t*)jl_atomic_sym; + bool needlock = isatomic && layout->size > MAX_ATOMIC_SIZE; + size_t elsz = layout->size; + size_t al = layout->alignment; + if (isatomic == (order == jl_memory_order_notatomic)) { + emit_atomic_error(ctx, + issetmemory ? + (isatomic ? "memoryrefset!: atomic memory cannot be written non-atomically" + : "memoryrefset!: non-atomic memory cannot be written atomically") : + isreplacememory ? + (isatomic ? "memoryrefreplace!: atomic memory cannot be written non-atomically" + : "memoryrefreplace!: non-atomic memory cannot be written atomically") : + isswapmemory ? + (isatomic ? "memoryrefswap!: atomic memory cannot be written non-atomically" + : "memoryrefswap!: non-atomic memory cannot be written atomically") : + ismodifymemory ? + (isatomic ? "memoryrefmodify!: atomic memory cannot be written non-atomically" + : "memoryrefmodify!: non-atomic memory cannot be written atomically") : + (isatomic ? "memoryrefsetonce!: atomic memory cannot be written non-atomically" + : "memoryrefsetonce!: non-atomic memory cannot be written atomically")); + *ret = jl_cgval_t(); + return true; + } + else if (isatomic == (fail_order == jl_memory_order_notatomic)) { + emit_atomic_error(ctx, + isreplacememory ? + (isatomic ? "memoryrefreplace!: atomic memory cannot be accessed non-atomically" + : "memoryrefreplace!: non-atomic memory cannot be accessed atomically") : + (isatomic ? "memoryrefsetonce!: atomic memory cannot be accessed non-atomically" + : "memoryrefsetonce!: non-atomic memory cannot be accessed atomically")); + *ret = jl_cgval_t(); + return true; + } + Value *mem = emit_memoryref_mem(ctx, ref, layout); + Value *mlen = emit_genericmemorylen(ctx, mem, ref.typ); + if (bounds_check_enabled(ctx, boundscheck)) { + BasicBlock *failBB, *endBB; + failBB = BasicBlock::Create(ctx.builder.getContext(), "oob"); + endBB = BasicBlock::Create(ctx.builder.getContext(), "load"); + ctx.builder.CreateCondBr(ctx.builder.CreateIsNull(mlen), failBB, endBB); + failBB->insertInto(ctx.f); + ctx.builder.SetInsertPoint(failBB); + ctx.builder.CreateCall(prepare_call(jlboundserror_func), { mark_callee_rooted(ctx, mem), ConstantInt::get(ctx.types().T_size, 1) }); + ctx.builder.CreateUnreachable(); + endBB->insertInto(ctx.f); + ctx.builder.SetInsertPoint(endBB); + } + if (!ismodifymemory) { + emit_typecheck(ctx, val, ety, fname); + val = update_julia_type(ctx, val, ety); + if (val.typ == jl_bottom_type) + return true; + } + AtomicOrdering Order = (needlock || order <= jl_memory_order_notatomic) + ? AtomicOrdering::NotAtomic + : get_llvm_atomic_order(order); + AtomicOrdering FailOrder = (needlock || fail_order <= jl_memory_order_notatomic) + ? AtomicOrdering::NotAtomic + : get_llvm_atomic_order(fail_order); + if (isunion) { + assert(!isatomic && !needlock); + Value *V = emit_memoryref_FCA(ctx, ref, layout); + Value *idx0 = CreateSimplifiedExtractValue(ctx, V, 0); + Value *mem = CreateSimplifiedExtractValue(ctx, V, 1); + Value *data = emit_genericmemoryptr(ctx, mem, layout, AddressSpace::Loaded); + Type *AT = ArrayType::get(IntegerType::get(ctx.builder.getContext(), 8 * al), (elsz + al - 1) / al); + data = emit_bitcast(ctx, data, AT->getPointerTo()); + // compute tindex from val + Value *ptindex; + if (elsz == 0) { + ptindex = data; + } + else { + data = emit_bitcast(ctx, data, AT->getPointerTo()); + // isbits union selector bytes are stored after mem->length + ptindex = ctx.builder.CreateInBoundsGEP(AT, data, mlen); + data = ctx.builder.CreateInBoundsGEP(AT, data, idx0); + } + ptindex = emit_bitcast(ctx, ptindex, getInt8PtrTy(ctx.builder.getContext())); + ptindex = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), ptindex, idx0); + *ret = union_store(ctx, data, ptindex, val, cmp, ety, + ctx.tbaa().tbaa_arraybuf, nullptr, ctx.tbaa().tbaa_arrayselbyte, + Order, FailOrder, + nullptr, issetmemory, isreplacememory, isswapmemory, ismodifymemory, issetmemoryonce, + modifyop, fname); + } + else { + Value *ptr = (layout->size == 0 ? nullptr : emit_memoryref_ptr(ctx, ref, layout)); + Value *lock = nullptr; + bool maybenull = true; + if (needlock) { + assert(ptr); + lock = ptr; + // ptr += sizeof(lock); + ptr = emit_bitcast(ctx, ptr, getInt8PtrTy(ctx.builder.getContext())); + ptr = ctx.builder.CreateConstInBoundsGEP1_32(getInt8Ty(ctx.builder.getContext()), ptr, LLT_ALIGN(sizeof(jl_mutex_t), JL_SMALL_BYTE_ALIGNMENT)); + } + Value *data_owner = NULL; // owner object against which the write barrier must check + if (isboxed || layout->first_ptr >= 0) { // if elements are just bits, don't need a write barrier + Value *V = emit_memoryref_FCA(ctx, ref, layout); + data_owner = emit_genericmemoryowner(ctx, CreateSimplifiedExtractValue(ctx, V, 1)); + } + *ret = typed_store(ctx, + ptr, + val, cmp, ety, + isboxed ? ctx.tbaa().tbaa_ptrarraybuf : ctx.tbaa().tbaa_arraybuf, + ctx.noalias().aliasscope.current, + data_owner, + isboxed, + Order, + FailOrder, + al, + lock, + issetmemory, + isreplacememory, + isswapmemory, + ismodifymemory, + issetmemoryonce, + maybenull, + modifyop, + fname, + nullptr, + nullptr); + } + return true; +} + static jl_llvm_functions_t emit_function( orc::ThreadSafeModule &TSM, jl_method_instance_t *lam, jl_code_info_t *src, jl_value_t *jlrettype, - jl_codegen_params_t ¶ms); + jl_codegen_params_t ¶ms, + size_t min_world, size_t max_world); static void emit_hasnofield_error_ifnot(jl_codectx_t &ctx, Value *ok, jl_sym_t *type, jl_cgval_t name); static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, - const jl_cgval_t *argv, size_t nargs, jl_value_t *rt, + ArrayRef argv, size_t nargs, jl_value_t *rt, jl_expr_t *ex, bool is_promotable) // returns true if the call has been handled { @@ -3385,7 +3941,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, if (f == jl_builtin_is && nargs == 2) { // emit comparison test Value *ans = emit_f_is(ctx, argv[1], argv[2]); - *ret = mark_julia_type(ctx, ctx.builder.CreateZExt(ans, getInt8Ty(ctx.builder.getContext())), false, jl_bool_type); + *ret = mark_julia_type(ctx, ans, false, jl_bool_type); return true; } @@ -3424,8 +3980,6 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, if (jl_is_type_type(ty.typ) && !jl_has_free_typevars(ty.typ)) { jl_value_t *tp0 = jl_tparam0(ty.typ); Value *isa_result = emit_isa(ctx, arg, tp0, Twine()).first; - if (isa_result->getType() == getInt1Ty(ctx.builder.getContext())) - isa_result = ctx.builder.CreateZExt(isa_result, getInt8Ty(ctx.builder.getContext())); *ret = mark_julia_type(ctx, isa_result, false, jl_bool_type); return true; } @@ -3465,7 +4019,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, return true; } if (jl_is_tuple_type(rt) && jl_is_concrete_type(rt) && nargs == jl_datatype_nfields(rt)) { - *ret = emit_new_struct(ctx, rt, nargs, &argv[1], is_promotable); + *ret = emit_new_struct(ctx, rt, nargs, argv.drop_front(), is_promotable); return true; } } @@ -3477,272 +4031,270 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, return true; } - else if (f == jl_builtin_arraysize && nargs == 2) { - const jl_cgval_t &ary = argv[1]; - const jl_cgval_t &idx = argv[2]; - jl_value_t *aty = jl_unwrap_unionall(ary.typ); - if (jl_is_array_type(aty) && idx.typ == (jl_value_t*)jl_long_type) { - jl_value_t *ndp = jl_tparam1(aty); - if (jl_is_long(ndp)) { - size_t ndims = jl_unbox_long(ndp); - if (idx.constant) { - uint32_t idx_const = (uint32_t)jl_unbox_long(idx.constant); - if (idx_const > 0 && idx_const <= ndims) { - jl_value_t *ary_ex = jl_exprarg(ex, 1); - *ret = mark_julia_type(ctx, emit_arraysize(ctx, ary, ary_ex, idx_const), false, jl_long_type); - return true; - } - else if (idx_const > ndims) { - *ret = mark_julia_type(ctx, ConstantInt::get(ctx.types().T_size, 1), false, jl_long_type); - return true; - } - } - else { - Value *idx_dyn = emit_unbox(ctx, ctx.types().T_size, idx, (jl_value_t*)jl_long_type); - auto positive = ctx.builder.CreateICmpSGT(idx_dyn, Constant::getNullValue(ctx.types().T_size)); - setName(ctx.emission_context, positive, "ispositive"); - error_unless(ctx, positive, "arraysize: dimension out of range"); - BasicBlock *outBB = BasicBlock::Create(ctx.builder.getContext(), "outofrange", ctx.f); - BasicBlock *inBB = BasicBlock::Create(ctx.builder.getContext(), "inrange"); - BasicBlock *ansBB = BasicBlock::Create(ctx.builder.getContext(), "arraysize"); - auto oor = ctx.builder.CreateICmpSLE(idx_dyn, - ConstantInt::get(ctx.types().T_size, ndims)); - setName(ctx.emission_context, oor, "sizeddim"); - ctx.builder.CreateCondBr(oor, inBB, outBB); - ctx.builder.SetInsertPoint(outBB); - Value *v_one = ConstantInt::get(ctx.types().T_size, 1); - ctx.builder.CreateBr(ansBB); - ctx.f->getBasicBlockList().push_back(inBB); - ctx.builder.SetInsertPoint(inBB); - Value *v_sz = emit_arraysize(ctx, ary, idx_dyn); - ctx.builder.CreateBr(ansBB); - inBB = ctx.builder.GetInsertBlock(); // could have changed - ctx.f->getBasicBlockList().push_back(ansBB); - ctx.builder.SetInsertPoint(ansBB); - PHINode *result = ctx.builder.CreatePHI(ctx.types().T_size, 2); - result->addIncoming(v_one, outBB); - result->addIncoming(v_sz, inBB); - setName(ctx.emission_context, result, "arraysize"); - *ret = mark_julia_type(ctx, result, false, jl_long_type); - return true; - } - } + else if (f == jl_builtin_memoryref && nargs == 1) { + const jl_cgval_t &mem = argv[1]; + jl_datatype_t *mty_dt = (jl_datatype_t*)jl_unwrap_unionall(mem.typ); + if (jl_is_genericmemory_type(mty_dt) && jl_is_concrete_type((jl_value_t*)mty_dt)) { + jl_value_t *typ = jl_apply_type((jl_value_t*)jl_genericmemoryref_type, jl_svec_data(mty_dt->parameters), jl_svec_len(mty_dt->parameters)); + const jl_datatype_layout_t *layout = mty_dt->layout; + *ret = _emit_memoryref(ctx, mem, layout, typ); + return true; } } - else if ((f == jl_builtin_arrayref || f == jl_builtin_const_arrayref) && nargs >= 3) { - const jl_cgval_t &ary = argv[2]; - bool indices_ok = true; - for (size_t i = 3; i <= nargs; i++) { - if (argv[i].typ != (jl_value_t*)jl_long_type) { - indices_ok = false; - break; - } + else if (f == jl_builtin_memoryref && (nargs == 2 || nargs == 3)) { + const jl_cgval_t &ref = argv[1]; + jl_value_t *mty_dt = jl_unwrap_unionall(ref.typ); + if (jl_is_genericmemoryref_type(mty_dt) && jl_is_concrete_type(mty_dt)) { + mty_dt = jl_field_type_concrete((jl_datatype_t*)mty_dt, 1); + const jl_datatype_layout_t *layout = ((jl_datatype_t*)mty_dt)->layout; + jl_value_t *boundscheck = nargs == 3 ? argv[3].constant : nullptr; + if (nargs == 3) + emit_typecheck(ctx, argv[3], (jl_value_t*)jl_bool_type, "memoryref"); + *ret = emit_memoryref(ctx, ref, argv[2], boundscheck, layout); + return true; } - jl_value_t *aty_dt = jl_unwrap_unionall(ary.typ); - if (jl_is_array_type(aty_dt) && indices_ok) { - jl_value_t *ety = jl_tparam0(aty_dt); - jl_value_t *ndp = jl_tparam1(aty_dt); - if (!jl_has_free_typevars(ety) && (jl_is_long(ndp) || nargs == 3)) { - jl_value_t *ary_ex = jl_exprarg(ex, 2); - size_t elsz = 0, al = 0; - int union_max = jl_islayout_inline(ety, &elsz, &al); - bool isboxed = (union_max == 0); - if (isboxed) - ety = (jl_value_t*)jl_any_type; - ssize_t nd = jl_is_long(ndp) ? jl_unbox_long(ndp) : -1; - jl_value_t *boundscheck = argv[1].constant; - emit_typecheck(ctx, argv[1], (jl_value_t*)jl_bool_type, "arrayref"); - Value *idx = emit_array_nd_index(ctx, ary, ary_ex, nd, &argv[3], nargs - 2, boundscheck); - auto get_arrayname = [&]() { - return ary.V ? ary.V->getName() : StringRef(""); - }; - if (!isboxed && jl_is_datatype(ety) && jl_datatype_size(ety) == 0) { - assert(((jl_datatype_t*)ety)->instance != NULL); - *ret = ghostValue(ctx, ety); - } - else if (!isboxed && jl_is_uniontype(ety)) { - Value *data = emit_arrayptr(ctx, ary, ary_ex); - Value *offset = emit_arrayoffset(ctx, ary, nd); - Value *ptindex; - if (elsz == 0) { - ptindex = data; - } - else { - Type *AT = ArrayType::get(IntegerType::get(ctx.builder.getContext(), 8 * al), (elsz + al - 1) / al); - data = emit_bitcast(ctx, data, AT->getPointerTo()); - // isbits union selector bytes are stored after a->maxsize - Value *ndims = (nd == -1 ? emit_arrayndims(ctx, ary) : ConstantInt::get(getInt16Ty(ctx.builder.getContext()), nd)); - Value *is_vector = ctx.builder.CreateICmpEQ(ndims, ConstantInt::get(getInt16Ty(ctx.builder.getContext()), 1)); - setName(ctx.emission_context, is_vector, get_arrayname() + ".isvec"); - Value *selidx_v = ctx.builder.CreateSub(emit_vectormaxsize(ctx, ary), ctx.builder.CreateZExt(offset, ctx.types().T_size)); - setName(ctx.emission_context, selidx_v, get_arrayname() + ".vec_selidx"); - Value *selidx_m = emit_arraylen(ctx, ary); - Value *selidx = ctx.builder.CreateSelect(is_vector, selidx_v, selidx_m); - setName(ctx.emission_context, selidx, get_arrayname() + ".selidx"); - ptindex = ctx.builder.CreateInBoundsGEP(AT, data, selidx); - data = ctx.builder.CreateInBoundsGEP(AT, data, idx); - setName(ctx.emission_context, data, get_arrayname() + ".data_ptr"); - } - ptindex = emit_bitcast(ctx, ptindex, getInt8PtrTy(ctx.builder.getContext())); - ptindex = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), ptindex, offset); - ptindex = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), ptindex, idx); - setName(ctx.emission_context, ptindex, get_arrayname() + ".tindex_ptr"); - *ret = emit_unionload(ctx, data, ptindex, ety, elsz, al, ctx.tbaa().tbaa_arraybuf, true, union_max, ctx.tbaa().tbaa_arrayselbyte); - if (ret->V) - setName(ctx.emission_context, ret->V, get_arrayname() + ".ref"); + } + + else if (f == jl_builtin_memoryrefoffset && nargs == 1) { + const jl_cgval_t &ref = argv[1]; + jl_value_t *mty_dt = jl_unwrap_unionall(ref.typ); + if (jl_is_genericmemoryref_type(mty_dt) && jl_is_concrete_type(mty_dt)) { + mty_dt = jl_field_type_concrete((jl_datatype_t*)mty_dt, 1); + const jl_datatype_layout_t *layout = ((jl_datatype_t*)mty_dt)->layout; + *ret = emit_memoryref_offset(ctx, ref, layout); + return true; + } + } + + else if (f == jl_builtin_memoryrefget && nargs == 3) { + const jl_cgval_t &ref = argv[1]; + jl_value_t *mty_dt = jl_unwrap_unionall(ref.typ); + if (jl_is_genericmemoryref_type(mty_dt) && jl_is_concrete_type(mty_dt)) { + jl_value_t *kind = jl_tparam0(mty_dt); + jl_value_t *ety = jl_tparam1(mty_dt); + jl_value_t *addrspace = jl_tparam2(mty_dt); (void)addrspace; // TODO + mty_dt = jl_field_type_concrete((jl_datatype_t*)mty_dt, 1); + if (kind != (jl_value_t*)jl_not_atomic_sym && kind != (jl_value_t*)jl_atomic_sym) + return false; + enum jl_memory_order order = jl_memory_order_unspecified; + const std::string fname = "memoryrefget"; + { + const jl_cgval_t &ord = argv[2]; + 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, true, false); + } + if (order == jl_memory_order_invalid) { + emit_atomic_error(ctx, "invalid atomic ordering"); + *ret = jl_cgval_t(); // unreachable + return true; + } + bool isatomic = kind == (jl_value_t*)jl_atomic_sym; + if (!isatomic && order != jl_memory_order_notatomic && order != jl_memory_order_unspecified) { + emit_atomic_error(ctx, "memoryrefget: non-atomic memory cannot be accessed atomically"); + *ret = jl_cgval_t(); // unreachable + return true; + } + if (isatomic && order == jl_memory_order_notatomic) { + emit_atomic_error(ctx, "memoryrefget: atomic memory cannot be accessed non-atomically"); + *ret = jl_cgval_t(); // unreachable + return true; + } + if (order == jl_memory_order_unspecified) { + order = isatomic ? jl_memory_order_unordered : jl_memory_order_notatomic; + } + jl_value_t *boundscheck = argv[3].constant; + emit_typecheck(ctx, argv[3], (jl_value_t*)jl_bool_type, "memoryref"); + const jl_datatype_layout_t *layout = ((jl_datatype_t*)mty_dt)->layout; + Value *mem = emit_memoryref_mem(ctx, ref, layout); + Value *mlen = emit_genericmemorylen(ctx, mem, ref.typ); + if (bounds_check_enabled(ctx, boundscheck)) { + BasicBlock *failBB, *endBB; + failBB = BasicBlock::Create(ctx.builder.getContext(), "oob"); + endBB = BasicBlock::Create(ctx.builder.getContext(), "load"); + ctx.builder.CreateCondBr(ctx.builder.CreateIsNull(mlen), failBB, endBB); + failBB->insertInto(ctx.f); + ctx.builder.SetInsertPoint(failBB); + ctx.builder.CreateCall(prepare_call(jlboundserror_func), { mark_callee_rooted(ctx, mem), ConstantInt::get(ctx.types().T_size, 1) }); + ctx.builder.CreateUnreachable(); + endBB->insertInto(ctx.f); + ctx.builder.SetInsertPoint(endBB); + } + bool isboxed = layout->flags.arrayelem_isboxed; + bool isunion = layout->flags.arrayelem_isunion; + size_t elsz = layout->size; + size_t al = layout->alignment; + bool needlock = isatomic && !isboxed && elsz > MAX_ATOMIC_SIZE; + AtomicOrdering Order = (needlock || order <= jl_memory_order_notatomic) + ? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) + : get_llvm_atomic_order(order); + bool maybenull = true; + if (!isboxed && !isunion && elsz == 0) { + assert(jl_is_datatype(ety) && jl_is_datatype_singleton((jl_datatype_t*)ety)); + *ret = ghostValue(ctx, ety); + if (isStrongerThanMonotonic(Order)) + ctx.builder.CreateFence(Order); + } + else if (isunion) { + assert(!isatomic && !needlock); + Value *V = emit_memoryref_FCA(ctx, ref, layout); + Value *idx0 = CreateSimplifiedExtractValue(ctx, V, 0); + Value *mem = CreateSimplifiedExtractValue(ctx, V, 1); + Value *data = emit_genericmemoryptr(ctx, mem, layout, AddressSpace::Loaded); + Value *ptindex; + if (elsz == 0) { + ptindex = data; } else { - MDNode *aliasscope = (f == jl_builtin_const_arrayref) ? ctx.noalias().aliasscope.current : nullptr; - *ret = typed_load(ctx, - emit_arrayptr(ctx, ary, ary_ex), - idx, ety, - isboxed ? ctx.tbaa().tbaa_ptrarraybuf : ctx.tbaa().tbaa_arraybuf, - aliasscope, - isboxed, - AtomicOrdering::NotAtomic); - if (ret->V) - setName(ctx.emission_context, ret->V, get_arrayname() + ".ref"); + Type *AT = ArrayType::get(IntegerType::get(ctx.builder.getContext(), 8 * al), (elsz + al - 1) / al); + data = emit_bitcast(ctx, data, AT->getPointerTo()); + // isbits union selector bytes are stored after mem->length bytes + ptindex = ctx.builder.CreateInBoundsGEP(AT, data, mlen); + data = ctx.builder.CreateInBoundsGEP(AT, data, idx0); } - return true; + ptindex = emit_bitcast(ctx, ptindex, getInt8PtrTy(ctx.builder.getContext())); + ptindex = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), ptindex, idx0); + size_t elsz_c = 0, al_c = 0; + int union_max = jl_islayout_inline(ety, &elsz_c, &al_c); + assert(union_max && LLT_ALIGN(elsz_c, al_c) == elsz && al_c == al); + *ret = emit_unionload(ctx, data, ptindex, ety, elsz_c, al, ctx.tbaa().tbaa_arraybuf, true, union_max, ctx.tbaa().tbaa_arrayselbyte); } + else { + Value *ptr = (layout->size == 0 ? nullptr : emit_memoryref_ptr(ctx, ref, layout)); + Value *lock = nullptr; + if (needlock) { + assert(ptr); + lock = ptr; + // ptr += sizeof(lock); + ptr = emit_bitcast(ctx, ptr, getInt8PtrTy(ctx.builder.getContext())); + ptr = ctx.builder.CreateConstInBoundsGEP1_32(getInt8Ty(ctx.builder.getContext()), ptr, LLT_ALIGN(sizeof(jl_mutex_t), JL_SMALL_BYTE_ALIGNMENT)); + emit_lockstate_value(ctx, lock, true); + } + *ret = typed_load(ctx, ptr, nullptr, ety, + isboxed ? ctx.tbaa().tbaa_ptrarraybuf : ctx.tbaa().tbaa_arraybuf, + ctx.noalias().aliasscope.current, + isboxed, Order, maybenull, al); + if (needlock) { + emit_lockstate_value(ctx, lock, false); + } + } + return true; } } - else if (f == jl_builtin_arrayset && nargs >= 4) { - const jl_cgval_t &ary = argv[2]; - jl_cgval_t val = argv[3]; - bool indices_ok = true; - for (size_t i = 4; i <= nargs; i++) { - if (argv[i].typ != (jl_value_t*)jl_long_type) { - indices_ok = false; - break; + else if ((f == jl_builtin_memoryrefset && nargs == 4) || + (f == jl_builtin_memoryrefswap && nargs == 4) || + (f == jl_builtin_memoryrefreplace && nargs == 6) || + (f == jl_builtin_memoryrefmodify && nargs == 5) || + (f == jl_builtin_memoryrefsetonce && nargs == 5)) { + return emit_f_opmemory(ctx, ret, f, argv, nargs, nullptr); + } + + + else if (f == jl_builtin_memoryref_isassigned && nargs == 3) { + const jl_cgval_t &ref = argv[1]; + jl_value_t *mty_dt = jl_unwrap_unionall(ref.typ); + if (jl_is_genericmemoryref_type(mty_dt) && jl_is_concrete_type(mty_dt)) { + jl_value_t *kind = jl_tparam0(mty_dt); + mty_dt = jl_field_type_concrete((jl_datatype_t*)mty_dt, 1); + if (kind != (jl_value_t*)jl_not_atomic_sym && kind != (jl_value_t*)jl_atomic_sym) + return false; + enum jl_memory_order order = jl_memory_order_unspecified; + const std::string fname = "memoryref_isassigned"; + { + const jl_cgval_t &ord = argv[2]; + 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, true, false); } - } - jl_value_t *aty_dt = jl_unwrap_unionall(ary.typ); - if (jl_is_array_type(aty_dt) && indices_ok) { - jl_value_t *ety = jl_tparam0(aty_dt); - jl_value_t *ndp = jl_tparam1(aty_dt); - if (!jl_has_free_typevars(ety) && (jl_is_long(ndp) || nargs == 4)) { - emit_typecheck(ctx, val, ety, "arrayset"); - val = update_julia_type(ctx, val, ety); - if (val.typ == jl_bottom_type) - return true; - size_t elsz = 0, al = 0; - int union_max = jl_islayout_inline(ety, &elsz, &al); - bool isboxed = (union_max == 0); - if (isboxed) - ety = (jl_value_t*)jl_any_type; - jl_value_t *ary_ex = jl_exprarg(ex, 2); - ssize_t nd = jl_is_long(ndp) ? jl_unbox_long(ndp) : -1; - jl_value_t *boundscheck = argv[1].constant; - emit_typecheck(ctx, argv[1], (jl_value_t*)jl_bool_type, "arrayset"); - Value *idx = emit_array_nd_index(ctx, ary, ary_ex, nd, &argv[4], nargs - 3, boundscheck); - if (!isboxed && jl_is_datatype(ety) && jl_datatype_size(ety) == 0) { - // no-op + if (order == jl_memory_order_invalid) { + emit_atomic_error(ctx, "invalid atomic ordering"); + *ret = jl_cgval_t(); // unreachable + return true; + } + bool isatomic = kind == (jl_value_t*)jl_atomic_sym; + if (!isatomic && order != jl_memory_order_notatomic && order != jl_memory_order_unspecified) { + emit_atomic_error(ctx, "memoryref_isassigned: non-atomic memory cannot be accessed atomically"); + *ret = jl_cgval_t(); // unreachable + return true; + } + if (isatomic && order == jl_memory_order_notatomic) { + emit_atomic_error(ctx, "memoryref_isassigned: atomic memory cannot be accessed non-atomically"); + *ret = jl_cgval_t(); // unreachable + return true; + } + if (order == jl_memory_order_unspecified) { + order = isatomic ? jl_memory_order_unordered : jl_memory_order_notatomic; + } + jl_value_t *boundscheck = argv[3].constant; + emit_typecheck(ctx, argv[3], (jl_value_t*)jl_bool_type, fname); + const jl_datatype_layout_t *layout = ((jl_datatype_t*)mty_dt)->layout; + Value *mem = emit_memoryref_mem(ctx, ref, layout); + Value *mlen = emit_genericmemorylen(ctx, mem, ref.typ); + Value *oob = bounds_check_enabled(ctx, boundscheck) ? ctx.builder.CreateIsNull(mlen) : nullptr; + bool isboxed = layout->flags.arrayelem_isboxed; + if (isboxed || layout->first_ptr >= 0) { + bool needlock = isatomic && !isboxed && layout->size > MAX_ATOMIC_SIZE; + AtomicOrdering Order = (needlock || order <= jl_memory_order_notatomic) + ? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) + : get_llvm_atomic_order(order); + PHINode *result = nullptr; + if (oob) { + BasicBlock *passBB, *endBB, *fromBB; + passBB = BasicBlock::Create(ctx.builder.getContext(), "load"); + endBB = BasicBlock::Create(ctx.builder.getContext(), "oob"); + + passBB->insertInto(ctx.f); + endBB->insertInto(ctx.f); + fromBB = ctx.builder.CreateCondBr(oob, endBB, passBB)->getParent(); + ctx.builder.SetInsertPoint(endBB); + result = ctx.builder.CreatePHI(getInt1Ty(ctx.builder.getContext()), 2); + result->addIncoming(ConstantInt::get(result->getType(), 0), fromBB); + setName(ctx.emission_context, result, "arraysize"); + ctx.builder.SetInsertPoint(passBB); } - else { - PHINode *data_owner = NULL; // owner object against which the write barrier must check - if (isboxed || (jl_is_datatype(ety) && ((jl_datatype_t*)ety)->layout->npointers > 0)) { // if elements are just bits, don't need a write barrier - Value *aryv = boxed(ctx, ary); - Value *flags = emit_arrayflags(ctx, ary); - // the owner of the data is ary itself except if ary->how == 3 - flags = ctx.builder.CreateAnd(flags, 3); - Value *is_owned = ctx.builder.CreateICmpEQ(flags, ConstantInt::get(getInt16Ty(ctx.builder.getContext()), 3)); - setName(ctx.emission_context, is_owned, "has_owner"); - BasicBlock *curBB = ctx.builder.GetInsertBlock(); - BasicBlock *ownedBB = BasicBlock::Create(ctx.builder.getContext(), "array_owned", ctx.f); - BasicBlock *mergeBB = BasicBlock::Create(ctx.builder.getContext(), "merge_own", ctx.f); - ctx.builder.CreateCondBr(is_owned, ownedBB, mergeBB); - ctx.builder.SetInsertPoint(ownedBB); - // load owner pointer - Instruction *own_ptr; - if (jl_is_long(ndp)) { - own_ptr = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, - ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_prjlvalue, - emit_bitcast(ctx, decay_derived(ctx, aryv), ctx.types().T_pprjlvalue), - jl_array_data_owner_offset(nd) / sizeof(jl_value_t*)), - Align(sizeof(void*))); - setName(ctx.emission_context, own_ptr, "external_owner"); - jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); - ai.decorateInst(maybe_mark_load_dereferenceable(own_ptr, false, (jl_value_t*)jl_array_any_type)); - } - else { - own_ptr = ctx.builder.CreateCall( - prepare_call(jlarray_data_owner_func), - {aryv}); - } - ctx.builder.CreateBr(mergeBB); - ctx.builder.SetInsertPoint(mergeBB); - data_owner = ctx.builder.CreatePHI(ctx.types().T_prjlvalue, 2); - data_owner->addIncoming(aryv, curBB); - data_owner->addIncoming(own_ptr, ownedBB); - setName(ctx.emission_context, data_owner, "data_owner"); - } - if (!isboxed && jl_is_uniontype(ety)) { - Type *AT = ArrayType::get(IntegerType::get(ctx.builder.getContext(), 8 * al), (elsz + al - 1) / al); - Value *data = emit_bitcast(ctx, emit_arrayptr(ctx, ary, ary_ex), AT->getPointerTo()); - Value *offset = emit_arrayoffset(ctx, ary, nd); - // compute tindex from val - jl_cgval_t rhs_union = convert_julia_type(ctx, val, ety); - Value *tindex = compute_tindex_unboxed(ctx, rhs_union, ety); - tindex = ctx.builder.CreateNUWSub(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 1)); - Value *ptindex; - if (elsz == 0) { - ptindex = data; - } - else { - Value *ndims = (nd == -1 ? emit_arrayndims(ctx, ary) : ConstantInt::get(getInt16Ty(ctx.builder.getContext()), nd)); - Value *is_vector = ctx.builder.CreateICmpEQ(ndims, ConstantInt::get(getInt16Ty(ctx.builder.getContext()), 1)); - setName(ctx.emission_context, is_vector, "is_vector"); - Value *selidx_v = ctx.builder.CreateSub(emit_vectormaxsize(ctx, ary), ctx.builder.CreateZExt(offset, ctx.types().T_size)); - setName(ctx.emission_context, selidx_v, "selidx_v"); - Value *selidx_m = emit_arraylen(ctx, ary); - Value *selidx = ctx.builder.CreateSelect(is_vector, selidx_v, selidx_m); - setName(ctx.emission_context, selidx, "selidx"); - ptindex = ctx.builder.CreateInBoundsGEP(AT, data, selidx); - setName(ctx.emission_context, ptindex, "ptindex"); - data = ctx.builder.CreateInBoundsGEP(AT, data, idx); - setName(ctx.emission_context, data, "data"); - } - ptindex = emit_bitcast(ctx, ptindex, getInt8PtrTy(ctx.builder.getContext())); - ptindex = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), ptindex, offset); - ptindex = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), ptindex, idx); - setName(ctx.emission_context, ptindex, "ptindex"); - jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_arrayselbyte); - ai.decorateInst(ctx.builder.CreateStore(tindex, ptindex)); - if (elsz > 0 && (!jl_is_datatype(val.typ) || jl_datatype_size(val.typ) > 0)) { - // copy data (if any) - emit_unionmove(ctx, data, ctx.tbaa().tbaa_arraybuf, val, nullptr); - } - } - else { - typed_store(ctx, - emit_arrayptr(ctx, ary, ary_ex, isboxed), - idx, val, jl_cgval_t(), ety, - isboxed ? ctx.tbaa().tbaa_ptrarraybuf : ctx.tbaa().tbaa_arraybuf, - ctx.noalias().aliasscope.current, - data_owner, - isboxed, - isboxed ? AtomicOrdering::Release : AtomicOrdering::NotAtomic, // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0 - /*FailOrder*/AtomicOrdering::NotAtomic, // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0 - 0, - false, - true, - false, - false, - false, - false, - nullptr, - ""); - } + Value *elem = emit_memoryref_ptr(ctx, ref, layout); + if (needlock) { + // n.b. no actual lock acquire needed, as the check itself only needs to load a single pointer and check for null + // elem += sizeof(lock); + elem = emit_bitcast(ctx, elem, getInt8PtrTy(ctx.builder.getContext())); + elem = ctx.builder.CreateConstInBoundsGEP1_32(getInt8Ty(ctx.builder.getContext()), elem, LLT_ALIGN(sizeof(jl_mutex_t), JL_SMALL_BYTE_ALIGNMENT)); } - *ret = ary; - return true; + elem = emit_bitcast(ctx, elem, ctx.types().T_pprjlvalue); + if (!isboxed) + elem = ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_prjlvalue, elem, layout->first_ptr); + // emit this using the same type as jl_builtin_memoryrefget + // so that LLVM may be able to load-load forward them and fold the result + auto tbaa = isboxed ? ctx.tbaa().tbaa_ptrarraybuf : ctx.tbaa().tbaa_arraybuf; + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + LoadInst *fldv = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, elem, ctx.types().alignof_ptr); + fldv->setOrdering(Order); + ai.decorateInst(fldv); + Value *isdef = ctx.builder.CreateIsNotNull(fldv); + setName(ctx.emission_context, isdef, fname); + if (oob) { + assert(result); + result->addIncoming(isdef, ctx.builder.CreateBr(result->getParent())->getParent()); + ctx.builder.SetInsertPoint(result->getParent()); + isdef = result; + } + *ret = mark_julia_type(ctx, isdef, false, jl_bool_type); } + else if (oob) { + Value *isdef = ctx.builder.CreateNot(oob); + *ret = mark_julia_type(ctx, isdef, false, jl_bool_type); + } + else { + *ret = mark_julia_const(ctx, jl_true); + } + return true; } } + else if (f == jl_builtin_getfield && (nargs == 2 || nargs == 3 || nargs == 4)) { const jl_cgval_t &obj = argv[1]; const jl_cgval_t &fld = argv[2]; @@ -3936,14 +4488,19 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, return false; } - else if (f == jl_builtin_setglobal && (nargs == 3 || nargs == 4)) { + else if ((f == jl_builtin_setglobal && (nargs == 3 || nargs == 4)) || + (f == jl_builtin_swapglobal && (nargs == 3 || nargs == 4)) || + (f == jl_builtin_replaceglobal && (nargs == 4 || nargs == 5 || nargs == 6)) || + (f == jl_builtin_modifyglobal && (nargs == 4 || nargs == 5)) || + (f == jl_builtin_setglobalonce && (nargs == 3 || nargs == 4 || nargs == 5))) { return emit_f_opglobal(ctx, ret, f, argv, nargs, nullptr); } else if ((f == jl_builtin_setfield && (nargs == 3 || nargs == 4)) || (f == jl_builtin_swapfield && (nargs == 3 || nargs == 4)) || (f == jl_builtin_replacefield && (nargs == 4 || nargs == 5 || nargs == 6)) || - (f == jl_builtin_modifyfield && (nargs == 4 || nargs == 5))) { + (f == jl_builtin_modifyfield && (nargs == 4 || nargs == 5)) || + (f == jl_builtin_setfieldonce && (nargs == 3 || nargs == 4 || nargs == 5))) { return emit_f_opfield(ctx, ret, f, argv, nargs, nullptr); } @@ -4040,19 +4597,11 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, *ret = mark_julia_type(ctx, len, false, jl_long_type); return true; } - else if (jl_is_array_type(sty)) { - auto len = emit_arraylen(ctx, obj); - Value *elsize; - size_t elsz; - if (arraytype_constelsize(sty, &elsz)) { - elsize = ConstantInt::get(ctx.types().T_size, elsz); - } - else { - elsize = ctx.builder.CreateZExt(emit_arrayelsize(ctx, obj), ctx.types().T_size); - } + else if (jl_is_genericmemory_type(sty)) { + Value *v = boxed(ctx, obj); + auto len = emit_genericmemorylen(ctx, v, (jl_value_t*)sty); + auto elsize = emit_genericmemoryelsize(ctx, v, obj.typ, true); *ret = mark_julia_type(ctx, ctx.builder.CreateMul(len, elsize), false, jl_long_type); - if (ret->V) - setName(ctx.emission_context, ret->V, "sizeof"); return true; } } @@ -4062,7 +4611,9 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, // don't bother codegen constant-folding for toplevel. jl_value_t *ty = static_apply_type(ctx, argv, nargs + 1); if (ty != NULL) { + JL_GC_PUSH1(&ty); ty = jl_ensure_rooted(ctx, ty); + JL_GC_POP(); *ret = mark_julia_const(ctx, ty); return true; } @@ -4147,10 +4698,8 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, else if (jl_field_isptr(stt, fieldidx) || jl_type_hasptr(jl_field_type(stt, fieldidx))) { Value *fldv; size_t offs = jl_field_offset(stt, fieldidx) / sizeof(jl_value_t*); - auto tbaa = obj.tbaa; - if (tbaa == ctx.tbaa().tbaa_datatype && offs != offsetof(jl_datatype_t, types)) - tbaa = ctx.tbaa().tbaa_const; if (obj.ispointer()) { + auto tbaa = best_field_tbaa(ctx, obj, stt, fieldidx, offs); if (!jl_field_isptr(stt, fieldidx)) offs += ((jl_datatype_t*)jl_field_type(stt, fieldidx))->layout->first_ptr; Value *ptr = emit_bitcast(ctx, data_pointer(ctx, obj), ctx.types().T_pprjlvalue); @@ -4223,7 +4772,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, // Returns ctx.types().T_prjlvalue static CallInst *emit_jlcall(jl_codectx_t &ctx, FunctionCallee theFptr, Value *theF, - const jl_cgval_t *argv, size_t nargs, JuliaFunction<> *trampoline) + ArrayRef argv, size_t nargs, JuliaFunction<> *trampoline) { ++EmittedJLCalls; Function *TheTrampoline = prepare_call(trampoline); @@ -4244,13 +4793,13 @@ static CallInst *emit_jlcall(jl_codectx_t &ctx, FunctionCallee theFptr, Value *t // Returns ctx.types().T_prjlvalue static CallInst *emit_jlcall(jl_codectx_t &ctx, JuliaFunction<> *theFptr, Value *theF, - const jl_cgval_t *argv, size_t nargs, JuliaFunction<> *trampoline) + ArrayRef argv, size_t nargs, JuliaFunction<> *trampoline) { return emit_jlcall(ctx, prepare_call(theFptr), theF, argv, nargs, trampoline); } static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, bool is_opaque_closure, jl_value_t *specTypes, jl_value_t *jlretty, llvm::Value *callee, StringRef specFunctionObject, jl_code_instance_t *fromexternal, - const jl_cgval_t *argv, size_t nargs, jl_returninfo_t::CallingConv *cc, unsigned *return_roots, jl_value_t *inferred_retty) + ArrayRef argv, size_t nargs, jl_returninfo_t::CallingConv *cc, unsigned *return_roots, jl_value_t *inferred_retty) { ++EmittedSpecfunCalls; // emit specialized call site @@ -4378,7 +4927,7 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, bool is_opaque_clos Value *tindex = ctx.builder.CreateExtractValue(call, 1); Value *derived = ctx.builder.CreateSelect( ctx.builder.CreateICmpEQ( - ctx.builder.CreateAnd(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)), + ctx.builder.CreateAnd(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER)), ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0)), decay_derived(ctx, ctx.builder.CreateBitCast(argvals[0], ctx.types().T_pjlvalue)), decay_derived(ctx, box) @@ -4399,7 +4948,7 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, bool is_opaque_clos } static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, jl_method_instance_t *mi, jl_value_t *jlretty, StringRef specFunctionObject, jl_code_instance_t *fromexternal, - const jl_cgval_t *argv, size_t nargs, jl_returninfo_t::CallingConv *cc, unsigned *return_roots, jl_value_t *inferred_retty) + ArrayRef argv, size_t nargs, jl_returninfo_t::CallingConv *cc, unsigned *return_roots, jl_value_t *inferred_retty) { bool is_opaque_closure = jl_is_method(mi->def.value) && mi->def.method->is_for_opaque_closure; return emit_call_specfun_other(ctx, is_opaque_closure, mi->specTypes, jlretty, NULL, @@ -4407,7 +4956,7 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, jl_method_instance_ } static jl_cgval_t emit_call_specfun_boxed(jl_codectx_t &ctx, jl_value_t *jlretty, StringRef specFunctionObject, jl_code_instance_t *fromexternal, - const jl_cgval_t *argv, size_t nargs, jl_value_t *inferred_retty) + ArrayRef argv, size_t nargs, jl_value_t *inferred_retty) { Value *theFptr; if (fromexternal) { @@ -4436,7 +4985,7 @@ static jl_cgval_t emit_call_specfun_boxed(jl_codectx_t &ctx, jl_value_t *jlretty static jl_cgval_t emit_invoke(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt) { - jl_value_t **args = (jl_value_t**)jl_array_data(ex->args); + jl_value_t **args = jl_array_data(ex->args, jl_value_t*); size_t arglen = jl_array_dim0(ex->args); size_t nargs = arglen - 1; assert(arglen >= 2); @@ -4448,10 +4997,10 @@ 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.data(), nargs, rt); + 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) +static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, ArrayRef argv, size_t nargs, jl_value_t *rt) { ++EmittedInvokes; bool handled = false; @@ -4475,7 +5024,7 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, const } } else { - jl_value_t *ci = ctx.params->lookup(mi, ctx.world, ctx.world); // TODO: need to use the right pair world here + jl_value_t *ci = ctx.params->lookup(mi, ctx.min_world, ctx.max_world); if (ci != jl_nothing) { jl_code_instance_t *codeinst = (jl_code_instance_t*)ci; auto invoke = jl_atomic_load_acquire(&codeinst->invoke); @@ -4528,7 +5077,7 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, const } } if (need_to_emit) { - raw_string_ostream(name) << (specsig ? "j_" : "j1_") << name_from_method_instance(mi) << "_" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); + raw_string_ostream(name) << (specsig ? "j_" : "j1_") << name_from_method_instance(mi) << "_" << jl_atomic_fetch_add_relaxed(&globalUniqueGeneratedNames, 1); protoname = StringRef(name); } jl_returninfo_t::CallingConv cc = jl_returninfo_t::CallingConv::Boxed; @@ -4558,7 +5107,7 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, const static jl_cgval_t emit_invoke_modify(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt) { ++EmittedInvokes; - jl_value_t **args = (jl_value_t**)jl_array_data(ex->args); + jl_value_t **args = jl_array_data(ex->args, jl_value_t*); size_t arglen = jl_array_dim0(ex->args); size_t nargs = arglen - 1; assert(arglen >= 2); @@ -4570,27 +5119,45 @@ static jl_cgval_t emit_invoke_modify(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_ 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.data(), nargs - 1, &lival)) - return ret; - auto it = builtin_func_map().find(jl_f_modifyfield_addr); - assert(it != builtin_func_map().end()); - Value *oldnew = emit_jlcall(ctx, it->second, Constant::getNullValue(ctx.types().T_prjlvalue), &argv[1], nargs - 1, julia_call); - return mark_julia_type(ctx, oldnew, true, rt); - } - if (f.constant && jl_typetagis(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.data(), nargs - 1, &lival); + if (f.constant) { + jl_cgval_t ret; + auto it = builtin_func_map().end(); + if (f.constant == jl_builtin_modifyfield) { + if (emit_f_opfield(ctx, &ret, jl_builtin_modifyfield, argv, nargs - 1, &lival)) + return ret; + it = builtin_func_map().find(jl_f_modifyfield_addr); + assert(it != builtin_func_map().end()); + } + else if (f.constant == jl_builtin_modifyglobal) { + if (emit_f_opglobal(ctx, &ret, jl_builtin_modifyglobal, argv, nargs - 1, &lival)) + return ret; + it = builtin_func_map().find(jl_f_modifyglobal_addr); + assert(it != builtin_func_map().end()); + } + else if (f.constant == jl_builtin_memoryrefmodify) { + if (emit_f_opmemory(ctx, &ret, jl_builtin_memoryrefmodify, argv, nargs - 1, &lival)) + return ret; + it = builtin_func_map().find(jl_f_memoryrefmodify_addr); + assert(it != builtin_func_map().end()); + } + else if (jl_typetagis(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, ArrayRef(argv).drop_front(), nargs - 1, &lival); + } + + if (it != builtin_func_map().end()) { + Value *oldnew = emit_jlcall(ctx, it->second, Constant::getNullValue(ctx.types().T_prjlvalue), ArrayRef(argv).drop_front(), nargs - 1, julia_call); + return mark_julia_type(ctx, oldnew, true, rt); + } } // emit function and arguments - Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, argv.data(), nargs, julia_call); + Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, argv, nargs, julia_call); return mark_julia_type(ctx, callval, true, rt); } -static jl_cgval_t emit_specsig_oc_call(jl_codectx_t &ctx, jl_value_t *oc_type, jl_value_t *sigtype, jl_cgval_t *argv, size_t nargs) +static jl_cgval_t emit_specsig_oc_call(jl_codectx_t &ctx, jl_value_t *oc_type, jl_value_t *sigtype, MutableArrayRef argv /*n.b. this mutation is unusual */, size_t nargs) { jl_datatype_t *oc_argt = (jl_datatype_t *)jl_tparam0(oc_type); jl_value_t *oc_rett = jl_tparam1(oc_type); @@ -4622,7 +5189,7 @@ static jl_cgval_t emit_specsig_oc_call(jl_codectx_t &ctx, jl_value_t *oc_type, j static jl_cgval_t emit_call(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt, bool is_promotable) { ++EmittedCalls; - jl_value_t **args = (jl_value_t**)jl_array_data(ex->args); + jl_value_t **args = jl_array_data(ex->args, jl_value_t*); size_t nargs = jl_array_dim0(ex->args); assert(nargs >= 1); jl_cgval_t f = emit_expr(ctx, args[0]); @@ -4637,8 +5204,7 @@ static jl_cgval_t emit_call(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt, bo size_t n_generic_args = nargs; - SmallVector generic_argv(n_generic_args); - jl_cgval_t *argv = generic_argv.data(); + SmallVector argv(n_generic_args); argv[0] = f; for (size_t i = 1; i < nargs; ++i) { @@ -4647,22 +5213,40 @@ static jl_cgval_t emit_call(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt, bo return jl_cgval_t(); // anything past here is unreachable } - if (f.constant && jl_isa(f.constant, (jl_value_t*)jl_builtin_type)) { - if (f.constant == jl_builtin_ifelse && nargs == 4) - return emit_ifelse(ctx, argv[1], argv[2], argv[3], rt); - jl_cgval_t result; - bool handled = emit_builtin_call(ctx, &result, f.constant, argv, nargs - 1, rt, ex, is_promotable); - if (handled) { - return result; + if (jl_subtype(f.typ, (jl_value_t*)jl_builtin_type)) { + if (f.constant) { + if (f.constant == jl_builtin_ifelse && nargs == 4) + return emit_ifelse(ctx, argv[1], argv[2], argv[3], rt); + jl_cgval_t result; + bool handled = emit_builtin_call(ctx, &result, f.constant, argv, nargs - 1, rt, ex, is_promotable); + if (handled) + return result; + + // special case for some known builtin not handled by emit_builtin_call + auto it = builtin_func_map().find(jl_get_builtin_fptr((jl_datatype_t*)jl_typeof(f.constant))); + if (it != builtin_func_map().end()) { + Value *ret = emit_jlcall(ctx, it->second, Constant::getNullValue(ctx.types().T_prjlvalue), ArrayRef(argv).drop_front(), nargs - 1, julia_call); + setName(ctx.emission_context, ret, it->second->name + "_ret"); + return mark_julia_type(ctx, ret, true, rt); + } + } + FunctionCallee fptr; + Value *F; + JuliaFunction<> *cc; + if (f.typ == (jl_value_t*)jl_intrinsic_type) { + fptr = prepare_call(jlintrinsic_func); + F = f.ispointer() ? data_pointer(ctx, f) : value_to_pointer(ctx, f).V; + F = decay_derived(ctx, maybe_bitcast(ctx, F, ctx.types().T_pjlvalue)); + cc = julia_call3; } - - // special case for known builtin not handled by emit_builtin_call - auto it = builtin_func_map().find(jl_get_builtin_fptr(f.constant)); - if (it != builtin_func_map().end()) { - Value *ret = emit_jlcall(ctx, it->second, Constant::getNullValue(ctx.types().T_prjlvalue), &argv[1], nargs - 1, julia_call); - setName(ctx.emission_context, ret, it->second->name + "_ret"); - return mark_julia_type(ctx, ret, true, rt); + else { + fptr = FunctionCallee(get_func_sig(ctx.builder.getContext()), ctx.builder.CreateCall(prepare_call(jlgetbuiltinfptr_func), {emit_typeof(ctx, f)})); + F = boxed(ctx, f); + cc = julia_call; } + Value *ret = emit_jlcall(ctx, fptr, F, ArrayRef(argv).drop_front(), nargs - 1, cc); + setName(ctx.emission_context, ret, "Builtin_ret"); + return mark_julia_type(ctx, ret, true, rt); } // handle calling an OpaqueClosure @@ -4681,26 +5265,12 @@ static jl_cgval_t emit_call(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt, bo } // emit function and arguments - Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, generic_argv.data(), n_generic_args, julia_call); + Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, argv, n_generic_args, julia_call); return mark_julia_type(ctx, callval, true, rt); } // --- accessing and assigning variables --- -static void undef_var_error_ifnot(jl_codectx_t &ctx, Value *ok, jl_sym_t *name) -{ - ++EmittedUndefVarErrors; - BasicBlock *err = BasicBlock::Create(ctx.builder.getContext(), "err", ctx.f); - BasicBlock *ifok = BasicBlock::Create(ctx.builder.getContext(), "ok"); - ctx.builder.CreateCondBr(ok, ifok, err); - ctx.builder.SetInsertPoint(err); - ctx.builder.CreateCall(prepare_call(jlundefvarerror_func), - mark_callee_rooted(ctx, literal_pointer_val(ctx, (jl_value_t*)name))); - ctx.builder.CreateUnreachable(); - ctx.f->getBasicBlockList().push_back(ifok); - ctx.builder.SetInsertPoint(ifok); -} - static void emit_hasnofield_error_ifnot(jl_codectx_t &ctx, Value *ok, jl_sym_t *type, jl_cgval_t name) { ++EmittedUndefVarErrors; @@ -4713,7 +5283,7 @@ static void emit_hasnofield_error_ifnot(jl_codectx_t &ctx, Value *ok, jl_sym_t * {mark_callee_rooted(ctx, literal_pointer_val(ctx, (jl_value_t*)type)), mark_callee_rooted(ctx, boxed(ctx, name))}); ctx.builder.CreateUnreachable(); - ctx.f->getBasicBlockList().push_back(ifok); + ifok->insertInto(ctx.f); ctx.builder.SetInsertPoint(ifok); } @@ -4739,7 +5309,7 @@ static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t // var not found. switch to delayed lookup. Constant *initnul = Constant::getNullValue(ctx.types().T_pjlvalue); GlobalVariable *bindinggv = new GlobalVariable(*ctx.f->getParent(), ctx.types().T_pjlvalue, - false, GlobalVariable::PrivateLinkage, initnul); + false, GlobalVariable::PrivateLinkage, initnul, "jl_binding_ptr"); // LLVM has bugs with nameless globals LoadInst *cachedval = ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, bindinggv, Align(sizeof(void*))); setName(ctx.emission_context, cachedval, jl_symbol_name(m->name) + StringRef(".") + jl_symbol_name(s) + ".cached"); cachedval->setOrdering(AtomicOrdering::Unordered); @@ -4749,7 +5319,7 @@ static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t auto iscached = ctx.builder.CreateICmpNE(cachedval, initnul); setName(ctx.emission_context, iscached, "iscached"); ctx.builder.CreateCondBr(iscached, have_val, not_found); - ctx.f->getBasicBlockList().push_back(not_found); + not_found->insertInto(ctx.f); ctx.builder.SetInsertPoint(not_found); Value *bval = ctx.builder.CreateCall(prepare_call(assign ? jlgetbindingwrorerror_func : jlgetbindingorerror_func), { literal_pointer_val(ctx, (jl_value_t*)m), @@ -4757,7 +5327,7 @@ static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t setName(ctx.emission_context, bval, jl_symbol_name(m->name) + StringRef(".") + jl_symbol_name(s) + ".found"); ctx.builder.CreateAlignedStore(bval, bindinggv, Align(sizeof(void*)))->setOrdering(AtomicOrdering::Release); ctx.builder.CreateBr(have_val); - ctx.f->getBasicBlockList().push_back(have_val); + have_val->insertInto(ctx.f); ctx.builder.SetInsertPoint(have_val); PHINode *p = ctx.builder.CreatePHI(ctx.types().T_pjlvalue, 2); p->addIncoming(cachedval, currentbb); @@ -4783,7 +5353,7 @@ static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t return julia_binding_gv(ctx, b); } -static jl_cgval_t emit_checked_var(jl_codectx_t &ctx, Value *bp, jl_sym_t *name, bool isvol, MDNode *tbaa) +static jl_cgval_t emit_checked_var(jl_codectx_t &ctx, Value *bp, jl_sym_t *name, jl_value_t *scope, bool isvol, MDNode *tbaa) { LoadInst *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*))); setName(ctx.emission_context, v, jl_symbol_name(name) + StringRef(".checked")); @@ -4794,7 +5364,7 @@ static jl_cgval_t emit_checked_var(jl_codectx_t &ctx, Value *bp, jl_sym_t *name, jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); ai.decorateInst(v); } - undef_var_error_ifnot(ctx, ctx.builder.CreateIsNotNull(v), name); + undef_var_error_ifnot(ctx, ctx.builder.CreateIsNotNull(v), name, scope); return mark_julia_type(ctx, v, true, jl_any_type); } @@ -4820,7 +5390,7 @@ static jl_cgval_t emit_sparam(jl_codectx_t &ctx, size_t i) sparam = (jl_unionall_t*)sparam->body; assert(jl_is_unionall(sparam)); } - undef_var_error_ifnot(ctx, isnull, sparam->var->name); + undef_var_error_ifnot(ctx, isnull, sparam->var->name, (jl_value_t*)jl_static_parameter_sym); return mark_julia_type(ctx, sp, true, jl_any_type); } @@ -4841,10 +5411,10 @@ static jl_cgval_t emit_isdefined(jl_codectx_t &ctx, jl_value_t *sym) Value *box_isnull = ctx.builder.CreateICmpNE(boxed, Constant::getNullValue(ctx.types().T_prjlvalue)); if (vi.pTIndex) { // value is either boxed in the stack slot, or unboxed in value - // as indicated by testing (pTIndex & 0x80) + // as indicated by testing (pTIndex & UNION_BOX_MARKER) Value *tindex = ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), vi.pTIndex, Align(sizeof(void*)), vi.isVolatile); Value *load_unbox = ctx.builder.CreateICmpEQ( - ctx.builder.CreateAnd(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)), + ctx.builder.CreateAnd(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER)), ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0)); isnull = ctx.builder.CreateSelect(load_unbox, isnull, box_isnull); } @@ -4885,7 +5455,7 @@ static jl_cgval_t emit_isdefined(jl_codectx_t &ctx, jl_value_t *sym) } jl_binding_t *bnd = jl_get_binding(modu, name); if (bnd) { - if (jl_atomic_load_relaxed(&bnd->value) != NULL) + if (jl_atomic_load_acquire(&bnd->value) != NULL && bnd->constp) return mark_julia_const(ctx, jl_true); Value *bp = julia_binding_gv(ctx, bnd); bp = julia_binding_pvalue(ctx, bp); @@ -4956,9 +5526,9 @@ static jl_cgval_t emit_varinfo(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_sym_t *va maybe_mark_load_dereferenceable(boxed, vi.usedUndef || vi.pTIndex, typ); if (vi.pTIndex) { // value is either boxed in the stack slot, or unboxed in value - // as indicated by testing (pTIndex & 0x80) + // as indicated by testing (pTIndex & UNION_BOX_MARKER) Value *load_unbox = ctx.builder.CreateICmpEQ( - ctx.builder.CreateAnd(v.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)), + ctx.builder.CreateAnd(v.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER)), ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0)); if (vi.usedUndef) isnull = ctx.builder.CreateSelect(load_unbox, isnull, box_isnull); @@ -4978,7 +5548,7 @@ static jl_cgval_t emit_varinfo(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_sym_t *va } if (isnull) { setName(ctx.emission_context, isnull, jl_symbol_name(varname) + StringRef("_is_null")); - undef_var_error_ifnot(ctx, isnull, varname); + undef_var_error_ifnot(ctx, isnull, varname, (jl_value_t*)jl_local_sym); } return v; } @@ -5019,7 +5589,7 @@ static void emit_vi_assignment_unboxed(jl_codectx_t &ctx, jl_varinfo_t &vi, Valu } else { Value *dest = vi.value.V; - if (vi.pTIndex) + if (vi.pTIndex) // TODO: use lifetime-end here instead ctx.builder.CreateStore(UndefValue::get(cast(vi.value.V)->getAllocatedType()), vi.value.V); Type *store_ty = julia_type_to_llvm(ctx, rval_info.constant ? jl_typeof(rval_info.constant) : rval_info.typ); Type *dest_ty = store_ty->getPointerTo(); @@ -5084,19 +5654,19 @@ static void emit_phinode_assign(jl_codectx_t &ctx, ssize_t idx, jl_value_t *r) if (dest) { Instruction *phi = dest->clone(); phi->insertAfter(dest); - PHINode *Tindex_phi = PHINode::Create(getInt8Ty(ctx.builder.getContext()), jl_array_len(edges), "tindex_phi"); - BB->getInstList().insert(InsertPt, Tindex_phi); - PHINode *ptr_phi = PHINode::Create(ctx.types().T_prjlvalue, jl_array_len(edges), "ptr_phi"); - BB->getInstList().insert(InsertPt, ptr_phi); + PHINode *Tindex_phi = PHINode::Create(getInt8Ty(ctx.builder.getContext()), jl_array_nrows(edges), "tindex_phi"); + Tindex_phi->insertInto(BB, InsertPt); + PHINode *ptr_phi = PHINode::Create(ctx.types().T_prjlvalue, jl_array_nrows(edges), "ptr_phi"); + ptr_phi->insertInto(BB, InsertPt); Value *isboxed = ctx.builder.CreateICmpNE( - ctx.builder.CreateAnd(Tindex_phi, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)), + ctx.builder.CreateAnd(Tindex_phi, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER)), ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0)); ctx.builder.CreateMemCpy(phi, MaybeAlign(min_align), dest, dest->getAlign(), nbytes, false); ctx.builder.CreateLifetimeEnd(dest); Value *ptr = ctx.builder.CreateSelect(isboxed, maybe_bitcast(ctx, decay_derived(ctx, ptr_phi), getInt8PtrTy(ctx.builder.getContext())), maybe_bitcast(ctx, decay_derived(ctx, phi), getInt8PtrTy(ctx.builder.getContext()))); - jl_cgval_t val = mark_julia_slot(ptr, phiType, Tindex_phi, ctx.tbaa().tbaa_stack); // XXX: this TBAA is wrong for ptr_phi + jl_cgval_t val = mark_julia_slot(ptr, phiType, Tindex_phi, best_tbaa(ctx.tbaa(), phiType)); val.Vboxed = ptr_phi; ctx.PhiNodes.push_back(std::make_tuple(val, BB, dest, ptr_phi, r)); ctx.SAvalues[idx] = val; @@ -5104,8 +5674,8 @@ static void emit_phinode_assign(jl_codectx_t &ctx, ssize_t idx, jl_value_t *r) return; } else if (allunbox) { - PHINode *Tindex_phi = PHINode::Create(getInt8Ty(ctx.builder.getContext()), jl_array_len(edges), "tindex_phi"); - BB->getInstList().insert(InsertPt, Tindex_phi); + PHINode *Tindex_phi = PHINode::Create(getInt8Ty(ctx.builder.getContext()), jl_array_nrows(edges), "tindex_phi"); + Tindex_phi->insertInto(BB, InsertPt); jl_cgval_t val = mark_julia_slot(NULL, phiType, Tindex_phi, ctx.tbaa().tbaa_stack); ctx.PhiNodes.push_back(std::make_tuple(val, BB, dest, (PHINode*)NULL, r)); ctx.SAvalues[idx] = val; @@ -5118,7 +5688,7 @@ static void emit_phinode_assign(jl_codectx_t &ctx, ssize_t idx, jl_value_t *r) // The frontend should really not emit this, but we allow it // for convenience. if (type_is_ghost(vtype)) { - assert(jl_is_datatype(phiType) && ((jl_datatype_t*)phiType)->instance); + assert(jl_is_datatype(phiType) && jl_is_datatype_singleton((jl_datatype_t*)phiType)); // Skip adding it to the PhiNodes list, since we didn't create one. ctx.SAvalues[idx] = mark_julia_const(ctx, ((jl_datatype_t*)phiType)->instance); ctx.ssavalue_assigned[idx] = true; @@ -5138,8 +5708,8 @@ static void emit_phinode_assign(jl_codectx_t &ctx, ssize_t idx, jl_value_t *r) slot = mark_julia_slot(phi, phiType, NULL, ctx.tbaa().tbaa_stack); } else { - value_phi = PHINode::Create(vtype, jl_array_len(edges), "value_phi"); - BB->getInstList().insert(InsertPt, value_phi); + value_phi = PHINode::Create(vtype, jl_array_nrows(edges), "value_phi"); + value_phi->insertInto(BB, InsertPt); slot = mark_julia_type(ctx, value_phi, isboxed, phiType); } ctx.PhiNodes.push_back(std::make_tuple(slot, BB, dest, value_phi, r)); @@ -5180,14 +5750,23 @@ static void emit_ssaval_assign(jl_codectx_t &ctx, ssize_t ssaidx_0based, jl_valu ctx.ssavalue_assigned[ssaidx_0based] = true; } -static void emit_varinfo_assign(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_cgval_t rval_info, jl_value_t *l=NULL) +static void emit_varinfo_assign(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_cgval_t rval_info, jl_value_t *l=NULL, bool allow_mismatch=false) { if (!vi.used || vi.value.typ == jl_bottom_type) return; // convert rval-type to lval-type jl_value_t *slot_type = vi.value.typ; - rval_info = convert_julia_type(ctx, rval_info, slot_type); + // If allow_mismatch is set, type mismatches will not result in traps. + // This is used for upsilon nodes, where the destination can have a narrower + // type than the store, if inference determines that the store is never read. + Value *skip = NULL; + rval_info = convert_julia_type(ctx, rval_info, slot_type, &skip); + if (!allow_mismatch && skip) { + CreateTrap(ctx.builder); + return; + } + if (rval_info.typ == jl_bottom_type) return; @@ -5197,13 +5776,13 @@ static void emit_varinfo_assign(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_cgval_t if (rval_info.TIndex) { tindex = rval_info.TIndex; if (!vi.boxroot) - tindex = ctx.builder.CreateAnd(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x7f)); + tindex = ctx.builder.CreateAnd(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), ~UNION_BOX_MARKER)); } else { assert(rval_info.isboxed || rval_info.constant); tindex = compute_tindex_unboxed(ctx, rval_info, vi.value.typ); if (vi.boxroot) - tindex = ctx.builder.CreateOr(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)); + tindex = ctx.builder.CreateOr(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER)); else rval_info.TIndex = tindex; } @@ -5217,7 +5796,7 @@ static void emit_varinfo_assign(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_cgval_t if (vi.pTIndex && rval_info.TIndex) { ctx.builder.CreateStore(rval_info.TIndex, vi.pTIndex, vi.isVolatile); isboxed = ctx.builder.CreateICmpNE( - ctx.builder.CreateAnd(rval_info.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)), + ctx.builder.CreateAnd(rval_info.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER)), ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0)); rval = rval_info.Vboxed ? rval_info.Vboxed : Constant::getNullValue(ctx.types().T_prjlvalue); assert(rval->getType() == ctx.types().T_prjlvalue); @@ -5232,8 +5811,13 @@ static void emit_varinfo_assign(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_cgval_t // store unboxed variables if (!vi.boxroot || (vi.pTIndex && rval_info.TIndex)) { - emit_vi_assignment_unboxed(ctx, vi, isboxed, rval_info); + emit_guarded_test(ctx, skip ? ctx.builder.CreateNot(skip) : nullptr, nullptr, [&]{ + emit_vi_assignment_unboxed(ctx, vi, isboxed, rval_info); + return nullptr; + }); } + + return; } static void emit_assignment(jl_codectx_t &ctx, jl_value_t *l, jl_value_t *r, ssize_t ssaval) @@ -5245,7 +5829,8 @@ static void emit_assignment(jl_codectx_t &ctx, jl_value_t *l, jl_value_t *r, ssi int sl = jl_slot_number(l) - 1; // it's a local variable jl_varinfo_t &vi = ctx.slots[sl]; - return emit_varinfo_assign(ctx, vi, rval_info, l); + emit_varinfo_assign(ctx, vi, rval_info, l); + return; } jl_module_t *mod; @@ -5259,7 +5844,8 @@ static void emit_assignment(jl_codectx_t &ctx, jl_value_t *l, jl_value_t *r, ssi mod = jl_globalref_mod(l); sym = jl_globalref_name(l); } - emit_globalset(ctx, mod, sym, rval_info, AtomicOrdering::Release); + emit_globalop(ctx, mod, sym, rval_info, jl_cgval_t(), AtomicOrdering::Release, AtomicOrdering::NotAtomic, + true, false, false, false, false, nullptr); // Global variable. Does not need debug info because the debugger knows about // its memory location. } @@ -5276,15 +5862,17 @@ static void emit_upsilonnode(jl_codectx_t &ctx, ssize_t phic, jl_value_t *val) // upsilon node is not dynamically observed. if (val) { jl_cgval_t rval_info = emit_expr(ctx, val); - if (rval_info.typ == jl_bottom_type) + if (rval_info.typ == jl_bottom_type) { // as a special case, PhiC nodes are allowed to use undefined // values, since they are just copy operations, so we need to // ignore the store (it will not by dynamically observed), while // normally, for any other operation result, we'd assume this store // was unreachable and dead val = NULL; - else - emit_varinfo_assign(ctx, vi, rval_info); + } + else { + emit_varinfo_assign(ctx, vi, rval_info, NULL, true); + } } if (!val) { if (vi.boxroot) { @@ -5296,7 +5884,7 @@ static void emit_upsilonnode(jl_codectx_t &ctx, ssize_t phic, jl_value_t *val) // does need to satisfy the union invariants (i.e. inbounds // tindex). ctx.builder.CreateAlignedStore( - vi.boxroot ? ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80) : + vi.boxroot ? ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER) : ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x01), vi.pTIndex, Align(1), true); } @@ -5326,16 +5914,15 @@ static Value *emit_condition(jl_codectx_t &ctx, const jl_cgval_t &condV, const T emit_typecheck(ctx, condV, (jl_value_t*)jl_bool_type, msg); } if (isbool) { - Value *cond = emit_unbox(ctx, getInt8Ty(ctx.builder.getContext()), condV, (jl_value_t*)jl_bool_type); - assert(cond->getType() == getInt8Ty(ctx.builder.getContext())); - return ctx.builder.CreateXor(ctx.builder.CreateTrunc(cond, getInt1Ty(ctx.builder.getContext())), ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1)); + Value *cond = emit_unbox(ctx, getInt1Ty(ctx.builder.getContext()), condV, (jl_value_t*)jl_bool_type); + return ctx.builder.CreateNot(cond); } if (condV.isboxed) { return ctx.builder.CreateICmpEQ(boxed(ctx, condV), track_pjlvalue(ctx, literal_pointer_val(ctx, jl_false))); } - // not a boolean - return ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0); // TODO: replace with Undef + // not a boolean (unreachable dead code) + return UndefValue::get(getInt1Ty(ctx.builder.getContext())); } static Value *emit_condition(jl_codectx_t &ctx, jl_value_t *cond, const Twine &msg) @@ -5377,7 +5964,7 @@ static void emit_stmtpos(jl_codectx_t &ctx, jl_value_t *expr, int ssaval_result) return; } jl_expr_t *ex = (jl_expr_t*)expr; - jl_value_t **args = (jl_value_t**)jl_array_data(ex->args); + jl_value_t **args = jl_array_data(ex->args, jl_value_t*); jl_sym_t *head = ex->head; if (head == jl_meta_sym || head == jl_inbounds_sym || head == jl_coverageeffect_sym || head == jl_aliasscope_sym || head == jl_popaliasscope_sym || head == jl_inline_sym || head == jl_noinline_sym) { @@ -5387,23 +5974,36 @@ static void emit_stmtpos(jl_codectx_t &ctx, jl_value_t *expr, int ssaval_result) } else if (head == jl_leave_sym) { int hand_n_leave = 0; + Value *scope_to_restore = nullptr; + Value *scope_ptr = nullptr; for (size_t i = 0; i < jl_expr_nargs(ex); ++i) { jl_value_t *arg = args[i]; if (arg == jl_nothing) continue; assert(jl_is_ssavalue(arg)); - jl_value_t *enter_stmt = jl_array_ptr_ref(ctx.code, ((jl_ssavalue_t*)arg)->id - 1); + size_t enter_idx = ((jl_ssavalue_t*)arg)->id - 1; + jl_value_t *enter_stmt = jl_array_ptr_ref(ctx.code, enter_idx); if (enter_stmt == jl_nothing) continue; - hand_n_leave += 1; + if (ctx.scope_restore.count(enter_idx)) + std::tie(scope_to_restore, scope_ptr) = ctx.scope_restore[enter_idx]; + if (jl_enternode_catch_dest(enter_stmt)) { + // We're not actually setting up the exception frames for these, so + // we don't need to exit them. + hand_n_leave += 1; + } + } + ctx.builder.CreateCall(prepare_call(jlleave_noexcept_func), {get_current_task(ctx), ConstantInt::get(getInt32Ty(ctx.builder.getContext()), hand_n_leave)}); + if (scope_to_restore) { + jl_aliasinfo_t scope_ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_gcframe); + scope_ai.decorateInst( + ctx.builder.CreateAlignedStore(scope_to_restore, scope_ptr, ctx.types().alignof_ptr)); } - ctx.builder.CreateCall(prepare_call(jlleave_func), - ConstantInt::get(getInt32Ty(ctx.builder.getContext()), hand_n_leave)); } else if (head == jl_pop_exception_sym) { jl_cgval_t excstack_state = emit_expr(ctx, jl_exprarg(expr, 0)); assert(excstack_state.V && excstack_state.V->getType() == ctx.types().T_size); - ctx.builder.CreateCall(prepare_call(jl_restore_excstack_func), excstack_state.V); + ctx.builder.CreateCall(prepare_call(jl_restore_excstack_func), {get_current_task(ctx), excstack_state.V}); return; } else { @@ -5427,7 +6027,7 @@ static std::pair get_oc_function(jl_codectx_t &ctx, jl_met sigtype = jl_apply_tuple_type_v(jl_svec_data(sig_args), nsig); jl_method_instance_t *mi = jl_specializations_get_linfo(closure_method, sigtype, jl_emptysvec); - jl_code_instance_t *ci = (jl_code_instance_t*)jl_rettype_inferred_addr(mi, ctx.world, ctx.world); + jl_code_instance_t *ci = (jl_code_instance_t*)jl_rettype_inferred_addr(mi, ctx.min_world, ctx.max_world); if (ci == NULL || (jl_value_t*)ci == jl_nothing) { JL_GC_POP(); @@ -5449,7 +6049,7 @@ static std::pair get_oc_function(jl_codectx_t &ctx, jl_met orc::ThreadSafeModule closure_m = jl_create_ts_module( name_from_method_instance(mi), ctx.emission_context.tsctx, jl_Module->getDataLayout(), Triple(jl_Module->getTargetTriple())); - jl_llvm_functions_t closure_decls = emit_function(closure_m, mi, ir, rettype, ctx.emission_context); + jl_llvm_functions_t closure_decls = emit_function(closure_m, mi, ir, rettype, ctx.emission_context, ctx.min_world, ctx.max_world); JL_GC_POP(); it = ctx.emission_context.compiled_functions.insert(std::make_pair(ci, std::make_pair(std::move(closure_m), std::move(closure_decls)))).first; } @@ -5538,8 +6138,8 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ } jl_expr_t *ex = (jl_expr_t*)expr; - jl_value_t **args = (jl_value_t**)jl_array_data(ex->args); - size_t nargs = jl_array_len(ex->args); + jl_value_t **args = jl_array_data(ex->args, jl_value_t*); + size_t nargs = jl_array_nrows(ex->args); jl_sym_t *head = ex->head; // this is object-disoriented. // however, this is a good way to do it because it should *not* be easy @@ -5557,7 +6157,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ literal_pointer_val(ctx, jl_undefref_exception)); } else { - undef_var_error_ifnot(ctx, cond, var); + undef_var_error_ifnot(ctx, cond, var, (jl_value_t*)jl_local_sym); } return ghostValue(ctx, jl_nothing_type); } @@ -5634,9 +6234,11 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ bnd = jl_get_binding_for_method_def(mod, (jl_sym_t*)mn); } JL_CATCH { - jl_value_t *e = jl_current_exception(); + jl_value_t *e = jl_current_exception(jl_current_task); // errors. boo. :( - e = jl_as_global_root(e); + JL_GC_PUSH1(&e); + e = jl_as_global_root(e, 1); + JL_GC_POP(); raise_exception(ctx, literal_pointer_val(ctx, e)); return ghostValue(ctx, jl_nothing_type); } @@ -5654,7 +6256,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ Value *mdargs[] = { name, literal_pointer_val(ctx, (jl_value_t*)mod), bp, literal_pointer_val(ctx, bnd) }; jl_cgval_t gf = mark_julia_type( ctx, - ctx.builder.CreateCall(prepare_call(jlgenericfunction_func), makeArrayRef(mdargs)), + ctx.builder.CreateCall(prepare_call(jlgenericfunction_func), ArrayRef(mdargs)), true, jl_function_type); return gf; @@ -5673,7 +6275,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ }; jl_cgval_t meth = mark_julia_type( ctx, - ctx.builder.CreateCall(prepare_call(jlmethod_func), makeArrayRef(mdargs)), + ctx.builder.CreateCall(prepare_call(jlmethod_func), ArrayRef(mdargs)), true, jl_method_type); return meth; @@ -5709,12 +6311,12 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ jl_is_datatype(jl_tparam0(ty)) && jl_is_concrete_type(jl_tparam0(ty))) { assert(nargs <= jl_datatype_nfields(jl_tparam0(ty)) + 1); - jl_cgval_t res = emit_new_struct(ctx, jl_tparam0(ty), nargs - 1, argv.data() + 1, is_promotable); + jl_cgval_t res = emit_new_struct(ctx, jl_tparam0(ty), nargs - 1, ArrayRef(argv).drop_front(), is_promotable); if (is_promotable && res.promotion_point && res.promotion_ssa==-1) res.promotion_ssa = ssaidx_0based; return res; } - Value *val = emit_jlcall(ctx, jlnew_func, nullptr, argv.data(), nargs, julia_call); + Value *val = emit_jlcall(ctx, jlnew_func, nullptr, argv, nargs, julia_call); // temporarily mark as `Any`, expecting `emit_ssaval_assign` to update // it to the inferred type. return mark_julia_type(ctx, val, true, (jl_value_t*)jl_any_type); @@ -5781,7 +6383,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ fptr = mark_julia_type(ctx, Constant::getNullValue(ctx.types().T_size), false, jl_voidpointer_type); // TODO: Inline the env at the end of the opaque closure and generate a descriptor for GC - jl_cgval_t env = emit_new_struct(ctx, env_t, nargs-4, &argv.data()[4]); + jl_cgval_t env = emit_new_struct(ctx, env_t, nargs-4, ArrayRef(argv).drop_front(4)); jl_cgval_t closure_fields[5] = { env, @@ -5802,13 +6404,13 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ } return mark_julia_type(ctx, - emit_jlcall(ctx, jl_new_opaque_closure_jlcall_func, Constant::getNullValue(ctx.types().T_prjlvalue), argv.data(), nargs, julia_call), + emit_jlcall(ctx, jl_new_opaque_closure_jlcall_func, Constant::getNullValue(ctx.types().T_prjlvalue), argv, nargs, julia_call), true, jl_any_type); } else if (head == jl_exc_sym) { assert(nargs == 0); return mark_julia_type(ctx, - ctx.builder.CreateCall(prepare_call(jl_current_exception_func)), + ctx.builder.CreateCall(prepare_call(jl_current_exception_func), {get_current_task(ctx)}), true, jl_any_type); } else if (head == jl_copyast_sym) { @@ -5843,7 +6445,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ return jl_cgval_t(); } else if (head == jl_leave_sym || head == jl_coverageeffect_sym - || head == jl_pop_exception_sym || head == jl_enter_sym || head == jl_inbounds_sym + || head == jl_pop_exception_sym || head == jl_inbounds_sym || head == jl_aliasscope_sym || head == jl_popaliasscope_sym || head == jl_inline_sym || head == jl_noinline_sym) { jl_errorf("Expr(:%s) in value position", jl_symbol_name(head)); } @@ -5858,16 +6460,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ } SmallVector vals; for (size_t i = 0; i < nargs; ++i) { - const jl_cgval_t &ai = argv[i]; - if (ai.constant || ai.typ == jl_bottom_type) - continue; - if (ai.isboxed) { - vals.push_back(ai.Vboxed); - } - else if (jl_is_concrete_immutable(ai.typ) && !jl_is_pointerfree(ai.typ)) { - Type *at = julia_type_to_llvm(ctx, ai.typ); - vals.push_back(emit_unbox(ctx, at, ai, ai.typ)); - } + vals.append(get_gc_roots_for(ctx, argv[i])); } Value *token = vals.empty() ? (Value*)ConstantTokenNone::get(ctx.builder.getContext()) @@ -5919,9 +6512,14 @@ static void allocate_gc_frame(jl_codectx_t &ctx, BasicBlock *b0, bool or_new=fal ctx.pgcstack->setName("pgcstack"); } +static Value *get_current_task(jl_codectx_t &ctx, Type *T) +{ + return emit_bitcast(ctx, get_current_task_from_pgcstack(ctx.builder, ctx.types().T_size, ctx.pgcstack), T); +} + static Value *get_current_task(jl_codectx_t &ctx) { - return get_current_task_from_pgcstack(ctx.builder, ctx.types().T_size, ctx.pgcstack); + return get_current_task(ctx, ctx.types().T_pjlvalue); } // Get PTLS through current task. @@ -5933,20 +6531,30 @@ static Value *get_current_ptls(jl_codectx_t &ctx) // Get the address of the world age of the current task static Value *get_last_age_field(jl_codectx_t &ctx) { - Value *ct = get_current_task(ctx); + Value *ct = get_current_task(ctx, ctx.types().T_size->getPointerTo()); return ctx.builder.CreateInBoundsGEP( ctx.types().T_size, - ctx.builder.CreateBitCast(ct, ctx.types().T_size->getPointerTo()), + ct, ConstantInt::get(ctx.types().T_size, offsetof(jl_task_t, world_age) / ctx.types().sizeof_ptr), "world_age"); } +static Value *get_scope_field(jl_codectx_t &ctx) +{ + Value *ct = get_current_task(ctx, ctx.types().T_prjlvalue->getPointerTo()); + return ctx.builder.CreateInBoundsGEP( + ctx.types().T_prjlvalue, + ct, + ConstantInt::get(ctx.types().T_size, offsetof(jl_task_t, scope) / ctx.types().sizeof_ptr), + "current_scope"); +} + static Function *emit_tojlinvoke(jl_code_instance_t *codeinst, Module *M, jl_codegen_params_t ¶ms) { ++EmittedToJLInvokes; - jl_codectx_t ctx(M->getContext(), params); + jl_codectx_t ctx(M->getContext(), params, codeinst); std::string name; - raw_string_ostream(name) << "tojlinvoke" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); + raw_string_ostream(name) << "tojlinvoke" << jl_atomic_fetch_add_relaxed(&globalUniqueGeneratedNames, 1); Function *f = Function::Create(ctx.types().T_jlfunc, GlobalVariable::InternalLinkage, name, M); @@ -5961,7 +6569,7 @@ static Function *emit_tojlinvoke(jl_code_instance_t *codeinst, Module *M, jl_cod auto invoke = jl_atomic_load_relaxed(&codeinst->invoke); bool cache_valid = params.cache; - if (cache_valid && invoke != NULL) { + if (cache_valid && invoke != NULL && invoke != &jl_fptr_wait_for_compiled) { StringRef theFptrName = jl_ExecutionEngine->getFunctionAtAddress((uintptr_t)invoke, codeinst); theFunc = cast( M->getOrInsertFunction(theFptrName, jlinvoke_func->_type(ctx.builder.getContext())).getCallee()); @@ -5992,10 +6600,11 @@ static void emit_cfunc_invalidate( jl_value_t *calltype, jl_value_t *rettype, bool is_for_opaque_closure, size_t nargs, jl_codegen_params_t ¶ms, - Function *target) + Function *target, + size_t min_world, size_t max_world) { ++EmittedCFuncInvalidates; - jl_codectx_t ctx(gf_thunk->getParent()->getContext(), params); + jl_codectx_t ctx(gf_thunk->getParent()->getContext(), params, min_world, max_world); ctx.f = gf_thunk; BasicBlock *b0 = BasicBlock::Create(ctx.builder.getContext(), "top", gf_thunk); @@ -6031,7 +6640,7 @@ static void emit_cfunc_invalidate( myargs[i] = mark_julia_const(ctx, jl_tparam0(jt)); } else if (type_is_ghost(et)) { - assert(jl_is_datatype(jt) && ((jl_datatype_t*)jt)->instance); + assert(jl_is_datatype(jt) && jl_is_datatype_singleton((jl_datatype_t*)jt)); myargs[i] = mark_julia_const(ctx, ((jl_datatype_t*)jt)->instance); } else { @@ -6049,7 +6658,7 @@ static void emit_cfunc_invalidate( } } assert(AI == gf_thunk->arg_end()); - Value *gf_ret = emit_jlcall(ctx, target, nullptr, myargs.data(), nargs, julia_call); + Value *gf_ret = emit_jlcall(ctx, target, nullptr, myargs, nargs, julia_call); jl_cgval_t gf_retbox = mark_julia_type(ctx, gf_ret, true, jl_any_type); if (cc != jl_returninfo_t::Boxed) { emit_typecheck(ctx, gf_retbox, rettype, "cfunction"); @@ -6086,7 +6695,7 @@ static void emit_cfunc_invalidate( Type *retty = gf_thunk->getReturnType(); Value *gf_retval = UndefValue::get(retty); Value *tindex = compute_box_tindex(ctx, emit_typeof(ctx, gf_retbox, false, true), (jl_value_t*)jl_any_type, rettype); - tindex = ctx.builder.CreateOr(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)); + tindex = ctx.builder.CreateOr(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER)); gf_retval = ctx.builder.CreateInsertValue(gf_retval, gf_ret, 0); gf_retval = ctx.builder.CreateInsertValue(gf_retval, tindex, 1); ctx.builder.CreateRet(gf_retval); @@ -6103,18 +6712,19 @@ static void emit_cfunc_invalidate( static void emit_cfunc_invalidate( Function *gf_thunk, jl_returninfo_t::CallingConv cc, unsigned return_roots, jl_value_t *calltype, jl_value_t *rettype, bool is_for_opaque_closure, - size_t nargs, - jl_codegen_params_t ¶ms) + size_t nargs, jl_codegen_params_t ¶ms, + size_t min_world, size_t max_world) { emit_cfunc_invalidate(gf_thunk, cc, return_roots, calltype, rettype, is_for_opaque_closure, nargs, params, - prepare_call_in(gf_thunk->getParent(), jlapplygeneric_func)); + prepare_call_in(gf_thunk->getParent(), jlapplygeneric_func), min_world, max_world); } static Function* gen_cfun_wrapper( Module *into, jl_codegen_params_t ¶ms, const function_sig_t &sig, jl_value_t *ff, const char *aliasname, jl_value_t *declrt, jl_method_instance_t *lam, - jl_unionall_t *unionall_env, jl_svec_t *sparam_vals, jl_array_t **closure_types) + jl_unionall_t *unionall_env, jl_svec_t *sparam_vals, jl_array_t **closure_types, + size_t min_world, size_t max_world) { ++GeneratedCFuncWrappers; // Generate a c-callable wrapper @@ -6168,7 +6778,7 @@ static Function* gen_cfun_wrapper( } std::string funcName; - raw_string_ostream(funcName) << "jlcapi_" << name << "_" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); + raw_string_ostream(funcName) << "jlcapi_" << name << "_" << jl_atomic_fetch_add_relaxed(&globalUniqueGeneratedNames, 1); Module *M = into; // Safe because ctx lock is held by params AttributeList attributes = sig.attributes; @@ -6232,9 +6842,8 @@ static Function* gen_cfun_wrapper( jl_init_function(cw, params.TargetTriple); cw->setAttributes(AttributeList::get(M->getContext(), {attributes, cw->getAttributes()})); - jl_codectx_t ctx(M->getContext(), params); + jl_codectx_t ctx(M->getContext(), params, min_world, max_world); ctx.f = cw; - ctx.world = world; ctx.name = name; ctx.funcName = name; @@ -6342,14 +6951,14 @@ static Function* gen_cfun_wrapper( *closure_types = jl_alloc_vec_any(0); jl_array_ptr_1d_push(*closure_types, jargty); Value *runtime_dt = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, - ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_prjlvalue, nestPtr, jl_array_len(*closure_types)), + ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_prjlvalue, nestPtr, jl_array_nrows(*closure_types)), Align(sizeof(void*))); BasicBlock *boxedBB = BasicBlock::Create(ctx.builder.getContext(), "isboxed", cw); BasicBlock *loadBB = BasicBlock::Create(ctx.builder.getContext(), "need-load", cw); BasicBlock *unboxedBB = BasicBlock::Create(ctx.builder.getContext(), "maybe-unboxed", cw); BasicBlock *isanyBB = BasicBlock::Create(ctx.builder.getContext(), "any", cw); BasicBlock *afterBB = BasicBlock::Create(ctx.builder.getContext(), "after", cw); - Value *isrtboxed = ctx.builder.CreateIsNull(val); // XXX: this is the wrong condition and should be inspecting runtime_dt intead + Value *isrtboxed = ctx.builder.CreateIsNull(val); // XXX: this is the wrong condition and should be inspecting runtime_dt instead ctx.builder.CreateCondBr(isrtboxed, boxedBB, loadBB); ctx.builder.SetInsertPoint(boxedBB); Value *p1 = ctx.builder.CreateBitCast(val, ctx.types().T_pjlvalue); @@ -6409,7 +7018,7 @@ static Function* gen_cfun_wrapper( *closure_types = jl_alloc_vec_any(0); jl_array_ptr_1d_push(*closure_types, jargty); Value *runtime_dt = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, - ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_prjlvalue, nestPtr, jl_array_len(*closure_types)), + ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_prjlvalue, nestPtr, jl_array_nrows(*closure_types)), Align(sizeof(void*))); Value *strct = box_ccall_result(ctx, val, runtime_dt, jargty); inputarg = mark_julia_type(ctx, strct, true, jargty_proper); @@ -6425,6 +7034,7 @@ static Function* gen_cfun_wrapper( jl_cgval_t retval; if (calltype == 2) { nargs = 0; // arguments not needed -- TODO: not really true, should emit an age_ok test and jlcall + (void)nargs; // silence unused variable warning jlfunc_sret = false; retval = mark_julia_const(ctx, (jl_value_t*)callptr); } @@ -6458,11 +7068,11 @@ static Function* gen_cfun_wrapper( // for jlcall, we need to pass the function object even if it is a ghost. Value *theF = boxed(ctx, inputargs[0]); assert(theF); - ret_jlcall = emit_jlcall(ctx, theFptr, theF, &inputargs[1], nargs, julia_call); + ret_jlcall = emit_jlcall(ctx, theFptr, theF, ArrayRef(inputargs).drop_front(), nargs, julia_call); ctx.builder.CreateBr(b_after); ctx.builder.SetInsertPoint(b_generic); } - Value *ret = emit_jlcall(ctx, jlapplygeneric_func, NULL, inputargs.data(), nargs + 1, julia_call); + Value *ret = emit_jlcall(ctx, jlapplygeneric_func, NULL, inputargs, nargs + 1, julia_call); if (age_ok) { ctx.builder.CreateBr(b_after); ctx.builder.SetInsertPoint(b_after); @@ -6554,7 +7164,8 @@ static Function* gen_cfun_wrapper( // build a specsig -> jl_apply_generic converter thunk // this builds a method that calls jl_apply_generic (as a closure over a singleton function pointer), // but which has the signature of a specsig - emit_cfunc_invalidate(gf_thunk, returninfo.cc, returninfo.return_roots, lam->specTypes, codeinst->rettype, is_opaque_closure, nargs + 1, ctx.emission_context); + emit_cfunc_invalidate(gf_thunk, returninfo.cc, returninfo.return_roots, lam->specTypes, codeinst->rettype, is_opaque_closure, nargs + 1, ctx.emission_context, + min_world, max_world); theFptr = ctx.builder.CreateSelect(age_ok, theFptr, gf_thunk); } @@ -6581,7 +7192,7 @@ static Function* gen_cfun_wrapper( Value *tindex = ctx.builder.CreateExtractValue(call, 1); Value *derived = ctx.builder.CreateSelect( ctx.builder.CreateICmpEQ( - ctx.builder.CreateAnd(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)), + ctx.builder.CreateAnd(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER)), ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0)), decay_derived(ctx, ctx.builder.CreateBitCast(result, ctx.types().T_pjlvalue)), decay_derived(ctx, box)); @@ -6762,6 +7373,7 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con if (ctx.emission_context.TargetTriple.isAArch64() || ctx.emission_context.TargetTriple.isARM() || ctx.emission_context.TargetTriple.isPPC64()) { if (nest) { emit_error(ctx, "cfunction: closures are not supported on this platform"); + JL_GC_POP(); return jl_cgval_t(); } } @@ -6774,7 +7386,7 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con jl_Module, ctx.emission_context, sig, fexpr_rt.constant, NULL, declrt, lam, - unionall_env, sparam_vals, &closure_types); + unionall_env, sparam_vals, &closure_types, min_valid, max_valid); bool outboxed; if (nest) { // F is actually an init_trampoline function that returns the real address @@ -6783,12 +7395,14 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con jl_svec_t *fill = jl_emptysvec; if (closure_types) { assert(ctx.spvals_ptr); - size_t n = jl_array_len(closure_types); - jl_svec_t *fill = jl_alloc_svec_uninit(n); + size_t n = jl_array_nrows(closure_types); + jl_svec_t *fill_i = jl_alloc_svec_uninit(n); for (size_t i = 0; i < n; i++) { - jl_svecset(fill, i, jl_array_ptr_ref(closure_types, i)); + jl_svecset(fill_i, i, jl_array_ptr_ref(closure_types, i)); } - fill = (jl_svec_t*)jl_ensure_rooted(ctx, (jl_value_t*)fill); + JL_GC_PUSH1(&fill_i); + fill = (jl_svec_t*)jl_ensure_rooted(ctx, (jl_value_t*)fill_i); + JL_GC_POP(); } Type *T_htable = ArrayType::get(ctx.types().T_size, sizeof(htable_t) / sizeof(void*)); Value *cache = new GlobalVariable(*jl_Module, T_htable, false, @@ -6810,7 +7424,7 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con outboxed = (output_type != (jl_value_t*)jl_voidpointer_type); if (outboxed) { assert(jl_datatype_size(output_type) == sizeof(void*) * 4); - Value *strct = emit_allocobj(ctx, (jl_datatype_t*)output_type); + Value *strct = emit_allocobj(ctx, (jl_datatype_t*)output_type, true); setName(ctx.emission_context, strct, "cfun_result"); Value *derived_strct = emit_bitcast(ctx, decay_derived(ctx, strct), ctx.types().T_size->getPointerTo()); MDNode *tbaa = best_tbaa(ctx.tbaa(), output_type); @@ -6877,7 +7491,7 @@ const char *jl_generate_ccallable(LLVMOrcThreadSafeModuleRef llvmmod, void *sysi else { jl_method_instance_t *lam = jl_get_specialization1((jl_tupletype_t*)sigt, world, &min_valid, &max_valid, 0); //Safe b/c params holds context lock - gen_cfun_wrapper(unwrap(llvmmod)->getModuleUnlocked(), params, sig, ff, name, declrt, lam, NULL, NULL, NULL); + gen_cfun_wrapper(unwrap(llvmmod)->getModuleUnlocked(), params, sig, ff, name, declrt, lam, NULL, NULL, NULL, min_valid, max_valid); } JL_GC_POP(); return name; @@ -6905,11 +7519,10 @@ static Function *gen_invoke_wrapper(jl_method_instance_t *lam, jl_value_t *jlret //Value *mfunc = &*AI++; (void)mfunc; // unused assert(AI == w->arg_end()); - jl_codectx_t ctx(M->getContext(), params); + jl_codectx_t ctx(M->getContext(), params, 0, 0); ctx.f = w; ctx.linfo = lam; ctx.rettype = jlretty; - ctx.world = 0; BasicBlock *b0 = BasicBlock::Create(ctx.builder.getContext(), "top", w); ctx.builder.SetInsertPoint(b0); @@ -6986,7 +7599,7 @@ static Function *gen_invoke_wrapper(jl_method_instance_t *lam, jl_value_t *jlret if (!lty->isAggregateType()) // keep "aggregate" type values in place as pointers theArg = ctx.builder.CreateAlignedLoad(lty, theArg, Align(julia_alignment(ty))); } - assert(dyn_cast(theArg) == NULL); + assert(!isa(theArg)); args[idx] = theArg; idx++; } @@ -7059,7 +7672,7 @@ static jl_returninfo_t get_specsig_function(jl_codectx_t &ctx, Module *M, Value fsig.push_back(AT->getPointerTo()); argnames.push_back("union_bytes_return"); Type *pair[] = { ctx.types().T_prjlvalue, getInt8Ty(ctx.builder.getContext()) }; - rt = StructType::get(ctx.builder.getContext(), makeArrayRef(pair)); + rt = StructType::get(ctx.builder.getContext(), ArrayRef(pair)); } else if (allunbox) { props.cc = jl_returninfo_t::Ghosts; @@ -7074,7 +7687,7 @@ static jl_returninfo_t get_specsig_function(jl_codectx_t &ctx, Module *M, Value rt = julia_type_to_llvm(ctx, jlrettype, &retboxed); assert(!retboxed); if (rt != getVoidTy(ctx.builder.getContext()) && deserves_sret(jlrettype, rt)) { - auto tracked = CountTrackedPointers(rt); + auto tracked = CountTrackedPointers(rt, true); assert(!tracked.derived); if (tracked.count && !tracked.all) props.return_roots = tracked.count; @@ -7280,7 +7893,7 @@ static std::string get_function_name(bool specsig, bool needsparams, const char if (unadorned_name[0] == '@') unadorned_name++; } - funcName << unadorned_name << "_" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); + funcName << unadorned_name << "_" << jl_atomic_fetch_add_relaxed(&globalUniqueGeneratedNames, 1); return funcName.str(); } @@ -7291,12 +7904,13 @@ static jl_llvm_functions_t jl_method_instance_t *lam, jl_code_info_t *src, jl_value_t *jlrettype, - jl_codegen_params_t ¶ms) + jl_codegen_params_t ¶ms, + size_t min_world, size_t max_world) { ++EmittedFunctions; // step 1. unpack AST and allocate codegen context for this function jl_llvm_functions_t declarations; - jl_codectx_t ctx(*params.tsctx.getContext(), params); + jl_codectx_t ctx(*params.tsctx.getContext(), params, min_world, max_world); jl_datatype_t *vatyp = NULL; JL_GC_PUSH2(&ctx.code, &vatyp); ctx.code = src->code; @@ -7348,10 +7962,14 @@ static jl_llvm_functions_t toplineno = lam->def.method->line; ctx.file = jl_symbol_name(lam->def.method->file); } - else if (jl_array_len(src->linetable) > 0) { - jl_value_t *locinfo = jl_array_ptr_ref(src->linetable, 0); - ctx.file = jl_symbol_name((jl_sym_t*)jl_fieldref_noalloc(locinfo, 2)); - toplineno = jl_unbox_int32(jl_fieldref(locinfo, 3)); + else if ((jl_value_t*)src->debuginfo != jl_nothing) { + // look for the file and line info of the original start of this block, as reported by lowering + jl_debuginfo_t *debuginfo = src->debuginfo; + while ((jl_value_t*)debuginfo->linetable != jl_nothing) + debuginfo = debuginfo->linetable; + ctx.file = jl_debuginfo_file(debuginfo); + struct jl_codeloc_t lineidx = jl_uncompress1_codeloc(debuginfo->codelocs, 0); + toplineno = std::max((int32_t)0, lineidx.line); } if (ctx.file.empty()) ctx.file = ""; @@ -7363,7 +7981,7 @@ static jl_llvm_functions_t debug_enabled = false; // step 2. process var-info lists to see what vars need boxing - int n_ssavalues = jl_is_long(src->ssavaluetypes) ? jl_unbox_long(src->ssavaluetypes) : jl_array_len(src->ssavaluetypes); + int n_ssavalues = jl_is_long(src->ssavaluetypes) ? jl_unbox_long(src->ssavaluetypes) : jl_array_nrows(src->ssavaluetypes); size_t vinfoslen = jl_array_dim0(src->slotflags); ctx.slots.resize(vinfoslen, jl_varinfo_t(ctx.builder.getContext())); assert(lam->specTypes); // the specTypes field should always be assigned @@ -7376,8 +7994,6 @@ static jl_llvm_functions_t bool specsig, needsparams; std::tie(specsig, needsparams) = uses_specsig(lam, jlrettype, params.params->prefer_specsig); - if (!src->inferred) - specsig = false; // step 3. some variable analysis size_t i; @@ -7393,7 +8009,7 @@ static jl_llvm_functions_t // OpaqueClosure implicitly loads the env if (i == 0 && ctx.is_opaque_closure) { if (jl_is_array(src->slottypes)) { - ty = jl_arrayref((jl_array_t*)src->slottypes, i); + ty = jl_array_ptr_ref((jl_array_t*)src->slottypes, i); } else { ty = (jl_value_t*)jl_any_type; @@ -7412,7 +8028,7 @@ static jl_llvm_functions_t jl_varinfo_t &varinfo = ctx.slots[i]; uint8_t flags = jl_array_uint8_ref(src->slotflags, i); varinfo.isSA = (jl_vinfo_sa(flags) != 0) || varinfo.isArgument; - varinfo.usedUndef = (jl_vinfo_usedundef(flags) != 0) || (!varinfo.isArgument && !src->inferred); + varinfo.usedUndef = (jl_vinfo_usedundef(flags) != 0) || !varinfo.isArgument; if (!varinfo.isArgument) { varinfo.value = mark_julia_type(ctx, (Value*)NULL, false, (jl_value_t*)jl_any_type); } @@ -7436,8 +8052,8 @@ static jl_llvm_functions_t //Safe because params holds ctx lock Module *M = TSM.getModuleUnlocked(); M->addModuleFlag(Module::Warning, "julia.debug_level", ctx.emission_context.debug_level); - jl_debugcache_t debuginfo; - debuginfo.initialize(M); + jl_debugcache_t debugcache; + debugcache.initialize(M); jl_returninfo_t returninfo = {}; Function *f = NULL; bool has_sret = false; @@ -7490,7 +8106,7 @@ static jl_llvm_functions_t // case the apply-generic call can re-use the original box for the return int retarg = [stmts, nreq]() { int retarg = -1; - for (size_t i = 0; i < jl_array_len(stmts); ++i) { + for (size_t i = 0; i < jl_array_nrows(stmts); ++i) { jl_value_t *stmt = jl_array_ptr_ref(stmts, i); if (jl_is_returnnode(stmt)) { stmt = jl_returnnode_value(stmt); @@ -7511,7 +8127,7 @@ static jl_llvm_functions_t }(); std::string wrapName; - raw_string_ostream(wrapName) << "jfptr_" << ctx.name << "_" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); + raw_string_ostream(wrapName) << "jfptr_" << ctx.name << "_" << jl_atomic_fetch_add_relaxed(&globalUniqueGeneratedNames, 1); declarations.functionObject = wrapName; (void)gen_invoke_wrapper(lam, jlrettype, returninfo, retarg, declarations.functionObject, M, ctx.emission_context); // TODO: add attributes: maybe_mark_argument_dereferenceable(Arg, argType) @@ -7589,11 +8205,11 @@ static jl_llvm_functions_t topfile = dbuilder.createFile(ctx.file, "."); DISubroutineType *subrty; if (ctx.emission_context.debug_level <= 1) - subrty = debuginfo.jl_di_func_null_sig; + subrty = debugcache.jl_di_func_null_sig; else if (!specsig) - subrty = debuginfo.jl_di_func_sig; + subrty = debugcache.jl_di_func_sig; else - subrty = get_specsig_di(ctx, debuginfo, jlrettype, lam->specTypes, dbuilder); + subrty = get_specsig_di(ctx, debugcache, jlrettype, lam->specTypes, dbuilder); SP = dbuilder.createFunction(nullptr ,dbgFuncName // Name ,f->getName() // LinkageName @@ -7624,7 +8240,7 @@ static jl_llvm_functions_t topfile, // File toplineno == -1 ? 0 : toplineno, // Line // Variable type - julia_type_to_di(ctx, debuginfo, varinfo.value.typ, &dbuilder, false), + julia_type_to_di(ctx, debugcache, varinfo.value.typ, &dbuilder, false), AlwaysPreserve, // May be deleted if optimized out DINode::FlagZero); // Flags (TODO: Do we need any) } @@ -7635,7 +8251,7 @@ static jl_llvm_functions_t has_sret + nreq + 1, // Argument number (1-based) topfile, // File toplineno == -1 ? 0 : toplineno, // Line (for now, use lineno of the function) - julia_type_to_di(ctx, debuginfo, ctx.slots[ctx.vaSlot].value.typ, &dbuilder, false), + julia_type_to_di(ctx, debugcache, ctx.slots[ctx.vaSlot].value.typ, &dbuilder, false), AlwaysPreserve, // May be deleted if optimized out DINode::FlagZero); // Flags (TODO: Do we need any) } @@ -7650,7 +8266,7 @@ static jl_llvm_functions_t jl_symbol_name(s), // Variable name topfile, // File toplineno == -1 ? 0 : toplineno, // Line (for now, use lineno of the function) - julia_type_to_di(ctx, debuginfo, varinfo.value.typ, &dbuilder, false), // Variable type + julia_type_to_di(ctx, debugcache, varinfo.value.typ, &dbuilder, false), // Variable type AlwaysPreserve, // May be deleted if optimized out DINode::FlagZero // Flags (TODO: Do we need any) ); @@ -7692,7 +8308,7 @@ static jl_llvm_functions_t // step 7. allocate local variables slots // must be in the first basic block for the llvm mem2reg pass to work - auto allocate_local = [&](jl_varinfo_t &varinfo, jl_sym_t *s) { + auto allocate_local = [&ctx, &dbuilder, &debugcache, topdebugloc, va, debug_enabled, M](jl_varinfo_t &varinfo, jl_sym_t *s, int i) { jl_value_t *jt = varinfo.value.typ; assert(!varinfo.boxroot); // variables shouldn't have memory locs already if (varinfo.value.constant) { @@ -7700,10 +8316,10 @@ static jl_llvm_functions_t alloc_def_flag(ctx, varinfo); return; } - else if (varinfo.isArgument && !(specsig && i == (size_t)ctx.vaSlot)) { - // if we can unbox it, just use the input pointer - if (i != (size_t)ctx.vaSlot && jl_is_concrete_immutable(jt)) - return; + else if (varinfo.isArgument && (!va || ctx.vaSlot == -1 || i != ctx.vaSlot)) { + // just use the input pointer, if we have it + // (we will need to attach debuginfo later to it) + return; } else if (jl_is_uniontype(jt)) { bool allunbox; @@ -7714,6 +8330,7 @@ static jl_llvm_functions_t varinfo.value = mark_julia_slot(lv, jt, NULL, ctx.tbaa().tbaa_stack); varinfo.pTIndex = emit_static_alloca(ctx, getInt8Ty(ctx.builder.getContext())); setName(ctx.emission_context, varinfo.pTIndex, "tindex"); + // TODO: attach debug metadata to this variable } else if (allunbox) { // all ghost values just need a selector allocated @@ -7722,6 +8339,7 @@ static jl_llvm_functions_t varinfo.pTIndex = lv; varinfo.value.tbaa = NULL; varinfo.value.isboxed = false; + // TODO: attach debug metadata to this variable } if (lv || allunbox) alloc_def_flag(ctx, varinfo); @@ -7741,36 +8359,28 @@ static jl_llvm_functions_t varinfo.value = mark_julia_slot(lv, jt, NULL, ctx.tbaa().tbaa_stack); alloc_def_flag(ctx, varinfo); if (debug_enabled && varinfo.dinfo) { - assert((Metadata*)varinfo.dinfo->getType() != debuginfo.jl_pvalue_dillvmt); + assert((Metadata*)varinfo.dinfo->getType() != debugcache.jl_pvalue_dillvmt); dbuilder.insertDeclare(lv, varinfo.dinfo, dbuilder.createExpression(), topdebugloc, ctx.builder.GetInsertBlock()); } return; } - if (!varinfo.isArgument || // always need a slot if the variable is assigned - specsig || // for arguments, give them stack slots if they aren't in `argArray` (otherwise, will use that pointer) - (va && (int)i == ctx.vaSlot) || // or it's the va arg tuple - i == 0) { // or it is the first argument (which isn't in `argArray`) - AllocaInst *av = new AllocaInst(ctx.types().T_prjlvalue, M->getDataLayout().getAllocaAddrSpace(), - nullptr, Align(sizeof(jl_value_t*)), jl_symbol_name(s), /*InsertBefore*/ctx.topalloca); - StoreInst *SI = new StoreInst(Constant::getNullValue(ctx.types().T_prjlvalue), av, false, Align(sizeof(void*))); - SI->insertAfter(ctx.topalloca); - varinfo.boxroot = av; - if (debug_enabled && varinfo.dinfo) { - DIExpression *expr; - if ((Metadata*)varinfo.dinfo->getType() == debuginfo.jl_pvalue_dillvmt) { - expr = dbuilder.createExpression(); - } - else { - SmallVector addr; - addr.push_back(llvm::dwarf::DW_OP_deref); - expr = dbuilder.createExpression(addr); - } - dbuilder.insertDeclare(av, varinfo.dinfo, expr, - topdebugloc, - ctx.builder.GetInsertBlock()); - } + // otherwise give it a boxroot in this function + AllocaInst *av = new AllocaInst(ctx.types().T_prjlvalue, M->getDataLayout().getAllocaAddrSpace(), + nullptr, Align(sizeof(jl_value_t*)), jl_symbol_name(s), /*InsertBefore*/ctx.topalloca); + StoreInst *SI = new StoreInst(Constant::getNullValue(ctx.types().T_prjlvalue), av, false, Align(sizeof(void*))); + SI->insertAfter(ctx.topalloca); + varinfo.boxroot = av; + if (debug_enabled && varinfo.dinfo) { + SmallVector addr; + DIExpression *expr; + if ((Metadata*)varinfo.dinfo->getType() != debugcache.jl_pvalue_dillvmt) + addr.push_back(llvm::dwarf::DW_OP_deref); + expr = dbuilder.createExpression(addr); + dbuilder.insertDeclare(av, varinfo.dinfo, expr, + topdebugloc, + ctx.builder.GetInsertBlock()); } }; @@ -7784,7 +8394,7 @@ static jl_llvm_functions_t varinfo.usedUndef = false; continue; } - allocate_local(varinfo, s); + allocate_local(varinfo, s, (int)i); } std::map upsilon_to_phic; @@ -7793,12 +8403,21 @@ static jl_llvm_functions_t // yield to them. // Also count ssavalue uses. { - for (size_t i = 0; i < jl_array_len(stmts); ++i) { + for (size_t i = 0; i < jl_array_nrows(stmts); ++i) { jl_value_t *stmt = jl_array_ptr_ref(stmts, i); auto scan_ssavalue = [&](jl_value_t *val) { if (jl_is_ssavalue(val)) { - ctx.ssavalue_usecount[((jl_ssavalue_t*)val)->id-1] += 1; + size_t ssa_idx = ((jl_ssavalue_t*)val)->id-1; + /* + * We technically allow out of bounds SSAValues in dead IR, so make + * sure to bounds check this here. It's still not *good* to leave + * dead code in the IR, because this will conservatively overcount + * it, but let's at least make it not crash. + */ + if (ssa_idx < ctx.ssavalue_usecount.size()) { + ctx.ssavalue_usecount[ssa_idx] += 1; + } return true; } return false; @@ -7807,7 +8426,7 @@ static jl_llvm_functions_t if (jl_is_phicnode(stmt)) { jl_array_t *values = (jl_array_t*)jl_fieldref_noalloc(stmt, 0); - for (size_t j = 0; j < jl_array_len(values); ++j) { + for (size_t j = 0; j < jl_array_nrows(values); ++j) { jl_value_t *val = jl_array_ptr_ref(values, j); assert(jl_is_ssavalue(val)); upsilon_to_phic[((jl_ssavalue_t*)val)->id] = i; @@ -7818,7 +8437,7 @@ static jl_llvm_functions_t vi.used = true; vi.isVolatile = true; vi.value = mark_julia_type(ctx, (Value*)NULL, false, typ); - allocate_local(vi, jl_symbol("phic")); + allocate_local(vi, jl_symbol("phic"), -1); } } } @@ -7958,12 +8577,12 @@ static jl_llvm_functions_t ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, argPtr, Align(sizeof(void*))), false, vi.value.typ)); theArg = mark_julia_type(ctx, load, true, vi.value.typ); - if (debug_enabled && vi.dinfo && !vi.boxroot && !vi.value.V) { + if (debug_enabled && vi.dinfo && !vi.boxroot) { SmallVector addr; addr.push_back(llvm::dwarf::DW_OP_deref); addr.push_back(llvm::dwarf::DW_OP_plus_uconst); addr.push_back((i - 1) * sizeof(void*)); - if ((Metadata*)vi.dinfo->getType() != debuginfo.jl_pvalue_dillvmt) + if ((Metadata*)vi.dinfo->getType() != debugcache.jl_pvalue_dillvmt) addr.push_back(llvm::dwarf::DW_OP_deref); dbuilder.insertDeclare(pargArray, vi.dinfo, dbuilder.createExpression(addr), topdebugloc, @@ -7977,21 +8596,15 @@ static jl_llvm_functions_t assert(vi.value.V == NULL && "unexpected variable slot created for argument"); // keep track of original (possibly boxed) value to avoid re-boxing or moving vi.value = theArg; - if (specsig && theArg.V && debug_enabled && vi.dinfo) { - SmallVector addr; - Value *parg; + if (debug_enabled && vi.dinfo && theArg.V) { if (theArg.ispointer()) { - parg = theArg.V; - if ((Metadata*)vi.dinfo->getType() != debuginfo.jl_pvalue_dillvmt) - addr.push_back(llvm::dwarf::DW_OP_deref); + dbuilder.insertDeclare(theArg.V, vi.dinfo, dbuilder.createExpression(), + topdebugloc, ctx.builder.GetInsertBlock()); } else { - parg = ctx.builder.CreateAlloca(theArg.V->getType(), NULL, jl_symbol_name(s)); - ctx.builder.CreateStore(theArg.V, parg); + dbuilder.insertDbgValueIntrinsic(theArg.V, vi.dinfo, dbuilder.createExpression(), + topdebugloc, ctx.builder.GetInsertBlock()); } - dbuilder.insertDeclare(parg, vi.dinfo, dbuilder.createExpression(addr), - topdebugloc, - ctx.builder.GetInsertBlock()); } } else { @@ -8019,12 +8632,12 @@ static jl_llvm_functions_t vargs[i - nreq] = get_specsig_arg(argType, llvmArgType, isboxed); } if (jl_is_concrete_type(vi.value.typ)) { - jl_cgval_t tuple = emit_new_struct(ctx, vi.value.typ, ctx.nvargs, vargs.data()); + jl_cgval_t tuple = emit_new_struct(ctx, vi.value.typ, ctx.nvargs, vargs); emit_varinfo_assign(ctx, vi, tuple); } else { restTuple = emit_jlcall(ctx, jltuple_func, Constant::getNullValue(ctx.types().T_prjlvalue), - vargs.data(), ctx.nvargs, julia_call); + vargs, ctx.nvargs, julia_call); jl_cgval_t tuple = mark_julia_type(ctx, restTuple, true, vi.value.typ); emit_varinfo_assign(ctx, vi, tuple); } @@ -8055,7 +8668,7 @@ static jl_llvm_functions_t return (!jl_is_submodule(mod, jl_base_module) && !jl_is_submodule(mod, jl_core_module)); }; - auto in_tracked_path = [] (StringRef file) { + auto in_tracked_path = [] (StringRef file) { // falls within an explicitly set file or directory return jl_options.tracked_path != NULL && file.startswith(jl_options.tracked_path); }; bool mod_is_user_mod = in_user_mod(ctx.module); @@ -8064,89 +8677,115 @@ static jl_llvm_functions_t DebugLoc loc; StringRef file; ssize_t line; + ssize_t line0; // if this represents pc=1, then also cover the entry to the function (pc=0) bool is_user_code; - bool is_tracked; // falls within an explicitly set file or directory - unsigned inlined_at; - bool operator ==(const DebugLineTable &other) const { - return other.loc == loc && other.file == file && other.line == line && other.is_user_code == is_user_code && other.is_tracked == is_tracked && other.inlined_at == inlined_at; - } + int32_t edgeid; + bool sameframe(const DebugLineTable &other) const { + // detect if the line info for this frame is unchanged (equivalent to loc == other.loc ignoring the inlined_at field) + return other.edgeid == edgeid && other.line == line; + }; }; - SmallVector linetable; - { // populate the linetable data format - assert(jl_is_array(src->linetable)); - size_t nlocs = jl_array_len(src->linetable); - std::map, DISubprogram*> subprograms; - linetable.resize(nlocs + 1); - DebugLineTable &topinfo = linetable[0]; - topinfo.file = ctx.file; - topinfo.line = toplineno; - topinfo.is_user_code = mod_is_user_mod; - topinfo.is_tracked = mod_is_tracked; - topinfo.inlined_at = 0; - topinfo.loc = topdebugloc; - for (size_t i = 0; i < nlocs; i++) { - // LineInfoNode(mod::Module, method::Any, file::Symbol, line::Int32, inlined_at::Int32) - jl_value_t *locinfo = jl_array_ptr_ref(src->linetable, i); - DebugLineTable &info = linetable[i + 1]; - assert(jl_typetagis(locinfo, jl_lineinfonode_type)); - jl_module_t *module = (jl_module_t*)jl_fieldref_noalloc(locinfo, 0); - jl_value_t *method = jl_fieldref_noalloc(locinfo, 1); - jl_sym_t *filesym = (jl_sym_t*)jl_fieldref_noalloc(locinfo, 2); - info.line = jl_unbox_int32(jl_fieldref(locinfo, 3)); - info.inlined_at = jl_unbox_int32(jl_fieldref(locinfo, 4)); - assert(info.inlined_at <= i); - info.file = jl_symbol_name(filesym); - if (info.file.empty()) - info.file = ""; - if (module == ctx.module) - info.is_user_code = mod_is_user_mod; - else - info.is_user_code = in_user_mod(module); - info.is_tracked = in_tracked_path(info.file); - if (debug_enabled) { - StringRef fname; - if (jl_is_method_instance(method)) - method = ((jl_method_instance_t*)method)->def.value; - if (jl_is_method(method)) - method = (jl_value_t*)((jl_method_t*)method)->name; - if (jl_is_symbol(method)) - fname = jl_symbol_name((jl_sym_t*)method); - if (fname.empty()) - fname = "macro expansion"; - if (info.inlined_at == 0 && info.file == ctx.file) { // if everything matches, emit a toplevel line number - info.loc = DILocation::get(ctx.builder.getContext(), info.line, 0, SP, NULL); + DebugLineTable topinfo; + topinfo.file = ctx.file; + topinfo.line = toplineno; + topinfo.line0 = 0; + topinfo.is_user_code = mod_is_user_mod; + topinfo.loc = topdebugloc; + topinfo.edgeid = 0; + std::map, DISubprogram*> subprograms; + SmallVector prev_lineinfo, new_lineinfo; + auto update_lineinfo = [&] (size_t pc) { + std::function append_lineinfo = + [&] (jl_debuginfo_t *debuginfo, jl_value_t *func, size_t to, size_t pc) -> bool { + while (1) { + if (!jl_is_symbol(debuginfo->def)) // this is a path + func = debuginfo->def; // this is inlined + struct jl_codeloc_t lineidx = jl_uncompress1_codeloc(debuginfo->codelocs, pc); + size_t i = lineidx.line; + if (i < 0) // pc out of range: broken debuginfo? + return false; + if (i == 0 && lineidx.to == 0) // no update + return false; + if (pc > 0 && (jl_value_t*)debuginfo->linetable != jl_nothing) { + // indirection node + if (!append_lineinfo(debuginfo->linetable, func, to, i)) + return false; // no update } - else { // otherwise, describe this as an inlining frame - DISubprogram *&inl_SP = subprograms[std::make_tuple(fname, info.file)]; - if (inl_SP == NULL) { - DIFile *difile = dbuilder.createFile(info.file, "."); - inl_SP = dbuilder.createFunction(difile - ,std::string(fname) + ";" // Name - ,fname // LinkageName - ,difile // File - ,0 // LineNo - ,debuginfo.jl_di_func_null_sig // Ty - ,0 // ScopeLine - ,DINode::FlagZero // Flags - ,DISubprogram::SPFlagDefinition | DISubprogram::SPFlagOptimized // SPFlags - ,nullptr // Template Parameters - ,nullptr // Template Declaration - ,nullptr // ThrownTypes - ); + else { + // actual node + DebugLineTable info; + info.edgeid = to; + jl_module_t *modu = func ? jl_debuginfo_module1(func) : NULL; + if (modu == NULL) + modu = ctx.module; + info.file = jl_debuginfo_file1(debuginfo); + info.line = i; + info.line0 = 0; + if (pc == 1) { + struct jl_codeloc_t lineidx = jl_uncompress1_codeloc(debuginfo->codelocs, 0); + assert(lineidx.to == 0 && lineidx.pc == 0); + if (lineidx.line > 0 && info.line != lineidx.line) + info.line0 = lineidx.line; } - DebugLoc inl_loc = (info.inlined_at == 0) ? DebugLoc(DILocation::get(ctx.builder.getContext(), 0, 0, SP, NULL)) : linetable[info.inlined_at].loc; - info.loc = DILocation::get(ctx.builder.getContext(), info.line, 0, inl_SP, inl_loc); + if (info.file.empty()) + info.file = ""; + if (modu == ctx.module) + info.is_user_code = mod_is_user_mod; + else + info.is_user_code = in_user_mod(modu); + if (debug_enabled) { + StringRef fname = jl_debuginfo_name(func); + if (new_lineinfo.empty() && info.file == ctx.file) { // if everything matches, emit a toplevel line number + info.loc = DILocation::get(ctx.builder.getContext(), info.line, 0, SP, NULL); + } + else { // otherwise, describe this as an inlining frame + DebugLoc inl_loc = new_lineinfo.empty() ? DebugLoc(DILocation::get(ctx.builder.getContext(), 0, 0, SP, NULL)) : new_lineinfo.back().loc; + DISubprogram *&inl_SP = subprograms[std::make_tuple(fname, info.file)]; + if (inl_SP == NULL) { + DIFile *difile = dbuilder.createFile(info.file, "."); + inl_SP = dbuilder.createFunction(difile + ,std::string(fname) + ";" // Name + ,fname // LinkageName + ,difile // File + ,0 // LineNo + ,debugcache.jl_di_func_null_sig // Ty + ,0 // ScopeLine + ,DINode::FlagZero // Flags + ,DISubprogram::SPFlagDefinition | DISubprogram::SPFlagOptimized // SPFlags + ,nullptr // Template Parameters + ,nullptr // Template Declaration + ,nullptr // ThrownTypes + ); + } + info.loc = DILocation::get(ctx.builder.getContext(), info.line, 0, inl_SP, inl_loc); + } + } + new_lineinfo.push_back(info); } + to = lineidx.to; + if (to == 0) + return true; + pc = lineidx.pc; + debuginfo = (jl_debuginfo_t*)jl_svecref(debuginfo->edges, to - 1); + func = NULL; } - } - } + }; + prev_lineinfo.resize(0); + std::swap(prev_lineinfo, new_lineinfo); + bool updated = append_lineinfo(src->debuginfo, (jl_value_t*)lam, 0, pc + 1); + if (!updated) + std::swap(prev_lineinfo, new_lineinfo); + else + assert(new_lineinfo.size() > 0); + return updated; + }; SmallVector aliasscopes; MDNode* current_aliasscope = nullptr; SmallVector scope_stack; SmallVector scope_list_stack; { - size_t nstmts = jl_array_len(stmts); + size_t nstmts = jl_array_nrows(stmts); aliasscopes.resize(nstmts + 1, nullptr); MDBuilder mbuilder(ctx.builder.getContext()); MDNode *alias_domain = mbuilder.createAliasScopeDomain(ctx.name); @@ -8231,54 +8870,81 @@ static jl_llvm_functions_t cursor = -1; }; + // If a pkgimage or sysimage is being generated, disable tracking. + // This means sysimage build or pkgimage precompilation workloads aren't tracked. auto do_coverage = [&] (bool in_user_code, bool is_tracked) { - return (coverage_mode == JL_LOG_ALL || + return (jl_generating_output() == 0 && + (coverage_mode == JL_LOG_ALL || (in_user_code && coverage_mode == JL_LOG_USER) || - (is_tracked && coverage_mode == JL_LOG_PATH)); + (is_tracked && coverage_mode == JL_LOG_PATH))); }; auto do_malloc_log = [&] (bool in_user_code, bool is_tracked) { - return (malloc_log_mode == JL_LOG_ALL || + return (jl_generating_output() == 0 && + (malloc_log_mode == JL_LOG_ALL || (in_user_code && malloc_log_mode == JL_LOG_USER) || - (is_tracked && malloc_log_mode == JL_LOG_PATH)); + (is_tracked && malloc_log_mode == JL_LOG_PATH))); }; - SmallVector current_lineinfo, new_lineinfo; - auto coverageVisitStmt = [&] (size_t dbg) { - if (dbg == 0 || dbg >= linetable.size()) - return; - // Compute inlining stack for current line, inner frame first - while (dbg) { - new_lineinfo.push_back(dbg); - dbg = linetable[dbg].inlined_at; - } + auto coverageVisitStmt = [&] () { // Visit frames which differ from previous statement as tracked in - // current_lineinfo (tracked outer frame first). - current_lineinfo.resize(new_lineinfo.size(), 0); + // prev_lineinfo (tracked outer frame first). + size_t dbg; for (dbg = 0; dbg < new_lineinfo.size(); dbg++) { - unsigned newdbg = new_lineinfo[new_lineinfo.size() - dbg - 1]; - if (newdbg != current_lineinfo[dbg]) { - current_lineinfo[dbg] = newdbg; - const auto &info = linetable[newdbg]; - if (do_coverage(info.is_user_code, info.is_tracked)) - coverageVisitLine(ctx, info.file, info.line); + if (dbg >= prev_lineinfo.size() || !new_lineinfo[dbg].sameframe(prev_lineinfo[dbg])) + break; + } + for (; dbg < new_lineinfo.size(); dbg++) { + const auto &newdbg = new_lineinfo[dbg]; + bool is_tracked = in_tracked_path(newdbg.file); + if (do_coverage(newdbg.is_user_code, is_tracked)) { + if (newdbg.line0 != 0 && (dbg >= prev_lineinfo.size() || newdbg.edgeid != prev_lineinfo[dbg].edgeid || newdbg.line0 != prev_lineinfo[dbg].line)) + coverageVisitLine(ctx, newdbg.file, newdbg.line0); + coverageVisitLine(ctx, newdbg.file, newdbg.line); } } - new_lineinfo.clear(); }; - auto mallocVisitStmt = [&] (unsigned dbg, Value *sync) { - if (!do_malloc_log(mod_is_user_mod, mod_is_tracked) || dbg == 0) { + auto mallocVisitStmt = [&] (Value *sync, bool have_dbg_update) { + if (!do_malloc_log(mod_is_user_mod, mod_is_tracked) || !have_dbg_update) { + // TODD: add || new_lineinfo[0].sameframe(prev_lineinfo[0])) above, but currently this breaks the test for it (by making an optimization better) if (do_malloc_log(true, mod_is_tracked) && sync) ctx.builder.CreateCall(prepare_call(sync_gc_total_bytes_func), {sync}); return; } - while (linetable[dbg].inlined_at) - dbg = linetable[dbg].inlined_at; - mallocVisitLine(ctx, ctx.file, linetable[dbg].line, sync); + mallocVisitLine(ctx, new_lineinfo[0].file, new_lineinfo[0].line, sync); }; if (coverage_mode != JL_LOG_NONE) { // record all lines that could be covered - for (const auto &info : linetable) - if (do_coverage(info.is_user_code, info.is_tracked)) - jl_coverage_alloc_line(info.file, info.line); + std::function record_line_exists = [&](jl_debuginfo_t *debuginfo, jl_value_t *func) { + if (!jl_is_symbol(debuginfo->def)) // this is a path + func = debuginfo->def; // this is inlined + for (size_t i = 0; i < jl_svec_len(debuginfo->edges); i++) { + jl_debuginfo_t *edge = (jl_debuginfo_t*)jl_svecref(debuginfo->edges, i); + record_line_exists(edge, NULL); + } + while ((jl_value_t*)debuginfo->linetable != jl_nothing) + debuginfo = debuginfo->linetable; + jl_module_t *modu = func ? jl_debuginfo_module1(func) : NULL; + if (modu == NULL) + modu = ctx.module; + StringRef file = jl_debuginfo_file1(debuginfo); + if (file.empty()) + file = ""; + bool is_user_code; + if (modu == ctx.module) + is_user_code = mod_is_user_mod; + else + is_user_code = in_user_mod(modu); + bool is_tracked = in_tracked_path(file); + if (do_coverage(is_user_code, is_tracked)) { + for (size_t pc = 0; 1; pc++) { + struct jl_codeloc_t lineidx = jl_uncompress1_codeloc(debuginfo->codelocs, pc); + if (lineidx.line == -1) + break; + if (lineidx.line > 0) + jl_coverage_alloc_line(file, lineidx.line); + } + } + }; + record_line_exists(src->debuginfo, (jl_value_t*)lam); } come_from_bb[0] = ctx.builder.GetInsertBlock(); @@ -8301,14 +8967,13 @@ static jl_llvm_functions_t // targets. if (i + 2 <= stmtslen) branch_targets.insert(i + 2); - } else if (jl_is_expr(stmt)) { - if (((jl_expr_t*)stmt)->head == jl_enter_sym) { - branch_targets.insert(i + 1); - if (i + 2 <= stmtslen) - branch_targets.insert(i + 2); - int dest = jl_unbox_long(jl_array_ptr_ref(((jl_expr_t*)stmt)->args, 0)); - branch_targets.insert(dest); - } + } else if (jl_is_enternode(stmt)) { + branch_targets.insert(i + 1); + if (i + 2 <= stmtslen) + branch_targets.insert(i + 2); + size_t catch_dest = jl_enternode_catch_dest(stmt); + if (catch_dest) + branch_targets.insert(catch_dest); } else if (jl_is_gotonode(stmt)) { int dest = jl_gotonode_label(stmt); branch_targets.insert(dest); @@ -8316,8 +8981,8 @@ static jl_llvm_functions_t branch_targets.insert(i + 2); } else if (jl_is_phinode(stmt)) { jl_array_t *edges = (jl_array_t*)jl_fieldref_noalloc(stmt, 0); - for (size_t j = 0; j < jl_array_len(edges); ++j) { - size_t edge = ((int32_t*)jl_array_data(edges))[j]; + for (size_t j = 0; j < jl_array_nrows(edges); ++j) { + size_t edge = jl_array_data(edges, int32_t)[j]; if (edge == i) branch_targets.insert(i + 1); } @@ -8331,30 +8996,23 @@ static jl_llvm_functions_t BB[label] = bb; } + new_lineinfo.push_back(topinfo); Value *sync_bytes = nullptr; if (do_malloc_log(true, mod_is_tracked)) sync_bytes = ctx.builder.CreateCall(prepare_call(diff_gc_total_bytes_func), {}); - { // coverage for the function definition line number - const auto &topinfo = linetable[0]; - if (linetable.size() > 1) { - if (topinfo == linetable[1]) - current_lineinfo.push_back(1); - } - if (do_coverage(topinfo.is_user_code, topinfo.is_tracked)) - coverageVisitLine(ctx, topinfo.file, topinfo.line); - } + // coverage for the function definition line number (topinfo) + coverageVisitStmt(); find_next_stmt(0); while (cursor != -1) { - int32_t debuginfoloc = ((int32_t*)jl_array_data(src->codelocs))[cursor]; - if (debuginfoloc > 0) { + bool have_dbg_update = update_lineinfo(cursor); + if (have_dbg_update) { if (debug_enabled) - ctx.builder.SetCurrentDebugLocation(linetable[debuginfoloc].loc); - coverageVisitStmt(debuginfoloc); + ctx.builder.SetCurrentDebugLocation(new_lineinfo.back().loc); + coverageVisitStmt(); } ctx.noalias().aliasscope.current = aliasscopes[cursor]; jl_value_t *stmt = jl_array_ptr_ref(stmts, cursor); - jl_expr_t *expr = jl_is_expr(stmt) ? (jl_expr_t*)stmt : nullptr; if (jl_is_returnnode(stmt)) { jl_value_t *retexpr = jl_returnnode_value(stmt); if (retexpr == NULL) { @@ -8407,7 +9065,7 @@ static jl_llvm_functions_t // also need to account for the possibility the return object is boxed // and avoid / skip copying it to the stack isboxed_union = ctx.builder.CreateICmpNE( - ctx.builder.CreateAnd(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)), + ctx.builder.CreateAnd(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER)), ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0)); data = ctx.builder.CreateSelect(isboxed_union, retvalinfo.Vboxed, data); } @@ -8416,7 +9074,7 @@ static jl_llvm_functions_t // treat this as a simple boxed returninfo //assert(retvalinfo.isboxed); tindex = compute_tindex_unboxed(ctx, retvalinfo, jlrettype); - tindex = ctx.builder.CreateOr(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)); + tindex = ctx.builder.CreateOr(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER)); data = boxed(ctx, retvalinfo); sret = NULL; } @@ -8459,7 +9117,7 @@ static jl_llvm_functions_t } } - mallocVisitStmt(debuginfoloc, sync_bytes); + mallocVisitStmt(sync_bytes, have_dbg_update); if (toplevel || ctx.is_opaque_closure) ctx.builder.CreateStore(last_age, world_age_field); assert(type_is_ghost(retty) || returninfo.cc == jl_returninfo_t::SRet || @@ -8489,7 +9147,7 @@ static jl_llvm_functions_t jl_value_t *cond = jl_gotoifnot_cond(stmt); int lname = jl_gotoifnot_label(stmt); Value *isfalse = emit_condition(ctx, cond, "if"); - mallocVisitStmt(debuginfoloc, nullptr); + mallocVisitStmt(nullptr, have_dbg_update); come_from_bb[cursor+1] = ctx.builder.GetInsertBlock(); workstack.push_back(lname - 1); BasicBlock *ifnot = BB[lname]; @@ -8508,32 +9166,64 @@ static jl_llvm_functions_t find_next_stmt(cursor + 1); continue; } - else if (expr && expr->head == jl_enter_sym) { - jl_value_t **args = (jl_value_t**)jl_array_data(expr->args); - - assert(jl_is_long(args[0])); - int lname = jl_unbox_long(args[0]); - // Save exception stack depth at enter for use in pop_exception - Value *excstack_state = - ctx.builder.CreateCall(prepare_call(jl_excstack_state_func)); - assert(!ctx.ssavalue_assigned[cursor]); - ctx.SAvalues[cursor] = jl_cgval_t(excstack_state, (jl_value_t*)jl_ulong_type, NULL); - ctx.ssavalue_assigned[cursor] = true; - CallInst *sj = ctx.builder.CreateCall(prepare_call(except_enter_func)); - // We need to mark this on the call site as well. See issue #6757 - sj->setCanReturnTwice(); - Value *isz = ctx.builder.CreateICmpEQ(sj, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 0)); - BasicBlock *tryblk = BasicBlock::Create(ctx.builder.getContext(), "try", f); - BasicBlock *handlr = NULL; - handlr = BB[lname]; - workstack.push_back(lname - 1); - come_from_bb[cursor + 1] = ctx.builder.GetInsertBlock(); - ctx.builder.CreateCondBr(isz, tryblk, handlr); - ctx.builder.SetInsertPoint(tryblk); + else if (jl_is_enternode(stmt)) { + // For the two-arg version of :enter, twiddle the scope + Value *scope_ptr = NULL; + Value *old_scope = NULL; + jl_aliasinfo_t scope_ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_gcframe); + if (jl_enternode_scope(stmt)) { + jl_cgval_t new_scope = emit_expr(ctx, jl_enternode_scope(stmt)); + if (new_scope.typ == jl_bottom_type) { + // Probably dead code, but let's be loud about it in case it isn't, so we fail + // at the point of the miscompile, rather than later when something attempts to + // read the scope. + emit_error(ctx, "(INTERNAL ERROR): Attempted to execute EnterNode with bad scope"); + find_next_stmt(-1); + continue; + } + Value *new_scope_boxed = boxed(ctx, new_scope); + scope_ptr = get_scope_field(ctx); + old_scope = scope_ai.decorateInst( + ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, scope_ptr, ctx.types().alignof_ptr)); + scope_ai.decorateInst( + ctx.builder.CreateAlignedStore(new_scope_boxed, scope_ptr, ctx.types().alignof_ptr)); + ctx.scope_restore[cursor] = std::make_pair(old_scope, scope_ptr); + } + int lname = jl_enternode_catch_dest(stmt); + if (lname) { + // Save exception stack depth at enter for use in pop_exception + Value *excstack_state = + ctx.builder.CreateCall(prepare_call(jl_excstack_state_func), {get_current_task(ctx)}); + assert(!ctx.ssavalue_assigned[cursor]); + ctx.SAvalues[cursor] = jl_cgval_t(excstack_state, (jl_value_t*)jl_ulong_type, NULL); + ctx.ssavalue_assigned[cursor] = true; + // Actually enter the exception frame + CallInst *sj = ctx.builder.CreateCall(prepare_call(except_enter_func), {get_current_task(ctx)}); + // We need to mark this on the call site as well. See issue #6757 + sj->setCanReturnTwice(); + Value *isz = ctx.builder.CreateICmpEQ(sj, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 0)); + BasicBlock *tryblk = BasicBlock::Create(ctx.builder.getContext(), "try", f); + BasicBlock *catchpop = BasicBlock::Create(ctx.builder.getContext(), "catch_pop", f); + BasicBlock *handlr = NULL; + handlr = BB[lname]; + workstack.push_back(lname - 1); + come_from_bb[cursor + 1] = ctx.builder.GetInsertBlock(); + ctx.builder.CreateCondBr(isz, tryblk, catchpop); + ctx.builder.SetInsertPoint(catchpop); + { + ctx.builder.CreateCall(prepare_call(jlleave_func), {get_current_task(ctx), ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 1)}); + if (old_scope) { + scope_ai.decorateInst( + ctx.builder.CreateAlignedStore(old_scope, scope_ptr, ctx.types().alignof_ptr)); + } + ctx.builder.CreateBr(handlr); + } + ctx.builder.SetInsertPoint(tryblk); + } } else { emit_stmtpos(ctx, stmt, cursor); - mallocVisitStmt(debuginfoloc, nullptr); + mallocVisitStmt(nullptr, have_dbg_update); } find_next_stmt(cursor + 1); } @@ -8562,8 +9252,8 @@ static jl_llvm_functions_t jl_array_t *values = (jl_array_t*)jl_fieldref_noalloc(r, 1); PHINode *TindexN = cast_or_null(phi_result.TIndex); DenseSet preds; - for (size_t i = 0; i < jl_array_len(edges); ++i) { - size_t edge = ((int32_t*)jl_array_data(edges))[i]; + for (size_t i = 0; i < jl_array_nrows(edges); ++i) { + size_t edge = jl_array_data(edges, int32_t)[i]; jl_value_t *value = jl_array_ptr_ref(values, i); // This edge value is undef, handle it the same as if the edge wasn't listed at all if (!value) @@ -8582,7 +9272,7 @@ static jl_llvm_functions_t // Only codegen this branch once for each PHI (the expression must be the same on all branches) #ifndef NDEBUG for (size_t j = 0; j < i; ++j) { - size_t j_edge = ((int32_t*)jl_array_data(edges))[j]; + size_t j_edge = jl_array_data(edges, int32_t)[j]; if (j_edge == edge) { assert(jl_egal(value, jl_array_ptr_ref(values, j))); } @@ -8596,9 +9286,7 @@ static jl_llvm_functions_t // Can't use `llvm::SplitCriticalEdge` here because // we may have invalid phi nodes in the destination. BasicBlock *NewBB = BasicBlock::Create(terminator->getContext(), - FromBB->getName() + "." + PhiBB->getName() + "_crit_edge"); - Function::iterator FBBI = FromBB->getIterator(); - ctx.f->getBasicBlockList().insert(++FBBI, NewBB); // insert after existing block + FromBB->getName() + "." + PhiBB->getName() + "_crit_edge", FromBB->getParent(), FromBB->getNextNode()); // insert after existing block terminator->replaceSuccessorWith(PhiBB, NewBB); DebugLoc Loc = terminator->getDebugLoc(); terminator = BranchInst::Create(PhiBB); @@ -8662,7 +9350,7 @@ static jl_llvm_functions_t if (tindex == 0) { if (VN) V = boxed(ctx, val); - RTindex = ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80); + RTindex = ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER); } else { if (VN) @@ -8684,7 +9372,7 @@ static jl_llvm_functions_t if (dest) { // If dest is not set, this is a ghost union, the recipient of which // is often not prepared to handle a boxed representation of the ghost. - RTindex = ctx.builder.CreateOr(RTindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)); + RTindex = ctx.builder.CreateOr(RTindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER)); } new_union.TIndex = RTindex; } @@ -8692,8 +9380,8 @@ static jl_llvm_functions_t V = new_union.Vboxed ? new_union.Vboxed : Constant::getNullValue(ctx.types().T_prjlvalue); if (dest) { // basically, if !ghost union if (new_union.Vboxed != nullptr) { - Value *isboxed = ctx.builder.CreateICmpNE( // if 0x80 is set, we won't select this slot anyways - ctx.builder.CreateAnd(RTindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)), + Value *isboxed = ctx.builder.CreateICmpNE( // if UNION_BOX_MARKER is set, we won't select this slot anyways + ctx.builder.CreateAnd(RTindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER)), ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0)); skip = skip ? ctx.builder.CreateOr(isboxed, skip) : isboxed; } @@ -8738,7 +9426,7 @@ static jl_llvm_functions_t Value *undef = undef_value_for_type(VN->getType()); VN->addIncoming(undef, FromBB); if (TindexN) // let the runtime / optimizer know this is unknown / boxed / null, so that it won't try to union_move / copy it later - RTindex = ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80); + RTindex = ConstantInt::get(getInt8Ty(ctx.builder.getContext()), UNION_BOX_MARKER); } if (TindexN) TindexN->addIncoming(RTindex, FromBB); @@ -8752,6 +9440,7 @@ static jl_llvm_functions_t } for (PHINode *PN : ToDelete) { + // This basic block is statically unreachable, thus so is this PHINode PN->replaceAllUsesWith(UndefValue::get(PN->getType())); PN->eraseFromParent(); } @@ -8857,7 +9546,8 @@ jl_llvm_functions_t jl_emit_code( jl_method_instance_t *li, jl_code_info_t *src, jl_value_t *jlrettype, - jl_codegen_params_t ¶ms) + jl_codegen_params_t ¶ms, + size_t min_world, size_t max_world) { JL_TIMING(CODEGEN, CODEGEN_LLVM); jl_timing_show_func_sig((jl_value_t *)li->specTypes, JL_TIMING_DEFAULT_BLOCK); @@ -8867,7 +9557,7 @@ jl_llvm_functions_t jl_emit_code( compare_cgparams(params.params, &jl_default_cgparams)) && "functions compiled with custom codegen params must not be cached"); JL_TRY { - decls = emit_function(m, li, src, jlrettype, params); + decls = emit_function(m, li, src, jlrettype, params, min_world, max_world); auto stream = *jl_ExecutionEngine->get_dump_emitted_mi_name_stream(); if (stream) { jl_printf(stream, "%s\t", decls.specFunctionObject.c_str()); @@ -8888,9 +9578,12 @@ jl_llvm_functions_t jl_emit_code( decls.functionObject = ""; decls.specFunctionObject = ""; jl_printf((JL_STREAM*)STDERR_FILENO, "Internal error: encountered unexpected error during compilation of %s:\n", mname.c_str()); - jl_static_show((JL_STREAM*)STDERR_FILENO, jl_current_exception()); + jl_static_show((JL_STREAM*)STDERR_FILENO, jl_current_exception(jl_current_task)); jl_printf((JL_STREAM*)STDERR_FILENO, "\n"); jlbacktrace(); // written to STDERR_FILENO +#ifndef JL_NDEBUG + abort(); +#endif } return decls; @@ -8899,7 +9592,7 @@ jl_llvm_functions_t jl_emit_code( static jl_llvm_functions_t jl_emit_oc_wrapper(orc::ThreadSafeModule &m, jl_codegen_params_t ¶ms, jl_method_instance_t *mi, jl_value_t *rettype) { Module *M = m.getModuleUnlocked(); - jl_codectx_t ctx(M->getContext(), params); + jl_codectx_t ctx(M->getContext(), params, 0, 0); ctx.name = M->getModuleIdentifier().data(); std::string funcName = get_function_name(true, false, ctx.name, ctx.emission_context.TargetTriple); jl_llvm_functions_t declarations; @@ -8909,7 +9602,7 @@ static jl_llvm_functions_t jl_emit_oc_wrapper(orc::ThreadSafeModule &m, jl_codeg Function *gf_thunk = cast(returninfo.decl.getCallee()); jl_init_function(gf_thunk, ctx.emission_context.TargetTriple); size_t nrealargs = jl_nparams(mi->specTypes); - emit_cfunc_invalidate(gf_thunk, returninfo.cc, returninfo.return_roots, mi->specTypes, rettype, true, nrealargs, ctx.emission_context); + emit_cfunc_invalidate(gf_thunk, returninfo.cc, returninfo.return_roots, mi->specTypes, rettype, true, nrealargs, ctx.emission_context, ctx.min_world, ctx.max_world); declarations.specFunctionObject = funcName; } return declarations; @@ -8950,7 +9643,9 @@ jl_llvm_functions_t jl_emit_codeinst( return jl_llvm_functions_t(); // failed } } - jl_llvm_functions_t decls = jl_emit_code(m, codeinst->def, src, codeinst->rettype, params); + assert(jl_egal((jl_value_t*)jl_atomic_load_relaxed(&codeinst->debuginfo), (jl_value_t*)src->debuginfo) && "trying to generate code for a codeinst for an incompatible src"); + jl_llvm_functions_t decls = jl_emit_code(m, codeinst->def, src, codeinst->rettype, params, + jl_atomic_load_relaxed(&codeinst->min_world), jl_atomic_load_relaxed(&codeinst->max_world)); const std::string &specf = decls.specFunctionObject; const std::string &f = decls.functionObject; @@ -8971,38 +9666,41 @@ jl_llvm_functions_t jl_emit_codeinst( jl_add_code_in_flight(f, codeinst, DL); } - if (params.world) {// don't alter `inferred` when the code is not directly being used - jl_value_t *inferred = jl_atomic_load_relaxed(&codeinst->inferred); - // don't change inferred state - if (inferred) { - jl_method_t *def = codeinst->def->def.method; - if (// keep code when keeping everything - !(JL_DELETE_NON_INLINEABLE) || - // aggressively keep code when debugging level >= 2 - // note that this uses the global jl_options.debug_level, not the local emission_ctx.debug_level - jl_options.debug_level > 1) { - // update the stored code - if (inferred != (jl_value_t*)src) { - if (jl_is_method(def)) { - src = (jl_code_info_t*)jl_compress_ir(def, src); - assert(jl_is_string(src)); - codeinst->relocatability = jl_string_data(src)[jl_string_len(src)-1]; - } - jl_atomic_store_release(&codeinst->inferred, (jl_value_t*)src); - jl_gc_wb(codeinst, src); + jl_value_t *inferred = jl_atomic_load_relaxed(&codeinst->inferred); + // don't change inferred state + if (inferred) { + jl_method_t *def = codeinst->def->def.method; + if (// keep code when keeping everything + !(JL_DELETE_NON_INLINEABLE) || + // aggressively keep code when debugging level >= 2 + // note that this uses the global jl_options.debug_level, not the local emission_ctx.debug_level + jl_options.debug_level > 1) { + // update the stored code + if (inferred != (jl_value_t*)src) { + // TODO: it is somewhat unclear what it means to be mutating this + if (jl_is_method(def)) { + src = (jl_code_info_t*)jl_compress_ir(def, src); + assert(jl_is_string(src)); + codeinst->relocatability = jl_string_data(src)[jl_string_len(src)-1]; } + jl_atomic_store_release(&codeinst->inferred, (jl_value_t*)src); + jl_gc_wb(codeinst, src); } - // delete non-inlineable code, since it won't be needed again - // because we already emitted LLVM code from it and the native - // Julia-level optimization will never need to see it - else if (jl_is_method(def) && // don't delete toplevel code - inferred != jl_nothing && // and there is something to delete (test this before calling jl_ir_inlining_cost) - !effects_foldable(codeinst->ipo_purity_bits) && // don't delete code we may want for irinterp - ((jl_ir_inlining_cost(inferred) == UINT16_MAX) || // don't delete inlineable code - jl_atomic_load_relaxed(&codeinst->invoke) == jl_fptr_const_return_addr) && // unless it is constant - !(params.imaging_mode || jl_options.incremental)) { // don't delete code when generating a precompile file - jl_atomic_store_release(&codeinst->inferred, jl_nothing); - } + } + // delete non-inlineable code, since it won't be needed again + // because we already emitted LLVM code from it and the native + // Julia-level optimization will never need to see it + else if (jl_is_method(def) && // don't delete toplevel code + def->source != NULL && // don't delete code from optimized opaque closures that can't be reconstructed + inferred != jl_nothing && // and there is something to delete (test this before calling jl_ir_inlining_cost) + !effects_foldable(codeinst->ipo_purity_bits) && // don't delete code we may want for irinterp + ((jl_ir_inlining_cost(inferred) == UINT16_MAX) || // don't delete inlineable code + jl_atomic_load_relaxed(&codeinst->invoke) == jl_fptr_const_return_addr) && // unless it is constant + !(params.imaging_mode || jl_options.incremental)) { // don't delete code when generating a precompile file + // Never end up in a situation where the codeinst has no invoke, but also no source, so we never fall + // through the cracks of SOURCE_MODE_ABI. + jl_atomic_store_release(&codeinst->invoke, &jl_fptr_wait_for_compiled); + jl_atomic_store_release(&codeinst->inferred, jl_nothing); } } } @@ -9025,14 +9723,12 @@ void jl_compile_workqueue( auto proto = it.second; params.workqueue.pop_back(); // try to emit code for this item from the workqueue - assert(codeinst->min_world <= params.world && codeinst->max_world >= params.world && - "invalid world for code-instance"); StringRef preal_decl = ""; bool preal_specsig = false; auto invoke = jl_atomic_load_acquire(&codeinst->invoke); bool cache_valid = params.cache; // WARNING: isspecsig is protected by the codegen-lock. If that lock is removed, then the isspecsig load needs to be properly atomically sequenced with this. - if (cache_valid && invoke != NULL) { + if (cache_valid && invoke != NULL && invoke != &jl_fptr_wait_for_compiled) { auto fptr = jl_atomic_load_relaxed(&codeinst->specptr.fptr); if (fptr) { while (!(jl_atomic_load_acquire(&codeinst->specsigflags) & 0b10)) { @@ -9056,17 +9752,10 @@ void jl_compile_workqueue( // method body. See #34993 if (policy != CompilationPolicy::Default && jl_atomic_load_relaxed(&codeinst->inferred) == jl_nothing) { - src = jl_type_infer(codeinst->def, jl_atomic_load_acquire(&jl_world_counter), 0); - if (src) { - orc::ThreadSafeModule result_m = - jl_create_ts_module(name_from_method_instance(codeinst->def), - params.tsctx, params.DL, params.TargetTriple); - auto decls = jl_emit_code(result_m, codeinst->def, src, src->rettype, params); - if (result_m) - it = params.compiled_functions.insert(std::make_pair(codeinst, std::make_pair(std::move(result_m), std::move(decls)))).first; - } + // Codegen lock is held, so SOURCE_MODE_FORCE_SOURCE_UNCACHED is not required + codeinst = jl_type_infer(codeinst->def, jl_atomic_load_relaxed(&codeinst->max_world), 0, SOURCE_MODE_FORCE_SOURCE); } - else { + if (codeinst) { orc::ThreadSafeModule result_m = jl_create_ts_module(name_from_method_instance(codeinst->def), params.tsctx, params.DL, params.TargetTriple); @@ -9099,7 +9788,7 @@ void jl_compile_workqueue( jl_init_function(proto.decl, params.TargetTriple); size_t nrealargs = jl_nparams(codeinst->def->specTypes); // number of actual arguments being passed // TODO: maybe this can be cached in codeinst->specfptr? - emit_cfunc_invalidate(proto.decl, proto.cc, proto.return_roots, codeinst->def->specTypes, codeinst->rettype, false, nrealargs, params, preal); + emit_cfunc_invalidate(proto.decl, proto.cc, proto.return_roots, codeinst->def->specTypes, codeinst->rettype, false, nrealargs, params, preal, 0, 0); preal_decl = ""; // no need to fixup the name } else { @@ -9143,58 +9832,6 @@ static JuliaVariable *julia_const_gv(jl_value_t *val) return nullptr; } -// Handle FLOAT16 ABI v2 -#if JULIA_FLOAT16_ABI == 2 -static void makeCastCall(Module &M, StringRef wrapperName, StringRef calledName, FunctionType *FTwrapper, FunctionType *FTcalled, bool external) -{ - Function *calledFun = M.getFunction(calledName); - if (!calledFun) { - calledFun = Function::Create(FTcalled, Function::ExternalLinkage, calledName, M); - } - auto linkage = external ? Function::ExternalLinkage : Function::InternalLinkage; - auto wrapperFun = Function::Create(FTwrapper, linkage, wrapperName, M); - wrapperFun->addFnAttr(Attribute::AlwaysInline); - llvm::IRBuilder<> builder(BasicBlock::Create(M.getContext(), "top", wrapperFun)); - SmallVector CallArgs; - if (wrapperFun->arg_size() != calledFun->arg_size()){ - llvm::errs() << "FATAL ERROR: Can't match wrapper to called function"; - abort(); - } - for (auto wrapperArg = wrapperFun->arg_begin(), calledArg = calledFun->arg_begin(); - wrapperArg != wrapperFun->arg_end() && calledArg != calledFun->arg_end(); ++wrapperArg, ++calledArg) - { - CallArgs.push_back(builder.CreateBitCast(wrapperArg, calledArg->getType())); - } - auto val = builder.CreateCall(calledFun, CallArgs); - auto retval = builder.CreateBitCast(val,wrapperFun->getReturnType()); - builder.CreateRet(retval); -} - -void emitFloat16Wrappers(Module &M, bool external) -{ - auto &ctx = M.getContext(); - makeCastCall(M, "__gnu_h2f_ieee", "julia__gnu_h2f_ieee", FunctionType::get(Type::getFloatTy(ctx), { Type::getHalfTy(ctx) }, false), - FunctionType::get(Type::getFloatTy(ctx), { Type::getInt16Ty(ctx) }, false), external); - makeCastCall(M, "__extendhfsf2", "julia__gnu_h2f_ieee", FunctionType::get(Type::getFloatTy(ctx), { Type::getHalfTy(ctx) }, false), - FunctionType::get(Type::getFloatTy(ctx), { Type::getInt16Ty(ctx) }, false), external); - makeCastCall(M, "__gnu_f2h_ieee", "julia__gnu_f2h_ieee", FunctionType::get(Type::getHalfTy(ctx), { Type::getFloatTy(ctx) }, false), - FunctionType::get(Type::getInt16Ty(ctx), { Type::getFloatTy(ctx) }, false), external); - makeCastCall(M, "__truncsfhf2", "julia__gnu_f2h_ieee", FunctionType::get(Type::getHalfTy(ctx), { Type::getFloatTy(ctx) }, false), - FunctionType::get(Type::getInt16Ty(ctx), { Type::getFloatTy(ctx) }, false), external); - makeCastCall(M, "__truncdfhf2", "julia__truncdfhf2", FunctionType::get(Type::getHalfTy(ctx), { Type::getDoubleTy(ctx) }, false), - FunctionType::get(Type::getInt16Ty(ctx), { Type::getDoubleTy(ctx) }, false), external); -} - -static void init_f16_funcs(void) -{ - auto ctx = jl_ExecutionEngine->acquireContext(); - auto TSM = jl_create_ts_module("F16Wrappers", ctx); - auto aliasM = TSM.getModuleUnlocked(); - emitFloat16Wrappers(*aliasM, true); - jl_ExecutionEngine->addModule(std::move(TSM)); -} -#endif - static void init_jit_functions(void) { add_named_global(jl_small_typeof_var, &jl_small_typeof); @@ -9237,6 +9874,8 @@ static void init_jit_functions(void) add_named_global(jlboundp_func, &jl_boundp); for (auto it : builtin_func_map()) add_named_global(it.second, it.first); + add_named_global(jlintrinsic_func, &jl_f_intrinsic_call); + add_named_global(jlgetbuiltinfptr_func, &jl_get_builtin_fptr); add_named_global(jlapplygeneric_func, &jl_apply_generic); add_named_global(jlinvoke_func, &jl_invoke); add_named_global(jltopeval_func, &jl_toplevel_eval); @@ -9246,6 +9885,7 @@ static void init_jit_functions(void) add_named_global(jlgenericfunction_func, &jl_generic_function_def); add_named_global(jlenter_func, &jl_enter_handler); add_named_global(jl_current_exception_func, &jl_current_exception); + add_named_global(jlleave_noexcept_func, &jl_pop_handler_noexcept); add_named_global(jlleave_func, &jl_pop_handler); add_named_global(jl_restore_excstack_func, &jl_restore_excstack); add_named_global(jl_excstack_state_func, &jl_excstack_state); @@ -9266,7 +9906,7 @@ static void init_jit_functions(void) add_named_global(jlfieldindex_func, &jl_field_index); add_named_global(diff_gc_total_bytes_func, &jl_gc_diff_total_bytes); add_named_global(sync_gc_total_bytes_func, &jl_gc_sync_total_bytes); - add_named_global(jlarray_data_owner_func, &jl_array_data_owner); + add_named_global(jl_allocgenericmemory, &jl_alloc_genericmemory); add_named_global(gcroot_flush_func, (void*)NULL); add_named_global(gc_preserve_begin_func, (void*)NULL); add_named_global(gc_preserve_end_func, (void*)NULL); @@ -9274,6 +9914,10 @@ static void init_jit_functions(void) add_named_global(except_enter_func, (void*)NULL); add_named_global(julia_call, (void*)NULL); add_named_global(julia_call2, (void*)NULL); + add_named_global(jllockvalue_func, &jl_lock_value); + add_named_global(jlunlockvalue_func, &jl_unlock_value); + add_named_global(jllockfield_func, &jl_lock_field); + add_named_global(jlunlockfield_func, &jl_unlock_field); #ifdef _OS_WINDOWS_ #if defined(_CPU_X86_64_) @@ -9313,6 +9957,10 @@ char jl_using_oprofile_jitevents = 0; // Non-zero if running under OProfile char jl_using_perf_jitevents = 0; #endif +int jl_is_timing_passes = 0; + +int jl_opaque_ptrs_set = 0; + extern "C" void jl_init_llvm(void) { jl_page_size = jl_getpagesize(); @@ -9326,16 +9974,17 @@ extern "C" void jl_init_llvm(void) // Initialize passes PassRegistry &Registry = *PassRegistry::getPassRegistry(); initializeCore(Registry); -#if JL_LLVM_VERSION < 150000 - initializeCoroutines(Registry); -#endif initializeScalarOpts(Registry); initializeVectorization(Registry); initializeAnalysis(Registry); initializeTransformUtils(Registry); initializeInstCombine(Registry); +#if JL_LLVM_VERSION >= 160000 + // TODO +#else initializeAggressiveInstCombine(Registry); initializeInstrumentation(Registry); +#endif initializeTarget(Registry); #ifdef USE_POLLY polly::initializePollyPasses(Registry); @@ -9361,16 +10010,18 @@ extern "C" void jl_init_llvm(void) if (clopt && clopt->getNumOccurrences() == 0) cl::ProvidePositionalOption(clopt, "4", 1); -#if JL_LLVM_VERSION >= 150000 + // we want the opaque-pointers to be opt-in, per LLVMContext, for this release + // so change the default value back to pre-14.x, without changing the NumOccurrences flag for it clopt = llvmopts.lookup("opaque-pointers"); if (clopt && clopt->getNumOccurrences() == 0) { -#ifdef JL_LLVM_OPAQUE_POINTERS - cl::ProvidePositionalOption(clopt, "true", 1); -#else - cl::ProvidePositionalOption(clopt, "false", 1); -#endif + clopt->addOccurrence(1, clopt->ArgStr, "false", true); + } else { + jl_opaque_ptrs_set = 1; } -#endif + + clopt = llvmopts.lookup("time-passes"); + if (clopt && clopt->getNumOccurrences() > 0) + jl_is_timing_passes = 1; jl_ExecutionEngine = new JuliaOJIT(); @@ -9437,9 +10088,6 @@ extern "C" JL_DLLEXPORT_CODEGEN void jl_init_codegen_impl(void) jl_init_llvm(); // Now that the execution engine exists, initialize all modules init_jit_functions(); -#if JULIA_FLOAT16_ABI == 2 - init_f16_funcs(); -#endif } extern "C" JL_DLLEXPORT_CODEGEN void jl_teardown_codegen_impl() JL_NOTSAFEPOINT diff --git a/src/common_symbols1.inc b/src/common_symbols1.inc index 547d5d0eabede..f54be52729a4f 100644 --- a/src/common_symbols1.inc +++ b/src/common_symbols1.inc @@ -1,98 +1,92 @@ jl_symbol("="), jl_symbol("getproperty"), -jl_symbol("apply_type"), jl_symbol("getfield"), +jl_symbol("apply_type"), +jl_symbol("==="), jl_symbol("getindex"), jl_symbol("convert"), -jl_symbol("==="), -jl_symbol("iterate"), jl_symbol("=="), jl_symbol("new"), -jl_symbol("foreigncall"), jl_symbol("int.jl"), -jl_symbol("throw"), -jl_symbol("nothing"), -jl_symbol("essentials.jl"), jl_symbol("+"), -jl_symbol("unsafe_convert"), +jl_symbol("boot.jl"), +jl_symbol("essentials.jl"), +jl_symbol("ccall"), +jl_symbol("foreigncall"), +jl_symbol("iterate"), jl_symbol("not_int"), +jl_symbol("Base.jl"), jl_symbol("-"), -jl_symbol("boot.jl"), -jl_symbol("number.jl"), +jl_symbol("throw"), +jl_symbol("promotion.jl"), jl_symbol("length"), jl_symbol("<"), -jl_symbol("cconvert"), -jl_symbol("Base.jl"), -jl_symbol("promotion.jl"), -jl_symbol("tuple.jl"), -jl_symbol("static_parameter"), -jl_symbol("isempty"), -jl_symbol("<="), -jl_symbol("array.jl"), +jl_symbol("isa"), jl_symbol("operators.jl"), -jl_symbol("NamedTuple"), +jl_symbol("number.jl"), +jl_symbol("unsafe_convert"), +jl_symbol("tuple.jl"), +jl_symbol("nothing"), jl_symbol("bitcast"), -jl_symbol("!"), +jl_symbol("NamedTuple"), jl_symbol("indexed_iterate"), -jl_symbol("sle_int"), jl_symbol("bool.jl"), -jl_symbol("Ptr"), -jl_symbol("size"), +jl_symbol("!"), +jl_symbol("isempty"), +jl_symbol("<="), +jl_symbol("cconvert"), jl_symbol("add_int"), +jl_symbol("static_parameter"), +jl_symbol("array.jl"), jl_symbol("slt_int"), -jl_symbol("*"), -jl_symbol("range.jl"), -jl_symbol("abstractarray.jl"), jl_symbol("!="), -jl_symbol("isa"), -jl_symbol("setindex!"), -jl_symbol("string"), -jl_symbol("ifelse"), -jl_symbol(":"), -jl_symbol(">"), -jl_symbol("_apply_iterate"), jl_symbol("UInt64"), +jl_symbol("range.jl"), +jl_symbol("sle_int"), +jl_symbol("size"), jl_symbol("&"), -jl_symbol("max"), +jl_symbol("abstractarray.jl"), jl_symbol("rem"), -jl_symbol("sub_int"), -jl_symbol(">="), -jl_symbol("UInt8"), -jl_symbol("iterators.jl"), +jl_symbol(">"), jl_symbol("Int64"), -jl_symbol("pairs"), +jl_symbol("sub_int"), +jl_symbol("*"), jl_symbol("and_int"), +jl_symbol("string"), +jl_symbol(">="), +jl_symbol("Ptr"), +jl_symbol("toInt64"), jl_symbol("last"), -jl_symbol("typeof"), -jl_symbol("arrayref"), jl_symbol("pointer.jl"), -jl_symbol("toInt64"), -jl_symbol("arraylen"), +jl_symbol("reinterpret"), +jl_symbol("first"), +jl_symbol("pairs"), +jl_symbol("_apply_iterate"), jl_symbol("typeassert"), -jl_symbol("map"), +jl_symbol(":"), +jl_symbol("UInt8"), +jl_symbol("setindex!"), +jl_symbol("isdefined"), +jl_symbol("typeof"), +jl_symbol("promote"), jl_symbol("kwcall"), -jl_symbol("ArgumentError"), +jl_symbol("unsigned"), +jl_symbol("_promote"), +jl_symbol("toUInt64"), +jl_symbol("map"), jl_symbol("lshr_int"), +jl_symbol("gc_preserve_begin"), +jl_symbol("gc_preserve_end"), +jl_symbol("trunc_int"), +jl_symbol("ArgumentError"), jl_symbol("axes"), -jl_symbol("reinterpret"), +jl_symbol("ult_int"), +jl_symbol("UInt"), +jl_symbol("zext_int"), +jl_symbol("strings/string.jl"), +jl_symbol("ifelse"), jl_symbol("Array"), -jl_symbol("first"), -jl_symbol("trunc_int"), -jl_symbol("OneTo"), -jl_symbol("haskey"), -jl_symbol("Int"), -jl_symbol("oneto"), jl_symbol("eq_int"), jl_symbol("throw_inexacterror"), -jl_symbol("toUInt64"), -jl_symbol("arraysize"), -jl_symbol("UInt"), +jl_symbol("|"), jl_symbol("setproperty!"), -jl_symbol("check_top_bit"), -jl_symbol("promote"), -jl_symbol("unsigned"), -jl_symbol("is_top_bit_set"), -jl_symbol("structdiff"), -jl_symbol("undef"), -jl_symbol("sizeof"), -jl_symbol("String"), diff --git a/src/common_symbols2.inc b/src/common_symbols2.inc index b5a334172dd76..ee2a0e2edd9fe 100644 --- a/src/common_symbols2.inc +++ b/src/common_symbols2.inc @@ -1,254 +1,248 @@ -jl_symbol("namedtuple.jl"), -jl_symbol("pop"), -jl_symbol("inbounds"), -jl_symbol("strings/string.jl"), -jl_symbol("Ref"), -jl_symbol("Vector"), -jl_symbol("kwerr"), -jl_symbol("_promote"), jl_symbol("sext_int"), -jl_symbol("pointer"), -jl_symbol("similar"), -jl_symbol("arrayset"), +jl_symbol("String"), +jl_symbol("Int"), +jl_symbol("iterators.jl"), +jl_symbol("Colon"), +jl_symbol("unchecked_oneto"), +jl_symbol("structdiff"), +jl_symbol("UnitRange"), +jl_symbol("unitrange_last"), +jl_symbol("sizeof"), +jl_symbol("check_sign_bit"), +jl_symbol("is_top_bit_set"), +jl_symbol("data"), +jl_symbol("kwerr"), jl_symbol("axes1"), jl_symbol("eachindex"), -jl_symbol("|"), -jl_symbol("ult_int"), -jl_symbol("lastindex"), -jl_symbol("setfield!"), -jl_symbol("UnitRange"), -jl_symbol("push!"), +jl_symbol("or_int"), jl_symbol("Bool"), -jl_symbol("Colon"), +jl_symbol("setfield!"), jl_symbol("fieldtype"), -jl_symbol("unitrange_last"), -jl_symbol("bitarray.jl"), -jl_symbol("<<"), -jl_symbol("zext_int"), -jl_symbol("Tuple"), +jl_symbol("Ref"), +jl_symbol("pointer"), +jl_symbol("max"), +jl_symbol("push!"), +jl_symbol("lastindex"), jl_symbol("reflection.jl"), -jl_symbol("TypeError"), -jl_symbol("print"), -jl_symbol("eltype"), +jl_symbol("<<"), +jl_symbol("similar"), +jl_symbol("Vector"), +jl_symbol("UInt32"), jl_symbol(">>"), -jl_symbol("strings/basic.jl"), -jl_symbol("gc_preserve_begin"), -jl_symbol("require_one_based_indexing"), -jl_symbol("gc_preserve_end"), -jl_symbol("DimensionMismatch"), -jl_symbol("indices.jl"), -jl_symbol("Cvoid"), -jl_symbol("oftype"), -jl_symbol("zero"), -jl_symbol("float.jl"), -jl_symbol("Any"), -jl_symbol("checkbounds"), -jl_symbol("or_int"), -jl_symbol("isdefined"), jl_symbol("dict.jl"), +jl_symbol("checkbounds"), +jl_symbol("undef"), +jl_symbol("jl_string_ptr"), +jl_symbol("error"), jl_symbol("strings/io.jl"), -jl_symbol("shl_int"), -jl_symbol("copy"), -jl_symbol("macro expansion"), -jl_symbol("abstractdict.jl"), +jl_symbol("strings/substring.jl"), +jl_symbol("bitarray.jl"), +jl_symbol("strings/basic.jl"), +jl_symbol("merge"), +jl_symbol("TypeError"), +jl_symbol("keyword argument"), jl_symbol("in"), -jl_symbol("io.jl"), -jl_symbol("BlasInt"), -jl_symbol("Float64"), +jl_symbol("print"), +jl_symbol("macro expansion"), jl_symbol("mul_int"), -jl_symbol("UInt32"), +jl_symbol("shl_int"), jl_symbol("C_NULL"), +jl_symbol("oftype"), +jl_symbol("_growend!"), +jl_symbol("Any"), +jl_symbol("Tuple"), +jl_symbol("float.jl"), +jl_symbol("ncodeunits"), jl_symbol("Integer"), +jl_symbol("io.jl"), +jl_symbol("eltype"), +jl_symbol("name"), +jl_symbol("parent"), jl_symbol("!=="), -jl_symbol("merge"), -jl_symbol("BoundsError"), -jl_symbol("broadcasted"), -jl_symbol("Cint"), -jl_symbol("min"), -jl_symbol("libblastrampoline"), jl_symbol("iszero"), +jl_symbol("min"), +jl_symbol("DimensionMismatch"), jl_symbol("refvalue.jl"), -jl_symbol("stride"), -jl_symbol("error"), -jl_symbol("ncodeunits"), -jl_symbol("LinearIndices"), -jl_symbol("Clong"), -jl_symbol("pair.jl"), -jl_symbol("_growend!"), -jl_symbol("char.jl"), -jl_symbol("copyto!"), -jl_symbol("get"), -jl_symbol("tail"), -jl_symbol("real"), jl_symbol("Union"), -jl_symbol("multidimensional.jl"), -jl_symbol("enter"), -jl_symbol("leave"), +jl_symbol("BlasInt"), +jl_symbol("unsafe_load"), +jl_symbol("indices.jl"), +jl_symbol("x"), +jl_symbol("require_one_based_indexing"), +jl_symbol("namedtuple.jl"), +jl_symbol("tail"), +jl_symbol("Float64"), +jl_symbol("head"), +jl_symbol("Cvoid"), +jl_symbol("copy"), +jl_symbol("libblastrampoline"), +jl_symbol("get"), +jl_symbol("neg_int"), +jl_symbol("stop"), +jl_symbol("zero"), jl_symbol("add_ptr"), -jl_symbol("chkstride1"), +jl_symbol("toUInt32"), +jl_symbol("ptr"), +jl_symbol("char.jl"), +jl_symbol("trunc"), +jl_symbol("not_atomic"), +jl_symbol("enter"), +jl_symbol("Pair"), +jl_symbol("jl_value_ptr"), jl_symbol("Expr"), -jl_symbol("write"), -jl_symbol("broadcast.jl"), +jl_symbol("broadcasted"), +jl_symbol("pointerref"), +jl_symbol("multidimensional.jl"), +jl_symbol("Generator"), +jl_symbol("leave"), +jl_symbol("memoryref"), jl_symbol("show.jl"), +jl_symbol("pointer_from_objref"), +jl_symbol("memoryrefget"), +jl_symbol("reduce.jl"), +jl_symbol("stride"), +jl_symbol("pair.jl"), +jl_symbol("_string"), +jl_symbol("cmem.jl"), +jl_symbol("generator.jl"), +jl_symbol("broadcast.jl"), jl_symbol("none"), -jl_symbol("Generator"), +jl_symbol("copyto!"), +jl_symbol("chkstride1"), +jl_symbol("value"), +jl_symbol("write"), +jl_symbol("identity"), +jl_symbol("real"), +jl_symbol("start"), +jl_symbol("Cint"), +jl_symbol("fill!"), +jl_symbol("checkindex"), +jl_symbol("keys"), +jl_symbol("BoundsError"), +jl_symbol("vals"), +jl_symbol("Symbol"), +jl_symbol("strings/util.jl"), jl_symbol("Int32"), -jl_symbol("materialize"), +jl_symbol("ht_keyindex"), +jl_symbol("io"), +jl_symbol("~"), +jl_symbol("AssertionError"), +jl_symbol("abstractdict.jl"), jl_symbol("show"), -jl_symbol("lock"), -jl_symbol("unsafe_load"), -jl_symbol("gmp.jl"), jl_symbol("mpfr.jl"), -jl_symbol("Symbol"), -jl_symbol("Pair"), -jl_symbol("resize!"), -jl_symbol("neg_int"), -jl_symbol("strings/substring.jl"), -jl_symbol("AssertionError"), -jl_symbol("identity"), -jl_symbol("one"), -jl_symbol("reduce.jl"), -jl_symbol("libcholmod"), jl_symbol("isless"), +jl_symbol("args"), +jl_symbol("lock"), jl_symbol("reducedim.jl"), +jl_symbol("gmp.jl"), +jl_symbol("offset"), +jl_symbol("resize!"), +jl_symbol("throw_boundserror"), +jl_symbol("Clong"), +jl_symbol("_call_latest"), +jl_symbol("argtail"), +jl_symbol("compiler/ssair/ir.jl"), +jl_symbol("sub_ptr"), +jl_symbol("materialize"), jl_symbol("checksquare"), -jl_symbol("sort.jl"), -jl_symbol("generator.jl"), -jl_symbol("pointer_from_objref"), -jl_symbol("Float32"), -jl_symbol("chklapackerror"), -jl_symbol("parent"), -jl_symbol("task.jl"), +jl_symbol("LinearIndices"), +jl_symbol("ule_int"), +jl_symbol("dict"), jl_symbol("div"), -jl_symbol("cholmod_common"), -jl_symbol("ht_keyindex"), -jl_symbol("pop_exception"), -jl_symbol("c.jl"), -jl_symbol("firstindex"), -jl_symbol("some.jl"), -jl_symbol("iobuffer.jl"), -jl_symbol("sub_ptr"), -jl_symbol("vect"), -jl_symbol("unsafe_string"), -jl_symbol("llvmcall"), -jl_symbol("checkindex"), -jl_symbol("_call_latest"), +jl_symbol("chklapackerror"), +jl_symbol("count"), +jl_symbol("Float32"), +jl_symbol("genericmemory.jl"), +jl_symbol("print_to_string"), jl_symbol("rethrow"), -jl_symbol("pointerref"), +jl_symbol("sort.jl"), +jl_symbol("boundscheck"), jl_symbol("println"), -jl_symbol("keys"), -jl_symbol("RefValue"), +jl_symbol("loading.jl"), +jl_symbol("collect"), +jl_symbol("ashr_int"), jl_symbol("_expr"), -jl_symbol("toUInt32"), -jl_symbol("ismissing"), -jl_symbol("throw_boundserror"), -jl_symbol("IteratorSize"), -jl_symbol("iddict.jl"), +jl_symbol("iobuffer.jl"), +jl_symbol("DataType"), +jl_symbol("Dict"), +jl_symbol("unsafe_string"), +jl_symbol("RefValue"), +jl_symbol("step"), jl_symbol("to_shape"), -jl_symbol("Csize_t"), -jl_symbol("~"), -jl_symbol("argtail"), -jl_symbol("include"), -jl_symbol("set.jl"), -jl_symbol("isequal"), +jl_symbol("pop_exception"), +jl_symbol("Memory"), +jl_symbol("KeyError"), +jl_symbol("chunks"), jl_symbol("refpointer.jl"), -jl_symbol("=>"), -jl_symbol("Val"), -jl_symbol("Base"), +jl_symbol("llvmcall"), +jl_symbol("c.jl"), +jl_symbol("set.jl"), +jl_symbol("abs"), +jl_symbol("checked_trunc_uint"), +jl_symbol("Type"), jl_symbol("%"), -jl_symbol("collect"), -jl_symbol("Type##kw"), -jl_symbol("typemax"), -jl_symbol("fill!"), -jl_symbol("ule_int"), -jl_symbol("atomics.jl"), -jl_symbol("libgit2"), +jl_symbol("len"), jl_symbol("BigFloat"), -jl_symbol("ashr_int"), -jl_symbol("boundscheck"), -jl_symbol("abs"), -jl_symbol("^"), -jl_symbol("ensure_initialized"), -jl_symbol("_array_for"), -jl_symbol("strings/util.jl"), -jl_symbol("Dict"), +jl_symbol("isequal"), +jl_symbol("vect"), +jl_symbol("sprint"), +jl_symbol("mode"), +jl_symbol("expr.jl"), jl_symbol("Nothing"), -jl_symbol("compiler/ssair/ir.jl"), +jl_symbol("Val"), +jl_symbol("IteratorSize"), +jl_symbol("=>"), +jl_symbol("haskey"), +jl_symbol("iddict.jl"), jl_symbol("unsafe_write"), -jl_symbol("util.jl"), +jl_symbol("val"), +jl_symbol("flags"), +jl_symbol("task.jl"), +jl_symbol("UnionAll"), +jl_symbol("memset"), +jl_symbol("xor"), +jl_symbol("jl_alloc_genericmemory"), +jl_symbol("uplo"), jl_symbol("toInt32"), -jl_symbol("loading.jl"), -jl_symbol("value"), -jl_symbol("expr.jl"), -jl_symbol("print_to_string"), +jl_symbol("Base"), +jl_symbol("atomics.jl"), +jl_symbol("uuid"), +jl_symbol("one"), +jl_symbol("math.jl"), +jl_symbol("position"), +jl_symbol("typemax"), +jl_symbol("all"), +jl_symbol("error.jl"), +jl_symbol("path.jl"), +jl_symbol("^"), +jl_symbol("nextind"), +jl_symbol("include"), jl_symbol("the_exception"), -jl_symbol("nonzeros"), -jl_symbol("<:"), -jl_symbol("KeyError"), -jl_symbol("xor"), -jl_symbol("logging.jl"), +jl_symbol("ensure_initialized"), +jl_symbol("Const"), +jl_symbol("UInt128"), +jl_symbol("codeunit"), jl_symbol("stat.jl"), -jl_symbol("close"), -jl_symbol("adjoint"), -jl_symbol("meta"), -jl_symbol("path.jl"), -jl_symbol("round"), -jl_symbol("Cstring"), -jl_symbol("SizeUnknown"), -jl_symbol("esc"), -jl_symbol("missing.jl"), +jl_symbol("gcutils.jl"), +jl_symbol("UndefRefError"), +jl_symbol("diag"), jl_symbol("throw_undef_if_not"), -jl_symbol("error.jl"), -jl_symbol("Type"), -jl_symbol("mul!"), -jl_symbol("math.jl"), -jl_symbol("unsafe_trunc"), jl_symbol("missing"), -jl_symbol("subarray.jl"), -jl_symbol("noinline"), jl_symbol("isnan"), -jl_symbol("ldiv!"), -jl_symbol("DataType"), -jl_symbol("codeunit"), -jl_symbol("condition.jl"), -jl_symbol("step"), -jl_symbol("copyast"), -jl_symbol("bitset.jl"), -jl_symbol("float"), +jl_symbol("Enums.jl"), +jl_symbol("logging.jl"), +jl_symbol("_deleteend!"), +jl_symbol("indices"), +jl_symbol("compiler/utilities.jl"), +jl_symbol("Pairs"), +jl_symbol("<:"), +jl_symbol("compiler/tfuncs.jl"), +jl_symbol("close"), +jl_symbol("subarray.jl"), jl_symbol("fastmath.jl"), +jl_symbol("invokelatest"), +jl_symbol("jl_array_del_end"), jl_symbol("_mod64"), -jl_symbol("_div64"), -jl_symbol("all"), -jl_symbol("parse"), -jl_symbol("joinpath"), -jl_symbol("nextind"), +jl_symbol("parameters"), +jl_symbol("monotonic"), jl_symbol("regex.jl"), -jl_symbol("Enums.jl"), -jl_symbol("promote_type"), -jl_symbol("Cdouble"), -jl_symbol("ComplexF32"), -jl_symbol("read"), -jl_symbol("intfuncs.jl"), -jl_symbol("Complex"), -jl_symbol("_deleteend!"), -jl_symbol("stat"), -jl_symbol("UnionAll"), -jl_symbol("special/trig.jl"), -jl_symbol("UInt128"), -jl_symbol("_copyto_impl!"), -jl_symbol("stream.jl"), -jl_symbol("lmul!"), -jl_symbol("repr"), -jl_symbol("promote_rule"), -jl_symbol("xor_int"), -jl_symbol("complex.jl"), -jl_symbol("transpose"), -jl_symbol(">>>"), -jl_symbol("cholmod_sparse"), -jl_symbol("filemode"), -jl_symbol("ComplexF64"), -jl_symbol("SparseMatrixCSC"), -jl_symbol("view"), -jl_symbol("GitError"), -jl_symbol("zeros"), -jl_symbol("InexactError"), diff --git a/src/coverage.cpp b/src/coverage.cpp index 3d3b3bc734eaa..100a4c66322bd 100644 --- a/src/coverage.cpp +++ b/src/coverage.cpp @@ -192,7 +192,7 @@ static void write_lcov_data(logdata_t &logData, const std::string &outfile) outf.close(); } -extern "C" void jl_write_coverage_data(const char *output) +extern "C" JL_DLLEXPORT void jl_write_coverage_data(const char *output) { if (output) { StringRef output_pattern(output); diff --git a/src/crc32c.c b/src/crc32c.c index 4ca8db06459a1..1d667b3dbf656 100644 --- a/src/crc32c.c +++ b/src/crc32c.c @@ -1,15 +1,16 @@ /* crc32c.c -- compute CRC-32C using software table or available hardware instructions - * Copyright (C) 2013 Mark Adler - * Version 1.1 1 Aug 2013 Mark Adler + * Copyright (C) 2013, 2021 Mark Adler + * Version 1.1 1 Aug 2013 Mark Adler, updates from Version 1.2 5 June 2021 * * Code retrieved in August 2016 from August 2013 post by Mark Adler on - * http://stackoverflow.com/questions/17645167/implementing-sse-4-2s-crc32c-in-software + * https://stackoverflow.com/questions/17645167/implementing-sse-4-2s-crc32c-in-software * Modified for use in libjulia: * - exported function renamed to jl_crc32c, DLL exports added. * - removed main() function * - architecture and compiler detection * - precompute crc32c tables and store in a generated .c file * - ARMv8 support + * Updated to incorporate upstream 2021 patch by Mark Adler to register constraints. */ /* @@ -39,6 +40,8 @@ /* Version history: 1.0 10 Feb 2013 First version 1.1 1 Aug 2013 Correct comments on why three crc instructions in parallel + 1.2 5 Jun 2021 Correct register constraints on assembly instructions + (+ other changes that were superfluous for us) */ #include "julia.h" @@ -53,14 +56,9 @@ #define POLY 0x82f63b78 /* Block sizes for three-way parallel crc computation. LONG and SHORT must - both be powers of two. The associated string constants must be set - accordingly, for use in constructing the assembler instructions. */ + both be powers of two. */ #define LONG 8192 -#define LONGx1 "8192" -#define LONGx2 "16384" #define SHORT 256 -#define SHORTx1 "256" -#define SHORTx2 "512" #ifndef GEN_CRC32C_TABLES #include "crc32c-tables.c" @@ -97,27 +95,27 @@ static uint32_t crc32c_sse42(uint32_t crc, const char *buf, size_t len) /* compute the crc for up to seven leading bytes to bring the data pointer to an eight-byte boundary */ while (len && ((uintptr_t)buf & 7) != 0) { - __asm__("crc32b\t" "(%1), %0" - : "=r"(crc0) - : "r"(buf), "0"(crc0)); + __asm__("crc32b\t" "%1, %0" + : "+r"(crc0) + : "m"(*buf)); buf++; len--; } - /* compute the crc on sets of LONG*3 bytes, executing three independent crc - instructions, each on LONG bytes -- this is optimized for the Nehalem, - Westmere, Sandy Bridge, and Ivy Bridge architectures, which have a - throughput of one crc per cycle, but a latency of three cycles */ + /* compute the crc on sets of LONG*3 bytes, + making use of three ALUs in parallel on a single core. */ while (len >= LONG * 3) { uintptr_t crc1 = 0; uintptr_t crc2 = 0; const char *end = buf + LONG; do { - __asm__(CRC32_PTR "\t" "(%3), %0\n\t" - CRC32_PTR "\t" LONGx1 "(%3), %1\n\t" - CRC32_PTR "\t" LONGx2 "(%3), %2" - : "=r"(crc0), "=r"(crc1), "=r"(crc2) - : "r"(buf), "0"(crc0), "1"(crc1), "2"(crc2)); + __asm__(CRC32_PTR "\t%3, %0\n\t" + CRC32_PTR "\t%4, %1\n\t" + CRC32_PTR "\t%5, %2" + : "+r"(crc0), "+r"(crc1), "+r"(crc2) + : "m"(* (const uintptr_t *) &buf[0]), + "m"(* (const uintptr_t *) &buf[LONG]), + "m"(* (const uintptr_t *) &buf[LONG*2])); buf += sizeof(void*); } while (buf < end); crc0 = crc32c_shift(crc32c_long, crc0) ^ crc1; @@ -133,11 +131,13 @@ static uint32_t crc32c_sse42(uint32_t crc, const char *buf, size_t len) uintptr_t crc2 = 0; const char *end = buf + SHORT; do { - __asm__(CRC32_PTR "\t" "(%3), %0\n\t" - CRC32_PTR "\t" SHORTx1 "(%3), %1\n\t" - CRC32_PTR "\t" SHORTx2 "(%3), %2" - : "=r"(crc0), "=r"(crc1), "=r"(crc2) - : "r"(buf), "0"(crc0), "1"(crc1), "2"(crc2)); + __asm__(CRC32_PTR "\t%3, %0\n\t" + CRC32_PTR "\t%4, %1\n\t" + CRC32_PTR "\t%5, %2" + : "+r"(crc0), "+r"(crc1), "+r"(crc2) + : "m"(* (const uintptr_t *) &buf[0]), + "m"(* (const uintptr_t *) &buf[SHORT]), + "m"(* (const uintptr_t *) &buf[SHORT*2])); buf += sizeof(void*); } while (buf < end); crc0 = crc32c_shift(crc32c_short, crc0) ^ crc1; @@ -150,18 +150,18 @@ static uint32_t crc32c_sse42(uint32_t crc, const char *buf, size_t len) block */ const char *end = buf + (len - (len & 7)); while (buf < end) { - __asm__(CRC32_PTR "\t" "(%1), %0" - : "=r"(crc0) - : "r"(buf), "0"(crc0)); + __asm__(CRC32_PTR "\t" "%1, %0" + : "+r"(crc0) + : "m"(* (const uintptr_t *) buf)); buf += sizeof(void*); } len &= 7; /* compute the crc for up to seven trailing bytes */ while (len) { - __asm__("crc32b\t" "(%1), %0" - : "=r"(crc0) - : "r"(buf), "0"(crc0)); + __asm__("crc32b\t" "%1, %0" + : "+r"(crc0) + : "m"(*buf)); buf++; len--; } diff --git a/src/datatype.c b/src/datatype.c index 905959fb80e0a..ee33e75869ee8 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "julia.h" #include "julia_internal.h" #include "julia_assert.h" @@ -49,7 +50,7 @@ JL_DLLEXPORT jl_methtable_t *jl_new_method_table(jl_sym_t *name, jl_module_t *mo mt->name = jl_demangle_typename(name); mt->module = module; jl_atomic_store_relaxed(&mt->defs, jl_nothing); - jl_atomic_store_relaxed(&mt->leafcache, (jl_array_t*)jl_an_empty_vec_any); + jl_atomic_store_relaxed(&mt->leafcache, (jl_genericmemory_t*)jl_an_empty_memory_any); jl_atomic_store_relaxed(&mt->cache, jl_nothing); jl_atomic_store_relaxed(&mt->max_args, 0); mt->backedges = NULL; @@ -134,10 +135,10 @@ static uint32_t _hash_layout_djb2(uintptr_t _layout, void *unused) JL_NOTSAFEPOI size_t own_size = sizeof(jl_datatype_layout_t); const char *fields = jl_dt_layout_fields(layout); assert(fields); - size_t fields_size = layout->nfields * jl_fielddesc_size(layout->fielddesc_type); + size_t fields_size = layout->nfields * jl_fielddesc_size(layout->flags.fielddesc_type); const char *pointers = jl_dt_layout_ptrs(layout); assert(pointers); - size_t pointers_size = (layout->npointers << layout->fielddesc_type); + size_t pointers_size = layout->first_ptr < 0 ? 0 : (layout->npointers << layout->flags.fielddesc_type); uint_t hash = 5381; hash = _hash_djb2(hash, (char *)layout, own_size); @@ -155,12 +156,12 @@ static int layout_eq(void *_l1, void *_l2, void *unused) JL_NOTSAFEPOINT return 0; const char *f1 = jl_dt_layout_fields(l1); const char *f2 = jl_dt_layout_fields(l2); - size_t fields_size = l1->nfields * jl_fielddesc_size(l1->fielddesc_type); + size_t fields_size = l1->nfields * jl_fielddesc_size(l1->flags.fielddesc_type); if (memcmp(f1, f2, fields_size)) return 0; const char *p1 = jl_dt_layout_ptrs(l1); const char *p2 = jl_dt_layout_ptrs(l2); - size_t pointers_size = (l1->npointers << l1->fielddesc_type); + size_t pointers_size = l1->first_ptr < 0 ? 0 : (l1->npointers << l1->flags.fielddesc_type); if (memcmp(p1, p2, pointers_size)) return 0; return 1; @@ -179,6 +180,7 @@ static jl_datatype_layout_t *jl_get_layout(uint32_t sz, uint32_t npointers, uint32_t alignment, int haspadding, + int arrayelem, jl_fielddesc32_t desc[], uint32_t pointers[]) JL_NOTSAFEPOINT { @@ -186,32 +188,34 @@ static jl_datatype_layout_t *jl_get_layout(uint32_t sz, // compute the smallest fielddesc type that can hold the layout description int fielddesc_type = 0; + uint32_t max_size = 0; + uint32_t max_offset = 0; if (nfields > 0) { - uint32_t max_size = 0; - uint32_t max_offset = desc[nfields - 1].offset; - if (npointers > 0 && pointers[npointers - 1] > max_offset) - max_offset = pointers[npointers - 1]; + max_offset = desc[nfields - 1].offset; for (size_t i = 0; i < nfields; i++) { if (desc[i].size > max_size) max_size = desc[i].size; } - jl_fielddesc8_t maxdesc8 = { 0, max_size, max_offset }; - jl_fielddesc16_t maxdesc16 = { 0, max_size, max_offset }; - jl_fielddesc32_t maxdesc32 = { 0, max_size, max_offset }; - if (maxdesc8.size != max_size || maxdesc8.offset != max_offset) { - fielddesc_type = 1; - if (maxdesc16.size != max_size || maxdesc16.offset != max_offset) { - fielddesc_type = 2; - if (maxdesc32.size != max_size || maxdesc32.offset != max_offset) { - assert(0); // should have been verified by caller - } + } + if (npointers > 0 && pointers[npointers - 1] > max_offset) + max_offset = pointers[npointers - 1]; + jl_fielddesc8_t maxdesc8 = { 0, max_size, max_offset }; + jl_fielddesc16_t maxdesc16 = { 0, max_size, max_offset }; + jl_fielddesc32_t maxdesc32 = { 0, max_size, max_offset }; + if (maxdesc8.size != max_size || maxdesc8.offset != max_offset) { + fielddesc_type = 1; + if (maxdesc16.size != max_size || maxdesc16.offset != max_offset) { + fielddesc_type = 2; + if (maxdesc32.size != max_size || maxdesc32.offset != max_offset) { + assert(0); // should have been verified by caller } } } + int32_t first_ptr = (npointers > 0 ? (int32_t)pointers[0] : -1); // allocate a new descriptor, on the stack if possible. size_t fields_size = nfields * jl_fielddesc_size(fielddesc_type); - size_t pointers_size = (npointers << fielddesc_type); + size_t pointers_size = first_ptr < 0 ? 0 : (npointers << fielddesc_type); size_t flddesc_sz = sizeof(jl_datatype_layout_t) + fields_size + pointers_size; int should_malloc = flddesc_sz >= jl_page_size; jl_datatype_layout_t *mallocmem = (jl_datatype_layout_t *)(should_malloc ? malloc(flddesc_sz) : NULL); @@ -221,11 +225,13 @@ static jl_datatype_layout_t *jl_get_layout(uint32_t sz, flddesc->size = sz; flddesc->nfields = nfields; flddesc->alignment = alignment; - flddesc->haspadding = haspadding; - flddesc->fielddesc_type = fielddesc_type; - flddesc->padding = 0; + flddesc->flags.haspadding = haspadding; + flddesc->flags.fielddesc_type = fielddesc_type; + flddesc->flags.arrayelem_isboxed = arrayelem == 1; + flddesc->flags.arrayelem_isunion = arrayelem == 2; + flddesc->flags.padding = 0; flddesc->npointers = npointers; - flddesc->first_ptr = (npointers > 0 ? pointers[0] : -1); + flddesc->first_ptr = first_ptr; // fill out the fields of the new descriptor jl_fielddesc8_t *desc8 = (jl_fielddesc8_t *)jl_dt_layout_fields(flddesc); @@ -248,18 +254,20 @@ static jl_datatype_layout_t *jl_get_layout(uint32_t sz, desc32[i].isptr = desc[i].isptr; } } - uint8_t *ptrs8 = (uint8_t *)jl_dt_layout_ptrs(flddesc); - uint16_t *ptrs16 = (uint16_t *)jl_dt_layout_ptrs(flddesc); - uint32_t *ptrs32 = (uint32_t *)jl_dt_layout_ptrs(flddesc); - for (size_t i = 0; i < npointers; i++) { - if (fielddesc_type == 0) { - ptrs8[i] = pointers[i]; - } - else if (fielddesc_type == 1) { - ptrs16[i] = pointers[i]; - } - else { - ptrs32[i] = pointers[i]; + if (first_ptr >= 0) { + uint8_t *ptrs8 = (uint8_t *)jl_dt_layout_ptrs(flddesc); + uint16_t *ptrs16 = (uint16_t *)jl_dt_layout_ptrs(flddesc); + uint32_t *ptrs32 = (uint32_t *)jl_dt_layout_ptrs(flddesc); + for (size_t i = 0; i < npointers; i++) { + if (fielddesc_type == 0) { + ptrs8[i] = pointers[i]; + } + else if (fielddesc_type == 1) { + ptrs16[i] = pointers[i]; + } + else { + ptrs32[i] = pointers[i]; + } } } @@ -318,23 +326,23 @@ unsigned jl_special_vector_alignment(size_t nfields, jl_value_t *t) STATIC_INLINE int jl_is_datatype_make_singleton(jl_datatype_t *d) JL_NOTSAFEPOINT { - return (!d->name->abstract && jl_datatype_size(d) == 0 && d != jl_symbol_type && d->name != jl_array_typename && - d->isconcretetype && !d->name->mutabl); + return d->isconcretetype && jl_datatype_size(d) == 0 && d->layout->npointers == 0 && !d->name->mutabl; // implies jl_is_layout_opaque } STATIC_INLINE void jl_maybe_allocate_singleton_instance(jl_datatype_t *st) JL_NOTSAFEPOINT { + // It's possible for st to already have an ->instance if it was redefined + if (st->instance) + return; if (jl_is_datatype_make_singleton(st)) { - // It's possible for st to already have an ->instance if it was redefined - if (!st->instance) - st->instance = jl_gc_permobj(0, st); + st->instance = jl_gc_permobj(0, st); } } // return whether all concrete subtypes of this type have the same layout int jl_struct_try_layout(jl_datatype_t *dt) { - if (dt->layout) + if (dt->layout || jl_is_genericmemory_type(dt)) return 1; else if (!jl_has_fixed_layout(dt)) return 0; @@ -352,7 +360,7 @@ int jl_datatype_isinlinealloc(jl_datatype_t *ty, int pointerfree) return 0; if (ty->name->n_uninitialized != 0) return 0; - if (ty->layout->fielddesc_type > 1) // GC only implements support for 8 and 16 (not array32) + if (ty->layout->flags.fielddesc_type > 1) // GC only implements support for 8 and 16 (not array32) return 0; } return 1; @@ -478,6 +486,101 @@ static int is_type_identityfree(jl_value_t *t) return 0; } +// make a copy of the layout of st, but with nfields=0 +void jl_get_genericmemory_layout(jl_datatype_t *st) +{ + jl_value_t *kind = jl_tparam0(st); + jl_value_t *eltype = jl_tparam1(st); + jl_value_t *addrspace = jl_tparam2(st); + if (!jl_is_typevar(eltype) && !jl_is_type(eltype)) { + // this is expected to have a layout, but since it is not constructable, we don't care too much what it is + static const jl_datatype_layout_t opaque_ptr_layout = {0, 0, 1, -1, sizeof(void*), {0}}; + st->layout = &opaque_ptr_layout; + st->has_concrete_subtype = 0; + return; + } + + size_t elsz = 0, al = 1; + int isunboxed = jl_islayout_inline(eltype, &elsz, &al) && (kind != (jl_value_t*)jl_atomic_sym || jl_is_datatype(eltype)); + int isunion = isunboxed && jl_is_uniontype(eltype); + int haspadding = 1; // we may want to eventually actually compute this more precisely + int nfields = 0; // aka jl_is_layout_opaque + int npointers = 1; + int zi; + uint32_t first_ptr = -1; + uint32_t *pointers = &first_ptr; + int needlock = 0; + + if (isunboxed) { + elsz = LLT_ALIGN(elsz, al); + if (kind == (jl_value_t*)jl_atomic_sym) { + if (elsz > MAX_ATOMIC_SIZE) + needlock = 1; + else if (elsz > 0) + al = elsz = next_power_of_two(elsz); + } + if (isunion) { + zi = 1; + } + else { + assert(jl_is_datatype(eltype)); + zi = ((jl_datatype_t*)eltype)->zeroinit; + const jl_datatype_layout_t *layout = ((jl_datatype_t*)eltype)->layout; + if (layout->first_ptr >= 0) { + first_ptr = layout->first_ptr; + npointers = layout->npointers; + if (layout->flags.fielddesc_type == 2) { + pointers = (uint32_t*)jl_dt_layout_ptrs(layout); + } + else { + pointers = (uint32_t*)alloca(npointers * sizeof(uint32_t)); + for (int j = 0; j < npointers; j++) { + pointers[j] = jl_ptr_offset((jl_datatype_t*)eltype, j); + } + } + } + } + if (needlock) { + assert(al <= JL_SMALL_BYTE_ALIGNMENT); + size_t offset = LLT_ALIGN(sizeof(jl_mutex_t), JL_SMALL_BYTE_ALIGNMENT); + elsz += offset; + haspadding = 1; + zi = 1; + } + } + else { + elsz = sizeof(void*); + al = elsz; + zi = 1; + } + + int arrayelem; + if (!isunboxed) + arrayelem = 1; + else if (isunion) + arrayelem = 2; + else + arrayelem = 0; + assert(!st->layout); + st->layout = jl_get_layout(elsz, nfields, npointers, al, haspadding, arrayelem, NULL, pointers); + st->zeroinit = zi; + //st->has_concrete_subtype = 1; + //st->isbitstype = 0; + //st->ismutationfree = 0; + //st->isidentityfree = 0; + + if (jl_is_addrspacecore(addrspace) && jl_unbox_uint8(addrspace) == 0) { + if (kind == (jl_value_t*)jl_not_atomic_sym || kind == (jl_value_t*)jl_atomic_sym) { + jl_genericmemory_t *zeroinst = (jl_genericmemory_t*)jl_gc_permobj(LLT_ALIGN(sizeof(jl_genericmemory_t), JL_SMALL_BYTE_ALIGNMENT) + (elsz ? elsz : isunion), st); + zeroinst->length = 0; + zeroinst->ptr = (char*)zeroinst + JL_SMALL_BYTE_ALIGNMENT; + memset(zeroinst->ptr, 0, elsz ? elsz : isunion); + assert(!st->instance); + st->instance = (jl_value_t*)zeroinst; + } + } +} + void jl_compute_field_offsets(jl_datatype_t *st) { const uint64_t max_offset = (((uint64_t)1) << 32) - 1; @@ -492,6 +595,10 @@ void jl_compute_field_offsets(jl_datatype_t *st) st->zeroinit = 0; st->has_concrete_subtype = 1; } + if (st->name == jl_genericmemory_typename) { + jl_get_genericmemory_layout(st); + return; + } int isbitstype = st->isconcretetype && st->name->mayinlinealloc; int ismutationfree = !w->layout || !jl_is_layout_opaque(w->layout); int isidentityfree = !st->name->mutabl; @@ -501,7 +608,7 @@ void jl_compute_field_offsets(jl_datatype_t *st) st->layout = w->layout; st->zeroinit = w->zeroinit; st->has_concrete_subtype = w->has_concrete_subtype; - if (!jl_is_layout_opaque(st->layout)) { // e.g. jl_array_typename + if (!jl_is_layout_opaque(st->layout)) { // e.g. jl_simplevector_type st->isbitstype = isbitstype && st->layout->npointers == 0; jl_maybe_allocate_singleton_instance(st); } @@ -514,18 +621,18 @@ void jl_compute_field_offsets(jl_datatype_t *st) // if we have no fields, we can trivially skip the rest if (st == jl_symbol_type || st == jl_string_type) { // opaque layout - heap-allocated blob - static const jl_datatype_layout_t opaque_byte_layout = {0, 0, 1, -1, 1, 0, 0}; + static const jl_datatype_layout_t opaque_byte_layout = {0, 0, 1, -1, 1, {0}}; st->layout = &opaque_byte_layout; return; } - else if (st == jl_simplevector_type || st == jl_module_type || st->name == jl_array_typename) { - static const jl_datatype_layout_t opaque_ptr_layout = {0, 0, 1, -1, sizeof(void*), 0, 0}; + else if (st == jl_simplevector_type || st == jl_module_type) { + static const jl_datatype_layout_t opaque_ptr_layout = {0, 0, 1, -1, sizeof(void*), {0}}; st->layout = &opaque_ptr_layout; return; } else { // reuse the same layout for all singletons - static const jl_datatype_layout_t singleton_layout = {0, 0, 0, -1, 1, 0, 0}; + static const jl_datatype_layout_t singleton_layout = {0, 0, 0, -1, 1, {0}}; st->layout = &singleton_layout; } } @@ -586,7 +693,7 @@ void jl_compute_field_offsets(jl_datatype_t *st) } else { uint32_t fld_npointers = ((jl_datatype_t*)fld)->layout->npointers; - if (((jl_datatype_t*)fld)->layout->haspadding) + if (((jl_datatype_t*)fld)->layout->flags.haspadding) haspadding = 1; if (i >= nfields - st->name->n_uninitialized && fld_npointers && fld_npointers * sizeof(void*) != fsz) { @@ -670,7 +777,7 @@ void jl_compute_field_offsets(jl_datatype_t *st) } } assert(ptr_i == npointers); - st->layout = jl_get_layout(sz, nfields, npointers, alignm, haspadding, desc, pointers); + st->layout = jl_get_layout(sz, nfields, npointers, alignm, haspadding, 0, desc, pointers); if (should_malloc) { free(desc); if (npointers) @@ -687,14 +794,6 @@ void jl_compute_field_offsets(jl_datatype_t *st) return; } -static int is_anonfn_typename(char *name) -{ - if (name[0] != '#' || name[1] == '#') - return 0; - char *other = strrchr(name, '#'); - return other > &name[1] && is10digit(other[1]); -} - JL_DLLEXPORT jl_datatype_t *jl_new_datatype( jl_sym_t *name, jl_module_t *module, @@ -710,7 +809,7 @@ JL_DLLEXPORT jl_datatype_t *jl_new_datatype( jl_typename_t *tn = NULL; JL_GC_PUSH2(&t, &tn); - assert(parameters); + assert(parameters && fnames); // init enough before possibly calling jl_new_typename_in t = jl_new_uninitialized_datatype(); @@ -832,7 +931,7 @@ JL_DLLEXPORT jl_datatype_t *jl_new_primitivetype(jl_value_t *name, jl_module_t * bt->ismutationfree = 1; bt->isidentityfree = 1; bt->isbitstype = (parameters == jl_emptysvec); - bt->layout = jl_get_layout(nbytes, 0, 0, alignm, 0, NULL, NULL); + bt->layout = jl_get_layout(nbytes, 0, 0, alignm, 0, 0, NULL, NULL); bt->instance = NULL; return bt; } @@ -853,10 +952,12 @@ JL_DLLEXPORT jl_datatype_t * jl_new_foreign_type(jl_sym_t *name, layout->size = large ? GC_MAX_SZCLASS+1 : 0; layout->nfields = 0; layout->alignment = sizeof(void *); - layout->haspadding = 1; layout->npointers = haspointers; - layout->fielddesc_type = 3; - layout->padding = 0; + layout->flags.haspadding = 1; + layout->flags.fielddesc_type = 3; + layout->flags.padding = 0; + layout->flags.arrayelem_isboxed = 0; + layout->flags.arrayelem_isunion = 0; jl_fielddescdyn_t * desc = (jl_fielddescdyn_t *) ((char *)layout + sizeof(*layout)); desc->markfunc = markfunc; @@ -884,7 +985,7 @@ JL_DLLEXPORT int jl_reinit_foreign_type(jl_datatype_t *dt, JL_DLLEXPORT int jl_is_foreign_type(jl_datatype_t *dt) { - return jl_is_datatype(dt) && dt->layout && dt->layout->fielddesc_type == 3; + return jl_is_datatype(dt) && dt->layout && dt->layout->flags.fielddesc_type == 3; } // bits constructors ---------------------------------------------------------- @@ -904,7 +1005,7 @@ JL_DLLEXPORT int jl_is_foreign_type(jl_datatype_t *dt) #if MAX_POINTERATOMIC_SIZE >= 16 typedef struct _jl_uint128_t { - uint64_t a; + alignas(16) uint64_t a; uint64_t b; } jl_uint128_t; #endif @@ -968,6 +1069,7 @@ JL_DLLEXPORT jl_value_t *jl_new_bits(jl_value_t *dt, const void *data) assert(!bt->smalltag); jl_task_t *ct = jl_current_task; jl_value_t *v = jl_gc_alloc(ct->ptls, nb, bt); + // TODO: make this a memmove_refs if relevant memcpy(jl_assume_aligned(v, sizeof(void*)), data, nb); return v; } @@ -1122,13 +1224,10 @@ 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, jl_datatype_t *rettyp, char *dst, const jl_value_t *expected, const jl_value_t *src, int nb) +JL_DLLEXPORT int jl_atomic_cmpswap_bits(jl_datatype_t *dt, jl_value_t *y /* pre-allocated output */, 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_task_t *ct = jl_current_task; - int isptr = jl_field_isptr(rettyp, 0); - jl_value_t *y = jl_gc_alloc(ct->ptls, isptr ? nb : jl_datatype_size(rettyp), isptr ? dt : rettyp); int success; jl_datatype_t *et = (jl_datatype_t*)jl_typeof(expected); if (nb == 0) { @@ -1136,7 +1235,7 @@ JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, jl_datatype_t } else if (nb == 1) { uint8_t *y8 = (uint8_t*)y; - assert(!dt->layout->haspadding); + assert(!dt->layout->flags.haspadding); if (dt == et) { *y8 = *(uint8_t*)expected; uint8_t z8 = *(uint8_t*)src; @@ -1149,7 +1248,7 @@ JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, jl_datatype_t } else if (nb == 2) { uint16_t *y16 = (uint16_t*)y; - assert(!dt->layout->haspadding); + assert(!dt->layout->flags.haspadding); if (dt == et) { *y16 = *(uint16_t*)expected; uint16_t z16 = *(uint16_t*)src; @@ -1167,7 +1266,7 @@ JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, jl_datatype_t uint32_t z32 = zext_read32(src, nb); while (1) { success = jl_atomic_cmpswap((_Atomic(uint32_t)*)dst, y32, z32); - if (success || !dt->layout->haspadding || !jl_egal__bits(y, expected, dt)) + if (success || !dt->layout->flags.haspadding || !jl_egal__bits(y, expected, dt)) break; } } @@ -1184,7 +1283,7 @@ JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, jl_datatype_t uint64_t z64 = zext_read64(src, nb); while (1) { success = jl_atomic_cmpswap((_Atomic(uint64_t)*)dst, y64, z64); - if (success || !dt->layout->haspadding || !jl_egal__bits(y, expected, dt)) + if (success || !dt->layout->flags.haspadding || !jl_egal__bits(y, expected, dt)) break; } } @@ -1202,7 +1301,7 @@ JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, jl_datatype_t jl_uint128_t z128 = zext_read128(src, nb); while (1) { success = jl_atomic_cmpswap((_Atomic(jl_uint128_t)*)dst, y128, z128); - if (success || !dt->layout->haspadding || !jl_egal__bits(y, expected, dt)) + if (success || !dt->layout->flags.haspadding || !jl_egal__bits(y, expected, dt)) break; } } @@ -1215,28 +1314,54 @@ JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, jl_datatype_t else { abort(); } - if (isptr) { - JL_GC_PUSH1(&y); - jl_value_t *z = jl_gc_alloc(ct->ptls, jl_datatype_size(rettyp), rettyp); - *(jl_value_t**)z = y; - JL_GC_POP(); - y = z; - nb = sizeof(jl_value_t*); - } - *((uint8_t*)y + nb) = success ? 1 : 0; - return y; + return success; } -// used by boot.jl -JL_DLLEXPORT jl_value_t *jl_typemax_uint(jl_datatype_t *bt) +JL_DLLEXPORT int jl_atomic_storeonce_bits(jl_datatype_t *dt, char *dst, const jl_value_t *src, int nb) { - uint64_t data = 0xffffffffffffffffULL; - jl_task_t *ct = jl_current_task; - jl_value_t *v = jl_gc_alloc(ct->ptls, sizeof(size_t), bt); - if (bt->smalltag) - jl_set_typetagof(v, bt->smalltag, 0); - memcpy(v, &data, sizeof(size_t)); - return v; + // dst must have the required alignment for an atomic of the given size + // n.b.: this does not spuriously fail + // n.b.: hasptr == 1 therefore nb >= sizeof(void*), because ((jl_datatype_t*)ty)->layout->has_ptr >= 0 + int success; +#ifdef _P64 + if (nb <= 4) { + uint32_t y32 = 0; + uint32_t z32 = zext_read32(src, nb); + success = jl_atomic_cmpswap((_Atomic(uint32_t)*)dst, &y32, z32); + } +#if MAX_POINTERATOMIC_SIZE >= 8 + else if (nb <= 8) { + uint64_t y64 = 0; + uint64_t z64 = zext_read64(src, nb); + while (1) { + success = jl_atomic_cmpswap((_Atomic(uint64_t)*)dst, &y64, z64); + if (success || undefref_check(dt, (jl_value_t*)&y64) != NULL) + break; + } + } +#endif +#else + if (nb <= 8) { + uint64_t y64 = 0; + uint64_t z64 = zext_read64(src, nb); + success = jl_atomic_cmpswap((_Atomic(uint64_t)*)dst, &y64, z64); + } +#endif +#if MAX_POINTERATOMIC_SIZE >= 16 + else if (nb <= 16) { + jl_uint128_t y128 = {0}; + jl_uint128_t z128 = zext_read128(src, nb); + while (1) { + success = jl_atomic_cmpswap((_Atomic(jl_uint128_t)*)dst, &y128, z128); + if (success || undefref_check(dt, (jl_value_t*)&y128) != NULL) + break; + } + } +#endif + else { + abort(); + } + return success; } #define PERMBOXN_FUNC(nb) \ @@ -1359,6 +1484,9 @@ void jl_init_int32_int64_cache(void) for(i=0; i < NBOX_C; i++) { boxed_int32_cache[i] = jl_permbox32(jl_int32_type, jl_int32_tag, i-NBOX_C/2); boxed_int64_cache[i] = jl_permbox64(jl_int64_type, jl_int64_tag, i-NBOX_C/2); + boxed_uint16_cache[i] = jl_permbox16(jl_uint16_type, jl_uint16_tag, i); + boxed_uint64_cache[i] = jl_permbox64(jl_uint64_type, jl_uint64_tag, i); + boxed_uint32_cache[i] = jl_permbox32(jl_uint32_type, jl_uint32_tag, i); #ifdef _P64 boxed_ssavalue_cache[i] = jl_permbox64(jl_ssavalue_type, 0, i); boxed_slotnumber_cache[i] = jl_permbox64(jl_slotnumber_type, 0, i); @@ -1383,9 +1511,6 @@ void jl_init_box_caches(void) } for (i = 0; i < NBOX_C; i++) { boxed_int16_cache[i] = jl_permbox16(jl_int16_type, jl_int16_tag, i-NBOX_C/2); - boxed_uint16_cache[i] = jl_permbox16(jl_uint16_type, jl_uint16_tag, i); - boxed_uint32_cache[i] = jl_permbox32(jl_uint32_type, jl_uint32_tag, i); - boxed_uint64_cache[i] = jl_permbox64(jl_uint64_type, jl_uint64_tag, i); } } @@ -1401,10 +1526,11 @@ JL_DLLEXPORT jl_value_t *jl_box_bool(int8_t x) JL_DLLEXPORT jl_value_t *jl_new_struct(jl_datatype_t *type, ...) { jl_task_t *ct = jl_current_task; - if (type->instance != NULL) return type->instance; - if (!jl_is_datatype(type) || !type->isconcretetype || type->layout == NULL) { + if (!jl_is_datatype(type) || !type->isconcretetype || type->layout == NULL || jl_is_layout_opaque(type->layout)) { jl_type_error("new", (jl_value_t*)jl_datatype_type, (jl_value_t*)type); } + if (type->instance != NULL) + return type->instance; va_list args; size_t i, nf = jl_datatype_nfields(type); va_start(args, type); @@ -1424,7 +1550,7 @@ 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_task_t *ct = jl_current_task; - if (!jl_is_datatype(type) || !type->isconcretetype || type->layout == NULL) { + if (!jl_is_datatype(type) || !type->isconcretetype || type->layout == NULL || jl_is_layout_opaque(type->layout)) { jl_type_error("new", (jl_value_t*)jl_datatype_type, (jl_value_t*)type); } size_t nf = jl_datatype_nfields(type); @@ -1463,7 +1589,7 @@ JL_DLLEXPORT jl_value_t *jl_new_structt(jl_datatype_t *type, jl_value_t *tup) jl_task_t *ct = jl_current_task; if (!jl_is_tuple(tup)) jl_type_error("new", (jl_value_t*)jl_tuple_type, tup); - if (!jl_is_datatype(type) || !type->isconcretetype || type->layout == NULL) + if (!jl_is_datatype(type) || !type->isconcretetype || type->layout == NULL || jl_is_layout_opaque(type->layout)) jl_type_error("new", (jl_value_t *)jl_datatype_type, (jl_value_t *)type); size_t nargs = jl_nfields(tup); size_t nf = jl_datatype_nfields(type); @@ -1510,10 +1636,11 @@ JL_DLLEXPORT jl_value_t *jl_new_structt(jl_datatype_t *type, jl_value_t *tup) JL_DLLEXPORT jl_value_t *jl_new_struct_uninit(jl_datatype_t *type) { jl_task_t *ct = jl_current_task; - if (type->instance != NULL) return type->instance; - if (!jl_is_datatype(type) || type->layout == NULL) { + if (!jl_is_datatype(type) || !type->isconcretetype || type->layout == NULL || jl_is_layout_opaque(type->layout)) { jl_type_error("new", (jl_value_t*)jl_datatype_type, (jl_value_t*)type); } + if (type->instance != NULL) + return type->instance; size_t size = jl_datatype_size(type); jl_value_t *jv = jl_gc_alloc(ct->ptls, size, type); if (type->smalltag) // TODO: do we need this? @@ -1525,14 +1652,51 @@ JL_DLLEXPORT jl_value_t *jl_new_struct_uninit(jl_datatype_t *type) // field access --------------------------------------------------------------- -JL_DLLEXPORT void jl_lock_value(jl_value_t *v) JL_NOTSAFEPOINT +// TODO(jwn): these lock/unlock pairs must be full seq-cst fences +JL_DLLEXPORT void jl_lock_value(jl_mutex_t *v) JL_NOTSAFEPOINT { - JL_LOCK_NOGC((jl_mutex_t*)v); + JL_LOCK_NOGC(v); } -JL_DLLEXPORT void jl_unlock_value(jl_value_t *v) JL_NOTSAFEPOINT +JL_DLLEXPORT void jl_unlock_value(jl_mutex_t *v) JL_NOTSAFEPOINT { - JL_UNLOCK_NOGC((jl_mutex_t*)v); + JL_UNLOCK_NOGC(v); +} + +JL_DLLEXPORT void jl_lock_field(jl_mutex_t *v) JL_NOTSAFEPOINT +{ + JL_LOCK_NOGC(v); +} + +JL_DLLEXPORT void jl_unlock_field(jl_mutex_t *v) JL_NOTSAFEPOINT +{ + JL_UNLOCK_NOGC(v); +} + +static inline char *lock(char *p, jl_value_t *parent, int needlock, enum atomic_kind isatomic) JL_NOTSAFEPOINT +{ + if (needlock) { + if (isatomic == isatomic_object) { + jl_lock_value((jl_mutex_t*)parent); + } + else { + jl_lock_field((jl_mutex_t*)p); + return p + LLT_ALIGN(sizeof(jl_mutex_t), JL_SMALL_BYTE_ALIGNMENT); + } + } + return p; +} + +static inline void unlock(char *p, jl_value_t *parent, int needlock, enum atomic_kind isatomic) JL_NOTSAFEPOINT +{ + if (needlock) { + if (isatomic == isatomic_object) { + jl_unlock_value((jl_mutex_t*)parent); + } + else { + jl_unlock_field((jl_mutex_t*)p); + } + } } JL_DLLEXPORT int jl_field_index(jl_datatype_t *t, jl_sym_t *fld, int err) @@ -1590,11 +1754,12 @@ JL_DLLEXPORT jl_value_t *jl_get_nth_field(jl_value_t *v, size_t i) else if (needlock) { jl_task_t *ct = jl_current_task; r = jl_gc_alloc(ct->ptls, fsz, ty); - jl_lock_value(v); + jl_lock_value((jl_mutex_t*)v); memcpy((char*)r, (char*)v + offs, fsz); - jl_unlock_value(v); + jl_unlock_value((jl_mutex_t*)v); } else { + // TODO: a finalizer here could make the isunion case not quite right r = jl_new_bits(ty, (char*)v + offs); } return undefref_check((jl_datatype_t*)ty, r); @@ -1617,30 +1782,7 @@ JL_DLLEXPORT jl_value_t *jl_get_nth_field_checked(jl_value_t *v, size_t i) return r; } -static inline void memassign_safe(int hasptr, jl_value_t *parent, char *dst, const jl_value_t *src, size_t nb) JL_NOTSAFEPOINT -{ - if (hasptr) { - // assert that although dst might have some undefined bits, the src heap box should be okay with that - assert(LLT_ALIGN(nb, sizeof(void*)) == LLT_ALIGN(jl_datatype_size(jl_typeof(src)), sizeof(void*))); - size_t nptr = nb / sizeof(void*); - memmove_refs((void**)dst, (void**)src, nptr); - jl_gc_multi_wb(parent, src); - src = (jl_value_t*)((char*)src + nptr * sizeof(void*)); - dst = dst + nptr * sizeof(void*); - nb -= nptr * sizeof(void*); - } - else { - // src must be a heap box. - assert(nb == jl_datatype_size(jl_typeof(src))); - if (nb >= 16) { - memcpy(dst, jl_assume_aligned(src, 16), nb); - return; - } - } - memcpy(dst, jl_assume_aligned(src, sizeof(void*)), nb); -} - -void set_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *rhs, int isatomic) JL_NOTSAFEPOINT +inline void set_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *rhs, int isatomic) JL_NOTSAFEPOINT { size_t offs = jl_field_offset(st, i); if (rhs == NULL) { // TODO: this should be invalid, but it happens frequently in ircode.c @@ -1669,26 +1811,77 @@ void set_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *rhs, hasptr = 0; } else { - hasptr = ((jl_datatype_t*)ty)->layout->npointers > 0; + hasptr = ((jl_datatype_t*)ty)->layout->first_ptr >= 0; } size_t fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy + assert(!isatomic || jl_typeis(rhs, ty)); int needlock = (isatomic && fsz > MAX_ATOMIC_SIZE); if (isatomic && !needlock) { jl_atomic_store_bits((char*)v + offs, rhs, fsz); - if (hasptr) - jl_gc_multi_wb(v, rhs); // rhs is immutable } else if (needlock) { - jl_lock_value(v); + jl_lock_value((jl_mutex_t*)v); memcpy((char*)v + offs, (char*)rhs, fsz); - jl_unlock_value(v); + jl_unlock_value((jl_mutex_t*)v); } else { - memassign_safe(hasptr, v, (char*)v + offs, rhs, fsz); + memassign_safe(hasptr, (char*)v + offs, rhs, fsz); } + if (hasptr) + jl_gc_multi_wb(v, rhs); // rhs is immutable } } +inline jl_value_t *swap_bits(jl_value_t *ty, char *v, uint8_t *psel, jl_value_t *parent, jl_value_t *rhs, enum atomic_kind isatomic) +{ + jl_value_t *rty = jl_typeof(rhs); + int hasptr; + int isunion = psel != NULL; + if (isunion) { + assert(!isatomic); + hasptr = 0; + } + else { + hasptr = ((jl_datatype_t*)ty)->layout->first_ptr >= 0; + } + size_t fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy + int needlock = (isatomic && fsz > MAX_ATOMIC_SIZE); + assert(!isatomic || jl_typeis(rhs, ty)); + jl_value_t *r; + if (isatomic && !needlock) { + r = jl_atomic_swap_bits(rty, v, rhs, fsz); + } + else { + if (needlock) { + jl_task_t *ct = jl_current_task; + r = jl_gc_alloc(ct->ptls, fsz, ty); + char *px = lock(v, parent, needlock, isatomic); + memcpy((char*)r, px, fsz); + memcpy(px, (char*)rhs, fsz); + unlock(v, parent, needlock, isatomic); + } + else { + r = jl_new_bits(isunion ? jl_nth_union_component(ty, *psel) : ty, v); + if (isunion) { + unsigned nth = 0; + if (!jl_find_union_component(ty, rty, &nth)) + assert(0 && "invalid field assignment to isbits union"); + *psel = nth; + if (jl_is_datatype_singleton((jl_datatype_t*)rty)) + return r; + } + memassign_safe(hasptr, v, rhs, fsz); + } + } + if (!isunion) + r = undefref_check((jl_datatype_t*)ty, r); + if (hasptr) + jl_gc_multi_wb(parent, rhs); // rhs is immutable + if (__unlikely(r == NULL)) + jl_throw(jl_undefref_exception); + return r; +} + jl_value_t *swap_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *rhs, int isatomic) { jl_value_t *ty = jl_field_type_concrete(st, i); @@ -1696,138 +1889,139 @@ jl_value_t *swap_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_ jl_type_error("swapfield!", ty, rhs); size_t offs = jl_field_offset(st, i); jl_value_t *r; + char *p = (char*)v + offs; if (jl_field_isptr(st, i)) { if (isatomic) - r = jl_atomic_exchange((_Atomic(jl_value_t*)*)((char*)v + offs), rhs); + r = jl_atomic_exchange((_Atomic(jl_value_t*)*)p, rhs); else - r = jl_atomic_exchange_relaxed((_Atomic(jl_value_t*)*)((char*)v + offs), rhs); + r = jl_atomic_exchange_release((_Atomic(jl_value_t*)*)p, rhs); jl_gc_wb(v, rhs); + if (__unlikely(r == NULL)) + jl_throw(jl_undefref_exception); + return r; } else { - jl_value_t *rty = jl_typeof(rhs); - int hasptr; - int isunion = jl_is_uniontype(ty); - if (isunion) { - assert(!isatomic); - r = jl_get_nth_field(v, i); - size_t fsz = jl_field_size(st, i); - uint8_t *psel = &((uint8_t*)v)[offs + fsz - 1]; - unsigned nth = 0; - if (!jl_find_union_component(ty, rty, &nth)) - assert(0 && "invalid field assignment to isbits union"); - *psel = nth; - if (jl_is_datatype_singleton((jl_datatype_t*)rty)) - return r; - hasptr = 0; - } - else { - hasptr = ((jl_datatype_t*)ty)->layout->npointers > 0; - } - size_t fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy - int needlock = (isatomic && fsz > MAX_ATOMIC_SIZE); - if (isatomic && !needlock) { - r = jl_atomic_swap_bits(rty, (char*)v + offs, rhs, fsz); - if (hasptr) - jl_gc_multi_wb(v, rhs); // rhs is immutable + uint8_t *psel = jl_is_uniontype(ty) ? (uint8_t*)&p[jl_field_size(st, i) - 1] : NULL; + return swap_bits(ty, p, psel, v, rhs, isatomic ? isatomic_object : isatomic_none); + } +} + +inline jl_value_t *modify_value(jl_value_t *ty, _Atomic(jl_value_t*) *p, jl_value_t *parent, jl_value_t *op, jl_value_t *rhs, int isatomic, jl_module_t *mod, jl_sym_t *name) +{ + jl_value_t *r = isatomic ? jl_atomic_load(p) : jl_atomic_load_relaxed(p); + if (__unlikely(r == NULL)) { + if (mod && name) + jl_undefined_var_error(name, (jl_value_t*)mod); + jl_throw(jl_undefref_exception); + } + jl_value_t **args; + JL_GC_PUSHARGS(args, 2); + args[0] = r; + while (1) { + args[1] = rhs; + jl_value_t *y = jl_apply_generic(op, args, 2); + args[1] = y; + if (!jl_isa(y, ty)) { + if (mod && name) + jl_errorf("cannot assign an incompatible value to the global %s.%s.", jl_symbol_name(mod->name), jl_symbol_name(name)); + jl_type_error(jl_is_genericmemory(parent) ? "memoryrefmodify!" : "modifyfield!", ty, y); } - else { - if (needlock) { - jl_task_t *ct = jl_current_task; - r = jl_gc_alloc(ct->ptls, fsz, ty); - jl_lock_value(v); - memcpy((char*)r, (char*)v + offs, fsz); - memcpy((char*)v + offs, (char*)rhs, fsz); - jl_unlock_value(v); - } - else { - if (!isunion) - r = jl_new_bits(ty, (char*)v + offs); - memassign_safe(hasptr, v, (char*)v + offs, rhs, fsz); - } - if (needlock || !isunion) - r = undefref_check((jl_datatype_t*)ty, r); + if (isatomic ? jl_atomic_cmpswap(p, &r, y) : jl_atomic_cmpswap_release(p, &r, y)) { + jl_gc_wb(parent, y); + break; } + args[0] = r; + jl_gc_safepoint(); } - if (__unlikely(r == NULL)) - jl_throw(jl_undefref_exception); - return r; + // 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]; } -jl_value_t *modify_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *op, jl_value_t *rhs, int isatomic) +inline jl_value_t *modify_bits(jl_value_t *ty, char *p, uint8_t *psel, jl_value_t *parent, jl_value_t *op, jl_value_t *rhs, enum atomic_kind isatomic) { - size_t offs = jl_field_offset(st, i); - jl_value_t *ty = jl_field_type_concrete(st, i); - jl_value_t *r = jl_get_nth_field_checked(v, i); - if (isatomic && jl_field_isptr(st, i)) - jl_fence(); // load was previously only relaxed + int hasptr; + int isunion = psel != NULL; + if (isunion) { + assert(!isatomic); + hasptr = 0; + } + else { + hasptr = ((jl_datatype_t*)ty)->layout->first_ptr >= 0; + } jl_value_t **args; JL_GC_PUSHARGS(args, 2); - args[0] = r; while (1) { + jl_value_t *r; + jl_value_t *rty = isunion ? jl_nth_union_component(ty, *psel) : ty; + size_t fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the initial copy + int needlock = (isatomic && fsz > MAX_ATOMIC_SIZE); + if (isatomic && !needlock) { + r = jl_atomic_new_bits(rty, p); + } + else if (needlock) { + jl_task_t *ct = jl_current_task; + r = jl_gc_alloc(ct->ptls, fsz, rty); + char *px = lock(p, parent, needlock, isatomic); + memcpy((char*)r, px, fsz); + unlock(p, parent, needlock, isatomic); + } + else { + r = jl_new_bits(rty, p); + } + r = undefref_check((jl_datatype_t*)rty, r); + if (__unlikely(r == NULL)) + jl_throw(jl_undefref_exception); + args[0] = r; args[1] = rhs; jl_value_t *y = jl_apply_generic(op, args, 2); args[1] = y; - if (!jl_isa(y, ty)) - jl_type_error("modifyfield!", ty, y); - if (jl_field_isptr(st, i)) { - _Atomic(jl_value_t*) *p = (_Atomic(jl_value_t*)*)((char*)v + offs); - if (isatomic ? jl_atomic_cmpswap(p, &r, y) : jl_atomic_cmpswap_relaxed(p, &r, y)) + if (!jl_isa(y, ty)) { + jl_type_error(jl_is_genericmemory(parent) ? "memoryrefmodify!" : "modifyfield!", ty, y); + } + jl_value_t *yty = jl_typeof(y); + if (isatomic && !needlock) { + assert(yty == rty); + if (jl_atomic_bool_cmpswap_bits(p, r, y, fsz)) { + if (hasptr) + jl_gc_multi_wb(parent, y); // y is immutable break; + } } else { - jl_value_t *yty = jl_typeof(y); - jl_value_t *rty = jl_typeof(r); - int hasptr; - int isunion = jl_is_uniontype(ty); - if (isunion) { - assert(!isatomic); - hasptr = 0; - } - else { - hasptr = ((jl_datatype_t*)ty)->layout->npointers > 0; - } - size_t fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy - int needlock = (isatomic && fsz > MAX_ATOMIC_SIZE); - if (isatomic && !needlock) { - if (jl_atomic_bool_cmpswap_bits((char*)v + offs, r, y, fsz)) { - if (hasptr) - jl_gc_multi_wb(v, y); // y is immutable - break; - } - r = jl_atomic_new_bits(ty, (char*)v + offs); - } - else { - if (needlock) - jl_lock_value(v); - int success = memcmp((char*)v + offs, r, fsz) == 0; - if (success) { - if (isunion) { - size_t fsz = jl_field_size(st, i); - uint8_t *psel = &((uint8_t*)v)[offs + fsz - 1]; - success = (jl_typeof(r) == jl_nth_union_component(ty, *psel)); - if (success) { - unsigned nth = 0; - if (!jl_find_union_component(ty, yty, &nth)) - assert(0 && "invalid field assignment to isbits union"); - *psel = nth; - if (jl_is_datatype_singleton((jl_datatype_t*)yty)) - break; - } - fsz = jl_datatype_size((jl_datatype_t*)yty); // need to shrink-wrap the final copy - } - else { - assert(yty == ty && rty == ty); + char *px = lock(p, parent, needlock, isatomic); + int success = memcmp(px, (char*)r, fsz) == 0; + if (!success && ((jl_datatype_t*)rty)->layout->flags.haspadding) + success = jl_egal__bits((jl_value_t*)px, r, (jl_datatype_t*)rty); + if (success) { + if (isunion) { + success = (rty == jl_nth_union_component(ty, *psel)); + if (success) { + unsigned nth = 0; + if (!jl_find_union_component(ty, yty, &nth)) + assert(0 && "invalid field assignment to isbits union"); + *psel = nth; + if (jl_is_datatype_singleton((jl_datatype_t*)yty)) + break; } - memassign_safe(hasptr, v, (char*)v + offs, y, fsz); + fsz = jl_datatype_size((jl_datatype_t*)yty); // need to shrink-wrap the final copy } - if (needlock) - jl_unlock_value(v); - if (success) - break; - r = jl_get_nth_field(v, i); + else { + assert(yty == ty && rty == ty); + } + memassign_safe(hasptr, px, y, fsz); + } + unlock(p, parent, needlock, isatomic); + if (success) { + if (hasptr) + jl_gc_multi_wb(parent, y); // y is immutable + break; } } - args[0] = r; jl_gc_safepoint(); } // args[0] == r (old) @@ -1839,91 +2033,105 @@ jl_value_t *modify_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_valu return args[0]; } -jl_value_t *replace_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *expected, jl_value_t *rhs, int isatomic) +jl_value_t *modify_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *op, jl_value_t *rhs, int isatomic) { - jl_value_t *ty = jl_field_type_concrete(st, i); - if (!jl_isa(rhs, ty)) - jl_type_error("replacefield!", ty, rhs); size_t offs = jl_field_offset(st, i); - jl_value_t *r = expected; + jl_value_t *ty = jl_field_type_concrete(st, i); + char *p = (char*)v + offs; + if (jl_field_isptr(st, i)) { + return modify_value(ty, (_Atomic(jl_value_t*)*)p, v, op, rhs, isatomic, NULL, NULL); + } + else { + uint8_t *psel = jl_is_uniontype(ty) ? (uint8_t*)&p[jl_field_size(st, i) - 1] : NULL; + return modify_bits(ty, p, psel, v, op, rhs, isatomic ? isatomic_object : isatomic_none); + } +} + +inline jl_value_t *replace_value(jl_value_t *ty, _Atomic(jl_value_t*) *p, jl_value_t *parent, jl_value_t *expected, jl_value_t *rhs, int isatomic, jl_module_t *mod, jl_sym_t *name) +{ jl_datatype_t *rettyp = jl_apply_cmpswap_type(ty); JL_GC_PROMISE_ROOTED(rettyp); // (JL_ALWAYS_LEAFTYPE) - if (jl_field_isptr(st, i)) { - _Atomic(jl_value_t*) *p = (_Atomic(jl_value_t*)*)((char*)v + offs); - int success; - while (1) { - success = isatomic ? jl_atomic_cmpswap(p, &r, rhs) : jl_atomic_cmpswap_relaxed(p, &r, rhs); - if (success) - jl_gc_wb(v, rhs); - if (__unlikely(r == NULL)) - jl_throw(jl_undefref_exception); - if (success || !jl_egal(r, expected)) - break; + jl_value_t *r = expected; + int success; + while (1) { + success = isatomic ? jl_atomic_cmpswap(p, &r, rhs) : jl_atomic_cmpswap_release(p, &r, rhs); + if (success) + jl_gc_wb(parent, rhs); + if (__unlikely(r == NULL)) { + if (mod && name) + jl_undefined_var_error(name, (jl_value_t*)mod); + jl_throw(jl_undefref_exception); } - JL_GC_PUSH1(&r); - r = jl_new_struct(rettyp, r, success ? jl_true : jl_false); - JL_GC_POP(); + if (success || !jl_egal(r, expected)) + break; + } + JL_GC_PUSH1(&r); + r = jl_new_struct(rettyp, r, success ? jl_true : jl_false); + JL_GC_POP(); + return r; +} + +inline jl_value_t *replace_bits(jl_value_t *ty, char *p, uint8_t *psel, jl_value_t *parent, jl_value_t *expected, jl_value_t *rhs, enum atomic_kind isatomic) +{ + jl_datatype_t *rettyp = jl_apply_cmpswap_type(ty); + JL_GC_PROMISE_ROOTED(rettyp); // (JL_ALWAYS_LEAFTYPE) + int hasptr; + int isunion = psel != NULL; + size_t fsz = jl_field_size(rettyp, 0); + int needlock = (isatomic && fsz > MAX_ATOMIC_SIZE); + assert(jl_field_offset(rettyp, 1) == fsz); + jl_value_t *rty = ty; + if (isunion) { + assert(!isatomic); + hasptr = 0; + isatomic = isatomic_none; // this makes GCC happy } else { - int hasptr; - int isunion = jl_is_uniontype(ty); - int needlock; - jl_value_t *rty = ty; - size_t fsz = jl_field_size(st, i); - if (isunion) { - assert(!isatomic); - hasptr = 0; - needlock = 0; - isatomic = 0; // this makes GCC happy - } - else { - hasptr = ((jl_datatype_t*)ty)->layout->npointers > 0; - fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy - needlock = (isatomic && fsz > MAX_ATOMIC_SIZE); - } - if (isatomic && !needlock) { - 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 + hasptr = ((jl_datatype_t*)ty)->layout->first_ptr >= 0; + assert(jl_typeis(rhs, ty)); + } + int success; + jl_task_t *ct = jl_current_task; + assert(!jl_field_isptr(rettyp, 0)); + jl_value_t *r = jl_gc_alloc(ct->ptls, jl_datatype_size(rettyp), rettyp); + if (isatomic && !needlock) { + size_t rsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the compare + success = jl_atomic_cmpswap_bits((jl_datatype_t*)rty, r, p, expected, rhs, rsz); + *((uint8_t*)r + fsz) = success ? 1 : 0; + } + else { + char *px = lock(p, parent, needlock, isatomic); + if (isunion) + rty = jl_nth_union_component(rty, *psel); + size_t rsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the compare + memcpy((char*)r, px, rsz); // copy field // TODO: make this a memmove_refs if relevant + if (isunion) + *((uint8_t*)r + fsz - 1) = *psel; // copy union bits + success = (rty == jl_typeof(expected)); + if (success) { + success = memcmp((char*)r, (char*)expected, rsz) == 0; + if (!success && ((jl_datatype_t*)rty)->layout->flags.haspadding) + success = jl_egal__bits(r, expected, (jl_datatype_t*)rty); } - else { - jl_task_t *ct = jl_current_task; - uint8_t *psel = NULL; + *((uint8_t*)r + fsz) = success ? 1 : 0; + if (success) { + jl_value_t *rty = jl_typeof(rhs); if (isunion) { - psel = &((uint8_t*)v)[offs + fsz - 1]; - rty = jl_nth_union_component(rty, *psel); + rsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy + unsigned nth = 0; + if (!jl_find_union_component(ty, rty, &nth)) + assert(0 && "invalid field assignment to isbits union"); + *psel = nth; + if (jl_is_datatype_singleton((jl_datatype_t*)rty)) + return r; } - assert(!jl_field_isptr(rettyp, 0)); - r = jl_gc_alloc(ct->ptls, jl_datatype_size(rettyp), (jl_value_t*)rettyp); - int success = (rty == jl_typeof(expected)); - if (needlock) - jl_lock_value(v); - 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 - success = memcmp((char*)r, (char*)expected, fsz) == 0; - } - *((uint8_t*)r + fsz) = success ? 1 : 0; - if (success) { - jl_value_t *rty = jl_typeof(rhs); - size_t fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy - if (isunion) { - unsigned nth = 0; - if (!jl_find_union_component(ty, rty, &nth)) - assert(0 && "invalid field assignment to isbits union"); - *psel = nth; - if (jl_is_datatype_singleton((jl_datatype_t*)rty)) - return r; - } - memassign_safe(hasptr, v, (char*)v + offs, rhs, fsz); - } - if (needlock) - jl_unlock_value(v); + memassign_safe(hasptr, px, rhs, rsz); } + unlock(p, parent, needlock, isatomic); + } + if (success && hasptr) + jl_gc_multi_wb(parent, rhs); // rhs is immutable + if (!isunion) { r = undefref_check((jl_datatype_t*)rty, r); if (__unlikely(r == NULL)) jl_throw(jl_undefref_exception); @@ -1931,6 +2139,74 @@ jl_value_t *replace_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_val return r; } +jl_value_t *replace_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *expected, jl_value_t *rhs, int isatomic) +{ + jl_value_t *ty = jl_field_type_concrete(st, i); + if (!jl_isa(rhs, ty)) + jl_type_error("replacefield!", ty, rhs); + size_t offs = jl_field_offset(st, i); + char *p = (char*)v + offs; + if (jl_field_isptr(st, i)) { + return replace_value(ty, (_Atomic(jl_value_t*)*)p, v, expected, rhs, isatomic, NULL, NULL); + } + else { + size_t fsz = jl_field_size(st, i); + int isunion = jl_is_uniontype(ty); + uint8_t *psel = isunion ? (uint8_t*)&p[fsz - 1] : NULL; + return replace_bits(ty, p, psel, v, expected, rhs, isatomic ? isatomic_object : isatomic_none); + } +} + +inline int setonce_bits(jl_datatype_t *rty, char *p, jl_value_t *parent, jl_value_t *rhs, enum atomic_kind isatomic) +{ + size_t fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy + assert(rty->layout->first_ptr >= 0); + int hasptr = 1; + int needlock = (isatomic && fsz > MAX_ATOMIC_SIZE); + int success; + if (isatomic && !needlock) { + success = jl_atomic_storeonce_bits(rty, p, rhs, fsz); + } + else { + char *px = lock(p, parent, needlock, isatomic); + success = undefref_check(rty, (jl_value_t*)px) != NULL; + if (success) + memassign_safe(hasptr, px, rhs, fsz); + unlock(p, parent, needlock, isatomic); + } + if (success) + jl_gc_multi_wb(parent, rhs); // rhs is immutable + return success; +} + +int set_nth_fieldonce(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *rhs, int isatomic) +{ + jl_value_t *ty = jl_field_type_concrete(st, i); + if (!jl_isa(rhs, ty)) + jl_type_error("setfieldonce!", ty, rhs); + size_t offs = jl_field_offset(st, i); + int success; + char *p = (char*)v + offs; + if (jl_field_isptr(st, i)) { + _Atomic(jl_value_t*) *px = (_Atomic(jl_value_t*)*)p; + jl_value_t *r = NULL; + success = isatomic ? jl_atomic_cmpswap(px, &r, rhs) : jl_atomic_cmpswap_release(px, &r, rhs); + if (success) + jl_gc_wb(v, rhs); + } + else { + int isunion = jl_is_uniontype(ty); + if (isunion) + return 0; + int hasptr = ((jl_datatype_t*)ty)->layout->first_ptr >= 0; + if (!hasptr) + return 0; + assert(ty == jl_typeof(rhs)); + success = setonce_bits((jl_datatype_t*)ty, p, v, rhs, isatomic ? isatomic_object : isatomic_none); + } + return success; +} + JL_DLLEXPORT int jl_field_isdefined(jl_value_t *v, size_t i) JL_NOTSAFEPOINT { jl_datatype_t *st = (jl_datatype_t*)jl_typeof(v); diff --git a/src/debug-registry.h b/src/debug-registry.h index 568a6156b5f29..f30049eb5b210 100644 --- a/src/debug-registry.h +++ b/src/debug-registry.h @@ -88,8 +88,9 @@ class JITDebugInfoRegistry struct libc_frames_t { #if defined(_OS_DARWIN_) && defined(LLVM_SHLIB) - std::atomic libc_register_frame_{nullptr}; - std::atomic libc_deregister_frame_{nullptr}; + typedef void (*frame_register_func)(void *) JL_NOTSAFEPOINT; + std::atomic libc_register_frame_{nullptr}; + std::atomic libc_deregister_frame_{nullptr}; void libc_register_frame(const char *Entry) JL_NOTSAFEPOINT; diff --git a/src/debuginfo.cpp b/src/debuginfo.cpp index 393b14c727b54..5cb7401a036d4 100644 --- a/src/debuginfo.cpp +++ b/src/debuginfo.cpp @@ -374,12 +374,13 @@ void JITDebugInfoRegistry::registerJITObject(const object::ObjectFile &Object, } jl_method_instance_t *mi = NULL; if (codeinst) { + JL_GC_PROMISE_ROOTED(codeinst); mi = codeinst->def; // Non-opaque-closure MethodInstances are considered globally rooted // through their methods, but for OC, we need to create a global root // here. if (jl_is_method(mi->def.value) && mi->def.method->is_for_opaque_closure) - mi = (jl_method_instance_t*)jl_as_global_root((jl_value_t*)mi); + mi = (jl_method_instance_t*)jl_as_global_root((jl_value_t*)mi, 1); } jl_profile_atomic([&]() JL_NOTSAFEPOINT { if (mi) @@ -549,7 +550,7 @@ static int lookup_pointer( #if defined(_OS_DARWIN_) && defined(LLVM_SHLIB) void JITDebugInfoRegistry::libc_frames_t::libc_register_frame(const char *Entry) { - auto libc_register_frame_ = jl_atomic_load_relaxed(&this->libc_register_frame_); + frame_register_func libc_register_frame_ = jl_atomic_load_relaxed(&this->libc_register_frame_); if (!libc_register_frame_) { libc_register_frame_ = (void(*)(void*))dlsym(RTLD_NEXT, "__register_frame"); jl_atomic_store_release(&this->libc_register_frame_, libc_register_frame_); @@ -562,7 +563,7 @@ void JITDebugInfoRegistry::libc_frames_t::libc_register_frame(const char *Entry) } void JITDebugInfoRegistry::libc_frames_t::libc_deregister_frame(const char *Entry) { - auto libc_deregister_frame_ = jl_atomic_load_relaxed(&this->libc_deregister_frame_); + frame_register_func libc_deregister_frame_ = jl_atomic_load_relaxed(&this->libc_deregister_frame_); if (!libc_deregister_frame_) { libc_deregister_frame_ = (void(*)(void*))dlsym(RTLD_NEXT, "__deregister_frame"); jl_atomic_store_release(&this->libc_deregister_frame_, libc_deregister_frame_); @@ -1136,7 +1137,7 @@ bool jl_dylib_DI_for_fptr(size_t pointer, object::SectionRef *Section, int64_t * if (entry.obj) *Section = getModuleSectionForAddress(entry.obj, pointer + entry.slide); // Assume we only need base address for sysimg for now - if (!inimage || !image_info.fptrs.base) + if (!inimage || 0 == image_info.fptrs.nptrs) saddr = nullptr; get_function_name_and_base(*Section, pointer, entry.slide, inimage, saddr, name, untrusted_dladdr); return true; @@ -1179,9 +1180,8 @@ static int jl_getDylibFunctionInfo(jl_frame_t **frames, size_t pointer, int skip JITDebugInfoRegistry::image_info_t image; bool inimage = getJITDebugRegistry().get_image_info(fbase, &image); if (isImage && saddr && inimage) { - intptr_t diff = (uintptr_t)saddr - (uintptr_t)image.fptrs.base; for (size_t i = 0; i < image.fptrs.nclones; i++) { - if (diff == image.fptrs.clone_offsets[i]) { + if (saddr == image.fptrs.clone_ptrs[i]) { uint32_t idx = image.fptrs.clone_idxs[i] & jl_sysimg_val_mask; if (idx < image.fvars_n) // items after this were cloned but not referenced directly by a method (such as our ccall PLT thunks) frame0->linfo = image.fvars_linfo[idx]; @@ -1189,7 +1189,7 @@ static int jl_getDylibFunctionInfo(jl_frame_t **frames, size_t pointer, int skip } } for (size_t i = 0; i < image.fvars_n; i++) { - if (diff == image.fptrs.offsets[i]) { + if (saddr == image.fptrs.ptrs[i]) { frame0->linfo = image.fvars_linfo[i]; break; } @@ -1363,7 +1363,7 @@ enum DW_EH_PE : uint8_t { // Parse the CIE and return the type of encoding used by FDE static DW_EH_PE parseCIE(const uint8_t *Addr, const uint8_t *End) { - // http://www.airs.com/blog/archives/460 + // https://www.airs.com/blog/archives/460 // Length (4 bytes) uint32_t cie_size = *(const uint32_t*)Addr; const uint8_t *cie_addr = Addr + 4; diff --git a/src/disasm.cpp b/src/disasm.cpp index 8967f793735f4..c2a55f23d12f2 100644 --- a/src/disasm.cpp +++ b/src/disasm.cpp @@ -16,7 +16,7 @@ // // University of Illinois at Urbana-Champaign // -// http://llvm.org +// https://llvm.org // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal with @@ -797,19 +797,15 @@ static const char *SymbolLookup(void *DisInfo, uint64_t ReferenceValue, uint64_t static int OpInfoLookup(void *DisInfo, uint64_t PC, uint64_t Offset, -#if JL_LLVM_VERSION < 150000 - uint64_t Size, -#else uint64_t OpSize, uint64_t InstSize, -#endif int TagType, void *TagBuf) { - SymbolTable *SymTab = (SymbolTable*)DisInfo; + // SymbolTable *SymTab = (SymbolTable*)DisInfo; LLVMOpInfo1 *info = (LLVMOpInfo1*)TagBuf; memset(info, 0, sizeof(*info)); if (TagType != 1) return 0; // Unknown data format - PC += SymTab->getIP() - (uint64_t)(uintptr_t)SymTab->getMemoryObject().data(); // add offset from MemoryObject base + // PC += SymTab->getIP() - (uint64_t)(uintptr_t)SymTab->getMemoryObject().data(); // add offset from MemoryObject base // TODO: see if we knew of a relocation applied at PC // info->AddSymbol.Present = 1; // info->AddSymbol.Name = name; @@ -910,11 +906,7 @@ static void jl_dump_asm_internal( std::unique_ptr CE; std::unique_ptr MAB; if (ShowEncoding) { -#if JL_LLVM_VERSION >= 150000 CE.reset(TheTarget->createMCCodeEmitter(*MCII, Ctx)); -#else - CE.reset(TheTarget->createMCCodeEmitter(*MCII, *MRI, Ctx)); -#endif MAB.reset(TheTarget->createMCAsmBackend(*STI, *MRI, Options)); } @@ -929,11 +921,7 @@ static void jl_dump_asm_internal( IP.release(), std::move(CE), std::move(MAB), /*ShowInst*/ false)); -#if JL_LLVM_VERSION >= 140000 Streamer->initSections(true, *STI); -#else - Streamer->InitSections(true); -#endif // Make the MemoryObject wrapper ArrayRef memoryObject(const_cast((const uint8_t*)Fptr),Fsize); @@ -1049,9 +1037,6 @@ static void jl_dump_asm_internal( MCInst Inst; MCDisassembler::DecodeStatus S; FuncMCView view = memoryObject.slice(Index); -#if JL_LLVM_VERSION < 150000 -#define getCommentOS() GetCommentOS() -#endif S = DisAsm->getInstruction(Inst, insSize, view, 0, /*CStream*/ pass != 0 ? Streamer->getCommentOS () : nulls()); if (pass != 0 && Streamer->getCommentOS ().tell() > 0) @@ -1256,11 +1241,7 @@ jl_value_t *jl_dump_function_asm_impl(jl_llvmf_dump_t* dump, char emit_mc, const STI, MRI, TM->Options.MCOptions)); std::unique_ptr MCE; if (binary) { // enable MCAsmStreamer::AddEncodingComment printing -#if JL_LLVM_VERSION >= 150000 MCE.reset(TM->getTarget().createMCCodeEmitter(MII, *Context)); -#else - MCE.reset(TM->getTarget().createMCCodeEmitter(MII, MRI, *Context)); -#endif } auto FOut = std::make_unique(asmfile); std::unique_ptr S(TM->getTarget().createAsmStreamer( diff --git a/src/dlload.c b/src/dlload.c index 8124605880b5e..484c36a228886 100644 --- a/src/dlload.c +++ b/src/dlload.c @@ -188,7 +188,7 @@ JL_DLLEXPORT JL_NO_SANITIZE void *jl_dlopen(const char *filename, unsigned flags dlopen = (dlopen_prototype*)dlsym(RTLD_NEXT, "dlopen"); if (!dlopen) return NULL; - void *libdl_handle = dlopen("libdl.so", RTLD_NOW | RTLD_NOLOAD); + void *libdl_handle = dlopen("libdl.so.2", RTLD_NOW | RTLD_NOLOAD); assert(libdl_handle); dlopen = (dlopen_prototype*)dlsym(libdl_handle, "dlopen"); dlclose(libdl_handle); @@ -312,7 +312,7 @@ JL_DLLEXPORT void *jl_load_dynamic_library(const char *modname, unsigned flags, jl_array_t *DL_LOAD_PATH = (jl_array_t*)(b ? jl_atomic_load_relaxed(&b->value) : NULL); if (DL_LOAD_PATH != NULL) { size_t j; - for (j = 0; j < jl_array_len(DL_LOAD_PATH); j++) { + for (j = 0; j < jl_array_nrows(DL_LOAD_PATH); j++) { char *dl_path = jl_string_data(jl_array_ptr_data(DL_LOAD_PATH)[j]); size_t len = strlen(dl_path); if (len == 0) diff --git a/src/flisp/compiler.lsp b/src/flisp/compiler.lsp index fdc516dce3ea8..e5a79e8fee6bb 100644 --- a/src/flisp/compiler.lsp +++ b/src/flisp/compiler.lsp @@ -864,7 +864,7 @@ (else #f))))))) -; From SRFI 89 by Marc Feeley (http://srfi.schemers.org/srfi-89/srfi-89.html) +; From SRFI 89 by Marc Feeley (https://srfi.schemers.org/srfi-89/srfi-89.html) ; Copyright (C) Marc Feeley 2006. All Rights Reserved. ; ; "alist" is a list of pairs of the form "(keyword . value)" diff --git a/src/flisp/cvalues.c b/src/flisp/cvalues.c index a5635c238ba3c..749b8802dfe82 100644 --- a/src/flisp/cvalues.c +++ b/src/flisp/cvalues.c @@ -101,7 +101,7 @@ void cv_autorelease(fl_context_t *fl_ctx, cvalue_t *cv) autorelease(fl_ctx, cv); } -static value_t cprim(fl_context_t *fl_ctx, fltype_t *type, size_t sz) +value_t cprim(fl_context_t *fl_ctx, fltype_t *type, size_t sz) { cprim_t *pcp = (cprim_t*)alloc_words(fl_ctx, CPRIM_NWORDS-1+NWORDS(sz)); pcp->type = type; diff --git a/src/flisp/flisp.h b/src/flisp/flisp.h index b031e456cd3fe..669753a9f5302 100644 --- a/src/flisp/flisp.h +++ b/src/flisp/flisp.h @@ -328,6 +328,7 @@ typedef float fl_float_t; typedef value_t (*builtin_t)(fl_context_t*, value_t*, uint32_t); value_t cvalue(fl_context_t *fl_ctx, fltype_t *type, size_t sz) JL_NOTSAFEPOINT; +value_t cprim(fl_context_t *fl_ctx, fltype_t *type, size_t sz) JL_NOTSAFEPOINT; value_t cvalue_no_finalizer(fl_context_t *fl_ctx, fltype_t *type, size_t sz) JL_NOTSAFEPOINT; void add_finalizer(fl_context_t *fl_ctx, cvalue_t *cv); void cv_autorelease(fl_context_t *fl_ctx, cvalue_t *cv); diff --git a/src/flisp/julia_extensions.c b/src/flisp/julia_extensions.c index f29e3972755c5..79a007df8bfde 100644 --- a/src/flisp/julia_extensions.c +++ b/src/flisp/julia_extensions.c @@ -405,7 +405,7 @@ value_t fl_string_only_julia_char(fl_context_t *fl_ctx, value_t *args, uint32_t uint8_t *s = (uint8_t*)cvalue_data(args[0]); size_t len = cv_len((cvalue_t*)ptr(args[0])); uint32_t u = _string_only_julia_char(s, len); - if (u == (uint32_t)-1) + if (u == UINT32_MAX) return fl_ctx->F; return fl_list2(fl_ctx, fl_ctx->jl_char_sym, mk_uint32(fl_ctx, u)); } diff --git a/src/flisp/print.c b/src/flisp/print.c index 2b20d0d98b225..a6f633c2e6701 100644 --- a/src/flisp/print.c +++ b/src/flisp/print.c @@ -518,7 +518,7 @@ static void print_string(fl_context_t *fl_ctx, ios_t *f, char *str, size_t sz) } else { while (i < sz) { - size_t n = u8_escape(buf, sizeof(buf), str, &i, sz, 1, 0); + size_t n = u8_escape(buf, sizeof(buf), str, &i, sz, "\"", 0); outsn(fl_ctx, buf, f, n-1); } } diff --git a/src/flisp/read.c b/src/flisp/read.c index 9a480e0536c7a..7a6039323a988 100644 --- a/src/flisp/read.c +++ b/src/flisp/read.c @@ -303,7 +303,7 @@ static uint32_t peek(fl_context_t *fl_ctx) fl_ctx->readtokval = fixnum(x); } else if (c == '!') { - // #! single line comment for shbang script support + // #! single line comment for shebang script support do { ch = ios_getc(readF(fl_ctx)); } while (ch != IOS_EOF && (char)ch != '\n'); diff --git a/src/flisp/table.c b/src/flisp/table.c index 1d8aed358e88d..8836c93f81513 100644 --- a/src/flisp/table.c +++ b/src/flisp/table.c @@ -102,7 +102,7 @@ value_t fl_table(fl_context_t *fl_ctx, value_t *args, uint32_t nargs) else k = arg; } - if (h->table != &h->_space[0]) { + if (cnt <= HT_N_INLINE && h->table != &h->_space[0]) { // We expected to use the inline table, but we ended up outgrowing it. // Make sure to register the finalizer. add_finalizer(fl_ctx, (cvalue_t*)ptr(nt)); diff --git a/src/flisp/unittest.lsp b/src/flisp/unittest.lsp index 584d5c81225e8..16774a97e3233 100644 --- a/src/flisp/unittest.lsp +++ b/src/flisp/unittest.lsp @@ -267,4 +267,23 @@ (assert (equal? `(a `(b c)) '(a (quasiquote (b c))))) (assert (equal? ````x '```x)) +;; make many initialized tables large enough not to be stored in-line +(for 1 100 + (lambda (i) + (table eq? 2 eqv? 2 + equal? 2 atom? 1 + not 1 null? 1 + boolean? 1 symbol? 1 + number? 1 bound? 1 + pair? 1 builtin? 1 + vector? 1 fixnum? 1 + cons 2 car 1 + cdr 1 set-car! 2 + set-cdr! 2 = 2 + < 2 compare 2 + aref 2 aset! 3 + div0 2))) +;; now allocate enough to trigger GC +(for 1 8000000 (lambda (i) (cons 1 2))) + #t diff --git a/src/gc-debug.c b/src/gc-debug.c index 7145531828853..124b7da74dee1 100644 --- a/src/gc-debug.c +++ b/src/gc-debug.c @@ -83,7 +83,6 @@ void add_lostval_parent(jl_value_t *parent) innocent looking functions which allocate (and thus trigger marking) only on special cases. If you can't find it, you can try the following : - - Ensure that should_timeout() is deterministic instead of clock based. - Once you have a completely deterministic program which crashes on gc_verify, the addresses should stay constant between different runs (with same binary, same environment ...). Do not forget to turn off ASLR (linux: echo 0 > /proc/sys/kernel/randomize_va_space). @@ -350,44 +349,15 @@ static void gc_verify_tags_page(jl_gc_pagemeta_t *pg) } } -static void gc_verify_tags_pagetable0(pagetable0_t *pagetable0) +static void gc_verify_tags_pagestack(void) { - for (int pg_i = 0; pg_i < REGION0_PG_COUNT / 32; pg_i++) { - uint32_t line = pagetable0->allocmap[pg_i]; - if (line) { - for (int j = 0; j < 32; j++) { - if ((line >> j) & 1) { - gc_verify_tags_page(pagetable0->meta[pg_i * 32 + j]); - } - } - } - } -} - -static void gc_verify_tags_pagetable1(pagetable1_t *pagetable1) -{ - for (int pg_i = 0; pg_i < REGION1_PG_COUNT / 32; pg_i++) { - uint32_t line = pagetable1->allocmap0[pg_i]; - if (line) { - for (int j = 0; j < 32; j++) { - if ((line >> j) & 1) { - gc_verify_tags_pagetable0(pagetable1->meta0[pg_i * 32 + j]); - } - } - } - } -} - -static void gc_verify_tags_pagetable(void) -{ - for (int pg_i = 0; pg_i < (REGION2_PG_COUNT + 31) / 32; pg_i++) { - uint32_t line = memory_map.allocmap1[pg_i]; - if (line) { - for (int j = 0; j < 32; j++) { - if ((line >> j) & 1) { - gc_verify_tags_pagetable1(memory_map.meta1[pg_i * 32 + j]); - } - } + for (int i = 0; i < gc_n_threads; i++) { + jl_ptls_t ptls2 = gc_all_tls_states[i]; + jl_gc_page_stack_t *pgstk = &ptls2->page_metadata_allocd; + jl_gc_pagemeta_t *pg = jl_atomic_load_relaxed(&pgstk->bottom); + while (pg != NULL) { + gc_verify_tags_page(pg); + pg = pg->next; } } } @@ -424,7 +394,7 @@ void gc_verify_tags(void) // verify that all the objects on every page are either valid julia objects // or are part of the freelist or are on the allocated half of a page - gc_verify_tags_pagetable(); + gc_verify_tags_pagestack(); } #endif @@ -616,8 +586,7 @@ void objprofile_count(void *ty, int old, int sz) ty = (void*)jl_buff_tag; } else if (ty != (void*)jl_buff_tag && ty != jl_malloc_tag && - jl_typeof(ty) == (jl_value_t*)jl_datatype_type && - ((jl_datatype_t*)ty)->instance) { + jl_is_datatype(ty) && jl_is_datatype_singleton((jl_datatype_t*)ty)) { ty = jl_singleton_tag; } void **bp = ptrhash_bp(&obj_counts[old], ty); @@ -851,11 +820,11 @@ void gc_time_pool_end(int sweep_full) double sweep_speed = sweep_gb / sweep_pool_sec; jl_safe_printf("GC sweep pools end %.2f ms at %.1f GB/s " "(skipped %.2f %% of %" PRId64 ", swept %" PRId64 " pgs, " - "%" PRId64 " freed with %" PRId64 " lazily) %s\n", + "%" PRId64 " freed) %s\n", sweep_pool_sec * 1000, sweep_speed, (total_pages ? ((double)skipped_pages * 100) / total_pages : 0), total_pages, total_pages - skipped_pages, - freed_pages, lazy_freed_pages, + freed_pages, sweep_full ? "full" : "quick"); } @@ -894,29 +863,29 @@ void gc_time_big_end(void) t_ms, big_freed, big_total, big_reset); } -static int64_t mallocd_array_total; -static int64_t mallocd_array_freed; -static int64_t mallocd_array_sweep_start; +static int64_t mallocd_memory_total; +static int64_t mallocd_memory_freed; +static int64_t mallocd_memory_sweep_start; -void gc_time_mallocd_array_start(void) +void gc_time_mallocd_memory_start(void) { - mallocd_array_total = 0; - mallocd_array_freed = 0; - mallocd_array_sweep_start = jl_hrtime(); + mallocd_memory_total = 0; + mallocd_memory_freed = 0; + mallocd_memory_sweep_start = jl_hrtime(); } -void gc_time_count_mallocd_array(int bits) +void gc_time_count_mallocd_memory(int bits) { - mallocd_array_total++; - mallocd_array_freed += !gc_marked(bits); + mallocd_memory_total++; + mallocd_memory_freed += !gc_marked(bits); } -void gc_time_mallocd_array_end(void) +void gc_time_mallocd_memory_end(void) { - double t_ms = jl_ns2ms(jl_hrtime() - mallocd_array_sweep_start); + double t_ms = jl_ns2ms(jl_hrtime() - mallocd_memory_sweep_start); jl_safe_printf("GC sweep arrays %.2f ms " "(freed %" PRId64 " / %" PRId64 ")\n", - t_ms, mallocd_array_freed, mallocd_array_total); + t_ms, mallocd_memory_freed, mallocd_memory_total); } void gc_time_mark_pause(int64_t t0, int64_t scanned_bytes, @@ -947,12 +916,12 @@ void gc_time_sweep_pause(uint64_t gc_end_t, int64_t actual_allocd, jl_safe_printf("GC sweep pause %.2f ms live %" PRId64 " kB " "(freed %" PRId64 " kB EST %" PRId64 " kB " "[error %" PRId64 "] = %d%% of allocd b %" PRIu64 ") " - "(%.2f ms in post_mark) %s | next in %" PRId64 " kB\n", + "(%.2f ms in post_mark) %s\n", jl_ns2ms(sweep_pause), live_bytes / 1024, gc_num.freed / 1024, estimate_freed / 1024, gc_num.freed - estimate_freed, pct, gc_num.allocd / 1024, jl_ns2ms(gc_postmark_end - gc_premark_end), - sweep_full ? "full" : "quick", -gc_num.allocd / 1024); + sweep_full ? "full" : "quick"); } void gc_time_summary(int sweep_full, uint64_t start, uint64_t end, @@ -972,11 +941,35 @@ void gc_time_summary(int sweep_full, uint64_t start, uint64_t end, jl_safe_printf("TS: %" PRIu64 " Minor collection: estimate freed = %" PRIu64 " live = %" PRIu64 "m new interval = %" PRIu64 "m pause time = %" PRIu64 "ms ttsp = %" PRIu64 "us mark time = %" PRIu64 - "ms sweep time = %" PRIu64 "ms \n", + "ms sweep time = %" PRIu64 "ms\n", end, freed, live/1024/1024, interval/1024/1024, pause/1000000, ttsp, mark/1000000,sweep/1000000); } + +void gc_heuristics_summary( + uint64_t old_alloc_diff, uint64_t alloc_mem, + uint64_t old_mut_time, uint64_t alloc_time, + uint64_t old_freed_diff, uint64_t gc_mem, + uint64_t old_pause_time, uint64_t gc_time, + int thrash_counter, const char *reason, + uint64_t current_heap, uint64_t target_heap) +{ + jl_safe_printf("Estimates: alloc_diff=%" PRIu64 "kB (%" PRIu64 ")" + //" nongc_time=%" PRIu64 "ns (%" PRIu64 ")" + " mut_time=%" PRIu64 "ns (%" PRIu64 ")" + " freed_diff=%" PRIu64 "kB (%" PRIu64 ")" + " pause_time=%" PRIu64 "ns (%" PRIu64 ")" + " thrash_counter=%d%s" + " current_heap=%" PRIu64 " MB" + " target_heap=%" PRIu64 " MB\n", + old_alloc_diff/1024, alloc_mem/1024, + old_mut_time/1000, alloc_time/1000, + old_freed_diff/1024, gc_mem/1024, + old_pause_time/1000, gc_time/1000, + thrash_counter, reason, + current_heap/1024/1024, target_heap/1024/1024); +} #endif void jl_gc_debug_init(void) @@ -1111,7 +1104,7 @@ void gc_stats_big_obj(void) while (ma != NULL) { if (gc_marked(jl_astaggedvalue(ma->a)->bits.gc)) { nused++; - nbytes += jl_array_nbytes(ma->a); + nbytes += jl_genericmemory_nbytes((jl_genericmemory_t*)ma->a); } ma = ma->next; } @@ -1203,12 +1196,6 @@ int gc_slot_to_arrayidx(void *obj, void *_slot) JL_NOTSAFEPOINT start = (char*)jl_svec_data(obj); len = jl_svec_len(obj); } - else if (vt->name == jl_array_typename) { - jl_array_t *a = (jl_array_t*)obj; - start = (char*)a->data; - len = jl_array_len(a); - elsize = a->elsize; - } if (slot < start || slot >= start + elsize * len) return -1; return (slot - start) / elsize; @@ -1220,6 +1207,10 @@ JL_DLLEXPORT void jl_enable_gc_logging(int enable) { gc_logging_enabled = enable; } +JL_DLLEXPORT int jl_is_gc_logging_enabled(void) { + return gc_logging_enabled; +} + void _report_gc_finished(uint64_t pause, uint64_t freed, int full, int recollect, int64_t live_bytes) JL_NOTSAFEPOINT { if (!gc_logging_enabled) { return; diff --git a/src/gc-heap-snapshot.cpp b/src/gc-heap-snapshot.cpp index 51bc5ab61610e..77a6e70a127e6 100644 --- a/src/gc-heap-snapshot.cpp +++ b/src/gc-heap-snapshot.cpp @@ -3,6 +3,7 @@ #include "gc-heap-snapshot.h" #include "julia_internal.h" +#include "julia_assert.h" #include "gc.h" #include "llvm/ADT/SmallVector.h" @@ -12,8 +13,11 @@ #include #include #include +#include +#include using std::string; +using std::set; using std::ostringstream; using std::pair; using std::make_pair; @@ -54,8 +58,9 @@ void print_str_escape_json(ios_t *stream, StringRef s) // mimicking https://github.com/nodejs/node/blob/5fd7a72e1c4fbaf37d3723c4c81dce35c149dc84/deps/v8/src/profiler/heap-snapshot-generator.cc#L2598-L2601 struct Edge { - size_t type; // These *must* match the Enums on the JS side; control interpretation of name_or_index. + uint8_t type; // These *must* match the Enums on the JS side; control interpretation of name_or_index. size_t name_or_index; // name of the field (for objects/modules) or index of array + size_t from_node; // This is a deviation from the .heapsnapshot format to support streaming. size_t to_node; }; @@ -64,29 +69,34 @@ struct Edge { // [ "type", "name", "id", "self_size", "edge_count", "trace_node_id", "detachedness" ] // mimicking https://github.com/nodejs/node/blob/5fd7a72e1c4fbaf37d3723c4c81dce35c149dc84/deps/v8/src/profiler/heap-snapshot-generator.cc#L2568-L2575 -const int k_node_number_of_fields = 7; struct Node { - size_t type; // index into snapshot->node_types + uint8_t type; // index into snapshot->node_types size_t name; size_t id; // This should be a globally-unique counter, but we use the memory address size_t self_size; size_t trace_node_id; // This is ALWAYS 0 in Javascript heap-snapshots. - // whether the from_node is attached or dettached from the main application state + // whether the from_node is attached or detached from the main application state // https://github.com/nodejs/node/blob/5fd7a72e1c4fbaf37d3723c4c81dce35c149dc84/deps/v8/include/v8-profiler.h#L739-L745 - int detachedness; // 0 - unknown, 1 - attached, 2 - detached - SmallVector edges; + uint8_t detachedness; // 0 - unknown, 1 - attached, 2 - detached ~Node() JL_NOTSAFEPOINT = default; }; -struct StringTable { +class StringTable { +protected: StringMap map; SmallVector strings; + size_t next_id; + +public: + StringTable() JL_NOTSAFEPOINT : map(), strings(), next_id(0) {}; size_t find_or_create_string_id(StringRef key) JL_NOTSAFEPOINT { - auto val = map.insert(make_pair(key, map.size())); - if (val.second) + auto val = map.insert(make_pair(key, next_id)); + if (val.second) { strings.push_back(val.first->first()); + next_id++; + } return val.first->second; } @@ -106,16 +116,67 @@ struct StringTable { } }; -struct HeapSnapshot { - SmallVector nodes; - // edges are stored on each from_node +// a string table with partial strings in memory and all strings serialized to a file +class SerializedStringTable: public StringTable { + public: + + // serialize the string only if it's not already in the table + size_t serialize_if_necessary(ios_t *stream, StringRef key) JL_NOTSAFEPOINT { + auto val = map.insert(make_pair(key, next_id)); + if (val.second) { + strings.push_back(val.first->first()); + // persist the string size first, then the string itself + // so that we could read it back in the same order + size_t s_size = key.size(); + ios_write(stream, reinterpret_cast(&s_size), sizeof(size_t)); + ios_write(stream, key.data(), s_size); + next_id++; + } + return val.first->second; + } - StringTable names; + // serialize the string without checking if it is in the table or not + // and return its index. This means that we might have duplicates in the + // output string file. + size_t serialize(ios_t *stream, StringRef key) JL_NOTSAFEPOINT { + size_t s_size = key.size(); + ios_write(stream, reinterpret_cast(&s_size), sizeof(size_t)); + ios_write(stream, key.data(), s_size); + size_t current = next_id; + next_id++; + return current; + } +}; + +struct HeapSnapshot { + // names could be very large, so we keep them in a separate binary file + // and use a StringTable to keep track of the indices of frequently used strings + // to reduce duplicates in the output file to some degree + SerializedStringTable names; + // node types and edge types are very small and keep them in memory StringTable node_types; StringTable edge_types; DenseMap node_ptr_to_index_map; - size_t num_edges = 0; // For metadata, updated as you add each edge. Needed because edges owned by nodes. + size_t num_nodes = 0; // Since we stream out to files, + size_t num_edges = 0; // we need to track the counts here. + + // Node internal_root; + + // Used for streaming + // Since nodes and edges are just one giant array of integers, we stream them as + // *BINARY DATA*: a sequence of bytes, each of which is a 64-bit integer (big enough to + // fit the pointer ids). + ios_t *nodes; + ios_t *edges; + // strings are serialized to a file in binary format + ios_t *strings; + // the following file is written out as json data. + ios_t *json; + + size_t internal_root_idx = 0; // node index of the internal root node + size_t _gc_root_idx = 1; // node index of the GC roots node + size_t _gc_finlist_root_idx = 2; // node index of the GC finlist roots node }; // global heap snapshot, mutated by garbage collector @@ -124,17 +185,22 @@ int gc_heap_snapshot_enabled = 0; HeapSnapshot *g_snapshot = nullptr; extern jl_mutex_t heapsnapshot_lock; +void final_serialize_heap_snapshot(ios_t *json, ios_t *strings, HeapSnapshot &snapshot, char all_one); void serialize_heap_snapshot(ios_t *stream, HeapSnapshot &snapshot, char all_one); static inline void _record_gc_edge(const char *edge_type, jl_value_t *a, jl_value_t *b, size_t name_or_index) JL_NOTSAFEPOINT; -void _record_gc_just_edge(const char *edge_type, Node &from_node, size_t to_idx, size_t name_or_idx) JL_NOTSAFEPOINT; -void _add_internal_root(HeapSnapshot *snapshot); +void _record_gc_just_edge(const char *edge_type, size_t from_idx, size_t to_idx, size_t name_or_idx) JL_NOTSAFEPOINT; +void _add_synthetic_root_entries(HeapSnapshot *snapshot) JL_NOTSAFEPOINT; -JL_DLLEXPORT void jl_gc_take_heap_snapshot(ios_t *stream, char all_one) +JL_DLLEXPORT void jl_gc_take_heap_snapshot(ios_t *nodes, ios_t *edges, + ios_t *strings, ios_t *json, char all_one) { HeapSnapshot snapshot; - _add_internal_root(&snapshot); + snapshot.nodes = nodes; + snapshot.edges = edges; + snapshot.strings = strings; + snapshot.json = json; jl_mutex_lock(&heapsnapshot_lock); @@ -142,6 +208,8 @@ JL_DLLEXPORT void jl_gc_take_heap_snapshot(ios_t *stream, char all_one) g_snapshot = &snapshot; gc_heap_snapshot_enabled = true; + _add_synthetic_root_entries(&snapshot); + // Do a full GC mark (and incremental sweep), which will invoke our callbacks on `g_snapshot` jl_gc_collect(JL_GC_FULL); @@ -153,30 +221,96 @@ JL_DLLEXPORT void jl_gc_take_heap_snapshot(ios_t *stream, char all_one) // When we return, the snapshot is full // Dump the snapshot - serialize_heap_snapshot((ios_t*)stream, snapshot, all_one); + final_serialize_heap_snapshot((ios_t*)json, (ios_t*)strings, snapshot, all_one); +} + +void serialize_node(HeapSnapshot *snapshot, const Node &node) JL_NOTSAFEPOINT +{ + // ["type","name","id","self_size","edge_count","trace_node_id","detachedness"] + ios_write(snapshot->nodes, (char*)&node.type, sizeof(node.type)); + ios_write(snapshot->nodes, (char*)&node.name, sizeof(node.name)); + ios_write(snapshot->nodes, (char*)&node.id, sizeof(node.id)); + ios_write(snapshot->nodes, (char*)&node.self_size, sizeof(node.self_size)); + // NOTE: We don't write edge_count, since it's always 0. It will be reconstructed in + // post-processing. + ios_write(snapshot->nodes, (char*)&node.trace_node_id, sizeof(node.trace_node_id)); + ios_write(snapshot->nodes, (char*)&node.detachedness, sizeof(node.detachedness)); + + g_snapshot->num_nodes += 1; } -// adds a node at id 0 which is the "uber root": -// a synthetic node which points to all the GC roots. -void _add_internal_root(HeapSnapshot *snapshot) +void serialize_edge(HeapSnapshot *snapshot, const Edge &edge) JL_NOTSAFEPOINT { + // ["type","name_or_index","to_node"] + ios_write(snapshot->edges, (char*)&edge.type, sizeof(edge.type)); + ios_write(snapshot->edges, (char*)&edge.name_or_index, sizeof(edge.name_or_index)); + // NOTE: Row numbers for nodes (not adjusted for k_node_number_of_fields, which is 7) + ios_write(snapshot->edges, (char*)&edge.from_node, sizeof(edge.from_node)); + ios_write(snapshot->edges, (char*)&edge.to_node, sizeof(edge.to_node)); + + g_snapshot->num_edges += 1; +} + +// mimicking https://github.com/nodejs/node/blob/5fd7a72e1c4fbaf37d3723c4c81dce35c149dc84/deps/v8/src/profiler/heap-snapshot-generator.cc#L212 +// add synthetic nodes for the uber root, the GC roots, and the GC finalizer list roots +void _add_synthetic_root_entries(HeapSnapshot *snapshot) JL_NOTSAFEPOINT +{ + // adds a node at id 0 which is the "uber root": + // a synthetic node which points to all the GC roots. Node internal_root{ - snapshot->node_types.find_or_create_string_id("synthetic"), - snapshot->names.find_or_create_string_id(""), // name + (uint8_t)snapshot->node_types.find_or_create_string_id("synthetic"), + snapshot->names.serialize_if_necessary(snapshot->strings, ""), // name 0, // id 0, // size 0, // size_t trace_node_id (unused) - 0, // int detachedness; // 0 - unknown, 1 - attached; 2 - detached - SmallVector() // outgoing edges + 0 // int detachedness; // 0 - unknown, 1 - attached; 2 - detached }; - snapshot->nodes.push_back(internal_root); + serialize_node(snapshot, internal_root); + + // Add a node for the GC roots + snapshot->_gc_root_idx = snapshot->internal_root_idx + 1; + Node gc_roots{ + (uint8_t)snapshot->node_types.find_or_create_string_id("synthetic"), + snapshot->names.serialize_if_necessary(snapshot->strings, "GC roots"), // name + snapshot->_gc_root_idx, // id + 0, // size + 0, // size_t trace_node_id (unused) + 0 // int detachedness; // 0 - unknown, 1 - attached; 2 - detached + }; + serialize_node(snapshot, gc_roots); + Edge root_to_gc_roots{ + (uint8_t)snapshot->edge_types.find_or_create_string_id("internal"), + snapshot->names.serialize_if_necessary(snapshot->strings, "GC roots"), // edge label + snapshot->internal_root_idx, // from + snapshot->_gc_root_idx // to + }; + serialize_edge(snapshot, root_to_gc_roots); + + // add a node for the gc finalizer list roots + snapshot->_gc_finlist_root_idx = snapshot->internal_root_idx + 2; + Node gc_finlist_roots{ + (uint8_t)snapshot->node_types.find_or_create_string_id("synthetic"), + snapshot->names.serialize_if_necessary(snapshot->strings, "GC finalizer list roots"), // name + snapshot->_gc_finlist_root_idx, // id + 0, // size + 0, // size_t trace_node_id (unused) + 0 // int detachedness; // 0 - unknown, 1 - attached; 2 - detached + }; + serialize_node(snapshot, gc_finlist_roots); + Edge root_to_gc_finlist_roots{ + (uint8_t)snapshot->edge_types.find_or_create_string_id("internal"), + snapshot->names.serialize_if_necessary(snapshot->strings, "GC finalizer list roots"), // edge label + snapshot->internal_root_idx, // from + snapshot->_gc_finlist_root_idx // to + }; + serialize_edge(snapshot, root_to_gc_finlist_roots); } // mimicking https://github.com/nodejs/node/blob/5fd7a72e1c4fbaf37d3723c4c81dce35c149dc84/deps/v8/src/profiler/heap-snapshot-generator.cc#L597-L597 // returns the index of the new node size_t record_node_to_gc_snapshot(jl_value_t *a) JL_NOTSAFEPOINT { - auto val = g_snapshot->node_ptr_to_index_map.insert(make_pair(a, g_snapshot->nodes.size())); + auto val = g_snapshot->node_ptr_to_index_map.insert(make_pair(a, g_snapshot->num_nodes)); if (!val.second) { return val.first->second; } @@ -246,17 +380,17 @@ size_t record_node_to_gc_snapshot(jl_value_t *a) JL_NOTSAFEPOINT name = StringRef((const char*)str_.buf, str_.size); } - g_snapshot->nodes.push_back(Node{ - g_snapshot->node_types.find_or_create_string_id(node_type), // size_t type; - g_snapshot->names.find_or_create_string_id(name), // size_t name; + auto node = Node{ + (uint8_t)g_snapshot->node_types.find_or_create_string_id(node_type), // size_t type; + g_snapshot->names.serialize(g_snapshot->strings, name), // size_t name; (size_t)a, // size_t id; // We add 1 to self-size for the type tag that all heap-allocated objects have. // Also because the Chrome Snapshot viewer ignores size-0 leaves! sizeof(void*) + self_size, // size_t self_size; 0, // size_t trace_node_id (unused) 0, // int detachedness; // 0 - unknown, 1 - attached; 2 - detached - SmallVector() // outgoing edges - }); + }; + serialize_node(g_snapshot, node); if (ios_need_close) ios_close(&str_); @@ -266,20 +400,20 @@ size_t record_node_to_gc_snapshot(jl_value_t *a) JL_NOTSAFEPOINT static size_t record_pointer_to_gc_snapshot(void *a, size_t bytes, StringRef name) JL_NOTSAFEPOINT { - auto val = g_snapshot->node_ptr_to_index_map.insert(make_pair(a, g_snapshot->nodes.size())); + auto val = g_snapshot->node_ptr_to_index_map.insert(make_pair(a, g_snapshot->num_nodes)); if (!val.second) { return val.first->second; } - g_snapshot->nodes.push_back(Node{ - g_snapshot->node_types.find_or_create_string_id( "object"), // size_t type; - g_snapshot->names.find_or_create_string_id(name), // size_t name; + auto node = Node{ + (uint8_t)g_snapshot->node_types.find_or_create_string_id( "object"), // size_t type; + g_snapshot->names.serialize(g_snapshot->strings, name), // size_t name; (size_t)a, // size_t id; bytes, // size_t self_size; 0, // size_t trace_node_id (unused) 0, // int detachedness; // 0 - unknown, 1 - attached; 2 - detached - SmallVector() // outgoing edges - }); + }; + serialize_node(g_snapshot, node); return val.first->second; } @@ -315,16 +449,29 @@ static string _fieldpath_for_slot(void *obj, void *slot) JL_NOTSAFEPOINT } } - void _gc_heap_snapshot_record_root(jl_value_t *root, char *name) JL_NOTSAFEPOINT { - record_node_to_gc_snapshot(root); + size_t to_node_idx = record_node_to_gc_snapshot(root); + auto edge_label = g_snapshot->names.serialize(g_snapshot->strings, name); + + _record_gc_just_edge("internal", g_snapshot->internal_root_idx, to_node_idx, edge_label); +} + +void _gc_heap_snapshot_record_gc_roots(jl_value_t *root, char *name) JL_NOTSAFEPOINT +{ + auto to_node_idx = record_node_to_gc_snapshot(root); + auto edge_label = g_snapshot->names.serialize(g_snapshot->strings, name); - auto &internal_root = g_snapshot->nodes.front(); - auto to_node_idx = g_snapshot->node_ptr_to_index_map[root]; - auto edge_label = g_snapshot->names.find_or_create_string_id(name); + _record_gc_just_edge("internal", g_snapshot->_gc_root_idx, to_node_idx, edge_label); +} - _record_gc_just_edge("internal", internal_root, to_node_idx, edge_label); +void _gc_heap_snapshot_record_finlist(jl_value_t *obj, size_t index) JL_NOTSAFEPOINT +{ + auto to_node_idx = record_node_to_gc_snapshot(obj); + ostringstream ss; + ss << "finlist-" << index; + auto edge_label = g_snapshot->names.serialize_if_necessary(g_snapshot->strings, ss.str()); + _record_gc_just_edge("internal", g_snapshot->_gc_finlist_root_idx, to_node_idx, edge_label); } // Add a node to the heap snapshot representing a Julia stack frame. @@ -333,20 +480,20 @@ void _gc_heap_snapshot_record_root(jl_value_t *root, char *name) JL_NOTSAFEPOINT // Stack frame nodes point at the objects they have as local variables. size_t _record_stack_frame_node(HeapSnapshot *snapshot, void *frame) JL_NOTSAFEPOINT { - auto val = g_snapshot->node_ptr_to_index_map.insert(make_pair(frame, g_snapshot->nodes.size())); + auto val = g_snapshot->node_ptr_to_index_map.insert(make_pair(frame, g_snapshot->num_nodes)); if (!val.second) { return val.first->second; } - snapshot->nodes.push_back(Node{ - snapshot->node_types.find_or_create_string_id("synthetic"), - snapshot->names.find_or_create_string_id("(stack frame)"), // name + auto node = Node{ + (uint8_t)snapshot->node_types.find_or_create_string_id("synthetic"), + snapshot->names.serialize_if_necessary(snapshot->strings, "(stack frame)"), // name (size_t)frame, // id 1, // size 0, // size_t trace_node_id (unused) 0, // int detachedness; // 0 - unknown, 1 - attached; 2 - detached - SmallVector() // outgoing edges - }); + }; + serialize_node(snapshot, node); return val.first->second; } @@ -355,30 +502,27 @@ void _gc_heap_snapshot_record_frame_to_object_edge(void *from, jl_value_t *to) J { auto from_node_idx = _record_stack_frame_node(g_snapshot, (jl_gcframe_t*)from); auto to_idx = record_node_to_gc_snapshot(to); - Node &from_node = g_snapshot->nodes[from_node_idx]; - auto name_idx = g_snapshot->names.find_or_create_string_id("local var"); - _record_gc_just_edge("internal", from_node, to_idx, name_idx); + auto name_idx = g_snapshot->names.serialize_if_necessary(g_snapshot->strings, "local var"); + _record_gc_just_edge("internal", from_node_idx, to_idx, name_idx); } void _gc_heap_snapshot_record_task_to_frame_edge(jl_task_t *from, void *to) JL_NOTSAFEPOINT { auto from_node_idx = record_node_to_gc_snapshot((jl_value_t*)from); auto to_node_idx = _record_stack_frame_node(g_snapshot, to); - Node &from_node = g_snapshot->nodes[from_node_idx]; - auto name_idx = g_snapshot->names.find_or_create_string_id("stack"); - _record_gc_just_edge("internal", from_node, to_node_idx, name_idx); + auto name_idx = g_snapshot->names.serialize_if_necessary(g_snapshot->strings, "stack"); + _record_gc_just_edge("internal", from_node_idx, to_node_idx, name_idx); } void _gc_heap_snapshot_record_frame_to_frame_edge(jl_gcframe_t *from, jl_gcframe_t *to) JL_NOTSAFEPOINT { auto from_node_idx = _record_stack_frame_node(g_snapshot, from); auto to_node_idx = _record_stack_frame_node(g_snapshot, to); - Node &from_node = g_snapshot->nodes[from_node_idx]; - auto name_idx = g_snapshot->names.find_or_create_string_id("next frame"); - _record_gc_just_edge("internal", from_node, to_node_idx, name_idx); + auto name_idx = g_snapshot->names.serialize_if_necessary(g_snapshot->strings, "next frame"); + _record_gc_just_edge("internal", from_node_idx, to_node_idx, name_idx); } void _gc_heap_snapshot_record_array_edge(jl_value_t *from, jl_value_t *to, size_t index) JL_NOTSAFEPOINT @@ -390,62 +534,52 @@ void _gc_heap_snapshot_record_object_edge(jl_value_t *from, jl_value_t *to, void { string path = _fieldpath_for_slot(from, slot); _record_gc_edge("property", from, to, - g_snapshot->names.find_or_create_string_id(path)); + g_snapshot->names.serialize_if_necessary(g_snapshot->strings, path)); } -void _gc_heap_snapshot_record_module_to_binding(jl_module_t *module, jl_binding_t *binding) JL_NOTSAFEPOINT +void _gc_heap_snapshot_record_module_to_binding(jl_module_t *module, jl_value_t *bindings, jl_value_t *bindingkeyset) JL_NOTSAFEPOINT { - jl_globalref_t *globalref = binding->globalref; - jl_sym_t *name = globalref->name; auto from_node_idx = record_node_to_gc_snapshot((jl_value_t*)module); - auto to_node_idx = record_pointer_to_gc_snapshot(binding, sizeof(jl_binding_t), jl_symbol_name(name)); - - jl_value_t *value = jl_atomic_load_relaxed(&binding->value); - auto value_idx = value ? record_node_to_gc_snapshot(value) : 0; - jl_value_t *ty = jl_atomic_load_relaxed(&binding->ty); - auto ty_idx = ty ? record_node_to_gc_snapshot(ty) : 0; - auto globalref_idx = record_node_to_gc_snapshot((jl_value_t*)globalref); + auto to_bindings_idx = record_node_to_gc_snapshot(bindings); + auto to_bindingkeyset_idx = record_node_to_gc_snapshot(bindingkeyset); - auto &from_node = g_snapshot->nodes[from_node_idx]; - auto &to_node = g_snapshot->nodes[to_node_idx]; - - _record_gc_just_edge("property", from_node, to_node_idx, g_snapshot->names.find_or_create_string_id("")); - if (value_idx) _record_gc_just_edge("internal", to_node, value_idx, g_snapshot->names.find_or_create_string_id("value")); - if (ty_idx) _record_gc_just_edge("internal", to_node, ty_idx, g_snapshot->names.find_or_create_string_id("ty")); - if (globalref_idx) _record_gc_just_edge("internal", to_node, globalref_idx, g_snapshot->names.find_or_create_string_id("globalref")); -} + if (to_bindings_idx > 0) { + _record_gc_just_edge("internal", from_node_idx, to_bindings_idx, g_snapshot->names.serialize_if_necessary(g_snapshot->strings, "bindings")); + } + if (to_bindingkeyset_idx > 0) { + _record_gc_just_edge("internal", from_node_idx, to_bindingkeyset_idx, g_snapshot->names.serialize_if_necessary(g_snapshot->strings, "bindingkeyset")); + } + } void _gc_heap_snapshot_record_internal_array_edge(jl_value_t *from, jl_value_t *to) JL_NOTSAFEPOINT { _record_gc_edge("internal", from, to, - g_snapshot->names.find_or_create_string_id("")); + g_snapshot->names.serialize_if_necessary(g_snapshot->strings, "")); } void _gc_heap_snapshot_record_hidden_edge(jl_value_t *from, void* to, size_t bytes, uint16_t alloc_type) JL_NOTSAFEPOINT { - size_t name_or_idx = g_snapshot->names.find_or_create_string_id(""); + // valid alloc_type values are 0, 1, 2 + assert(alloc_type <= 2); + size_t name_or_idx = g_snapshot->names.serialize_if_necessary(g_snapshot->strings, ""); auto from_node_idx = record_node_to_gc_snapshot(from); - const char *alloc_kind; + const char *alloc_kind = NULL; switch (alloc_type) { case 0: - alloc_kind = ""; + alloc_kind = ""; break; case 1: - alloc_kind = ""; + alloc_kind = ""; break; case 2: - alloc_kind = ""; - break; - default: - alloc_kind = ""; + alloc_kind = ""; break; } auto to_node_idx = record_pointer_to_gc_snapshot(to, bytes, alloc_kind); - auto &from_node = g_snapshot->nodes[from_node_idx]; - _record_gc_just_edge("hidden", from_node, to_node_idx, name_or_idx); + _record_gc_just_edge("hidden", from_node_idx, to_node_idx, name_or_idx); } static inline void _record_gc_edge(const char *edge_type, jl_value_t *a, @@ -454,84 +588,57 @@ static inline void _record_gc_edge(const char *edge_type, jl_value_t *a, auto from_node_idx = record_node_to_gc_snapshot(a); auto to_node_idx = record_node_to_gc_snapshot(b); - auto &from_node = g_snapshot->nodes[from_node_idx]; - - _record_gc_just_edge(edge_type, from_node, to_node_idx, name_or_idx); + _record_gc_just_edge(edge_type, from_node_idx, to_node_idx, name_or_idx); } -void _record_gc_just_edge(const char *edge_type, Node &from_node, size_t to_idx, size_t name_or_idx) JL_NOTSAFEPOINT +void _record_gc_just_edge(const char *edge_type, size_t from_idx, size_t to_idx, size_t name_or_idx) JL_NOTSAFEPOINT { - from_node.edges.push_back(Edge{ - g_snapshot->edge_types.find_or_create_string_id(edge_type), + auto edge = Edge{ + (uint8_t)g_snapshot->edge_types.find_or_create_string_id(edge_type), name_or_idx, // edge label + from_idx, // from to_idx // to - }); + }; - g_snapshot->num_edges += 1; + serialize_edge(g_snapshot, edge); } -void serialize_heap_snapshot(ios_t *stream, HeapSnapshot &snapshot, char all_one) +void final_serialize_heap_snapshot(ios_t *json, ios_t *strings, HeapSnapshot &snapshot, char all_one) { // mimicking https://github.com/nodejs/node/blob/5fd7a72e1c4fbaf37d3723c4c81dce35c149dc84/deps/v8/src/profiler/heap-snapshot-generator.cc#L2567-L2567 - ios_printf(stream, "{\"snapshot\":{"); - ios_printf(stream, "\"meta\":{"); - ios_printf(stream, "\"node_fields\":[\"type\",\"name\",\"id\",\"self_size\",\"edge_count\",\"trace_node_id\",\"detachedness\"],"); - ios_printf(stream, "\"node_types\":["); - snapshot.node_types.print_json_array(stream, false); - ios_printf(stream, ","); - ios_printf(stream, "\"string\", \"number\", \"number\", \"number\", \"number\", \"number\"],"); - ios_printf(stream, "\"edge_fields\":[\"type\",\"name_or_index\",\"to_node\"],"); - ios_printf(stream, "\"edge_types\":["); - snapshot.edge_types.print_json_array(stream, false); - ios_printf(stream, ","); - ios_printf(stream, "\"string_or_number\",\"from_node\"]"); - ios_printf(stream, "},\n"); // end "meta" - ios_printf(stream, "\"node_count\":%zu,", snapshot.nodes.size()); - ios_printf(stream, "\"edge_count\":%zu", snapshot.num_edges); - ios_printf(stream, "},\n"); // end "snapshot" - - ios_printf(stream, "\"nodes\":["); - bool first_node = true; - for (const auto &from_node : snapshot.nodes) { - if (first_node) { - first_node = false; - } - else { - ios_printf(stream, ","); - } - // ["type","name","id","self_size","edge_count","trace_node_id","detachedness"] - ios_printf(stream, "%zu,%zu,%zu,%zu,%zu,%zu,%d\n", - from_node.type, - from_node.name, - from_node.id, - all_one ? (size_t)1 : from_node.self_size, - from_node.edges.size(), - from_node.trace_node_id, - from_node.detachedness); - } - ios_printf(stream, "],\n"); - - ios_printf(stream, "\"edges\":["); - bool first_edge = true; - for (const auto &from_node : snapshot.nodes) { - for (const auto &edge : from_node.edges) { - if (first_edge) { - first_edge = false; - } - else { - ios_printf(stream, ","); - } - ios_printf(stream, "%zu,%zu,%zu\n", - edge.type, - edge.name_or_index, - edge.to_node * k_node_number_of_fields); - } - } - ios_printf(stream, "],\n"); // end "edges" - - ios_printf(stream, "\"strings\":"); - - snapshot.names.print_json_array(stream, true); - - ios_printf(stream, "}"); + // also https://github.com/microsoft/vscode-v8-heap-tools/blob/c5b34396392397925ecbb4ecb904a27a2754f2c1/v8-heap-parser/src/decoder.rs#L43-L51 + ios_printf(json, "{\"snapshot\":{"); + + ios_printf(json, "\"meta\":{"); + ios_printf(json, "\"node_fields\":[\"type\",\"name\",\"id\",\"self_size\",\"edge_count\",\"trace_node_id\",\"detachedness\"],"); + ios_printf(json, "\"node_types\":["); + snapshot.node_types.print_json_array(json, false); + ios_printf(json, ","); + ios_printf(json, "\"string\", \"number\", \"number\", \"number\", \"number\", \"number\"],"); + ios_printf(json, "\"edge_fields\":[\"type\",\"name_or_index\",\"to_node\"],"); + ios_printf(json, "\"edge_types\":["); + snapshot.edge_types.print_json_array(json, false); + ios_printf(json, ","); + ios_printf(json, "\"string_or_number\",\"from_node\"],"); + // not used. Required by microsoft/vscode-v8-heap-tools + ios_printf(json, "\"trace_function_info_fields\":[\"function_id\",\"name\",\"script_name\",\"script_id\",\"line\",\"column\"],"); + ios_printf(json, "\"trace_node_fields\":[\"id\",\"function_info_index\",\"count\",\"size\",\"children\"],"); + ios_printf(json, "\"sample_fields\":[\"timestamp_us\",\"last_assigned_id\"],"); + ios_printf(json, "\"location_fields\":[\"object_index\",\"script_id\",\"line\",\"column\"]"); + // end not used + ios_printf(json, "},\n"); // end "meta" + + ios_printf(json, "\"node_count\":%zu,", snapshot.num_nodes); + ios_printf(json, "\"edge_count\":%zu,", snapshot.num_edges); + ios_printf(json, "\"trace_function_count\":0"); // not used. Required by microsoft/vscode-v8-heap-tools + ios_printf(json, "},\n"); // end "snapshot" + + // not used. Required by microsoft/vscode-v8-heap-tools + ios_printf(json, "\"trace_function_infos\":[],"); + ios_printf(json, "\"trace_tree\":[],"); + ios_printf(json, "\"samples\":[],"); + ios_printf(json, "\"locations\":[]"); + // end not used + + ios_printf(json, "}"); } diff --git a/src/gc-heap-snapshot.h b/src/gc-heap-snapshot.h index 8c3af5b86bec7..70884f5f62d6a 100644 --- a/src/gc-heap-snapshot.h +++ b/src/gc-heap-snapshot.h @@ -20,7 +20,7 @@ void _gc_heap_snapshot_record_task_to_frame_edge(jl_task_t *from, void *to) JL_N void _gc_heap_snapshot_record_frame_to_frame_edge(jl_gcframe_t *from, jl_gcframe_t *to) JL_NOTSAFEPOINT; void _gc_heap_snapshot_record_array_edge(jl_value_t *from, jl_value_t *to, size_t index) JL_NOTSAFEPOINT; void _gc_heap_snapshot_record_object_edge(jl_value_t *from, jl_value_t *to, void* slot) JL_NOTSAFEPOINT; -void _gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_binding_t* binding) JL_NOTSAFEPOINT; +void _gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_value_t *bindings, jl_value_t *bindingkeyset) JL_NOTSAFEPOINT; // Used for objects managed by GC, but which aren't exposed in the julia object, so have no // field or index. i.e. they're not reachable from julia code, but we _will_ hit them in // the GC mark phase (so we can check their type tag to get the size). @@ -28,7 +28,10 @@ void _gc_heap_snapshot_record_internal_array_edge(jl_value_t *from, jl_value_t * // Used for objects manually allocated in C (outside julia GC), to still tell the heap snapshot about the // size of the object, even though we're never going to mark that object. void _gc_heap_snapshot_record_hidden_edge(jl_value_t *from, void* to, size_t bytes, uint16_t alloc_type) JL_NOTSAFEPOINT; - +// Used for objects that are reachable from the GC roots +void _gc_heap_snapshot_record_gc_roots(jl_value_t *root, char *name) JL_NOTSAFEPOINT; +// Used for objects that are reachable from the finalizer list +void _gc_heap_snapshot_record_finlist(jl_value_t *finlist, size_t index) JL_NOTSAFEPOINT; extern int gc_heap_snapshot_enabled; extern int prev_sweep_full; @@ -60,6 +63,12 @@ static inline void gc_heap_snapshot_record_root(jl_value_t *root, char *name) JL _gc_heap_snapshot_record_root(root, name); } } +static inline void gc_heap_snapshot_record_array_edge_index(jl_value_t *from, jl_value_t *to, size_t index) JL_NOTSAFEPOINT +{ + if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full && from != NULL && to != NULL)) { + _gc_heap_snapshot_record_array_edge(from, to, index); + } +} static inline void gc_heap_snapshot_record_array_edge(jl_value_t *from, jl_value_t **to) JL_NOTSAFEPOINT { if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full)) { @@ -73,10 +82,10 @@ static inline void gc_heap_snapshot_record_object_edge(jl_value_t *from, jl_valu } } -static inline void gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_binding_t* binding) JL_NOTSAFEPOINT +static inline void gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_value_t *bindings, jl_value_t *bindingkeyset) JL_NOTSAFEPOINT { - if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full)) { - _gc_heap_snapshot_record_module_to_binding(module, binding); + if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full) && bindings != NULL && bindingkeyset != NULL) { + _gc_heap_snapshot_record_module_to_binding(module, bindings, bindingkeyset); } } @@ -94,10 +103,25 @@ static inline void gc_heap_snapshot_record_hidden_edge(jl_value_t *from, void* t } } +static inline void gc_heap_snapshot_record_gc_roots(jl_value_t *root, char *name) JL_NOTSAFEPOINT +{ + if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full && root != NULL)) { + _gc_heap_snapshot_record_gc_roots(root, name); + } +} + +static inline void gc_heap_snapshot_record_finlist(jl_value_t *finlist, size_t index) JL_NOTSAFEPOINT +{ + if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full && finlist != NULL)) { + _gc_heap_snapshot_record_finlist(finlist, index); + } +} + // --------------------------------------------------------------------- // Functions to call from Julia to take heap snapshot // --------------------------------------------------------------------- -JL_DLLEXPORT void jl_gc_take_heap_snapshot(ios_t *stream, char all_one); +JL_DLLEXPORT void jl_gc_take_heap_snapshot(ios_t *nodes, ios_t *edges, + ios_t *strings, ios_t *json, char all_one); #ifdef __cplusplus diff --git a/src/gc-page-profiler.c b/src/gc-page-profiler.c new file mode 100644 index 0000000000000..5af1c3d014770 --- /dev/null +++ b/src/gc-page-profiler.c @@ -0,0 +1,167 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +#include "gc-page-profiler.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// whether page profiling is enabled +int page_profile_enabled; +// number of pages written +size_t page_profile_pages_written; +// stream to write page profile to +ios_t *page_profile_stream; +// mutex for page profile +uv_mutex_t page_profile_lock; + +gc_page_profiler_serializer_t gc_page_serializer_create(void) JL_NOTSAFEPOINT +{ + gc_page_profiler_serializer_t serializer; + if (__unlikely(page_profile_enabled)) { + arraylist_new(&serializer.typestrs, GC_PAGE_SZ); + } + else { + serializer.typestrs.len = 0; + } + return serializer; +} + +void gc_page_serializer_init(gc_page_profiler_serializer_t *serializer, + jl_gc_pagemeta_t *pg) JL_NOTSAFEPOINT +{ + if (__unlikely(page_profile_enabled)) { + serializer->typestrs.len = 0; + serializer->data = (char *)pg->data; + serializer->osize = pg->osize; + } +} + +void gc_page_serializer_destroy(gc_page_profiler_serializer_t *serializer) JL_NOTSAFEPOINT +{ + if (__unlikely(page_profile_enabled)) { + arraylist_free(&serializer->typestrs); + } +} + +void gc_page_serializer_write(gc_page_profiler_serializer_t *serializer, + const char *str) JL_NOTSAFEPOINT +{ + if (__unlikely(page_profile_enabled)) { + arraylist_push(&serializer->typestrs, (void *)str); + } +} + +void gc_enable_page_profile(void) JL_NOTSAFEPOINT +{ + page_profile_enabled = 1; +} + +void gc_disable_page_profile(void) JL_NOTSAFEPOINT +{ + page_profile_enabled = 0; +} + +int gc_page_profile_is_enabled(void) JL_NOTSAFEPOINT +{ + return page_profile_enabled; +} + +void gc_page_profile_write_preamble(gc_page_profiler_serializer_t *serializer) + JL_NOTSAFEPOINT +{ + if (__unlikely(page_profile_enabled)) { + char str[GC_TYPE_STR_MAXLEN]; + snprintf(str, GC_TYPE_STR_MAXLEN, + "{\"address\": \"%p\",\"object_size\": %d,\"objects\": [", + serializer->data, serializer->osize); + ios_write(page_profile_stream, str, strlen(str)); + } +} + +void gc_page_profile_write_epilogue(gc_page_profiler_serializer_t *serializer) + JL_NOTSAFEPOINT +{ + if (__unlikely(page_profile_enabled)) { + const char *str = "]}"; + ios_write(page_profile_stream, str, strlen(str)); + } +} + +void gc_page_profile_write_comma(gc_page_profiler_serializer_t *serializer) JL_NOTSAFEPOINT +{ + if (__unlikely(page_profile_enabled)) { + // write comma if not first page + if (page_profile_pages_written > 0) { + const char *str = ","; + ios_write(page_profile_stream, str, strlen(str)); + } + } +} + +void gc_page_profile_write_to_file(gc_page_profiler_serializer_t *serializer) + JL_NOTSAFEPOINT +{ + if (__unlikely(page_profile_enabled)) { + // write to file + uv_mutex_lock(&page_profile_lock); + gc_page_profile_write_comma(serializer); + gc_page_profile_write_preamble(serializer); + char str[GC_TYPE_STR_MAXLEN]; + for (size_t i = 0; i < serializer->typestrs.len; i++) { + const char *name = (const char *)serializer->typestrs.items[i]; + if (name == GC_SERIALIZER_EMPTY) { + snprintf(str, GC_TYPE_STR_MAXLEN, "\"empty\","); + } + else if (name == GC_SERIALIZER_GARBAGE) { + snprintf(str, GC_TYPE_STR_MAXLEN, "\"garbage\","); + } + else { + snprintf(str, GC_TYPE_STR_MAXLEN, "\"%s\",", name); + } + // remove trailing comma for last element + if (i == serializer->typestrs.len - 1) { + str[strlen(str) - 1] = '\0'; + } + ios_write(page_profile_stream, str, strlen(str)); + } + gc_page_profile_write_epilogue(serializer); + page_profile_pages_written++; + uv_mutex_unlock(&page_profile_lock); + } +} + +void gc_page_profile_write_json_preamble(ios_t *stream) JL_NOTSAFEPOINT +{ + if (__unlikely(page_profile_enabled)) { + uv_mutex_lock(&page_profile_lock); + const char *str = "{\"pages\": ["; + ios_write(stream, str, strlen(str)); + uv_mutex_unlock(&page_profile_lock); + } +} + +void gc_page_profile_write_json_epilogue(ios_t *stream) JL_NOTSAFEPOINT +{ + if (__unlikely(page_profile_enabled)) { + uv_mutex_lock(&page_profile_lock); + const char *str = "]}"; + ios_write(stream, str, strlen(str)); + uv_mutex_unlock(&page_profile_lock); + } +} + +JL_DLLEXPORT void jl_gc_take_page_profile(ios_t *stream) +{ + gc_enable_page_profile(); + page_profile_pages_written = 0; + page_profile_stream = stream; + gc_page_profile_write_json_preamble(stream); + jl_gc_collect(JL_GC_FULL); + gc_page_profile_write_json_epilogue(stream); + gc_disable_page_profile(); +} + +#ifdef __cplusplus +} +#endif diff --git a/src/gc-page-profiler.h b/src/gc-page-profiler.h new file mode 100644 index 0000000000000..b103e23905ba5 --- /dev/null +++ b/src/gc-page-profiler.h @@ -0,0 +1,63 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +#ifndef GC_PAGE_PROFILER_H +#define GC_PAGE_PROFILER_H + +#include "gc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define GC_TYPE_STR_MAXLEN (512) + +typedef struct { + arraylist_t typestrs; + char *data; + int osize; +} gc_page_profiler_serializer_t; + +// mutex for page profile +extern uv_mutex_t page_profile_lock; + +// Serializer functions +gc_page_profiler_serializer_t gc_page_serializer_create(void) JL_NOTSAFEPOINT; +void gc_page_serializer_init(gc_page_profiler_serializer_t *serializer, jl_gc_pagemeta_t *pg) JL_NOTSAFEPOINT; +void gc_page_serializer_destroy(gc_page_profiler_serializer_t *serializer) JL_NOTSAFEPOINT; +void gc_page_serializer_write(gc_page_profiler_serializer_t *serializer, const char *str) JL_NOTSAFEPOINT; +// Page profile functions +#define GC_SERIALIZER_EMPTY ((const char *)0x1) +#define GC_SERIALIZER_GARBAGE ((const char *)0x2) +STATIC_INLINE void gc_page_profile_write_empty_page(gc_page_profiler_serializer_t *serializer, + int enabled) JL_NOTSAFEPOINT +{ + if (__unlikely(enabled)) { + gc_page_serializer_write(serializer, GC_SERIALIZER_EMPTY); + } +} +STATIC_INLINE void gc_page_profile_write_garbage(gc_page_profiler_serializer_t *serializer, + int enabled) JL_NOTSAFEPOINT +{ + if (__unlikely(enabled)) { + gc_page_serializer_write(serializer, GC_SERIALIZER_GARBAGE); + } +} +STATIC_INLINE void gc_page_profile_write_live_obj(gc_page_profiler_serializer_t *serializer, + jl_taggedvalue_t *v, + int enabled) JL_NOTSAFEPOINT +{ + if (__unlikely(enabled)) { + const char *name = jl_typeof_str(jl_valueof(v)); + gc_page_serializer_write(serializer, name); + } +} +void gc_enable_page_profile(void) JL_NOTSAFEPOINT; +void gc_disable_page_profile(void) JL_NOTSAFEPOINT; +int gc_page_profile_is_enabled(void) JL_NOTSAFEPOINT; +void gc_page_profile_write_to_file(gc_page_profiler_serializer_t *serializer) JL_NOTSAFEPOINT; + +#ifdef __cplusplus +} +#endif + +#endif // GC_PAGE_PROFILER_H diff --git a/src/gc-pages.c b/src/gc-pages.c index 696f0831762be..698cf84c3cfb6 100644 --- a/src/gc-pages.c +++ b/src/gc-pages.c @@ -9,6 +9,11 @@ extern "C" { #endif +JL_DLLEXPORT uint64_t jl_get_pg_size(void) +{ + return GC_PAGE_SZ; +} + // Try to allocate memory in chunks to permit faster allocation // and improve memory locality of the pools #ifdef _P64 diff --git a/src/gc-stacks.c b/src/gc-stacks.c index 6f97323f22528..e42b2fddaf8aa 100644 --- a/src/gc-stacks.c +++ b/src/gc-stacks.c @@ -37,7 +37,7 @@ static void *malloc_stack(size_t bufsz) JL_NOTSAFEPOINT VirtualFree(stk, 0, MEM_RELEASE); return MAP_FAILED; } - jl_atomic_fetch_add(&num_stack_mappings, 1); + jl_atomic_fetch_add_relaxed(&num_stack_mappings, 1); return stk; } @@ -45,11 +45,21 @@ static void *malloc_stack(size_t bufsz) JL_NOTSAFEPOINT static void free_stack(void *stkbuf, size_t bufsz) { VirtualFree(stkbuf, 0, MEM_RELEASE); - jl_atomic_fetch_add(&num_stack_mappings, -1); + jl_atomic_fetch_add_relaxed(&num_stack_mappings, -1); } #else +# ifdef _OS_OPENBSD_ +static void *malloc_stack(size_t bufsz) JL_NOTSAFEPOINT +{ + void* stk = mmap(0, bufsz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); + if (stk == MAP_FAILED) + return MAP_FAILED; + jl_atomic_fetch_add(&num_stack_mappings, 1); + return stk; +} +# else static void *malloc_stack(size_t bufsz) JL_NOTSAFEPOINT { void* stk = mmap(0, bufsz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); @@ -62,14 +72,15 @@ static void *malloc_stack(size_t bufsz) JL_NOTSAFEPOINT return MAP_FAILED; } #endif - jl_atomic_fetch_add(&num_stack_mappings, 1); + jl_atomic_fetch_add_relaxed(&num_stack_mappings, 1); return stk; } +# endif static void free_stack(void *stkbuf, size_t bufsz) { munmap(stkbuf, bufsz); - jl_atomic_fetch_add(&num_stack_mappings, -1); + jl_atomic_fetch_add_relaxed(&num_stack_mappings, -1); } #endif @@ -203,12 +214,17 @@ void sweep_stack_pools(void) assert(gc_n_threads); for (int i = 0; i < gc_n_threads; i++) { jl_ptls_t ptls2 = gc_all_tls_states[i]; + if (ptls2 == NULL) + continue; // free half of stacks that remain unused since last sweep for (int p = 0; p < JL_N_STACK_POOLS; p++) { small_arraylist_t *al = &ptls2->heap.free_stacks[p]; size_t n_to_free; - if (al->len > MIN_STACK_MAPPINGS_PER_POOL) { + if (jl_atomic_load_relaxed(&ptls2->current_task) == NULL) { + n_to_free = al->len; // not alive yet or dead, so it does not need these anymore + } + else if (al->len > MIN_STACK_MAPPINGS_PER_POOL) { n_to_free = al->len / 2; if (n_to_free > (al->len - MIN_STACK_MAPPINGS_PER_POOL)) n_to_free = al->len - MIN_STACK_MAPPINGS_PER_POOL; @@ -220,6 +236,12 @@ void sweep_stack_pools(void) void *stk = small_arraylist_pop(al); free_stack(stk, pool_sizes[p]); } + if (jl_atomic_load_relaxed(&ptls2->current_task) == NULL) { + small_arraylist_free(al); + } + } + if (jl_atomic_load_relaxed(&ptls2->current_task) == NULL) { + small_arraylist_free(ptls2->heap.free_stacks); } small_arraylist_t *live_tasks = &ptls2->heap.live_tasks; @@ -298,7 +320,7 @@ JL_DLLEXPORT jl_array_t *jl_live_tasks(void) if (t->stkbuf != NULL) { if (j == l) goto restart; - ((void**)jl_array_data(a))[j++] = t; + jl_array_data(a,void*)[j++] = t; } small_arraylist_t *live_tasks = &ptls2->heap.live_tasks; size_t n = mtarraylist_length(live_tasks); @@ -307,7 +329,7 @@ JL_DLLEXPORT jl_array_t *jl_live_tasks(void) if (t->stkbuf != NULL) { if (j == l) goto restart; - ((void**)jl_array_data(a))[j++] = t; + jl_array_data(a,void*)[j++] = t; } } } diff --git a/src/gc.c b/src/gc.c index 42a9daa01a747..06a41393953b9 100644 --- a/src/gc.c +++ b/src/gc.c @@ -1,7 +1,9 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license #include "gc.h" +#include "gc-page-profiler.h" #include "julia.h" +#include "julia_atomics.h" #include "julia_gcext.h" #include "julia_assert.h" #ifdef __GLIBC__ @@ -20,8 +22,8 @@ int jl_n_sweepthreads; _Atomic(int) gc_n_threads_marking; // Number of threads sweeping _Atomic(int) gc_n_threads_sweeping; -// Temporary for the `ptls->page_metadata_allocd` used during parallel sweeping -_Atomic(jl_gc_page_stack_t *) gc_allocd_scratch; +// Temporary for the `ptls->page_metadata_allocd` used during parallel sweeping (padded to avoid false sharing) +_Atomic(jl_gc_padded_page_stack_t *) gc_allocd_scratch; // `tid` of mutator thread that triggered GC _Atomic(int) gc_master_tid; // `tid` of first GC thread @@ -31,6 +33,8 @@ uv_mutex_t gc_threads_lock; uv_cond_t gc_threads_cond; // To indicate whether concurrent sweeping should run uv_sem_t gc_sweep_assists_needed; +// Mutex used to coordinate entry of GC threads in the mark loop +uv_mutex_t gc_queue_observer_lock; // Linked list of callback functions @@ -48,7 +52,6 @@ static jl_gc_callback_list_t *gc_cblist_post_gc; static jl_gc_callback_list_t *gc_cblist_notify_external_alloc; static jl_gc_callback_list_t *gc_cblist_notify_external_free; static jl_gc_callback_list_t *gc_cblist_notify_gc_pressure; -typedef void (*jl_gc_cb_notify_gc_pressure_t)(void); #define gc_invoke_callbacks(ty, list, args) \ do { \ @@ -220,32 +223,7 @@ NOINLINE uintptr_t gc_get_stack_ptr(void) return (uintptr_t)jl_get_frame_addr(); } -#define should_timeout() 0 - -void jl_gc_wait_for_the_world(jl_ptls_t* gc_all_tls_states, int gc_n_threads) -{ - JL_TIMING(GC, GC_Stop); -#ifdef USE_TRACY - TracyCZoneCtx ctx = JL_TIMING_DEFAULT_BLOCK->tracy_ctx; - TracyCZoneColor(ctx, 0x696969); -#endif - assert(gc_n_threads); - if (gc_n_threads > 1) - jl_wake_libuv(); - for (int i = 0; i < gc_n_threads; i++) { - jl_ptls_t ptls2 = gc_all_tls_states[i]; - if (ptls2 != NULL) { - // This acquire load pairs with the release stores - // in the signal handler of safepoint so we are sure that - // all the stores on those threads are visible. - // We're currently also using atomic store release in mutator threads - // (in jl_gc_state_set), but we may want to use signals to flush the - // memory operations on those threads lazily instead. - while (!jl_atomic_load_relaxed(&ptls2->gc_state) || !jl_atomic_load_acquire(&ptls2->gc_state)) - jl_cpu_pause(); // yield? - } - } -} +void jl_gc_wait_for_the_world(jl_ptls_t* gc_all_tls_states, int gc_n_threads); // malloc wrappers, aligned allocation @@ -323,7 +301,7 @@ static void run_finalizer(jl_task_t *ct, void *o, void *ff) } JL_CATCH { jl_printf((JL_STREAM*)STDERR_FILENO, "error in running finalizer: "); - jl_static_show((JL_STREAM*)STDERR_FILENO, jl_current_exception()); + jl_static_show((JL_STREAM*)STDERR_FILENO, jl_current_exception(ct)); jl_printf((JL_STREAM*)STDERR_FILENO, "\n"); jlbacktrace(); // written to STDERR_FILENO } @@ -425,7 +403,7 @@ JL_DLLEXPORT void jl_gc_init_finalizer_rng_state(void) jl_rng_split(finalizer_rngState, jl_current_task->rngState); } -static void run_finalizers(jl_task_t *ct) +static void run_finalizers(jl_task_t *ct, int finalizers_thread) { // Racy fast path: // The race here should be OK since the race can only happen if @@ -453,7 +431,7 @@ static void run_finalizers(jl_task_t *ct) // This releases the finalizers lock. int8_t was_in_finalizer = ct->ptls->in_finalizer; - ct->ptls->in_finalizer = 1; + ct->ptls->in_finalizer = !finalizers_thread; jl_gc_run_finalizers_in_list(ct, &copied_list); ct->ptls->in_finalizer = was_in_finalizer; arraylist_free(&copied_list); @@ -467,7 +445,7 @@ JL_DLLEXPORT void jl_gc_run_pending_finalizers(jl_task_t *ct) ct = jl_current_task; jl_ptls_t ptls = ct->ptls; if (!ptls->in_finalizer && ptls->locks.len == 0 && ptls->finalizers_inhibited == 0) { - run_finalizers(ct); + run_finalizers(ct, 0); } } @@ -558,14 +536,12 @@ void jl_gc_run_all_finalizers(jl_task_t *ct) } // unlock here because `run_finalizers` locks this JL_UNLOCK_NOGC(&finalizers_lock); - gc_n_threads = 0; - gc_all_tls_states = NULL; - run_finalizers(ct); + run_finalizers(ct, 1); } void jl_gc_add_finalizer_(jl_ptls_t ptls, void *v, void *f) JL_NOTSAFEPOINT { - assert(jl_atomic_load_relaxed(&ptls->gc_state) == 0); + assert(jl_atomic_load_relaxed(&ptls->gc_state) == JL_GC_STATE_UNSAFE); arraylist_t *a = &ptls->finalizers; // This acquire load and the release store at the end are used to // synchronize with `finalize_object` on another thread. Apart from the GC, @@ -633,8 +609,6 @@ JL_DLLEXPORT void jl_finalize_th(jl_task_t *ct, jl_value_t *o) finalize_object(&ptls2->finalizers, o, &copied_list, jl_atomic_load_relaxed(&ct->tid) != i); } finalize_object(&finalizer_list_marked, o, &copied_list, 0); - gc_n_threads = 0; - gc_all_tls_states = NULL; if (copied_list.len > 0) { // This releases the finalizers lock. jl_gc_run_finalizers_in_list(ct, &copied_list); @@ -695,15 +669,17 @@ static const size_t default_collect_interval = 3200 * 1024 * sizeof(void*); static memsize_t max_total_memory = (memsize_t) MAX32HEAP; #endif // heuristic stuff for https://dl.acm.org/doi/10.1145/3563323 -static uint64_t old_pause_time = 0; -static uint64_t old_mut_time = 0; +// start with values that are in the target ranges to reduce transient hiccups at startup +static uint64_t old_pause_time = 1e7; // 10 ms +static uint64_t old_mut_time = 1e9; // 1 second static uint64_t old_heap_size = 0; -static uint64_t old_alloc_diff = 0; -static uint64_t old_freed_diff = 0; +static uint64_t old_alloc_diff = default_collect_interval; +static uint64_t old_freed_diff = default_collect_interval; static uint64_t gc_end_time = 0; static int thrash_counter = 0; static int thrashing = 0; // global variables for GC stats +static uint64_t freed_in_runtime = 0; // Resetting the object to a young object, this is used when marking the // finalizer list to collect them the next time because the object is very @@ -758,7 +734,6 @@ int current_sweep_full = 0; int under_pressure = 0; // Full collection heuristics -static int64_t pool_live_bytes = 0; static int64_t live_bytes = 0; static int64_t promoted_bytes = 0; static int64_t last_live_bytes = 0; // live_bytes at last collection @@ -1010,6 +985,22 @@ static void sweep_weak_refs(void) } +STATIC_INLINE void jl_batch_accum_heap_size(jl_ptls_t ptls, uint64_t sz) JL_NOTSAFEPOINT +{ + uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc) + sz; + if (alloc_acc < 16*1024) + jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, alloc_acc); + else { + jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, alloc_acc); + jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); + } +} + +STATIC_INLINE void jl_batch_accum_free_size(jl_ptls_t ptls, uint64_t sz) JL_NOTSAFEPOINT +{ + jl_atomic_store_relaxed(&ptls->gc_num.free_acc, jl_atomic_load_relaxed(&ptls->gc_num.free_acc) + sz); +} + // big value list // Size includes the tag and the tag is not cleared!! @@ -1032,13 +1023,7 @@ STATIC_INLINE jl_value_t *jl_gc_big_alloc_inner(jl_ptls_t ptls, size_t sz) jl_atomic_load_relaxed(&ptls->gc_num.allocd) + allocsz); jl_atomic_store_relaxed(&ptls->gc_num.bigalloc, jl_atomic_load_relaxed(&ptls->gc_num.bigalloc) + 1); - uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc); - if (alloc_acc + allocsz < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, alloc_acc + allocsz); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, alloc_acc + allocsz); - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); - } + jl_batch_accum_heap_size(ptls, allocsz); #ifdef MEMDEBUG memset(v, 0xee, allocsz); #endif @@ -1129,10 +1114,9 @@ static void sweep_big(jl_ptls_t ptls, int sweep_full) JL_NOTSAFEPOINT gc_time_big_end(); } -// tracking Arrays with malloc'd storage +// tracking Memorys with malloc'd storage -void jl_gc_track_malloced_array(jl_ptls_t ptls, jl_array_t *a) JL_NOTSAFEPOINT -{ +void jl_gc_track_malloced_genericmemory(jl_ptls_t ptls, jl_genericmemory_t *m, int isaligned){ // This is **NOT** a GC safe point. mallocarray_t *ma; if (ptls->heap.mafreelist == NULL) { @@ -1142,26 +1126,21 @@ void jl_gc_track_malloced_array(jl_ptls_t ptls, jl_array_t *a) JL_NOTSAFEPOINT ma = ptls->heap.mafreelist; ptls->heap.mafreelist = ma->next; } - ma->a = a; + ma->a = (jl_value_t*)((uintptr_t)m | !!isaligned); ma->next = ptls->heap.mallocarrays; ptls->heap.mallocarrays = ma; } + void jl_gc_count_allocd(size_t sz) JL_NOTSAFEPOINT { jl_ptls_t ptls = jl_current_task->ptls; jl_atomic_store_relaxed(&ptls->gc_num.allocd, jl_atomic_load_relaxed(&ptls->gc_num.allocd) + sz); - uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc); - if (alloc_acc + sz < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, alloc_acc + sz); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, alloc_acc + sz); - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); - } + jl_batch_accum_heap_size(ptls, sz); } - -static void combine_thread_gc_counts(jl_gc_num_t *dest) JL_NOTSAFEPOINT +// Only safe to update the heap inside the GC +static void combine_thread_gc_counts(jl_gc_num_t *dest, int update_heap) JL_NOTSAFEPOINT { int gc_n_threads; jl_ptls_t* gc_all_tls_states; @@ -1175,12 +1154,14 @@ static void combine_thread_gc_counts(jl_gc_num_t *dest) JL_NOTSAFEPOINT dest->realloc += jl_atomic_load_relaxed(&ptls->gc_num.realloc); dest->poolalloc += jl_atomic_load_relaxed(&ptls->gc_num.poolalloc); dest->bigalloc += jl_atomic_load_relaxed(&ptls->gc_num.bigalloc); - uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc); - uint64_t free_acc = jl_atomic_load_relaxed(&ptls->gc_num.free_acc); dest->freed += jl_atomic_load_relaxed(&ptls->gc_num.free_acc); - jl_atomic_store_relaxed(&gc_heap_stats.heap_size, alloc_acc - free_acc + jl_atomic_load_relaxed(&gc_heap_stats.heap_size)); - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); - jl_atomic_store_relaxed(&ptls->gc_num.free_acc, 0); + if (update_heap) { + uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc); + freed_in_runtime += jl_atomic_load_relaxed(&ptls->gc_num.free_acc); + jl_atomic_store_relaxed(&gc_heap_stats.heap_size, alloc_acc + jl_atomic_load_relaxed(&gc_heap_stats.heap_size)); + jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); + jl_atomic_store_relaxed(&ptls->gc_num.free_acc, 0); + } } } } @@ -1194,8 +1175,14 @@ static void reset_thread_gc_counts(void) JL_NOTSAFEPOINT for (int i = 0; i < gc_n_threads; i++) { jl_ptls_t ptls = gc_all_tls_states[i]; if (ptls != NULL) { - memset(&ptls->gc_num, 0, sizeof(ptls->gc_num)); + // don't reset `pool_live_bytes` here jl_atomic_store_relaxed(&ptls->gc_num.allocd, -(int64_t)gc_num.interval); + jl_atomic_store_relaxed(&ptls->gc_num.malloc, 0); + jl_atomic_store_relaxed(&ptls->gc_num.realloc, 0); + jl_atomic_store_relaxed(&ptls->gc_num.poolalloc, 0); + jl_atomic_store_relaxed(&ptls->gc_num.bigalloc, 0); + jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); + jl_atomic_store_relaxed(&ptls->gc_num.free_acc, 0); } } } @@ -1208,45 +1195,43 @@ static int64_t inc_live_bytes(int64_t inc) JL_NOTSAFEPOINT void jl_gc_reset_alloc_count(void) JL_NOTSAFEPOINT { - combine_thread_gc_counts(&gc_num); + combine_thread_gc_counts(&gc_num, 0); inc_live_bytes(gc_num.deferred_alloc + gc_num.allocd); gc_num.allocd = 0; gc_num.deferred_alloc = 0; reset_thread_gc_counts(); } -size_t jl_array_nbytes(jl_array_t *a) JL_NOTSAFEPOINT +size_t jl_genericmemory_nbytes(jl_genericmemory_t *m) JL_NOTSAFEPOINT { - size_t sz = 0; - int isbitsunion = jl_array_isbitsunion(a); - if (jl_array_ndims(a) == 1) - sz = a->elsize * a->maxsize + ((a->elsize == 1 && !isbitsunion) ? 1 : 0); - else - sz = a->elsize * jl_array_len(a); - if (isbitsunion) + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(m))->layout; + size_t sz = layout->size * m->length; + if (layout->flags.arrayelem_isunion) // account for isbits Union array selector bytes - sz += jl_array_len(a); + sz += m->length; return sz; } -static void jl_gc_free_array(jl_array_t *a) JL_NOTSAFEPOINT + +static void jl_gc_free_memory(jl_value_t *v, int isaligned) JL_NOTSAFEPOINT { - if (a->flags.how == 2) { - char *d = (char*)a->data - a->offset*a->elsize; - if (a->flags.isaligned) - jl_free_aligned(d); - else - free(d); - jl_atomic_store_relaxed(&gc_heap_stats.heap_size, - jl_atomic_load_relaxed(&gc_heap_stats.heap_size) - jl_array_nbytes(a)); - gc_num.freed += jl_array_nbytes(a); - gc_num.freecall++; - } + assert(jl_is_genericmemory(v)); + jl_genericmemory_t *m = (jl_genericmemory_t*)v; + assert(jl_genericmemory_how(m) == 1 || jl_genericmemory_how(m) == 2); + char *d = (char*)m->ptr; + if (isaligned) + jl_free_aligned(d); + else + free(d); + jl_atomic_store_relaxed(&gc_heap_stats.heap_size, + jl_atomic_load_relaxed(&gc_heap_stats.heap_size) - jl_genericmemory_nbytes(m)); + gc_num.freed += jl_genericmemory_nbytes(m); + gc_num.freecall++; } -static void sweep_malloced_arrays(void) JL_NOTSAFEPOINT +static void sweep_malloced_memory(void) JL_NOTSAFEPOINT { - gc_time_mallocd_array_start(); + gc_time_mallocd_memory_start(); assert(gc_n_threads); for (int t_i = 0; t_i < gc_n_threads; t_i++) { jl_ptls_t ptls2 = gc_all_tls_states[t_i]; @@ -1255,23 +1240,24 @@ static void sweep_malloced_arrays(void) JL_NOTSAFEPOINT mallocarray_t **pma = &ptls2->heap.mallocarrays; while (ma != NULL) { mallocarray_t *nxt = ma->next; - int bits = jl_astaggedvalue(ma->a)->bits.gc; + jl_value_t *a = (jl_value_t*)((uintptr_t)ma->a & ~1); + int bits = jl_astaggedvalue(a)->bits.gc; if (gc_marked(bits)) { pma = &ma->next; } else { *pma = nxt; - assert(ma->a->flags.how == 2); - jl_gc_free_array(ma->a); + int isaligned = (uintptr_t)ma->a & 1; + jl_gc_free_memory(a, isaligned); ma->next = ptls2->heap.mafreelist; ptls2->heap.mafreelist = ma; } - gc_time_count_mallocd_array(bits); + gc_time_count_mallocd_memory(bits); ma = nxt; } } } - gc_time_mallocd_array_end(); + gc_time_mallocd_memory_end(); } // pool allocation @@ -1333,6 +1319,8 @@ STATIC_INLINE jl_value_t *jl_gc_pool_alloc_inner(jl_ptls_t ptls, int pool_offset maybe_collect(ptls); jl_atomic_store_relaxed(&ptls->gc_num.allocd, jl_atomic_load_relaxed(&ptls->gc_num.allocd) + osize); + jl_atomic_store_relaxed(&ptls->gc_num.pool_live_bytes, + jl_atomic_load_relaxed(&ptls->gc_num.pool_live_bytes) + osize); jl_atomic_store_relaxed(&ptls->gc_num.poolalloc, jl_atomic_load_relaxed(&ptls->gc_num.poolalloc) + 1); // first try to use the freelist @@ -1410,14 +1398,40 @@ int jl_gc_classify_pools(size_t sz, int *osize) // sweep phase +gc_fragmentation_stat_t gc_page_fragmentation_stats[JL_GC_N_POOLS]; +JL_DLLEXPORT double jl_gc_page_utilization_stats[JL_GC_N_MAX_POOLS]; + +STATIC_INLINE void gc_update_page_fragmentation_data(jl_gc_pagemeta_t *pg) JL_NOTSAFEPOINT +{ + gc_fragmentation_stat_t *stats = &gc_page_fragmentation_stats[pg->pool_n]; + jl_atomic_fetch_add_relaxed(&stats->n_freed_objs, pg->nfree); + jl_atomic_fetch_add_relaxed(&stats->n_pages_allocd, 1); +} + +STATIC_INLINE void gc_dump_page_utilization_data(void) JL_NOTSAFEPOINT +{ + for (int i = 0; i < JL_GC_N_POOLS; i++) { + gc_fragmentation_stat_t *stats = &gc_page_fragmentation_stats[i]; + double utilization = 1.0; + size_t n_freed_objs = jl_atomic_load_relaxed(&stats->n_freed_objs); + size_t n_pages_allocd = jl_atomic_load_relaxed(&stats->n_pages_allocd); + if (n_pages_allocd != 0) { + utilization -= ((double)n_freed_objs * (double)jl_gc_sizeclasses[i]) / (double)n_pages_allocd / (double)GC_PAGE_SZ; + } + jl_gc_page_utilization_stats[i] = utilization; + jl_atomic_store_relaxed(&stats->n_freed_objs, 0); + jl_atomic_store_relaxed(&stats->n_pages_allocd, 0); + } +} + int64_t buffered_pages = 0; // Returns pointer to terminal pointer of list rooted at *pfl. -static void gc_sweep_page(jl_gc_pool_t *p, jl_gc_page_stack_t *allocd, jl_gc_page_stack_t *buffered, +static void gc_sweep_page(gc_page_profiler_serializer_t *s, jl_gc_pool_t *p, jl_gc_page_stack_t *allocd, jl_gc_page_stack_t *buffered, jl_gc_pagemeta_t *pg, int osize) JL_NOTSAFEPOINT { char *data = pg->data; - jl_taggedvalue_t *v = (jl_taggedvalue_t*)(data + GC_PAGE_OFFSET); + jl_taggedvalue_t *v0 = (jl_taggedvalue_t*)(data + GC_PAGE_OFFSET); char *lim = data + GC_PAGE_SZ - osize; char *lim_newpages = data + GC_PAGE_SZ; if (gc_page_data((char*)p->newpages - 1) == data) { @@ -1425,6 +1439,9 @@ static void gc_sweep_page(jl_gc_pool_t *p, jl_gc_page_stack_t *allocd, jl_gc_pag } size_t old_nfree = pg->nfree; size_t nfree; + // avoid loading a global variable in the hot path + int page_profile_enabled = gc_page_profile_is_enabled(); + gc_page_serializer_init(s, pg); int re_use_page = 1; int keep_as_local_buffer = 0; @@ -1444,6 +1461,7 @@ static void gc_sweep_page(jl_gc_pool_t *p, jl_gc_page_stack_t *allocd, jl_gc_pag } #endif nfree = (GC_PAGE_SZ - GC_PAGE_OFFSET) / osize; + gc_page_profile_write_empty_page(s, page_profile_enabled); goto done; } // For quick sweep, we might be able to skip the page if the page doesn't @@ -1453,12 +1471,13 @@ static void gc_sweep_page(jl_gc_pool_t *p, jl_gc_page_stack_t *allocd, jl_gc_pag if (!prev_sweep_full || pg->prev_nold == pg->nold) { freedall = 0; nfree = pg->nfree; + gc_page_profile_write_empty_page(s, page_profile_enabled); goto done; } } pg_skpd = 0; - { // scope to avoid clang goto errors + { // scope to avoid clang goto errors int has_marked = 0; int has_young = 0; int16_t prev_nold = 0; @@ -1466,6 +1485,22 @@ static void gc_sweep_page(jl_gc_pool_t *p, jl_gc_page_stack_t *allocd, jl_gc_pag jl_taggedvalue_t *fl = NULL; jl_taggedvalue_t **pfl = &fl; jl_taggedvalue_t **pfl_begin = NULL; + // collect page profile + jl_taggedvalue_t *v = v0; + if (page_profile_enabled) { + while ((char*)v <= lim) { + int bits = v->bits.gc; + if (!gc_marked(bits) || (char*)v >= lim_newpages) { + gc_page_profile_write_garbage(s, page_profile_enabled); + } + else { + gc_page_profile_write_live_obj(s, v, page_profile_enabled); + } + v = (jl_taggedvalue_t*)((char*)v + osize); + } + v = v0; + } + // sweep the page while ((char*)v <= lim) { int bits = v->bits.gc; // if an object is past `lim_newpages` then we can guarantee it's garbage @@ -1519,13 +1554,16 @@ static void gc_sweep_page(jl_gc_pool_t *p, jl_gc_page_stack_t *allocd, jl_gc_pag push_lf_back(&global_page_pool_lazily_freed, pg); } } + gc_page_profile_write_to_file(s); + gc_update_page_fragmentation_data(pg); gc_time_count_page(freedall, pg_skpd); - jl_atomic_fetch_add((_Atomic(int64_t) *)&pool_live_bytes, GC_PAGE_SZ - GC_PAGE_OFFSET - nfree * osize); - jl_atomic_fetch_add((_Atomic(int64_t) *)&gc_num.freed, (nfree - old_nfree) * osize); + jl_ptls_t ptls = gc_all_tls_states[pg->thread_n]; + jl_atomic_fetch_add_relaxed(&ptls->gc_num.pool_live_bytes, GC_PAGE_SZ - GC_PAGE_OFFSET - nfree * osize); //TODO: Could we avoid doing a fetch_add here? + jl_atomic_fetch_add_relaxed((_Atomic(int64_t) *)&gc_num.freed, (nfree - old_nfree) * osize); } // the actual sweeping over all allocated pages in a memory pool -STATIC_INLINE void gc_sweep_pool_page(jl_gc_page_stack_t *allocd, jl_gc_page_stack_t *lazily_freed, +STATIC_INLINE void gc_sweep_pool_page(gc_page_profiler_serializer_t *s, jl_gc_page_stack_t *allocd, jl_gc_page_stack_t *lazily_freed, jl_gc_pagemeta_t *pg) JL_NOTSAFEPOINT { int p_n = pg->pool_n; @@ -1533,13 +1571,13 @@ STATIC_INLINE void gc_sweep_pool_page(jl_gc_page_stack_t *allocd, jl_gc_page_sta jl_ptls_t ptls2 = gc_all_tls_states[t_n]; jl_gc_pool_t *p = &ptls2->heap.norm_pools[p_n]; int osize = pg->osize; - gc_sweep_page(p, allocd, lazily_freed, pg, osize); + gc_sweep_page(s, p, allocd, lazily_freed, pg, osize); } // sweep over all memory that is being used and not in a pool static void gc_sweep_other(jl_ptls_t ptls, int sweep_full) JL_NOTSAFEPOINT { - sweep_malloced_arrays(); + sweep_malloced_memory(); sweep_big(ptls, sweep_full); } @@ -1559,61 +1597,164 @@ static void gc_pool_sync_nfree(jl_gc_pagemeta_t *pg, jl_taggedvalue_t *last) JL_ pg->nfree = nfree; } -void gc_sweep_wake_all(void) +// pre-scan pages to check whether there are enough pages so that's worth parallelizing +// also sweeps pages that don't need to be linearly scanned +int gc_sweep_prescan(jl_ptls_t ptls, jl_gc_padded_page_stack_t *new_gc_allocd_scratch) +{ + // 4MB worth of pages is worth parallelizing + const int n_pages_worth_parallel_sweep = (int)(4 * (1 << 20) / GC_PAGE_SZ); + int n_pages_to_scan = 0; + gc_page_profiler_serializer_t serializer = gc_page_serializer_create(); + for (int t_i = 0; t_i < gc_n_threads; t_i++) { + jl_ptls_t ptls2 = gc_all_tls_states[t_i]; + if (ptls2 == NULL) { + continue; + } + jl_gc_page_stack_t *dest = &new_gc_allocd_scratch[ptls2->tid].stack; + jl_gc_page_stack_t tmp; + jl_gc_pagemeta_t *tail = NULL; + memset(&tmp, 0, sizeof(tmp)); + while (1) { + jl_gc_pagemeta_t *pg = pop_lf_back_nosync(&ptls2->page_metadata_allocd); + if (pg == NULL) { + break; + } + int should_scan = 1; + if (!pg->has_marked) { + should_scan = 0; + } + if (!current_sweep_full && !pg->has_young) { + assert(!prev_sweep_full || pg->prev_nold >= pg->nold); + if (!prev_sweep_full || pg->prev_nold == pg->nold) { + should_scan = 0; + } + } + if (should_scan) { + if (tail == NULL) { + tail = pg; + } + n_pages_to_scan++; + push_lf_back_nosync(&tmp, pg); + } + else { + gc_sweep_pool_page(&serializer, dest, &ptls2->page_metadata_buffered, pg); + } + if (n_pages_to_scan >= n_pages_worth_parallel_sweep) { + break; + } + } + if (tail != NULL) { + tail->next = jl_atomic_load_relaxed(&ptls2->page_metadata_allocd.bottom); + } + ptls2->page_metadata_allocd = tmp; + if (n_pages_to_scan >= n_pages_worth_parallel_sweep) { + break; + } + } + gc_page_serializer_destroy(&serializer); + return n_pages_to_scan >= n_pages_worth_parallel_sweep; +} + +// wake up all threads to sweep the pages +void gc_sweep_wake_all(jl_ptls_t ptls, jl_gc_padded_page_stack_t *new_gc_allocd_scratch) { + int parallel_sweep_worthwhile = gc_sweep_prescan(ptls, new_gc_allocd_scratch); + jl_atomic_store(&gc_allocd_scratch, new_gc_allocd_scratch); + if (!parallel_sweep_worthwhile) { + return; + } uv_mutex_lock(&gc_threads_lock); for (int i = gc_first_tid; i < gc_first_tid + jl_n_markthreads; i++) { jl_ptls_t ptls2 = gc_all_tls_states[i]; + assert(ptls2 != NULL); // should be a GC thread jl_atomic_fetch_add(&ptls2->gc_sweeps_requested, 1); } uv_cond_broadcast(&gc_threads_cond); uv_mutex_unlock(&gc_threads_lock); } -void gc_sweep_pool_parallel(void) +// wait for all threads to finish sweeping +void gc_sweep_wait_for_all(void) +{ + jl_atomic_store(&gc_allocd_scratch, NULL); + while (jl_atomic_load_acquire(&gc_n_threads_sweeping) != 0) { + jl_cpu_pause(); + } +} + +// sweep all pools +void gc_sweep_pool_parallel(jl_ptls_t ptls) { jl_atomic_fetch_add(&gc_n_threads_sweeping, 1); - jl_gc_page_stack_t *allocd_scratch = jl_atomic_load(&gc_allocd_scratch); + jl_gc_padded_page_stack_t *allocd_scratch = jl_atomic_load(&gc_allocd_scratch); if (allocd_scratch != NULL) { + gc_page_profiler_serializer_t serializer = gc_page_serializer_create(); while (1) { int found_pg = 0; + // sequentially walk the threads and sweep the pages for (int t_i = 0; t_i < gc_n_threads; t_i++) { jl_ptls_t ptls2 = gc_all_tls_states[t_i]; + // skip foreign threads that already exited if (ptls2 == NULL) { continue; } - jl_gc_page_stack_t *allocd = &allocd_scratch[t_i]; - jl_gc_pagemeta_t *pg = pop_lf_back(&ptls2->page_metadata_allocd); + jl_gc_page_stack_t *dest = &allocd_scratch[ptls2->tid].stack; + jl_gc_pagemeta_t *pg = try_pop_lf_back(&ptls2->page_metadata_allocd); + // failed steal attempt if (pg == NULL) { continue; } - gc_sweep_pool_page(allocd, &ptls2->page_metadata_buffered, pg); + gc_sweep_pool_page(&serializer, dest, &ptls2->page_metadata_buffered, pg); found_pg = 1; } if (!found_pg) { - break; + // check for termination + int no_more_work = 1; + for (int t_i = 0; t_i < gc_n_threads; t_i++) { + jl_ptls_t ptls2 = gc_all_tls_states[t_i]; + // skip foreign threads that already exited + if (ptls2 == NULL) { + continue; + } + jl_gc_pagemeta_t *pg = jl_atomic_load_relaxed(&ptls2->page_metadata_allocd.bottom); + if (pg != NULL) { + no_more_work = 0; + break; + } + } + if (no_more_work) { + break; + } } + jl_cpu_pause(); } + gc_page_serializer_destroy(&serializer); } jl_atomic_fetch_add(&gc_n_threads_sweeping, -1); } -void gc_sweep_wait_for_all(void) +// free all pages (i.e. through `madvise` on Linux) that were lazily freed +void gc_free_pages(void) { - jl_atomic_store(&gc_allocd_scratch, NULL); - while (jl_atomic_load_relaxed(&gc_n_threads_sweeping) != 0) { - jl_cpu_pause(); + while (1) { + jl_gc_pagemeta_t *pg = pop_lf_back(&global_page_pool_lazily_freed); + if (pg == NULL) { + break; + } + jl_gc_free_page(pg); + push_lf_back(&global_page_pool_freed, pg); } } -void gc_free_pages(void) +void gc_move_to_global_page_pool(jl_gc_page_stack_t *pgstack) { while (1) { - jl_gc_pagemeta_t *pg = pop_lf_back(&global_page_pool_lazily_freed); + jl_gc_pagemeta_t *pg = pop_lf_back(pgstack); if (pg == NULL) { break; } jl_gc_free_page(pg); + jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, -GC_PAGE_SZ); push_lf_back(&global_page_pool_freed, pg); } } @@ -1630,7 +1771,7 @@ static void gc_sweep_pool(void) // allocate enough space to hold the end of the free list chain // for every thread and pool size - jl_taggedvalue_t ***pfl = (jl_taggedvalue_t ***) alloca(n_threads * JL_GC_N_POOLS * sizeof(jl_taggedvalue_t**)); + jl_taggedvalue_t ***pfl = (jl_taggedvalue_t ***) malloc_s(n_threads * JL_GC_N_POOLS * sizeof(jl_taggedvalue_t**)); // update metadata of pages that were pointed to by freelist or newpages from a pool // i.e. pages being the current allocation target @@ -1642,6 +1783,7 @@ static void gc_sweep_pool(void) } continue; } + jl_atomic_store_relaxed(&ptls2->gc_num.pool_live_bytes, 0); for (int i = 0; i < JL_GC_N_POOLS; i++) { jl_gc_pool_t *p = &ptls2->heap.norm_pools[i]; jl_taggedvalue_t *last = p->freelist; @@ -1671,17 +1813,18 @@ static void gc_sweep_pool(void) } // the actual sweeping - jl_gc_page_stack_t *tmp = (jl_gc_page_stack_t *)alloca(n_threads * sizeof(jl_gc_page_stack_t)); - memset(tmp, 0, n_threads * sizeof(jl_gc_page_stack_t)); - jl_atomic_store(&gc_allocd_scratch, tmp); - gc_sweep_wake_all(); - gc_sweep_pool_parallel(); + jl_gc_padded_page_stack_t *new_gc_allocd_scratch = (jl_gc_padded_page_stack_t *) malloc_s(n_threads * sizeof(jl_gc_padded_page_stack_t)); + memset(new_gc_allocd_scratch, 0, n_threads * sizeof(jl_gc_padded_page_stack_t)); + jl_ptls_t ptls = jl_current_task->ptls; + gc_sweep_wake_all(ptls, new_gc_allocd_scratch); + gc_sweep_pool_parallel(ptls); gc_sweep_wait_for_all(); + // reset half-pages pointers for (int t_i = 0; t_i < n_threads; t_i++) { jl_ptls_t ptls2 = gc_all_tls_states[t_i]; if (ptls2 != NULL) { - ptls2->page_metadata_allocd = tmp[t_i]; + ptls2->page_metadata_allocd = new_gc_allocd_scratch[t_i].stack; for (int i = 0; i < JL_GC_N_POOLS; i++) { jl_gc_pool_t *p = &ptls2->heap.norm_pools[i]; p->newpages = NULL; @@ -1719,6 +1862,10 @@ static void gc_sweep_pool(void) } } + // cleanup + free(pfl); + free(new_gc_allocd_scratch); + #ifdef _P64 // only enable concurrent sweeping on 64bit // wake thread up to sweep concurrently if (jl_n_sweepthreads > 0) { @@ -1730,6 +1877,7 @@ static void gc_sweep_pool(void) #else gc_free_pages(); #endif + gc_dump_page_utilization_data(); gc_time_pool_end(current_sweep_full); } @@ -1746,20 +1894,21 @@ JL_DLLEXPORT void jl_gc_queue_root(const jl_value_t *ptr) { jl_ptls_t ptls = jl_current_task->ptls; jl_taggedvalue_t *o = jl_astaggedvalue(ptr); - // The modification of the `gc_bits` is not atomic but it - // should be safe here since GC is not allowed to run here and we only - // write GC_OLD to the GC bits outside GC. This could cause - // duplicated objects in the remset but that shouldn't be a problem. - o->bits.gc = GC_MARKED; - arraylist_push(ptls->heap.remset, (jl_value_t*)ptr); - ptls->heap.remset_nptr++; // conservative + // The modification of the `gc_bits` needs to be atomic. + // We need to ensure that objects are in the remset at + // most once, since the mark phase may update page metadata, + // which is not idempotent. See comments in https://github.com/JuliaLang/julia/issues/50419 + uintptr_t header = jl_atomic_load_relaxed((_Atomic(uintptr_t) *)&o->header); + header &= ~GC_OLD; // clear the age bit + header = jl_atomic_exchange_relaxed((_Atomic(uintptr_t) *)&o->header, header); + if (header & GC_OLD) { // write barrier has not been triggered in this object yet + arraylist_push(ptls->heap.remset, (jl_value_t*)ptr); + ptls->heap.remset_nptr++; // conservative + } } -void jl_gc_queue_multiroot(const jl_value_t *parent, const jl_value_t *ptr) JL_NOTSAFEPOINT +void jl_gc_queue_multiroot(const jl_value_t *parent, const void *ptr, jl_datatype_t *dt) JL_NOTSAFEPOINT { - // first check if this is really necessary - // TODO: should we store this info in one of the extra gc bits? - jl_datatype_t *dt = (jl_datatype_t*)jl_typeof(ptr); const jl_datatype_layout_t *ly = dt->layout; uint32_t npointers = ly->npointers; //if (npointers == 0) // this was checked by the caller @@ -1775,14 +1924,14 @@ void jl_gc_queue_multiroot(const jl_value_t *parent, const jl_value_t *ptr) JL_N const uint32_t *ptrs32 = (const uint32_t*)jl_dt_layout_ptrs(ly); for (size_t i = 1; i < npointers; i++) { uint32_t fld; - if (ly->fielddesc_type == 0) { + if (ly->flags.fielddesc_type == 0) { fld = ptrs8[i]; } - else if (ly->fielddesc_type == 1) { + else if (ly->flags.fielddesc_type == 1) { fld = ptrs16[i]; } else { - assert(ly->fielddesc_type == 2); + assert(ly->flags.fielddesc_type == 2); fld = ptrs32[i]; } jl_value_t *ptrf = ((jl_value_t**)ptr)[fld]; @@ -1819,7 +1968,7 @@ STATIC_INLINE uintptr_t gc_read_stack(void *_addr, uintptr_t offset, STATIC_INLINE void gc_assert_parent_validity(jl_value_t *parent, jl_value_t *child) JL_NOTSAFEPOINT { -#ifdef GC_ASSERT_PARENT_VALIDITY +#if defined(GC_VERIFY) || defined(GC_ASSERT_PARENT_VALIDITY) jl_taggedvalue_t *child_astagged = jl_astaggedvalue(child); jl_taggedvalue_t *child_vtag = (jl_taggedvalue_t *)(child_astagged->header & ~(uintptr_t)0xf); uintptr_t child_vt = (uintptr_t)child_vtag; @@ -1872,6 +2021,10 @@ STATIC_INLINE void gc_mark_push_remset(jl_ptls_t ptls, jl_value_t *obj, // Push a work item to the queue STATIC_INLINE void gc_ptr_queue_push(jl_gc_markqueue_t *mq, jl_value_t *obj) JL_NOTSAFEPOINT { +#ifdef JL_DEBUG_BUILD + if (obj == gc_findval) + jl_raise_debugger(); +#endif ws_array_t *old_a = ws_queue_push(&mq->ptr_queue, &obj, sizeof(jl_value_t*)); // Put `old_a` in `reclaim_set` to be freed after the mark phase if (__unlikely(old_a != NULL)) @@ -2058,6 +2211,7 @@ STATIC_INLINE void gc_mark_objarray(jl_ptls_t ptls, jl_value_t *obj_parent, jl_v jl_gc_markqueue_t *mq = &ptls->mark_queue; jl_value_t *new_obj; // Decide whether need to chunk objary + assert(step > 0); (void)jl_assume(step > 0); if ((nptr & 0x2) == 0x2) { // pre-scan this object: most of this object should be old, so look for @@ -2118,14 +2272,14 @@ STATIC_INLINE void gc_mark_objarray(jl_ptls_t ptls, jl_value_t *obj_parent, jl_v } // Mark array with 8bit field descriptors -STATIC_INLINE void gc_mark_array8(jl_ptls_t ptls, jl_value_t *ary8_parent, jl_value_t **ary8_begin, - jl_value_t **ary8_end, uint8_t *elem_begin, uint8_t *elem_end, +STATIC_INLINE void gc_mark_memory8(jl_ptls_t ptls, jl_value_t *ary8_parent, jl_value_t **ary8_begin, + jl_value_t **ary8_end, uint8_t *elem_begin, uint8_t *elem_end, uintptr_t elsize, uintptr_t nptr) JL_NOTSAFEPOINT { jl_gc_markqueue_t *mq = &ptls->mark_queue; jl_value_t *new_obj; - size_t elsize = ((jl_array_t *)ary8_parent)->elsize / sizeof(jl_value_t *); assert(elsize > 0); + (void)jl_assume(elsize > 0); // Decide whether need to chunk objary if ((nptr & 0x2) == 0x2) { // pre-scan this object: most of this object should be old, so look for @@ -2165,7 +2319,7 @@ STATIC_INLINE void gc_mark_array8(jl_ptls_t ptls, jl_value_t *ary8_parent, jl_va // case 2: lowest two bits of `nptr` are already set to 0x3, so won't change after // scanning the array elements if ((nptr & 0x2) != 0x2 || (nptr & 0x3) == 0x3) { - jl_gc_chunk_t c = {GC_ary8_chunk, ary8_parent, scan_end, ary8_end, elem_begin, elem_end, 0, nptr}; + jl_gc_chunk_t c = {GC_ary8_chunk, ary8_parent, scan_end, ary8_end, elem_begin, elem_end, elsize, nptr}; gc_chunkqueue_push(mq, &c); pushed_chunk = 1; } @@ -2185,7 +2339,7 @@ STATIC_INLINE void gc_mark_array8(jl_ptls_t ptls, jl_value_t *ary8_parent, jl_va } if (too_big) { if (!pushed_chunk) { - jl_gc_chunk_t c = {GC_ary8_chunk, ary8_parent, scan_end, ary8_end, elem_begin, elem_end, 0, nptr}; + jl_gc_chunk_t c = {GC_ary8_chunk, ary8_parent, scan_end, ary8_end, elem_begin, elem_end, elsize, nptr}; gc_chunkqueue_push(mq, &c); } } @@ -2195,14 +2349,14 @@ STATIC_INLINE void gc_mark_array8(jl_ptls_t ptls, jl_value_t *ary8_parent, jl_va } // Mark array with 16bit field descriptors -STATIC_INLINE void gc_mark_array16(jl_ptls_t ptls, jl_value_t *ary16_parent, jl_value_t **ary16_begin, - jl_value_t **ary16_end, uint16_t *elem_begin, uint16_t *elem_end, +STATIC_INLINE void gc_mark_memory16(jl_ptls_t ptls, jl_value_t *ary16_parent, jl_value_t **ary16_begin, + jl_value_t **ary16_end, uint16_t *elem_begin, uint16_t *elem_end, size_t elsize, uintptr_t nptr) JL_NOTSAFEPOINT { jl_gc_markqueue_t *mq = &ptls->mark_queue; jl_value_t *new_obj; - size_t elsize = ((jl_array_t *)ary16_parent)->elsize / sizeof(jl_value_t *); assert(elsize > 0); + (void)jl_assume(elsize > 0); // Decide whether need to chunk objary if ((nptr & 0x2) == 0x2) { // pre-scan this object: most of this object should be old, so look for @@ -2281,8 +2435,8 @@ STATIC_INLINE void gc_mark_chunk(jl_ptls_t ptls, jl_gc_markqueue_t *mq, jl_gc_ch jl_value_t **obj_end = c->end; uint32_t step = c->step; uintptr_t nptr = c->nptr; - gc_mark_objarray(ptls, obj_parent, obj_begin, obj_end, step, - nptr); + gc_mark_objarray(ptls, obj_parent, obj_begin, obj_end, + step, nptr); break; } case GC_ary8_chunk: { @@ -2291,9 +2445,10 @@ STATIC_INLINE void gc_mark_chunk(jl_ptls_t ptls, jl_gc_markqueue_t *mq, jl_gc_ch jl_value_t **ary8_end = c->end; uint8_t *elem_begin = (uint8_t *)c->elem_begin; uint8_t *elem_end = (uint8_t *)c->elem_end; + size_t elsize = c->step; uintptr_t nptr = c->nptr; - gc_mark_array8(ptls, ary8_parent, ary8_begin, ary8_end, elem_begin, elem_end, - nptr); + gc_mark_memory8(ptls, ary8_parent, ary8_begin, ary8_end, elem_begin, elem_end, + elsize, nptr); break; } case GC_ary16_chunk: { @@ -2302,15 +2457,17 @@ STATIC_INLINE void gc_mark_chunk(jl_ptls_t ptls, jl_gc_markqueue_t *mq, jl_gc_ch jl_value_t **ary16_end = c->end; uint16_t *elem_begin = (uint16_t *)c->elem_begin; uint16_t *elem_end = (uint16_t *)c->elem_end; + size_t elsize = c->step; uintptr_t nptr = c->nptr; - gc_mark_array16(ptls, ary16_parent, ary16_begin, ary16_end, elem_begin, elem_end, - nptr); + gc_mark_memory16(ptls, ary16_parent, ary16_begin, ary16_end, elem_begin, elem_end, + elsize, nptr); break; } case GC_finlist_chunk: { + jl_value_t *fl_parent = c->parent; jl_value_t **fl_begin = c->begin; jl_value_t **fl_end = c->end; - gc_mark_finlist_(mq, fl_begin, fl_end); + gc_mark_finlist_(mq, fl_parent, fl_begin, fl_end); break; } default: { @@ -2408,6 +2565,7 @@ STATIC_INLINE void gc_mark_module_binding(jl_ptls_t ptls, jl_module_t *mb_parent jl_value_t *bindingkeyset = (jl_value_t *)jl_atomic_load_relaxed(&mb_parent->bindingkeyset); gc_assert_parent_validity((jl_value_t *)mb_parent, bindingkeyset); gc_try_claim_and_push(mq, bindingkeyset, &nptr); + gc_heap_snapshot_record_module_to_binding(mb_parent, bindings, bindingkeyset); gc_assert_parent_validity((jl_value_t *)mb_parent, (jl_value_t *)mb_parent->parent); gc_try_claim_and_push(mq, (jl_value_t *)mb_parent->parent, &nptr); size_t nusings = mb_parent->usings.len; @@ -2426,7 +2584,7 @@ STATIC_INLINE void gc_mark_module_binding(jl_ptls_t ptls, jl_module_t *mb_parent } } -void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t **fl_begin, jl_value_t **fl_end) +void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t *fl_parent, jl_value_t **fl_begin, jl_value_t **fl_end) { jl_value_t *new_obj; // Decide whether need to chunk finlist @@ -2436,8 +2594,10 @@ void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t **fl_begin, jl_value_t * gc_chunkqueue_push(mq, &c); fl_end = fl_begin + GC_CHUNK_BATCH_SIZE; } + size_t i = 0; for (; fl_begin < fl_end; fl_begin++) { - new_obj = *fl_begin; + jl_value_t **slot = fl_begin; + new_obj = *slot; if (__unlikely(new_obj == NULL)) continue; if (gc_ptr_tag(new_obj, 1)) { @@ -2448,6 +2608,13 @@ void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t **fl_begin, jl_value_t * if (gc_ptr_tag(new_obj, 2)) continue; gc_try_claim_and_push(mq, new_obj, NULL); + if (fl_parent != NULL) { + gc_heap_snapshot_record_array_edge(fl_parent, slot); + } else { + // This is a list of objects following the same format as a finlist + // if `fl_parent` is NULL + gc_heap_snapshot_record_finlist(new_obj, ++i); + } } } @@ -2459,7 +2626,7 @@ void gc_mark_finlist(jl_gc_markqueue_t *mq, arraylist_t *list, size_t start) return; jl_value_t **fl_begin = (jl_value_t **)list->items + start; jl_value_t **fl_end = (jl_value_t **)list->items + len; - gc_mark_finlist_(mq, fl_begin, fl_end); + gc_mark_finlist_(mq, NULL, fl_begin, fl_end); } JL_DLLEXPORT int jl_gc_mark_queue_obj(jl_ptls_t ptls, jl_value_t *obj) @@ -2477,18 +2644,13 @@ JL_DLLEXPORT void jl_gc_mark_queue_objarray(jl_ptls_t ptls, jl_value_t *parent, gc_mark_objarray(ptls, parent, objs, objs + nobjs, 1, nptr); } -// Enqueue and mark all outgoing references from `new_obj` which have not been marked -// yet. `meta_updated` is mostly used to make sure we don't update metadata twice for -// objects which have been enqueued into the `remset` -FORCE_INLINE void gc_mark_outrefs(jl_ptls_t ptls, jl_gc_markqueue_t *mq, void *_new_obj, - int meta_updated) +// Enqueue and mark all outgoing references from `new_obj` which have not been marked yet. +// `_new_obj` has its lowest bit tagged if it's in the remset (in which case we shouldn't update page metadata) +FORCE_INLINE void gc_mark_outrefs(jl_ptls_t ptls, jl_gc_markqueue_t *mq, void *_new_obj) { - jl_value_t *new_obj = (jl_value_t *)_new_obj; + int meta_updated = (uintptr_t)_new_obj & GC_REMSET_PTR_TAG; + jl_value_t *new_obj = (jl_value_t *)((uintptr_t)_new_obj & ~(uintptr_t)GC_REMSET_PTR_TAG); mark_obj: { - #ifdef JL_DEBUG_BUILD - if (new_obj == gc_findval) - jl_raise_debugger(); - #endif jl_taggedvalue_t *o = jl_astaggedvalue(new_obj); uintptr_t vtag = o->header & ~(uintptr_t)0xf; uint8_t bits = (gc_old(o->header) && !mark_reset_age) ? GC_OLD_MARKED : GC_MARKED; @@ -2588,7 +2750,7 @@ FORCE_INLINE void gc_mark_outrefs(jl_ptls_t ptls, jl_gc_markqueue_t *mq, void *_ gc_mark_excstack(ptls, excstack, itr); } const jl_datatype_layout_t *layout = jl_task_type->layout; - assert(layout->fielddesc_type == 0); + assert(layout->flags.fielddesc_type == 0); assert(layout->nfields > 0); uint32_t npointers = layout->npointers; char *obj8_parent = (char *)ta; @@ -2627,72 +2789,64 @@ FORCE_INLINE void gc_mark_outrefs(jl_ptls_t ptls, jl_gc_markqueue_t *mq, void *_ gc_dump_queue_and_abort(ptls, vt); } jl_datatype_t *vt = (jl_datatype_t *)vtag; - if (vt->name == jl_array_typename) { - jl_array_t *a = (jl_array_t *)new_obj; - jl_array_flags_t flags = a->flags; + if (vt->name == jl_genericmemory_typename) { + jl_genericmemory_t *m = (jl_genericmemory_t*)new_obj; + int pooled = 1; // The jl_genericmemory_t itself is always pooled-size, even with data attached to it if (update_meta) { - if (flags.pooled) + if (pooled) gc_setmark_pool(ptls, o, bits); else gc_setmark_big(ptls, o, bits); } else if (foreign_alloc) { - objprofile_count(vt, bits == GC_OLD_MARKED, sizeof(jl_array_t)); - } - if (flags.how == 0) { - void *data_ptr = (char*)a + sizeof(jl_array_t) +jl_array_ndimwords(a->flags.ndims) * sizeof(size_t); - gc_heap_snapshot_record_hidden_edge(new_obj, data_ptr, jl_array_nbytes(a), 2); + objprofile_count(vt, bits == GC_OLD_MARKED, sizeof(jl_genericmemory_t)); } - if (flags.how == 1) { - void *val_buf = jl_astaggedvalue((char*)a->data - a->offset * a->elsize); - verify_parent1("array", new_obj, &val_buf, "buffer ('loc' addr is meaningless)"); - gc_heap_snapshot_record_hidden_edge(new_obj, jl_valueof(val_buf), jl_array_nbytes(a), flags.pooled); - (void)val_buf; - gc_setmark_buf_(ptls, (char*)a->data - a->offset * a->elsize, - bits, jl_array_nbytes(a)); + int how = jl_genericmemory_how(m); + if (how == 0 || how == 2) { + gc_heap_snapshot_record_hidden_edge(new_obj, m->ptr, jl_genericmemory_nbytes(m), how == 0 ? 2 : 0); } - else if (flags.how == 2) { + else if (how == 1) { if (update_meta || foreign_alloc) { objprofile_count(jl_malloc_tag, bits == GC_OLD_MARKED, - jl_array_nbytes(a)); - gc_heap_snapshot_record_hidden_edge(new_obj, a->data, jl_array_nbytes(a), flags.pooled); + jl_genericmemory_nbytes(m)); + size_t nb = jl_genericmemory_nbytes(m); + gc_heap_snapshot_record_hidden_edge(new_obj, m->ptr, nb, 0); if (bits == GC_OLD_MARKED) { - ptls->gc_cache.perm_scanned_bytes += jl_array_nbytes(a); + ptls->gc_cache.perm_scanned_bytes += nb; } else { - ptls->gc_cache.scanned_bytes += jl_array_nbytes(a); + ptls->gc_cache.scanned_bytes += nb; } } } - else if (flags.how == 3) { - jl_value_t *owner = jl_array_data_owner(a); + else if (how == 3) { + jl_value_t *owner = jl_genericmemory_data_owner_field(m); uintptr_t nptr = (1 << 2) | (bits & GC_OLD); gc_try_claim_and_push(mq, owner, &nptr); gc_heap_snapshot_record_internal_array_edge(new_obj, owner); gc_mark_push_remset(ptls, new_obj, nptr); return; } - if (!a->data || jl_array_len(a) == 0) + if (m->length == 0) return; - if (flags.ptrarray) { - if ((jl_datatype_t *)jl_tparam0(vt) == jl_symbol_type) + const jl_datatype_layout_t *layout = vt->layout; + if (layout->flags.arrayelem_isboxed) { + if ((jl_datatype_t*)jl_tparam1(vt) == jl_symbol_type) return; - size_t l = jl_array_len(a); jl_value_t *objary_parent = new_obj; - jl_value_t **objary_begin = (jl_value_t **)a->data; - jl_value_t **objary_end = objary_begin + l; + jl_value_t **objary_begin = (jl_value_t **)m->ptr; + jl_value_t **objary_end = objary_begin + m->length; uint32_t step = 1; - uintptr_t nptr = (l << 2) | (bits & GC_OLD); + uintptr_t nptr = (m->length << 2) | (bits & GC_OLD); gc_mark_objarray(ptls, objary_parent, objary_begin, objary_end, step, nptr); } - else if (flags.hasptr) { - jl_datatype_t *et = (jl_datatype_t *)jl_tparam0(vt); - const jl_datatype_layout_t *layout = et->layout; + else if (layout->first_ptr >= 0) { + const jl_datatype_layout_t *layout = vt->layout; unsigned npointers = layout->npointers; - unsigned elsize = a->elsize / sizeof(jl_value_t *); - size_t l = jl_array_len(a); + unsigned elsize = layout->size / sizeof(jl_value_t*); + size_t l = m->length; jl_value_t *objary_parent = new_obj; - jl_value_t **objary_begin = (jl_value_t **)a->data; + jl_value_t **objary_begin = (jl_value_t**)m->ptr; jl_value_t **objary_end = objary_begin + l * elsize; uint32_t step = elsize; uintptr_t nptr = ((l * npointers) << 2) | (bits & GC_OLD); @@ -2700,17 +2854,17 @@ FORCE_INLINE void gc_mark_outrefs(jl_ptls_t ptls, jl_gc_markqueue_t *mq, void *_ objary_begin += layout->first_ptr; gc_mark_objarray(ptls, objary_parent, objary_begin, objary_end, step, nptr); } - else if (layout->fielddesc_type == 0) { - uint8_t *obj8_begin = (uint8_t *)jl_dt_layout_ptrs(layout); + else if (layout->flags.fielddesc_type == 0) { + uint8_t *obj8_begin = (uint8_t*)jl_dt_layout_ptrs(layout); uint8_t *obj8_end = obj8_begin + npointers; - gc_mark_array8(ptls, objary_parent, objary_begin, objary_end, obj8_begin, - obj8_end, nptr); + gc_mark_memory8(ptls, objary_parent, objary_begin, objary_end, obj8_begin, obj8_end, + elsize, nptr); } - else if (layout->fielddesc_type == 1) { - uint16_t *obj16_begin = (uint16_t *)jl_dt_layout_ptrs(layout); + else if (layout->flags.fielddesc_type == 1) { + uint16_t *obj16_begin = (uint16_t*)jl_dt_layout_ptrs(layout); uint16_t *obj16_end = obj16_begin + npointers; - gc_mark_array16(ptls, objary_parent, objary_begin, objary_end, obj16_begin, - obj16_end, nptr); + gc_mark_memory16(ptls, objary_parent, objary_begin, objary_end, obj16_begin, obj16_end, + elsize, nptr); } else { assert(0 && "unimplemented"); @@ -2730,9 +2884,9 @@ FORCE_INLINE void gc_mark_outrefs(jl_ptls_t ptls, jl_gc_markqueue_t *mq, void *_ if (npointers == 0) return; uintptr_t nptr = (npointers << 2 | (bits & GC_OLD)); - assert((layout->nfields > 0 || layout->fielddesc_type == 3) && + assert((layout->nfields > 0 || layout->flags.fielddesc_type == 3) && "opaque types should have been handled specially"); - if (layout->fielddesc_type == 0) { + if (layout->flags.fielddesc_type == 0) { char *obj8_parent = (char *)new_obj; uint8_t *obj8_begin = (uint8_t *)jl_dt_layout_ptrs(layout); uint8_t *obj8_end = obj8_begin + npointers; @@ -2745,7 +2899,7 @@ FORCE_INLINE void gc_mark_outrefs(jl_ptls_t ptls, jl_gc_markqueue_t *mq, void *_ gc_ptr_queue_push(mq, new_obj); } } - else if (layout->fielddesc_type == 1) { + else if (layout->flags.fielddesc_type == 1) { char *obj16_parent = (char *)new_obj; uint16_t *obj16_begin = (uint16_t *)jl_dt_layout_ptrs(layout); uint16_t *obj16_end = obj16_begin + npointers; @@ -2758,7 +2912,7 @@ FORCE_INLINE void gc_mark_outrefs(jl_ptls_t ptls, jl_gc_markqueue_t *mq, void *_ gc_ptr_queue_push(mq, new_obj); } } - else if (layout->fielddesc_type == 2) { + else if (layout->flags.fielddesc_type == 2) { // This is very uncommon // Do not do store to load forwarding to save some code size char *obj32_parent = (char *)new_obj; @@ -2774,7 +2928,7 @@ FORCE_INLINE void gc_mark_outrefs(jl_ptls_t ptls, jl_gc_markqueue_t *mq, void *_ } } else { - assert(layout->fielddesc_type == 3); + assert(layout->flags.fielddesc_type == 3); jl_fielddescdyn_t *desc = (jl_fielddescdyn_t *)jl_dt_layout_fields(layout); int old = jl_astaggedvalue(new_obj)->bits.gc & 2; uintptr_t young = desc->markfunc(ptls, new_obj); @@ -2793,7 +2947,7 @@ void gc_mark_loop_serial_(jl_ptls_t ptls, jl_gc_markqueue_t *mq) if (__unlikely(new_obj == NULL)) { return; } - gc_mark_outrefs(ptls, mq, new_obj, 0); + gc_mark_outrefs(ptls, mq, new_obj); } } @@ -2811,7 +2965,7 @@ void gc_drain_own_chunkqueue(jl_ptls_t ptls, jl_gc_markqueue_t *mq) } // Main mark loop. Stack (allocated on the heap) of `jl_value_t *` -// is used to keep track of processed items. Maintaning this stack (instead of +// is used to keep track of processed items. Maintaining this stack (instead of // native one) avoids stack overflow when marking deep objects and // makes it easier to implement parallel marking via work-stealing JL_EXTENSION NOINLINE void gc_mark_loop_serial(jl_ptls_t ptls) @@ -2822,11 +2976,10 @@ JL_EXTENSION NOINLINE void gc_mark_loop_serial(jl_ptls_t ptls) void gc_mark_and_steal(jl_ptls_t ptls) { - jl_gc_markqueue_t *mq = &ptls->mark_queue; - jl_gc_markqueue_t *mq_master = NULL; int master_tid = jl_atomic_load(&gc_master_tid); - if (master_tid != -1) - mq_master = &gc_all_tls_states[master_tid]->mark_queue; + assert(master_tid != -1); + jl_gc_markqueue_t *mq = &ptls->mark_queue; + jl_gc_markqueue_t *mq_master = &gc_all_tls_states[master_tid]->mark_queue; void *new_obj; jl_gc_chunk_t c; pop : { @@ -2842,11 +2995,11 @@ void gc_mark_and_steal(jl_ptls_t ptls) goto steal; } mark : { - gc_mark_outrefs(ptls, mq, new_obj, 0); + gc_mark_outrefs(ptls, mq, new_obj); goto pop; } // Note that for the stealing heuristics, we try to - // steal chunks much more agressively than pointers, + // steal chunks much more aggressively than pointers, // since we know chunks will likely expand into a lot // of work for the mark loop steal : { @@ -2870,12 +3023,10 @@ void gc_mark_and_steal(jl_ptls_t ptls) } } // Try to steal chunk from master thread - if (mq_master != NULL) { - c = gc_chunkqueue_steal_from(mq_master); - if (c.cid != GC_empty_chunk) { - gc_mark_chunk(ptls, mq, &c); - goto pop; - } + c = gc_chunkqueue_steal_from(mq_master); + if (c.cid != GC_empty_chunk) { + gc_mark_chunk(ptls, mq, &c); + goto pop; } // Try to steal pointer from random GC thread for (int i = 0; i < 4 * jl_n_markthreads; i++) { @@ -2893,36 +3044,103 @@ void gc_mark_and_steal(jl_ptls_t ptls) goto mark; } // Try to steal pointer from master thread - if (mq_master != NULL) { - new_obj = gc_ptr_queue_steal_from(mq_master); - if (new_obj != NULL) - goto mark; + new_obj = gc_ptr_queue_steal_from(mq_master); + if (new_obj != NULL) + goto mark; + } +} + +size_t gc_count_work_in_queue(jl_ptls_t ptls) JL_NOTSAFEPOINT +{ + assert(ptls != NULL); + // assume each chunk is worth 256 units of work and each pointer + // is worth 1 unit of work + size_t work = 256 * (jl_atomic_load_relaxed(&ptls->mark_queue.chunk_queue.bottom) - + jl_atomic_load_relaxed(&ptls->mark_queue.chunk_queue.top)); + work += (jl_atomic_load_relaxed(&ptls->mark_queue.ptr_queue.bottom) - + jl_atomic_load_relaxed(&ptls->mark_queue.ptr_queue.top)); + return work; +} + +/** + * Correctness argument for the mark-loop termination protocol. + * + * Safety properties: + * - No work items shall be in any thread's queues when `gc_should_mark` observes + * that `gc_n_threads_marking` is zero. + * + * - No work item shall be stolen from the master thread (i.e. mutator thread which started + * GC and which helped the `jl_n_markthreads` - 1 threads to mark) after + * `gc_should_mark` observes that `gc_n_threads_marking` is zero. This property is + * necessary because we call `gc_mark_loop_serial` after marking the finalizer list in + * `_jl_gc_collect`, and want to ensure that we have the serial mark-loop semantics there, + * and that no work is stolen from us at that point. + * + * Proof: + * - If a thread observes that `gc_n_threads_marking` is zero inside `gc_should_mark`, that + * means that no thread has work on their queue, this is guaranteed because a thread may only exit + * `gc_mark_and_steal` when its own queue is empty, this information is synchronized by the + * seq-cst fetch_add to a thread that is in `gc_should_mark`. `gc_queue_observer_lock` + * guarantees that once `gc_n_threads_marking` reaches zero, no thread will increment it again, + * because incrementing is only legal from inside the lock. Therefore, no thread will reenter + * the mark-loop after `gc_n_threads_marking` reaches zero. + */ + +int gc_should_mark(void) +{ + int should_mark = 0; + uv_mutex_lock(&gc_queue_observer_lock); + while (1) { + int n_threads_marking = jl_atomic_load(&gc_n_threads_marking); + if (n_threads_marking == 0) { + break; } + int tid = jl_atomic_load_relaxed(&gc_master_tid); + assert(tid != -1); + size_t work = gc_count_work_in_queue(gc_all_tls_states[tid]); + for (tid = gc_first_tid; tid < gc_first_tid + jl_n_markthreads; tid++) { + jl_ptls_t ptls2 = gc_all_tls_states[tid]; + if (ptls2 == NULL) { + continue; + } + work += gc_count_work_in_queue(ptls2); + } + // if there is a lot of work left, enter the mark loop + if (work >= 16 * n_threads_marking) { + jl_atomic_fetch_add(&gc_n_threads_marking, 1); // A possibility would be to allow a thread that found lots + // of work to increment this + should_mark = 1; + break; + } + jl_cpu_pause(); } + uv_mutex_unlock(&gc_queue_observer_lock); + return should_mark; +} + +void gc_wake_all_for_marking(jl_ptls_t ptls) +{ + uv_mutex_lock(&gc_threads_lock); + uv_cond_broadcast(&gc_threads_cond); + uv_mutex_unlock(&gc_threads_lock); } void gc_mark_loop_parallel(jl_ptls_t ptls, int master) { - int backoff = GC_BACKOFF_MIN; if (master) { jl_atomic_store(&gc_master_tid, ptls->tid); - // Wake threads up and try to do some work - uv_mutex_lock(&gc_threads_lock); jl_atomic_fetch_add(&gc_n_threads_marking, 1); - uv_cond_broadcast(&gc_threads_cond); - uv_mutex_unlock(&gc_threads_lock); + gc_wake_all_for_marking(ptls); gc_mark_and_steal(ptls); jl_atomic_fetch_add(&gc_n_threads_marking, -1); } - while (jl_atomic_load(&gc_n_threads_marking) > 0) { - // Try to become a thief while other threads are marking - jl_atomic_fetch_add(&gc_n_threads_marking, 1); - if (jl_atomic_load(&gc_master_tid) != -1) { - gc_mark_and_steal(ptls); + while (1) { + int should_mark = gc_should_mark(); + if (!should_mark) { + break; } + gc_mark_and_steal(ptls); jl_atomic_fetch_add(&gc_n_threads_marking, -1); - // Failed to steal - gc_backoff(&backoff); } } @@ -2938,10 +3156,8 @@ void gc_mark_loop(jl_ptls_t ptls) void gc_mark_loop_barrier(void) { - jl_atomic_store(&gc_master_tid, -1); - while (jl_atomic_load(&gc_n_threads_marking) != 0) { - jl_cpu_pause(); - } + assert(jl_atomic_load_relaxed(&gc_n_threads_marking) == 0); + jl_atomic_store_relaxed(&gc_master_tid, -1); } void gc_mark_clean_reclaim_sets(void) @@ -2949,6 +3165,9 @@ void gc_mark_clean_reclaim_sets(void) // Clean up `reclaim-sets` for (int i = 0; i < gc_n_threads; i++) { jl_ptls_t ptls2 = gc_all_tls_states[i]; + if (ptls2 == NULL) { + continue; + } arraylist_t *reclaim_set2 = &ptls2->mark_queue.reclaim_set; ws_array_t *a = NULL; while ((a = (ws_array_t *)arraylist_pop(reclaim_set2)) != NULL) { @@ -2956,6 +3175,17 @@ void gc_mark_clean_reclaim_sets(void) free(a); } } + // Reset queue indices + for (int i = 0; i < gc_n_threads; i++) { + jl_ptls_t ptls2 = gc_all_tls_states[i]; + if (ptls2 == NULL) { + continue; + } + jl_atomic_store_relaxed(&ptls2->mark_queue.ptr_queue.bottom, 0); + jl_atomic_store_relaxed(&ptls2->mark_queue.ptr_queue.top, 0); + jl_atomic_store_relaxed(&ptls2->mark_queue.chunk_queue.bottom, 0); + jl_atomic_store_relaxed(&ptls2->mark_queue.chunk_queue.top, 0); + } } static void gc_premark(jl_ptls_t ptls2) @@ -3024,9 +3254,9 @@ static void gc_queue_remset(jl_ptls_t ptls, jl_ptls_t ptls2) size_t len = ptls2->heap.last_remset->len; void **items = ptls2->heap.last_remset->items; for (size_t i = 0; i < len; i++) { - // Objects in the `remset` are already marked, - // so a `gc_try_claim_and_push` wouldn't work here - gc_mark_outrefs(ptls, &ptls->mark_queue, (jl_value_t *)items[i], 1); + // Tag the pointer to indicate it's in the remset + jl_value_t *v = (jl_value_t *)((uintptr_t)items[i] | GC_REMSET_PTR_TAG); + gc_ptr_queue_push(&ptls->mark_queue, v); } } @@ -3038,27 +3268,36 @@ static void gc_mark_roots(jl_gc_markqueue_t *mq) { // modules gc_try_claim_and_push(mq, jl_main_module, NULL); - gc_heap_snapshot_record_root((jl_value_t*)jl_main_module, "main_module"); + gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_main_module, "main_module"); // invisible builtin values gc_try_claim_and_push(mq, jl_an_empty_vec_any, NULL); + gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_an_empty_vec_any, "an_empty_vec_any"); gc_try_claim_and_push(mq, jl_module_init_order, NULL); + gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_module_init_order, "module_init_order"); for (size_t i = 0; i < jl_current_modules.size; i += 2) { if (jl_current_modules.table[i + 1] != HT_NOTFOUND) { gc_try_claim_and_push(mq, jl_current_modules.table[i], NULL); - gc_heap_snapshot_record_root((jl_value_t*)jl_current_modules.table[i], "top level module"); + gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_current_modules.table[i], "top level module"); } } gc_try_claim_and_push(mq, jl_anytuple_type_type, NULL); + gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_anytuple_type_type, "anytuple_type_type"); for (size_t i = 0; i < N_CALL_CACHE; i++) { jl_typemap_entry_t *v = jl_atomic_load_relaxed(&call_cache[i]); gc_try_claim_and_push(mq, v, NULL); + gc_heap_snapshot_record_array_edge_index((jl_value_t*)jl_anytuple_type_type, (jl_value_t*)v, i); } - gc_try_claim_and_push(mq, jl_all_methods, NULL); gc_try_claim_and_push(mq, _jl_debug_method_invalidation, NULL); + gc_heap_snapshot_record_gc_roots((jl_value_t*)_jl_debug_method_invalidation, "debug_method_invalidation"); // constants gc_try_claim_and_push(mq, jl_emptytuple_type, NULL); + gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_emptytuple_type, "emptytuple_type"); gc_try_claim_and_push(mq, cmpswap_names, NULL); - gc_try_claim_and_push(mq, jl_global_roots_table, NULL); + gc_heap_snapshot_record_gc_roots((jl_value_t*)cmpswap_names, "cmpswap_names"); + gc_try_claim_and_push(mq, jl_global_roots_list, NULL); + gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_global_roots_list, "global_roots_list"); + gc_try_claim_and_push(mq, jl_global_roots_keyset, NULL); + gc_heap_snapshot_record_gc_roots((jl_value_t*)jl_global_roots_keyset, "global_roots_keyset"); } // find unmarked objects that need to be finalized from the finalizer list "list". @@ -3145,7 +3384,7 @@ JL_DLLEXPORT int jl_gc_is_enabled(void) JL_DLLEXPORT void jl_gc_get_total_bytes(int64_t *bytes) JL_NOTSAFEPOINT { jl_gc_num_t num = gc_num; - combine_thread_gc_counts(&num); + combine_thread_gc_counts(&num, 0); // Sync this logic with `base/util.jl:GC_Diff` *bytes = (num.total_allocd + num.deferred_alloc + num.allocd); } @@ -3158,7 +3397,7 @@ JL_DLLEXPORT uint64_t jl_gc_total_hrtime(void) JL_DLLEXPORT jl_gc_num_t jl_gc_num(void) { jl_gc_num_t num = gc_num; - combine_thread_gc_counts(&num); + combine_thread_gc_counts(&num, 0); return num; } @@ -3190,6 +3429,15 @@ JL_DLLEXPORT int64_t jl_gc_sync_total_bytes(int64_t offset) JL_NOTSAFEPOINT JL_DLLEXPORT int64_t jl_gc_pool_live_bytes(void) { + int n_threads = jl_atomic_load_acquire(&jl_n_threads); + jl_ptls_t *all_tls_states = jl_atomic_load_relaxed(&jl_all_tls_states); + int64_t pool_live_bytes = 0; + for (int i = 0; i < n_threads; i++) { + jl_ptls_t ptls2 = all_tls_states[i]; + if (ptls2 != NULL) { + pool_live_bytes += jl_atomic_load_relaxed(&ptls2->gc_num.pool_live_bytes); + } + } return pool_live_bytes; } @@ -3198,9 +3446,37 @@ JL_DLLEXPORT int64_t jl_gc_live_bytes(void) return live_bytes; } -double jl_gc_smooth(uint64_t old_val, uint64_t new_val, double factor) +uint64_t jl_gc_smooth(uint64_t old_val, uint64_t new_val, double factor) { - return factor * old_val + (1.0-factor) * new_val; + double est = factor * old_val + (1 - factor) * new_val; + if (est <= 1) + return 1; // avoid issues with <= 0 + if (est > (uint64_t)2<<36) + return (uint64_t)2<<36; // avoid overflow + return est; +} + +// an overallocation curve inspired by array allocations +// grows very fast initially, then much slower at large heaps +static uint64_t overallocation(uint64_t old_val, uint64_t val, uint64_t max_val) +{ + // compute maxsize = maxsize + 4*maxsize^(7/8) + maxsize/8 + // for small n, we grow much faster than O(n) + // for large n, we grow at O(n/8) + // and as we reach O(memory) for memory>>1MB, + // this means we end by adding about 10% of memory each time at most + int exp2 = sizeof(old_val) * 8 - +#ifdef _P64 + __builtin_clzll(old_val); +#else + __builtin_clz(old_val); +#endif + uint64_t inc = (uint64_t)((size_t)1 << (exp2 * 7 / 8)) * 4 + old_val / 8; + // once overallocation would exceed max_val, grow by no more than 5% of max_val + if (inc + val > max_val) + if (inc > max_val / 20) + return max_val / 20; + return inc; } size_t jl_maxrss(void); @@ -3208,7 +3484,7 @@ size_t jl_maxrss(void); // Only one thread should be running in this function static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) { - combine_thread_gc_counts(&gc_num); + combine_thread_gc_counts(&gc_num, 1); // We separate the update of the graph from the update of live_bytes here // so that the sweep shows a downward trend in memory usage. @@ -3217,7 +3493,7 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) jl_gc_markqueue_t *mq = &ptls->mark_queue; uint64_t gc_start_time = jl_hrtime(); - uint64_t mutator_time = gc_start_time - gc_end_time; + uint64_t mutator_time = gc_end_time == 0 ? old_mut_time : gc_start_time - gc_end_time; uint64_t before_free_heap_size = jl_atomic_load_relaxed(&gc_heap_stats.heap_size); int64_t last_perm_scanned_bytes = perm_scanned_bytes; uint64_t start_mark_time = jl_hrtime(); @@ -3354,7 +3630,6 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) promoted_bytes = 0; } scanned_bytes = 0; - pool_live_bytes = 0; // 6. start sweeping uint64_t start_sweep_time = jl_hrtime(); JL_PROBE_GC_SWEEP_BEGIN(sweep_full); @@ -3393,55 +3668,102 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) gc_num.last_incremental_sweep = gc_end_time; } - size_t heap_size = jl_atomic_load_relaxed(&gc_heap_stats.heap_size); - double target_allocs = 0.0; - double min_interval = default_collect_interval; + size_t heap_size = jl_atomic_load_relaxed(&gc_heap_stats.heap_size) - freed_in_runtime; + jl_atomic_store_relaxed(&gc_heap_stats.heap_size, heap_size); + freed_in_runtime = 0; + uint64_t user_max = max_total_memory * 0.8; + uint64_t alloc_diff = before_free_heap_size - old_heap_size; + uint64_t freed_diff = before_free_heap_size - heap_size; + uint64_t target_heap; + const char *reason = ""; (void)reason; // for GC_TIME output stats + old_heap_size = heap_size; // TODO: Update these values dynamically instead of just during the GC if (collection == JL_GC_AUTO) { - uint64_t alloc_diff = before_free_heap_size - old_heap_size; - uint64_t freed_diff = before_free_heap_size - heap_size; + // update any heuristics only when the user does not force the GC + // but still update the timings, since GC was run and reset, even if it was too early + uint64_t target_allocs = 0.0; double alloc_smooth_factor = 0.95; double collect_smooth_factor = 0.5; - double tuning_factor = 0.03; - double alloc_mem = jl_gc_smooth(old_alloc_diff, alloc_diff, alloc_smooth_factor); - double alloc_time = jl_gc_smooth(old_mut_time, mutator_time + sweep_time, alloc_smooth_factor); // Charge sweeping to the mutator - double gc_mem = jl_gc_smooth(old_freed_diff, freed_diff, collect_smooth_factor); - double gc_time = jl_gc_smooth(old_pause_time, pause - sweep_time, collect_smooth_factor); - old_alloc_diff = alloc_diff; - old_mut_time = mutator_time; - old_freed_diff = freed_diff; - old_pause_time = pause; - old_heap_size = heap_size; // TODO: Update these values dynamically instead of just during the GC - if (gc_time > alloc_time * 95 && !(thrash_counter < 4)) + double tuning_factor = 2e4; + uint64_t alloc_mem = jl_gc_smooth(old_alloc_diff, alloc_diff, alloc_smooth_factor); + uint64_t alloc_time = jl_gc_smooth(old_mut_time, mutator_time, alloc_smooth_factor); // TODO: subtract estimated finalizer time? + uint64_t gc_mem = jl_gc_smooth(old_freed_diff, freed_diff, collect_smooth_factor); + uint64_t gc_time = jl_gc_smooth(old_pause_time, pause - sweep_time, collect_smooth_factor); + old_alloc_diff = alloc_mem; + old_mut_time = alloc_time; + old_freed_diff = gc_mem; + old_pause_time = gc_time; + // thrashing estimator: if GC time more than 50% of the runtime + if (pause > mutator_time && !(thrash_counter < 4)) thrash_counter += 1; else if (thrash_counter > 0) thrash_counter -= 1; - if (alloc_mem != 0 && alloc_time != 0 && gc_mem != 0 && gc_time != 0 ) { - double alloc_rate = alloc_mem/alloc_time; - double gc_rate = gc_mem/gc_time; - target_allocs = sqrt(((double)heap_size/min_interval * alloc_rate)/(gc_rate * tuning_factor)); // work on multiples of min interval - } - } - if (thrashing == 0 && thrash_counter >= 3) - thrashing = 1; - else if (thrashing == 1 && thrash_counter <= 2) - thrashing = 0; // maybe we should report this to the user or error out? - - int bad_result = (target_allocs*min_interval + heap_size) > 2 * jl_atomic_load_relaxed(&gc_heap_stats.heap_target); // Don't follow through on a bad decision - if (target_allocs == 0.0 || thrashing || bad_result) // If we are thrashing go back to default - target_allocs = 2*sqrt((double)heap_size/min_interval); - uint64_t target_heap = (uint64_t)target_allocs*min_interval + heap_size; - if (target_heap > max_total_memory && !thrashing) // Allow it to go over if we are thrashing if we die we die - target_heap = max_total_memory; - else if (target_heap < default_collect_interval) - target_heap = default_collect_interval; - jl_atomic_store_relaxed(&gc_heap_stats.heap_target, target_heap); + if (alloc_mem != 0 && alloc_time != 0 && gc_mem != 0 && gc_time != 0) { + double alloc_rate = (double)alloc_mem/alloc_time; + double gc_rate = (double)gc_mem/gc_time; + target_allocs = sqrt((double)heap_size * alloc_rate / gc_rate) * tuning_factor; + } + + if (thrashing == 0 && thrash_counter >= 3) { + // require 3 consecutive thrashing cycles to force the default allocator rate + thrashing = 1; + // and require 4 default allocations to clear + thrash_counter = 6; + } + else if (thrashing == 1 && thrash_counter <= 2) { + thrashing = 0; // maybe we should report this to the user or error out? + } + + target_heap = target_allocs + heap_size; + // optionally smooth this: + // target_heap = jl_gc_smooth(jl_atomic_load_relaxed(&gc_heap_stats.heap_target), target_heap, alloc_smooth_factor); + + // compute some guardrails values + uint64_t min_target_allocs = heap_size / 20; // minimum 5% of current heap + if (min_target_allocs < default_collect_interval / 8) // unless the heap is small + min_target_allocs = default_collect_interval / 8; + uint64_t max_target_allocs = overallocation(before_free_heap_size, heap_size, user_max); + if (max_target_allocs < min_target_allocs) + max_target_allocs = min_target_allocs; + // respect max_total_memory first + if (target_heap > user_max) { + target_allocs = heap_size < user_max ? user_max - heap_size : 1; + reason = " user limit"; + } + // If we are thrashing use a default only (an average) for a couple collections + if (thrashing) { + uint64_t thrashing_allocs = sqrt((double)min_target_allocs * max_target_allocs); + if (target_allocs < thrashing_allocs) { + target_allocs = thrashing_allocs; + reason = " thrashing"; + } + } + // then add the guardrails for transient issues + if (target_allocs > max_target_allocs) { + target_allocs = max_target_allocs; + reason = " rate limit max"; + } + else if (target_allocs < min_target_allocs) { + target_allocs = min_target_allocs; + reason = " min limit"; + } + // and set the heap detection threshold + target_heap = target_allocs + heap_size; + if (target_heap < default_collect_interval) { + target_heap = default_collect_interval; + reason = " min heap"; + } + jl_atomic_store_relaxed(&gc_heap_stats.heap_target, target_heap); + } + else { + target_heap = jl_atomic_load_relaxed(&gc_heap_stats.heap_target); + } double old_ratio = (double)promoted_bytes/(double)heap_size; - if (heap_size > max_total_memory * 0.8 || old_ratio > 0.15) + if (heap_size > user_max || old_ratio > 0.15) next_sweep_full = 1; else next_sweep_full = 0; - if (heap_size > max_total_memory * 0.8 || thrashing) + if (heap_size > user_max || thrashing) under_pressure = 1; // sweeping is over // 7. if it is a quick sweep, put back the remembered objects in queued state @@ -3460,6 +3782,24 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) else { ptls2->heap.remset->len = 0; } + // free empty GC state for threads that have exited + if (jl_atomic_load_relaxed(&ptls2->current_task) == NULL && + (ptls->tid < gc_first_tid || ptls2->tid >= gc_first_tid + jl_n_gcthreads)) { + jl_thread_heap_t *heap = &ptls2->heap; + if (heap->weak_refs.len == 0) + small_arraylist_free(&heap->weak_refs); + if (heap->live_tasks.len == 0) + small_arraylist_free(&heap->live_tasks); + if (heap->remset->len == 0) + arraylist_free(heap->remset); + if (heap->last_remset->len == 0) + arraylist_free(heap->last_remset); + if (ptls2->finalizers.len == 0) + arraylist_free(&ptls2->finalizers); + if (ptls2->sweep_objs.len == 0) + arraylist_free(&ptls2->sweep_objs); + gc_move_to_global_page_pool(&ptls2->page_metadata_buffered); + } } #ifdef __GLIBC__ @@ -3480,8 +3820,8 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) gc_num.max_memory = max_memory; } gc_final_pause_end(gc_start_time, gc_end_time); - gc_time_sweep_pause(gc_end_time, allocd, live_bytes, - estimate_freed, sweep_full); + gc_time_sweep_pause(gc_end_time, gc_num.allocd, live_bytes, + gc_num.freed, sweep_full); gc_num.full_sweep += sweep_full; last_live_bytes = live_bytes; live_bytes += -gc_num.freed + gc_num.allocd; @@ -3491,6 +3831,15 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) live_bytes, gc_num.interval, pause, gc_num.time_to_safepoint, gc_num.mark_time, gc_num.sweep_time); + if (collection == JL_GC_AUTO) { + gc_heuristics_summary( + old_alloc_diff, alloc_diff, + old_mut_time, mutator_time, + old_freed_diff, freed_diff, + old_pause_time, pause - sweep_time, + thrash_counter, reason, + heap_size, target_heap); + } prev_sweep_full = sweep_full; gc_num.pause += !recollect; @@ -3515,7 +3864,7 @@ JL_DLLEXPORT void jl_gc_collect(jl_gc_collection_t collection) size_t localbytes = jl_atomic_load_relaxed(&ptls->gc_num.allocd) + gc_num.interval; jl_atomic_store_relaxed(&ptls->gc_num.allocd, -(int64_t)gc_num.interval); static_assert(sizeof(_Atomic(uint64_t)) == sizeof(gc_num.deferred_alloc), ""); - jl_atomic_fetch_add((_Atomic(uint64_t)*)&gc_num.deferred_alloc, localbytes); + jl_atomic_fetch_add_relaxed((_Atomic(uint64_t)*)&gc_num.deferred_alloc, localbytes); return; } jl_gc_debug_print(); @@ -3588,7 +3937,7 @@ JL_DLLEXPORT void jl_gc_collect(jl_gc_collection_t collection) // or wait for finalizers on other threads without dead lock). if (!ptls->finalizers_inhibited && ptls->locks.len == 0) { JL_TIMING(GC, GC_Finalizers); - run_finalizers(ct); + run_finalizers(ct, 0); } JL_PROBE_GC_FINALIZER(); @@ -3669,16 +4018,30 @@ void jl_init_thread_heap(jl_ptls_t ptls) jl_atomic_store_relaxed(&ptls->gc_num.allocd, -(int64_t)gc_num.interval); } +void jl_free_thread_gc_state(jl_ptls_t ptls) +{ + jl_gc_markqueue_t *mq = &ptls->mark_queue; + ws_queue_t *cq = &mq->chunk_queue; + free_ws_array(jl_atomic_load_relaxed(&cq->array)); + jl_atomic_store_relaxed(&cq->array, NULL); + ws_queue_t *q = &mq->ptr_queue; + free_ws_array(jl_atomic_load_relaxed(&q->array)); + jl_atomic_store_relaxed(&q->array, NULL); + arraylist_free(&mq->reclaim_set); +} + // System-wide initializations void jl_gc_init(void) { JL_MUTEX_INIT(&heapsnapshot_lock, "heapsnapshot_lock"); JL_MUTEX_INIT(&finalizers_lock, "finalizers_lock"); + uv_mutex_init(&page_profile_lock); uv_mutex_init(&gc_cache_lock); uv_mutex_init(&gc_perm_lock); uv_mutex_init(&gc_threads_lock); uv_cond_init(&gc_threads_cond); uv_sem_init(&gc_sweep_assists_needed, 0); + uv_mutex_init(&gc_queue_observer_lock); jl_gc_init_page(); jl_gc_debug_init(); @@ -3692,14 +4055,22 @@ void jl_gc_init(void) gc_num.max_pause = 0; gc_num.max_memory = 0; + uint64_t mem_reserve = 250*1024*1024; // LLVM + other libraries need some amount of memory + uint64_t min_heap_size_hint = mem_reserve + 1*1024*1024; + uint64_t hint = jl_options.heap_size_hint; #ifdef _P64 total_mem = uv_get_total_memory(); - uint64_t constrained_mem = uv_get_constrained_memory(); - if (constrained_mem > 0 && constrained_mem < total_mem) - jl_gc_set_max_memory(constrained_mem - 250*1024*1024); // LLVM + other libraries need some amount of memory + if (hint == 0) { + uint64_t constrained_mem = uv_get_constrained_memory(); + if (constrained_mem > 0 && constrained_mem < total_mem) + hint = constrained_mem; + } #endif - if (jl_options.heap_size_hint) - jl_gc_set_max_memory(jl_options.heap_size_hint - 250*1024*1024); + if (hint) { + if (hint < min_heap_size_hint) + hint = min_heap_size_hint; + jl_gc_set_max_memory(hint - mem_reserve); + } t_start = jl_hrtime(); } @@ -3741,13 +4112,7 @@ JL_DLLEXPORT void *jl_gc_counted_malloc(size_t sz) jl_atomic_load_relaxed(&ptls->gc_num.allocd) + sz); jl_atomic_store_relaxed(&ptls->gc_num.malloc, jl_atomic_load_relaxed(&ptls->gc_num.malloc) + 1); - uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc); - if (alloc_acc + sz < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, alloc_acc + sz); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, alloc_acc + sz); - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); - } + jl_batch_accum_heap_size(ptls, sz); } return data; } @@ -3764,13 +4129,7 @@ JL_DLLEXPORT void *jl_gc_counted_calloc(size_t nm, size_t sz) jl_atomic_load_relaxed(&ptls->gc_num.allocd) + nm*sz); jl_atomic_store_relaxed(&ptls->gc_num.malloc, jl_atomic_load_relaxed(&ptls->gc_num.malloc) + 1); - uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc); - if (alloc_acc + sz < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, alloc_acc + sz * nm); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, alloc_acc + sz * nm); - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); - } + jl_batch_accum_heap_size(ptls, sz * nm); } return data; } @@ -3781,14 +4140,7 @@ JL_DLLEXPORT void jl_gc_counted_free_with_size(void *p, size_t sz) jl_task_t *ct = jl_current_task; free(p); if (pgcstack != NULL && ct->world_age) { - jl_ptls_t ptls = ct->ptls; - uint64_t free_acc = jl_atomic_load_relaxed(&ptls->gc_num.free_acc); - if (free_acc + sz < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.free_acc, free_acc + sz); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, -(free_acc + sz)); - jl_atomic_store_relaxed(&ptls->gc_num.free_acc, 0); - } + jl_batch_accum_free_size(ct->ptls, sz); } } @@ -3808,23 +4160,10 @@ JL_DLLEXPORT void *jl_gc_counted_realloc_with_old_size(void *p, size_t old, size int64_t diff = sz - old; if (diff < 0) { - diff = -diff; - uint64_t free_acc = jl_atomic_load_relaxed(&ptls->gc_num.free_acc); - if (free_acc + diff < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.free_acc, free_acc + diff); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, -(free_acc + diff)); - jl_atomic_store_relaxed(&ptls->gc_num.free_acc, 0); - } + jl_batch_accum_free_size(ptls, -diff); } else { - uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc); - if (alloc_acc + diff < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, alloc_acc + diff); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, alloc_acc + diff); - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); - } + jl_batch_accum_heap_size(ptls, diff); } } return data; @@ -3909,13 +4248,7 @@ JL_DLLEXPORT void *jl_gc_managed_malloc(size_t sz) jl_atomic_load_relaxed(&ptls->gc_num.allocd) + allocsz); jl_atomic_store_relaxed(&ptls->gc_num.malloc, jl_atomic_load_relaxed(&ptls->gc_num.malloc) + 1); - uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc); - if (alloc_acc + allocsz < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, alloc_acc + allocsz); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, alloc_acc + allocsz); - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); - } + jl_batch_accum_heap_size(ptls, allocsz); #ifdef _OS_WINDOWS_ SetLastError(last_error); #endif @@ -3963,23 +4296,10 @@ static void *gc_managed_realloc_(jl_ptls_t ptls, void *d, size_t sz, size_t olds int64_t diff = allocsz - oldsz; if (diff < 0) { - diff = -diff; - uint64_t free_acc = jl_atomic_load_relaxed(&ptls->gc_num.free_acc); - if (free_acc + diff < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.free_acc, free_acc + diff); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, -(free_acc + diff)); - jl_atomic_store_relaxed(&ptls->gc_num.free_acc, 0); - } + jl_batch_accum_free_size(ptls, -diff); } else { - uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc); - if (alloc_acc + diff < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, alloc_acc + diff); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, alloc_acc + diff); - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); - } + jl_batch_accum_heap_size(ptls, diff); } if (allocsz > oldsz) { maybe_record_alloc_to_profile((jl_value_t*)b, allocsz - oldsz, (jl_datatype_t*)jl_buff_tag); @@ -4060,8 +4380,7 @@ static void *gc_perm_alloc_large(size_t sz, int zero, unsigned align, unsigned o errno = last_errno; jl_may_leak(base); assert(align > 0); - unsigned diff = (offset - (uintptr_t)base) % align; - return (void*)((char*)base + diff); + return (void*)(LLT_ALIGN((uintptr_t)base + offset, (uintptr_t)align) - offset); } STATIC_INLINE void *gc_try_perm_alloc_pool(size_t sz, unsigned align, unsigned offset) JL_NOTSAFEPOINT diff --git a/src/gc.h b/src/gc.h index 7985a3ddd43ef..f27fa9954eae2 100644 --- a/src/gc.h +++ b/src/gc.h @@ -33,8 +33,12 @@ extern "C" { #endif +#ifdef GC_SMALL_PAGE +#define GC_PAGE_LG2 12 // log2(size of a page) +#else #define GC_PAGE_LG2 14 // log2(size of a page) -#define GC_PAGE_SZ (1 << GC_PAGE_LG2) // 16k +#endif +#define GC_PAGE_SZ (1 << GC_PAGE_LG2) #define GC_PAGE_OFFSET (JL_HEAP_ALIGNMENT - (sizeof(jl_taggedvalue_t) % JL_HEAP_ALIGNMENT)) #define jl_malloc_tag ((void*)0xdeadaa01) @@ -114,6 +118,8 @@ typedef struct _jl_gc_chunk_t { #define GC_PTR_QUEUE_INIT_SIZE (1 << 18) // initial size of queue of `jl_value_t *` #define GC_CHUNK_QUEUE_INIT_SIZE (1 << 14) // initial size of chunk-queue +#define GC_REMSET_PTR_TAG (0x1) // lowest bit of `jl_value_t *` is tagged if it's in the remset + // layout for big (>2k) objects JL_EXTENSION typedef struct _bigval_t { @@ -137,10 +143,10 @@ JL_EXTENSION typedef struct _bigval_t { // must be 64-byte aligned here, in 32 & 64 bit modes } bigval_t; -// data structure for tracking malloc'd arrays. +// data structure for tracking malloc'd arrays and genericmemory. typedef struct _mallocarray_t { - jl_array_t *a; + jl_value_t *a; struct _mallocarray_t *next; } mallocarray_t; @@ -186,28 +192,32 @@ extern jl_gc_page_stack_t global_page_pool_lazily_freed; extern jl_gc_page_stack_t global_page_pool_clean; extern jl_gc_page_stack_t global_page_pool_freed; -#define GC_BACKOFF_MIN 4 -#define GC_BACKOFF_MAX 12 - -STATIC_INLINE void gc_backoff(int *i) JL_NOTSAFEPOINT -{ - if (*i < GC_BACKOFF_MAX) { - (*i)++; - } - for (int j = 0; j < (1 << *i); j++) { - jl_cpu_pause(); - } -} - // Lock-free stack implementation taken // from Herlihy's "The Art of Multiprocessor Programming" // XXX: this is not a general-purpose lock-free stack. We can // get away with just using a CAS and not implementing some ABA // prevention mechanism since once a node is popped from the -// `jl_gc_global_page_pool_t`, it may only be pushed back to them +// `jl_gc_page_stack_t`, it may only be pushed back to them // in the sweeping phase, which also doesn't push a node into the // same stack after it's popped +STATIC_INLINE void push_lf_back_nosync(jl_gc_page_stack_t *pool, jl_gc_pagemeta_t *elt) JL_NOTSAFEPOINT +{ + jl_gc_pagemeta_t *old_back = jl_atomic_load_relaxed(&pool->bottom); + elt->next = old_back; + jl_atomic_store_relaxed(&pool->bottom, elt); +} + +STATIC_INLINE jl_gc_pagemeta_t *pop_lf_back_nosync(jl_gc_page_stack_t *pool) JL_NOTSAFEPOINT +{ + jl_gc_pagemeta_t *old_back = jl_atomic_load_relaxed(&pool->bottom); + if (old_back == NULL) { + return NULL; + } + jl_atomic_store_relaxed(&pool->bottom, old_back->next); + return old_back; +} + STATIC_INLINE void push_lf_back(jl_gc_page_stack_t *pool, jl_gc_pagemeta_t *elt) JL_NOTSAFEPOINT { while (1) { @@ -220,6 +230,23 @@ STATIC_INLINE void push_lf_back(jl_gc_page_stack_t *pool, jl_gc_pagemeta_t *elt) } } +#define MAX_POP_ATTEMPTS (1 << 10) + +STATIC_INLINE jl_gc_pagemeta_t *try_pop_lf_back(jl_gc_page_stack_t *pool) JL_NOTSAFEPOINT +{ + for (int i = 0; i < MAX_POP_ATTEMPTS; i++) { + jl_gc_pagemeta_t *old_back = jl_atomic_load_relaxed(&pool->bottom); + if (old_back == NULL) { + return NULL; + } + if (jl_atomic_cmpswap(&pool->bottom, &old_back, old_back->next)) { + return old_back; + } + jl_cpu_pause(); + } + return NULL; +} + STATIC_INLINE jl_gc_pagemeta_t *pop_lf_back(jl_gc_page_stack_t *pool) JL_NOTSAFEPOINT { while (1) { @@ -233,7 +260,39 @@ STATIC_INLINE jl_gc_pagemeta_t *pop_lf_back(jl_gc_page_stack_t *pool) JL_NOTSAFE jl_cpu_pause(); } } +typedef struct { + jl_gc_page_stack_t stack; + // pad to 128 bytes to avoid false-sharing +#ifdef _P64 + void *_pad[15]; +#else + void *_pad[31]; +#endif +} jl_gc_padded_page_stack_t; +static_assert(sizeof(jl_gc_padded_page_stack_t) == 128, "jl_gc_padded_page_stack_t is not 128 bytes"); + +typedef struct { + _Atomic(size_t) n_freed_objs; + _Atomic(size_t) n_pages_allocd; +} gc_fragmentation_stat_t; +#ifdef GC_SMALL_PAGE +#ifdef _P64 +#define REGION0_PG_COUNT (1 << 16) +#define REGION1_PG_COUNT (1 << 18) +#define REGION2_PG_COUNT (1 << 18) +#define REGION0_INDEX(p) (((uintptr_t)(p) >> 12) & 0xFFFF) // shift by GC_PAGE_LG2 +#define REGION1_INDEX(p) (((uintptr_t)(p) >> 28) & 0x3FFFF) +#define REGION_INDEX(p) (((uintptr_t)(p) >> 46) & 0x3FFFF) +#else +#define REGION0_PG_COUNT (1 << 10) +#define REGION1_PG_COUNT (1 << 10) +#define REGION2_PG_COUNT (1 << 0) +#define REGION0_INDEX(p) (((uintptr_t)(p) >> 12) & 0x3FF) // shift by GC_PAGE_LG2 +#define REGION1_INDEX(p) (((uintptr_t)(p) >> 22) & 0x3FF) +#define REGION_INDEX(p) (0) +#endif +#else #ifdef _P64 #define REGION0_PG_COUNT (1 << 16) #define REGION1_PG_COUNT (1 << 16) @@ -249,6 +308,7 @@ STATIC_INLINE jl_gc_pagemeta_t *pop_lf_back(jl_gc_page_stack_t *pool) JL_NOTSAFE #define REGION1_INDEX(p) (((uintptr_t)(p) >> 22) & 0x3FF) #define REGION_INDEX(p) (0) #endif +#endif // define the representation of the levels of the page-table (0 to 2) typedef struct { @@ -454,12 +514,12 @@ extern uv_sem_t gc_sweep_assists_needed; extern _Atomic(int) gc_n_threads_marking; extern _Atomic(int) gc_n_threads_sweeping; void gc_mark_queue_all_roots(jl_ptls_t ptls, jl_gc_markqueue_t *mq); -void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t **fl_begin, jl_value_t **fl_end) JL_NOTSAFEPOINT; +void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t *fl_parent, jl_value_t **fl_begin, jl_value_t **fl_end) JL_NOTSAFEPOINT; void gc_mark_finlist(jl_gc_markqueue_t *mq, arraylist_t *list, size_t start) JL_NOTSAFEPOINT; void gc_mark_loop_serial_(jl_ptls_t ptls, jl_gc_markqueue_t *mq); void gc_mark_loop_serial(jl_ptls_t ptls); void gc_mark_loop_parallel(jl_ptls_t ptls, int master); -void gc_sweep_pool_parallel(void); +void gc_sweep_pool_parallel(jl_ptls_t ptls); void gc_free_pages(void); void sweep_stack_pools(void); void jl_gc_debug_init(void); @@ -498,9 +558,9 @@ void gc_time_big_start(void) JL_NOTSAFEPOINT; void gc_time_count_big(int old_bits, int bits) JL_NOTSAFEPOINT; void gc_time_big_end(void) JL_NOTSAFEPOINT; -void gc_time_mallocd_array_start(void) JL_NOTSAFEPOINT; -void gc_time_count_mallocd_array(int bits) JL_NOTSAFEPOINT; -void gc_time_mallocd_array_end(void) JL_NOTSAFEPOINT; +void gc_time_mallocd_memory_start(void) JL_NOTSAFEPOINT; +void gc_time_count_mallocd_memory(int bits) JL_NOTSAFEPOINT; +void gc_time_mallocd_memory_end(void) JL_NOTSAFEPOINT; void gc_time_mark_pause(int64_t t0, int64_t scanned_bytes, int64_t perm_scanned_bytes); @@ -511,6 +571,13 @@ void gc_time_summary(int sweep_full, uint64_t start, uint64_t end, uint64_t freed, uint64_t live, uint64_t interval, uint64_t pause, uint64_t ttsp, uint64_t mark, uint64_t sweep); +void gc_heuristics_summary( + uint64_t old_alloc_diff, uint64_t alloc_mem, + uint64_t old_mut_time, uint64_t alloc_time, + uint64_t old_freed_diff, uint64_t gc_mem, + uint64_t old_pause_time, uint64_t gc_time, + int thrash_counter, const char *reason, + uint64_t current_heap, uint64_t target_heap); #else #define gc_time_pool_start() STATIC_INLINE void gc_time_count_page(int freedall, int pg_skpd) JL_NOTSAFEPOINT @@ -527,17 +594,24 @@ STATIC_INLINE void gc_time_count_big(int old_bits, int bits) JL_NOTSAFEPOINT (void)bits; } #define gc_time_big_end() -#define gc_time_mallocd_array_start() -STATIC_INLINE void gc_time_count_mallocd_array(int bits) JL_NOTSAFEPOINT +#define gc_time_mallocd_memory_start() +STATIC_INLINE void gc_time_count_mallocd_memory(int bits) JL_NOTSAFEPOINT { (void)bits; } -#define gc_time_mallocd_array_end() +#define gc_time_mallocd_memory_end() #define gc_time_mark_pause(t0, scanned_bytes, perm_scanned_bytes) #define gc_time_sweep_pause(gc_end_t, actual_allocd, live_bytes, \ estimate_freed, sweep_full) #define gc_time_summary(sweep_full, start, end, freed, live, \ interval, pause, ttsp, mark, sweep) +#define gc_heuristics_summary( \ + old_alloc_diff, alloc_mem, \ + old_mut_time, alloc_time, \ + old_freed_diff, gc_mem, \ + old_pause_time, gc_time, \ + thrash_counter, reason, \ + current_heap, target_heap) #endif #ifdef MEMFENCE @@ -651,9 +725,10 @@ void gc_stats_big_obj(void); // For debugging void gc_count_pool(void); -size_t jl_array_nbytes(jl_array_t *a) JL_NOTSAFEPOINT; +size_t jl_genericmemory_nbytes(jl_genericmemory_t *a) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_enable_gc_logging(int enable); +JL_DLLEXPORT int jl_is_gc_logging_enabled(void); JL_DLLEXPORT uint32_t jl_get_num_stack_mappings(void); void _report_gc_finished(uint64_t pause, uint64_t freed, int full, int recollect, int64_t live_bytes) JL_NOTSAFEPOINT; diff --git a/src/gen_sysimg_symtab.jl b/src/gen_sysimg_symtab.jl index 8f03cc1560767..a91f2f994194c 100644 --- a/src/gen_sysimg_symtab.jl +++ b/src/gen_sysimg_symtab.jl @@ -15,12 +15,6 @@ function _eachmethod(f, m::Module, visited, vmt) x = getfield(m, nm) if isa(x, Module) && !in(x, visited) _eachmethod(f, x, visited, vmt) - elseif isa(x, Function) - mt = typeof(x).name.mt - if !in(mt, vmt) - push!(vmt, mt) - Base.visit(f, mt) - end elseif isa(x, Type) x = Base.unwrap_unionall(x) if isa(x, DataType) && isdefined(x.name, :mt) @@ -69,5 +63,5 @@ function outputline(io, name) println(io, "jl_symbol(\"", name, "\"),") end -open(f->foreach(l->outputline(f,l), take(syms, 100)), "common_symbols1.inc", "w") -open(f->foreach(l->outputline(f,l), take(drop(syms, 100), 254)), "common_symbols2.inc", "w") +open(f->foreach(l->outputline(f,l), take(syms, 94)), "common_symbols1.inc", "w") +open(f->foreach(l->outputline(f,l), take(drop(syms, 94), 254)), "common_symbols2.inc", "w") diff --git a/src/genericmemory.c b/src/genericmemory.c new file mode 100644 index 0000000000000..f0e7b695f1122 --- /dev/null +++ b/src/genericmemory.c @@ -0,0 +1,662 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +/* + GenericMemory{kind, T} constructors and primitives +*/ +#include +#include +#ifdef _OS_WINDOWS_ +#include +#endif +#include "julia.h" +#include "julia_internal.h" +#include "julia_assert.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// genericmemory constructors --------------------------------------------------------- +JL_DLLEXPORT char *jl_genericmemory_typetagdata(jl_genericmemory_t *m) JL_NOTSAFEPOINT +{ + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(m))->layout; + assert(layout->flags.arrayelem_isunion); + return (char*)m->ptr + m->length * layout->size; +} + +#if defined(_P64) && defined(UINT128MAX) +typedef __uint128_t wideint_t; +#else +typedef uint64_t wideint_t; +#endif + +#define MAXINTVAL (((size_t)-1)>>1) + +jl_genericmemory_t *_new_genericmemory_(jl_value_t *mtype, size_t nel, int8_t isunion, int8_t zeroinit, size_t elsz) +{ + jl_task_t *ct = jl_current_task; + char *data; + jl_genericmemory_t *m; + if (nel == 0) // zero-sized allocation optimization + return (jl_genericmemory_t*)((jl_datatype_t*)mtype)->instance; + wideint_t prod = (wideint_t)nel * elsz; + if (isunion) { + // an extra byte for each isbits union memory element, stored at m->ptr + m->length + prod += nel; + } + if (nel >= MAXINTVAL || prod >= (wideint_t) MAXINTVAL) + jl_exceptionf(jl_argumenterror_type, "invalid GenericMemory size: too large for system address width"); + size_t tot = (size_t)prod + LLT_ALIGN(sizeof(jl_genericmemory_t),JL_SMALL_BYTE_ALIGNMENT); + + int pooled = tot <= GC_MAX_SZCLASS; + if (!pooled) { + data = (char*)jl_gc_managed_malloc(prod); + tot = sizeof(jl_genericmemory_t) + sizeof(void*); + } + m = (jl_genericmemory_t*)jl_gc_alloc(ct->ptls, tot, mtype); + if (pooled) { + data = (char*)m + JL_SMALL_BYTE_ALIGNMENT; + } + else { + int isaligned = 1; // jl_gc_managed_malloc is always aligned + jl_gc_track_malloced_genericmemory(ct->ptls, m, isaligned); + jl_genericmemory_data_owner_field(m) = (jl_value_t*)m; + } + m->length = nel; + m->ptr = data; + + if (zeroinit) + memset(data, 0, (size_t)prod); + return m; +} + +JL_DLLEXPORT jl_genericmemory_t *jl_alloc_genericmemory(jl_value_t *mtype, size_t nel) +{ + assert(jl_is_datatype(mtype)); + jl_genericmemory_t *m = (jl_genericmemory_t*)((jl_datatype_t*)mtype)->instance; + const jl_datatype_layout_t *layout = ((jl_datatype_t*)mtype)->layout; + if (m == NULL) { + jl_value_t *kind = jl_tparam0((jl_datatype_t*)mtype); + if (kind != (jl_value_t*)jl_not_atomic_sym && kind != (jl_value_t*)jl_atomic_sym) + jl_error("GenericMemory kind must be :not_atomic or :atomic"); + jl_value_t *addrspace = jl_tparam2((jl_datatype_t*)mtype); + if (!jl_is_addrspacecore(addrspace) || jl_unbox_uint8(addrspace) != 0) + jl_error("GenericMemory addrspace must be Core.CPU"); + if (!((jl_datatype_t*)mtype)->has_concrete_subtype || layout == NULL) + jl_type_error_rt("GenericMemory", "element type", (jl_value_t*)jl_type_type, jl_tparam1(mtype)); + abort(); // this is checked already by jl_get_genericmemory_layout + } + assert(((jl_datatype_t*)mtype)->has_concrete_subtype && layout != NULL); + if (nel == 0) // zero-sized allocation optimization fast path + return m; + + size_t elsz = layout->size; + int isboxed = layout->flags.arrayelem_isboxed; + int isunion = layout->flags.arrayelem_isunion; + int zi = ((jl_datatype_t*)mtype)->zeroinit; + if (isboxed) + elsz = sizeof(void*); + return _new_genericmemory_(mtype, nel, isunion, zi, elsz); +} + +JL_DLLEXPORT jl_genericmemory_t *jl_string_to_genericmemory(jl_value_t *str) +{ + if (jl_string_len(str) == 0) + return (jl_genericmemory_t*)((jl_datatype_t*)jl_memory_uint8_type)->instance; + jl_task_t *ct = jl_current_task; + int tsz = sizeof(jl_genericmemory_t) + sizeof(void*); + jl_genericmemory_t *m = (jl_genericmemory_t*)jl_gc_alloc(ct->ptls, tsz, jl_memory_uint8_type); + m->length = jl_string_len(str); + m->ptr = jl_string_data(str); + jl_genericmemory_data_owner_field(m) = str; + return m; +} + +// own_buffer != 0 iff GC should call free() on this pointer eventually +JL_DLLEXPORT jl_genericmemory_t *jl_ptr_to_genericmemory(jl_value_t *mtype, void *data, + size_t nel, int own_buffer) +{ + jl_task_t *ct = jl_current_task; + assert(jl_is_datatype(mtype)); + jl_genericmemory_t *m = (jl_genericmemory_t*)((jl_datatype_t*)mtype)->instance; + const jl_datatype_layout_t *layout = ((jl_datatype_t*)mtype)->layout; + if (m == NULL) { + jl_value_t *kind = jl_tparam0((jl_datatype_t*)mtype); + if (kind != (jl_value_t*)jl_not_atomic_sym && kind != (jl_value_t*)jl_atomic_sym) + jl_error("GenericMemory kind must be :not_atomic or :atomic"); + jl_value_t *addrspace = jl_tparam2((jl_datatype_t*)mtype); + if (!jl_is_addrspacecore(addrspace) || jl_unbox_uint8(addrspace) != 0) + jl_error("GenericMemory addrspace must be Core.CPU"); + if (!((jl_datatype_t*)mtype)->has_concrete_subtype || layout == NULL) + jl_type_error_rt("GenericMemory", "element type", (jl_value_t*)jl_type_type, jl_tparam1(mtype)); + abort(); + } + assert(((jl_datatype_t*)mtype)->has_concrete_subtype && layout != NULL); + //if (nel == 0) {// zero-sized allocation optimization fast path + // if (own_buffer) + // free(data); + // return m; + //} + + size_t elsz = layout->size; + size_t align = layout->alignment; + int isboxed = layout->flags.arrayelem_isboxed; + int isunion = layout->flags.arrayelem_isunion; + if (isboxed) + elsz = sizeof(void*); + if (isunion) + jl_exceptionf(jl_argumenterror_type, + "unsafe_wrap: unspecified layout for union element type"); + if (((uintptr_t)data) & ((align > JL_HEAP_ALIGNMENT ? JL_HEAP_ALIGNMENT : align) - 1)) + jl_exceptionf(jl_argumenterror_type, + "unsafe_wrap: pointer %p is not properly aligned to %u bytes", data, align); + wideint_t prod = (wideint_t)nel * elsz; + if (isunion) { + // an extra byte for each isbits union memory element, stored at m->ptr + m->length + prod += nel; + } + if (nel >= MAXINTVAL || prod >= (wideint_t) MAXINTVAL) + jl_exceptionf(jl_argumenterror_type, "invalid GenericMemory size: too large for system address width"); + int tsz = sizeof(jl_genericmemory_t) + sizeof(void*); + m = (jl_genericmemory_t*)jl_gc_alloc(ct->ptls, tsz, mtype); + m->ptr = data; + m->length = nel; + jl_genericmemory_data_owner_field(m) = NULL; + int isaligned = 0; // TODO: allow passing memalign'd buffers + if (own_buffer) { + jl_gc_track_malloced_genericmemory(ct->ptls, m, isaligned); + jl_gc_count_allocd(nel*elsz); + } + return m; +} + +JL_DLLEXPORT jl_genericmemory_t *jl_new_genericmemory(jl_value_t *mtype, jl_value_t *nel) +{ + return jl_alloc_genericmemory(mtype, jl_unbox_long(nel)); +} + +JL_DLLEXPORT jl_genericmemory_t *jl_pchar_to_genericmemory(const char *str, size_t len) +{ + jl_genericmemory_t *m = jl_alloc_genericmemory(jl_memory_uint8_type, len); + memcpy(m->ptr, str, len); + return m; +} + +JL_DLLEXPORT jl_value_t *jl_genericmemory_to_string(jl_genericmemory_t *m, size_t len) +{ + assert(len <= m->length); + if (len == 0) { + // this may seem like purely an optimization (which it also is), but it + // also ensures that calling `String(m)` doesn't corrupt a previous + // string also created the same way, where `m = StringVector(_)`. + return jl_an_empty_string; + } + int how = jl_genericmemory_how(m); + size_t mlength = m->length; + m->length = 0; + if (how != 0) { + jl_value_t *o = jl_genericmemory_data_owner_field(m); + jl_genericmemory_data_owner_field(m) = NULL; + if (how == 3 && + ((mlength + sizeof(void*) + 1 <= GC_MAX_SZCLASS) == (len + sizeof(void*) + 1 <= GC_MAX_SZCLASS))) { + if (jl_string_data(o)[len] != '\0') + jl_string_data(o)[len] = '\0'; + if (*(size_t*)o != len) + *(size_t*)o = len; + return o; + } + JL_GC_PUSH1(&o); + jl_value_t *str = jl_pchar_to_string((const char*)m->ptr, len); + JL_GC_POP(); + return str; + } + return jl_pchar_to_string((const char*)m->ptr, len); +} + +JL_DLLEXPORT jl_genericmemory_t *jl_alloc_memory_any(size_t n) +{ + return jl_alloc_genericmemory(jl_memory_any_type, n); +} + +JL_DLLEXPORT jl_genericmemory_t *jl_genericmemory_slice(jl_genericmemory_t *mem, void *data, size_t len) +{ + // Given a GenericMemoryRef represented as `jl_genericmemory_ref ref = {data, mem}`, + // return a new GenericMemory that only accesses the slice from the given GenericMemoryRef to + // the given length if this is possible to return. This allows us to make + // `length(Array)==length(Array.ref.mem)`, for simplification of this. + jl_datatype_t *dt = (jl_datatype_t*)jl_typetagof(mem); + const jl_datatype_layout_t *layout = dt->layout; + // repeated checks here ensure the values cannot overflow, since we know mem->length is a reasonable value + if (len > mem->length) + jl_exceptionf(jl_argumenterror_type, "invalid GenericMemory slice"); // TODO: make a BoundsError + if (layout->flags.arrayelem_isunion) { + if (!((size_t)data == 0 && mem->length == len)) + jl_exceptionf(jl_argumenterror_type, "invalid GenericMemory slice"); // only exact slices are supported + data = mem->ptr; + } + else if (layout->size == 0) { + if ((size_t)data > mem->length || (size_t)data + len > mem->length) + jl_exceptionf(jl_argumenterror_type, "invalid GenericMemory slice"); // TODO: make a BoundsError + data = mem->ptr; + } + else { + if (data < mem->ptr || (char*)data > (char*)mem->ptr + mem->length * layout->size || (char*)data + len * layout->size > (char*)mem->ptr + mem->length * layout->size) + jl_exceptionf(jl_argumenterror_type, "invalid GenericMemory slice"); // TODO: make a BoundsError + } + jl_task_t *ct = jl_current_task; + jl_genericmemory_t *newmem = (jl_genericmemory_t*)jl_gc_alloc(ct->ptls, sizeof(jl_genericmemory_t) + sizeof(void*), dt); + newmem->length = len; + newmem->ptr = data; + jl_genericmemory_data_owner_field(newmem) = jl_genericmemory_owner(mem); + return newmem; +} + +JL_DLLEXPORT void jl_genericmemory_copyto(jl_genericmemory_t *dest, char* destdata, + jl_genericmemory_t *src, char* srcdata, + size_t n) JL_NOTSAFEPOINT +{ + jl_datatype_t *dt = (jl_datatype_t*)jl_typetagof(dest); + if (dt != (jl_datatype_t*)jl_typetagof(src)) + jl_exceptionf(jl_argumenterror_type, "jl_genericmemory_copyto requires source and dest to have same type"); + const jl_datatype_layout_t *layout = dt->layout; + if (layout->flags.arrayelem_isboxed) { + _Atomic(void*) * dest_p = (_Atomic(void*)*)destdata; + _Atomic(void*) * src_p = (_Atomic(void*)*)srcdata; + jl_value_t *owner = jl_genericmemory_owner(dest); + if (__unlikely(jl_astaggedvalue(owner)->bits.gc == GC_OLD_MARKED)) { + jl_value_t *src_owner = jl_genericmemory_owner(src); + ssize_t done = 0; + if (jl_astaggedvalue(src_owner)->bits.gc != GC_OLD_MARKED) { + if (dest_p < src_p || dest_p > src_p + n) { + for (; done < n; done++) { // copy forwards + void *val = jl_atomic_load_relaxed(src_p + done); + jl_atomic_store_release(dest_p + done, val); + // `val` is young or old-unmarked + if (val && !(jl_astaggedvalue(val)->bits.gc & GC_MARKED)) { + jl_gc_queue_root(owner); + break; + } + } + src_p += done; + dest_p += done; + } else { + for (; done < n; done++) { // copy backwards + void *val = jl_atomic_load_relaxed(src_p + n - done - 1); + jl_atomic_store_release(dest_p + n - done - 1, val); + // `val` is young or old-unmarked + if (val && !(jl_astaggedvalue(val)->bits.gc & GC_MARKED)) { + jl_gc_queue_root(owner); + break; + } + } + } + n -= done; + } + } + return memmove_refs(dest_p, src_p, n); + } + size_t elsz = layout->size; + char *src_p = srcdata; + int isbitsunion = layout->flags.arrayelem_isunion; + if (isbitsunion) { + char *sourcetypetagdata = jl_genericmemory_typetagdata(src); + char *desttypetagdata = jl_genericmemory_typetagdata(dest); + memmove(desttypetagdata+(size_t)destdata, sourcetypetagdata+(size_t)srcdata, n); + srcdata = (char*)src->ptr + elsz*(size_t)srcdata; + destdata = (char*)dest->ptr + elsz*(size_t)destdata; + } + if (layout->first_ptr != -1) { + memmove_refs((_Atomic(void*)*)destdata, (_Atomic(void*)*)srcdata, n * elsz / sizeof(void*)); + jl_value_t *owner = jl_genericmemory_owner(dest); + if (__unlikely(jl_astaggedvalue(owner)->bits.gc == GC_OLD_MARKED)) { + jl_value_t *src_owner = jl_genericmemory_owner(src); + if (jl_astaggedvalue(src_owner)->bits.gc != GC_OLD_MARKED) { + dt = (jl_datatype_t*)jl_tparam1(dt); + for (size_t done = 0; done < n; done++) { // copy forwards + char* s = (char*)src_p+done*elsz; + if (*((jl_value_t**)s+layout->first_ptr) != NULL) + jl_gc_queue_multiroot(owner, s, dt); + } + } + } + } + else { + memmove(destdata, srcdata, n * elsz); + } +} + + +// genericmemory primitives ----------------------------------------------------------- + +JL_DLLEXPORT jl_value_t *jl_genericmemoryref(jl_genericmemory_t *mem, size_t i) +{ + int isatomic = (jl_tparam0(jl_typetagof(mem)) == (jl_value_t*)jl_atomic_sym); + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(mem))->layout; + jl_genericmemoryref_t m; + m.mem = mem; + m.ptr_or_offset = (layout->flags.arrayelem_isunion || layout->size == 0) ? (void*)i : (void*)((char*)mem->ptr + layout->size * i); + return jl_memoryrefget(m, isatomic); +} + +JL_DLLEXPORT jl_genericmemory_t *jl_genericmemory_copy_slice(jl_genericmemory_t *mem, void *data, size_t len) +{ + jl_value_t *mtype = (jl_value_t*)jl_typetagof(mem); + const jl_datatype_layout_t *layout = ((jl_datatype_t*)mtype)->layout; + size_t elsz = layout->size; + int isunion = layout->flags.arrayelem_isunion; + jl_genericmemory_t *new_mem = _new_genericmemory_(mtype, len, isunion, 0, elsz); + if (isunion) { + memcpy(new_mem->ptr, (char*)mem->ptr + (size_t)data * elsz, len * elsz); + memcpy(jl_genericmemory_typetagdata(new_mem), jl_genericmemory_typetagdata(mem) + (size_t)data, len); + } + else if (layout->first_ptr != -1) { + memmove_refs((_Atomic(void*)*)new_mem->ptr, (_Atomic(void*)*)data, len * elsz / sizeof(void*)); + } + else if (data != NULL) { + memcpy(new_mem->ptr, data, len * elsz); + } + return new_mem; +} + +JL_DLLEXPORT jl_genericmemory_t *jl_genericmemory_copy(jl_genericmemory_t *mem) +{ + jl_value_t *mtype = (jl_value_t*)jl_typetagof(mem); + const jl_datatype_layout_t *layout = ((jl_datatype_t*)mtype)->layout; + return jl_genericmemory_copy_slice(mem, layout->flags.arrayelem_isunion || layout->size == 0 ? (void*)0 : mem->ptr, mem->length); +} + +JL_DLLEXPORT jl_value_t *(jl_genericmemory_data_owner)(jl_genericmemory_t *m) JL_NOTSAFEPOINT +{ + return jl_genericmemory_data_owner_field(m); +} + +jl_genericmemoryref_t *jl_new_memoryref(jl_value_t *typ, jl_genericmemory_t *mem, void *data) +{ + jl_task_t *ct = jl_current_task; + jl_genericmemoryref_t *m = (jl_genericmemoryref_t*)jl_gc_alloc(ct->ptls, sizeof(jl_genericmemoryref_t), typ); + m->mem = mem; + m->ptr_or_offset = data; + return m; +} + +// memoryref primitives +JL_DLLEXPORT jl_genericmemoryref_t jl_memoryrefindex(jl_genericmemoryref_t m JL_ROOTING_ARGUMENT, size_t idx) +{ + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(m.mem))->layout; + if ((layout->flags.arrayelem_isboxed || !layout->flags.arrayelem_isunion) && layout->size != 0) { + m.ptr_or_offset = (void*)((char*)m.ptr_or_offset + idx * layout->size); + assert((char*)m.ptr_or_offset - (char*)m.mem->ptr < layout->size * m.mem->length); + } + else { + m.ptr_or_offset = (void*)((size_t)m.ptr_or_offset + idx); + assert((size_t)m.ptr_or_offset < m.mem->length); + } + return m; +} + +static jl_value_t *jl_ptrmemrefget(jl_genericmemoryref_t m JL_PROPAGATES_ROOT, int isatomic) JL_NOTSAFEPOINT +{ + assert((char*)m.ptr_or_offset - (char*)m.mem->ptr < sizeof(jl_value_t*) * m.mem->length); + assert(((jl_datatype_t*)jl_typetagof(m.mem))->layout->flags.arrayelem_isboxed); + _Atomic(jl_value_t*) *ptr = (_Atomic(jl_value_t*)*)m.ptr_or_offset; + jl_value_t *elt = isatomic ? jl_atomic_load(ptr) : jl_atomic_load_relaxed(ptr); + if (elt == NULL) + jl_throw(jl_undefref_exception); + return elt; +} + +JL_DLLEXPORT jl_value_t *jl_memoryrefget(jl_genericmemoryref_t m, int isatomic) +{ + assert(isatomic == (jl_tparam0(jl_typetagof(m.mem)) == (jl_value_t*)jl_atomic_sym)); + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(m.mem))->layout; + if (layout->flags.arrayelem_isboxed) + return jl_ptrmemrefget(m, isatomic); + jl_value_t *eltype = jl_tparam1(jl_typetagof(m.mem)); + char *data = (char*)m.ptr_or_offset; + if (layout->flags.arrayelem_isunion) { + assert(!isatomic); + assert(jl_is_uniontype(eltype)); + size_t i = (size_t)data; + assert(i < m.mem->length); + // isbits union selector bytes are always stored directly after the last memory element + uint8_t sel = jl_genericmemory_typetagdata(m.mem)[i]; + eltype = jl_nth_union_component(eltype, sel); + data = (char*)m.mem->ptr + i * layout->size; + } + if (layout->size == 0) { + assert(jl_is_datatype_singleton((jl_datatype_t*)eltype)); + return ((jl_datatype_t*)eltype)->instance; + } + assert(data - (char*)m.mem->ptr < layout->size * m.mem->length); + jl_value_t *r; + size_t fsz = jl_datatype_size(eltype); + int needlock = isatomic && fsz > MAX_ATOMIC_SIZE; + if (isatomic && !needlock) { + r = jl_atomic_new_bits(eltype, data); + } + else if (needlock) { + jl_task_t *ct = jl_current_task; + r = jl_gc_alloc(ct->ptls, fsz, eltype); + jl_lock_field((jl_mutex_t*)data); + memcpy((char*)r, data + LLT_ALIGN(sizeof(jl_mutex_t), JL_SMALL_BYTE_ALIGNMENT), fsz); + jl_unlock_field((jl_mutex_t*)data); + } + else { + // TODO: a finalizer here could make the isunion case not quite right + r = jl_new_bits(eltype, data); + } + r = undefref_check((jl_datatype_t*)eltype, r); + if (__unlikely(r == NULL)) + jl_throw(jl_undefref_exception); + return r; +} + +static int _jl_memoryref_isassigned(jl_genericmemoryref_t m, int isatomic) +{ + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(m.mem))->layout; + _Atomic(jl_value_t*) *elem = (_Atomic(jl_value_t*)*)m.ptr_or_offset; + if (layout->flags.arrayelem_isboxed) { + } + else if (layout->first_ptr >= 0) { + int needlock = isatomic && layout->size > MAX_ATOMIC_SIZE; + if (needlock) + elem = elem + LLT_ALIGN(sizeof(jl_mutex_t), JL_SMALL_BYTE_ALIGNMENT) / sizeof(jl_value_t*); + elem = &elem[layout->first_ptr]; + } + else { + return 1; + } + return (isatomic ? jl_atomic_load(elem) : jl_atomic_load_relaxed(elem)) != NULL; +} + +JL_DLLEXPORT jl_value_t *jl_memoryref_isassigned(jl_genericmemoryref_t m, int isatomic) +{ + return _jl_memoryref_isassigned(m, isatomic) ? jl_true : jl_false; +} + +JL_DLLEXPORT void jl_memoryrefset(jl_genericmemoryref_t m JL_ROOTING_ARGUMENT, jl_value_t *rhs JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED, int isatomic) +{ + assert(isatomic == (jl_tparam0(jl_typetagof(m.mem)) == (jl_value_t*)jl_atomic_sym)); + jl_value_t *eltype = jl_tparam1(jl_typetagof(m.mem)); + if (eltype != (jl_value_t*)jl_any_type && !jl_typeis(rhs, eltype)) { + JL_GC_PUSH1(&rhs); + if (!jl_isa(rhs, eltype)) + jl_type_error("memoryrefset!", eltype, rhs); + JL_GC_POP(); + } + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(m.mem))->layout; + if (layout->flags.arrayelem_isboxed) { + assert((char*)m.ptr_or_offset - (char*)m.mem->ptr < sizeof(jl_value_t*) * m.mem->length); + if (isatomic) + jl_atomic_store((_Atomic(jl_value_t*)*)m.ptr_or_offset, rhs); + else + jl_atomic_store_release((_Atomic(jl_value_t*)*)m.ptr_or_offset, rhs); + jl_gc_wb(jl_genericmemory_owner(m.mem), rhs); + return; + } + int hasptr; + char *data = (char*)m.ptr_or_offset; + if (layout->flags.arrayelem_isunion) { + assert(!isatomic); + assert(jl_is_uniontype(eltype)); + size_t i = (size_t)data; + assert(i < m.mem->length); + uint8_t *psel = (uint8_t*)jl_genericmemory_typetagdata(m.mem) + i; + unsigned nth = 0; + if (!jl_find_union_component(eltype, jl_typeof(rhs), &nth)) + assert(0 && "invalid genericmemoryset to isbits union"); + *psel = nth; + hasptr = 0; + data = (char*)m.mem->ptr + i * layout->size; + } + else { + hasptr = layout->first_ptr >= 0; + } + if (layout->size != 0) { + assert(data - (char*)m.mem->ptr < layout->size * m.mem->length); + int needlock = isatomic && layout->size > MAX_ATOMIC_SIZE; + size_t fsz = jl_datatype_size((jl_datatype_t*)jl_typeof(rhs)); // need to shrink-wrap the final copy + if (isatomic && !needlock) { + jl_atomic_store_bits(data, rhs, fsz); + } + else if (needlock) { + jl_lock_field((jl_mutex_t*)data); + memassign_safe(hasptr, data + LLT_ALIGN(sizeof(jl_mutex_t), JL_SMALL_BYTE_ALIGNMENT), rhs, fsz); + jl_unlock_field((jl_mutex_t*)data); + } + else { + memassign_safe(hasptr, data, rhs, fsz); + } + if (hasptr) + jl_gc_multi_wb(jl_genericmemory_owner(m.mem), rhs); // rhs is immutable + } +} + +JL_DLLEXPORT jl_value_t *jl_memoryrefswap(jl_genericmemoryref_t m, jl_value_t *rhs, int isatomic) +{ + jl_value_t *eltype = jl_tparam1(jl_typetagof(m.mem)); + if (eltype != (jl_value_t*)jl_any_type && !jl_typeis(rhs, eltype)) { + if (!jl_isa(rhs, eltype)) + jl_type_error("memoryrefswap!", eltype, rhs); + } + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(m.mem))->layout; + jl_value_t *owner = jl_genericmemory_owner(m.mem); + char *data = (char*)m.ptr_or_offset; + if (layout->flags.arrayelem_isboxed) { + assert(data - (char*)m.mem->ptr < sizeof(jl_value_t*) * m.mem->length); + jl_value_t *r; + if (isatomic) + r = jl_atomic_exchange((_Atomic(jl_value_t*)*)data, rhs); + else + r = jl_atomic_exchange_release((_Atomic(jl_value_t*)*)data, rhs); + jl_gc_wb(owner, rhs); + if (__unlikely(r == NULL)) + jl_throw(jl_undefref_exception); + return r; + } + uint8_t *psel = NULL; + if (layout->flags.arrayelem_isunion) { + assert(!isatomic); + assert(jl_is_uniontype(eltype)); + size_t i = (size_t)data; + assert(i < m.mem->length); + psel = (uint8_t*)jl_genericmemory_typetagdata(m.mem) + i; + data = (char*)m.mem->ptr + i * layout->size; + } + return swap_bits(eltype, data, psel, owner, rhs, isatomic ? isatomic_field : isatomic_none); +} + +JL_DLLEXPORT jl_value_t *jl_memoryrefmodify(jl_genericmemoryref_t m, jl_value_t *op, jl_value_t *rhs, int isatomic) +{ + jl_value_t *eltype = jl_tparam1(jl_typetagof(m.mem)); + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(m.mem))->layout; + jl_value_t *owner = jl_genericmemory_owner(m.mem); + char *data = (char*)m.ptr_or_offset; + if (layout->flags.arrayelem_isboxed) { + assert(data - (char*)m.mem->ptr < sizeof(jl_value_t*) * m.mem->length); + return modify_value(eltype, (_Atomic(jl_value_t*)*)data, owner, op, rhs, isatomic, NULL, NULL); + } + size_t fsz = layout->size; + uint8_t *psel = NULL; + if (layout->flags.arrayelem_isunion) { + assert(!isatomic); + assert(jl_is_uniontype(eltype)); + size_t i = (size_t)data; + assert(i < m.mem->length); + psel = (uint8_t*)jl_genericmemory_typetagdata(m.mem) + i; + data = (char*)m.mem->ptr + i * fsz; + } + return modify_bits(eltype, data, psel, owner, op, rhs, isatomic ? isatomic_field : isatomic_none); +} + +JL_DLLEXPORT jl_value_t *jl_memoryrefreplace(jl_genericmemoryref_t m, jl_value_t *expected, jl_value_t *rhs, int isatomic) +{ + jl_value_t *eltype = jl_tparam1(jl_typetagof(m.mem)); + if (eltype != (jl_value_t*)jl_any_type && !jl_typeis(rhs, eltype)) { + if (!jl_isa(rhs, eltype)) + jl_type_error("memoryrefreplace!", eltype, rhs); + } + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(m.mem))->layout; + jl_value_t *owner = jl_genericmemory_owner(m.mem); + char *data = (char*)m.ptr_or_offset; + if (layout->flags.arrayelem_isboxed) { + assert(data - (char*)m.mem->ptr < sizeof(jl_value_t*) * m.mem->length); + return replace_value(eltype, (_Atomic(jl_value_t*)*)data, owner, expected, rhs, isatomic, NULL, NULL); + } + uint8_t *psel = NULL; + if (layout->flags.arrayelem_isunion) { + assert(!isatomic); + assert(jl_is_uniontype(eltype)); + size_t i = (size_t)data; + assert(i < m.mem->length); + psel = (uint8_t*)jl_genericmemory_typetagdata(m.mem) + i; + data = (char*)m.mem->ptr + i * layout->size; + } + return replace_bits(eltype, data, psel, owner, expected, rhs, isatomic ? isatomic_field : isatomic_none); +} + +JL_DLLEXPORT jl_value_t *jl_memoryrefsetonce(jl_genericmemoryref_t m, jl_value_t *rhs, int isatomic) +{ + jl_value_t *eltype = jl_tparam1(jl_typetagof(m.mem)); + if (eltype != (jl_value_t*)jl_any_type && !jl_typeis(rhs, eltype)) { + if (!jl_isa(rhs, eltype)) + jl_type_error("memoryrefsetonce!", eltype, rhs); + } + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(m.mem))->layout; + jl_value_t *owner = jl_genericmemory_owner(m.mem); + char *data = (char*)m.ptr_or_offset; + int success; + if (layout->flags.arrayelem_isboxed) { + assert(data - (char*)m.mem->ptr < sizeof(jl_value_t*) * m.mem->length); + jl_value_t *r = NULL; + _Atomic(jl_value_t*) *px = (_Atomic(jl_value_t*)*)data; + success = isatomic ? jl_atomic_cmpswap(px, &r, rhs) : jl_atomic_cmpswap_release(px, &r, rhs); + if (success) + jl_gc_wb(owner, rhs); + } + else { + if (layout->flags.arrayelem_isunion) { + assert(!isatomic); + assert(jl_is_uniontype(eltype)); + size_t i = (size_t)data; + assert(i < m.mem->length); + (void)i; + success = 0; + } + else if (layout->first_ptr < 0) { + success = 0; + } + else { + success = setonce_bits((jl_datatype_t*)eltype, data, owner, rhs, isatomic ? isatomic_field : isatomic_none); + } + } + return success ? jl_true : jl_false; +} + +JL_DLLEXPORT jl_value_t *ijl_genericmemory_owner(jl_genericmemory_t *m JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT +{ + return jl_genericmemory_owner(m); +} +#ifdef __cplusplus +} +#endif diff --git a/src/gf.c b/src/gf.c index 407d9c517a55b..2bab2b2271e0b 100644 --- a/src/gf.c +++ b/src/gf.c @@ -25,6 +25,7 @@ extern "C" { #endif JL_DLLEXPORT _Atomic(size_t) jl_world_counter = 1; // uses atomic acquire/release +jl_mutex_t world_counter_lock; JL_DLLEXPORT size_t jl_get_world_counter(void) JL_NOTSAFEPOINT { jl_task_t *ct = jl_current_task; @@ -94,7 +95,7 @@ void jl_call_tracer(tracer_cb callback, jl_value_t *tracee) JL_CATCH { ct->ptls->in_pure_callback = last_in; jl_printf((JL_STREAM*)STDERR_FILENO, "WARNING: tracer callback function threw an error:\n"); - jl_static_show((JL_STREAM*)STDERR_FILENO, jl_current_exception()); + jl_static_show((JL_STREAM*)STDERR_FILENO, jl_current_exception(ct)); jl_printf((JL_STREAM*)STDERR_FILENO, "\n"); jlbacktrace(); // written to STDERR_FILENO } @@ -110,7 +111,7 @@ static int8_t jl_cachearg_offset(jl_methtable_t *mt) /// ----- Insertion logic for special entries ----- /// -static uint_t speccache_hash(size_t idx, jl_svec_t *data) +static uint_t speccache_hash(size_t idx, jl_value_t *data) { jl_method_instance_t *ml = (jl_method_instance_t*)jl_svecref(data, idx); jl_value_t *sig = ml->specTypes; @@ -119,7 +120,7 @@ static uint_t speccache_hash(size_t idx, jl_svec_t *data) return ((jl_datatype_t*)sig)->hash; } -static int speccache_eq(size_t idx, const void *ty, jl_svec_t *data, uint_t hv) +static int speccache_eq(size_t idx, const void *ty, jl_value_t *data, uint_t hv) { jl_method_instance_t *ml = (jl_method_instance_t*)jl_svecref(data, idx); jl_value_t *sig = ml->specTypes; @@ -134,12 +135,12 @@ static int speccache_eq(size_t idx, const void *ty, jl_svec_t *data, uint_t hv) // get or create the MethodInstance for a specialization static jl_method_instance_t *jl_specializations_get_linfo_(jl_method_t *m JL_PROPAGATES_ROOT, jl_value_t *type, jl_svec_t *sparams, jl_method_instance_t *mi_insert) { - if (m->sig == (jl_value_t*)jl_anytuple_type && jl_atomic_load_relaxed(&m->unspecialized) != NULL && m != jl_opaque_closure_method) + if (m->sig == (jl_value_t*)jl_anytuple_type && jl_atomic_load_relaxed(&m->unspecialized) != NULL && m != jl_opaque_closure_method && !m->is_for_opaque_closure) return jl_atomic_load_relaxed(&m->unspecialized); // handle builtin methods jl_value_t *ut = jl_is_unionall(type) ? jl_unwrap_unionall(type) : type; JL_TYPECHK(specializations, datatype, ut); uint_t hv = ((jl_datatype_t*)ut)->hash; - jl_array_t *speckeyset = NULL; + jl_genericmemory_t *speckeyset = NULL; jl_value_t *specializations = NULL; size_t i = -1, cl = 0, lastcl; for (int locked = 0; locked < 2; locked++) { @@ -164,7 +165,7 @@ static jl_method_instance_t *jl_specializations_get_linfo_(jl_method_t *m JL_PRO } cl = jl_svec_len(specializations); if (hv) { - ssize_t idx = jl_smallintset_lookup(speckeyset, speccache_eq, type, (jl_svec_t*)specializations, hv); + ssize_t idx = jl_smallintset_lookup(speckeyset, speccache_eq, type, specializations, hv, 0); if (idx != -1) { jl_method_instance_t *mi = (jl_method_instance_t*)jl_svecref(specializations, idx); if (locked) @@ -210,7 +211,7 @@ static jl_method_instance_t *jl_specializations_get_linfo_(jl_method_t *m JL_PRO jl_atomic_store_release(&m->specializations, specializations); jl_gc_wb(m, specializations); if (hv) - jl_smallintset_insert(&m->speckeyset, (jl_value_t*)m, speccache_hash, 0, (jl_svec_t*)specializations); + jl_smallintset_insert(&m->speckeyset, (jl_value_t*)m, speccache_hash, 0, specializations); } if (hv) { _Atomic(jl_method_instance_t*) *data = (_Atomic(jl_method_instance_t*)*)jl_svec_data(specializations); @@ -242,7 +243,7 @@ static jl_method_instance_t *jl_specializations_get_linfo_(jl_method_t *m JL_PRO assert(jl_svecref(specializations, i) == jl_nothing); jl_svecset(specializations, i, mi); if (hv) - jl_smallintset_insert(&m->speckeyset, (jl_value_t*)m, speccache_hash, i, (jl_svec_t*)specializations); + jl_smallintset_insert(&m->speckeyset, (jl_value_t*)m, speccache_hash, i, specializations); JL_GC_POP(); } JL_UNLOCK(&m->writelock); // may gc @@ -275,7 +276,7 @@ JL_DLLEXPORT jl_value_t *jl_methtable_lookup(jl_methtable_t *mt, jl_value_t *typ // TODO: this is sort of an odd lookup strategy (and the only user of // jl_typemap_assoc_by_type with subtype=0), while normally jl_gf_invoke_lookup would be // expected to be used instead - struct jl_typemap_assoc search = {type, world, NULL, 0, ~(size_t)0}; + struct jl_typemap_assoc search = {type, world, NULL}; jl_typemap_entry_t *sf = jl_typemap_assoc_by_type(jl_atomic_load_relaxed(&mt->defs), &search, jl_cachearg_offset(mt), /*subtype*/0); if (!sf) return jl_nothing; @@ -284,13 +285,6 @@ JL_DLLEXPORT jl_value_t *jl_methtable_lookup(jl_methtable_t *mt, jl_value_t *typ // ----- MethodInstance specialization instantiation ----- // -JL_DLLEXPORT jl_code_instance_t* jl_new_codeinst( - jl_method_instance_t *mi, jl_value_t *rettype, - jl_value_t *inferred_const, jl_value_t *inferred, - int32_t const_flags, size_t min_world, size_t max_world, - uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, - uint8_t relocatability); - jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_args_t fptr) JL_GC_DISABLED { jl_sym_t *sname = jl_symbol(name); @@ -305,6 +299,8 @@ jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_a m->module = jl_core_module; m->isva = 1; m->nargs = 2; + jl_atomic_store_relaxed(&m->primary_world, 1); + jl_atomic_store_relaxed(&m->deleted_world, ~(size_t)0); m->sig = (jl_value_t*)jl_anytuple_type; m->slot_syms = jl_an_empty_string; m->nospecialize = 0; @@ -315,16 +311,16 @@ jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_a JL_GC_PUSH2(&m, &newentry); newentry = jl_typemap_alloc(jl_anytuple_type, NULL, jl_emptysvec, - (jl_value_t*)m, 1, ~(size_t)0); + (jl_value_t*)m, jl_atomic_load_relaxed(&m->primary_world), jl_atomic_load_relaxed(&m->deleted_world)); jl_typemap_insert(&mt->defs, (jl_value_t*)mt, newentry, jl_cachearg_offset(mt)); jl_method_instance_t *mi = jl_get_specialized(m, (jl_value_t*)jl_anytuple_type, jl_emptysvec); jl_atomic_store_relaxed(&m->unspecialized, mi); jl_gc_wb(m, mi); - jl_code_instance_t *codeinst = jl_new_codeinst(mi, - (jl_value_t*)jl_any_type, jl_nothing, jl_nothing, - 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); + jl_code_instance_t *codeinst = jl_new_codeinst(mi, jl_nothing, + (jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, jl_nothing, jl_nothing, + 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0, NULL); jl_mi_cache_insert(mi, codeinst); jl_atomic_store_relaxed(&codeinst->specptr.fptr1, fptr); jl_atomic_store_relaxed(&codeinst->invoke, jl_fptr_args); @@ -342,7 +338,7 @@ jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_a // returns the inferred source, and may cache the result in mi // if successful, also updates the mi argument to describe the validity of this src // if inference doesn't occur (or can't finish), returns NULL instead -jl_code_info_t *jl_type_infer(jl_method_instance_t *mi, size_t world, int force) +jl_code_instance_t *jl_type_infer(jl_method_instance_t *mi, size_t world, int force, uint8_t source_mode) { if (jl_typeinf_func == NULL) return NULL; @@ -356,16 +352,17 @@ jl_code_info_t *jl_type_infer(jl_method_instance_t *mi, size_t world, int force) if ((ct->reentrant_timing & 0b1111) >= 0b110) return NULL; - jl_code_info_t *src = NULL; + jl_code_instance_t *ci = NULL; #ifdef ENABLE_INFERENCE if (mi->inInference && !force) return NULL; JL_TIMING(INFERENCE, INFERENCE); jl_value_t **fargs; - JL_GC_PUSHARGS(fargs, 3); + JL_GC_PUSHARGS(fargs, 4); fargs[0] = (jl_value_t*)jl_typeinf_func; fargs[1] = (jl_value_t*)mi; fargs[2] = jl_box_ulong(world); + fargs[3] = jl_box_uint8(source_mode); jl_timing_show_method_instance(mi, JL_TIMING_DEFAULT_BLOCK); #ifdef TRACE_INFERENCE @@ -379,6 +376,8 @@ jl_code_info_t *jl_type_infer(jl_method_instance_t *mi, size_t world, int force) #ifdef _OS_WINDOWS_ DWORD last_error = GetLastError(); #endif + int last_pure = ct->ptls->in_pure_callback; + ct->ptls->in_pure_callback = 0; size_t last_age = ct->world_age; ct->world_age = jl_typeinf_world; mi->inInference = 1; @@ -391,39 +390,44 @@ jl_code_info_t *jl_type_infer(jl_method_instance_t *mi, size_t world, int force) // allocate another bit for the counter. ct->reentrant_timing += 0b10; JL_TRY { - src = (jl_code_info_t*)jl_apply(fargs, 3); + ci = (jl_code_instance_t*)jl_apply(fargs, 4); } JL_CATCH { - jl_value_t *e = jl_current_exception(); + jl_value_t *e = jl_current_exception(ct); + jl_printf((JL_STREAM*)STDERR_FILENO, "Internal error: during type inference of\n"); + jl_static_show_func_sig((JL_STREAM*)STDERR_FILENO, (jl_value_t*)mi->specTypes); + jl_printf((JL_STREAM*)STDERR_FILENO, "\nEncountered "); if (e == jl_stackovf_exception) { - jl_printf((JL_STREAM*)STDERR_FILENO, "Internal error: stack overflow in type inference of "); - jl_static_show_func_sig((JL_STREAM*)STDERR_FILENO, (jl_value_t*)mi->specTypes); - jl_printf((JL_STREAM*)STDERR_FILENO, ".\n"); + jl_printf((JL_STREAM*)STDERR_FILENO, "stack overflow.\n"); jl_printf((JL_STREAM*)STDERR_FILENO, "This might be caused by recursion over very long tuples or argument lists.\n"); } else { - jl_printf((JL_STREAM*)STDERR_FILENO, "Internal error: encountered unexpected error in runtime:\n"); + jl_printf((JL_STREAM*)STDERR_FILENO, "unexpected error in runtime:\n"); jl_static_show((JL_STREAM*)STDERR_FILENO, e); jl_printf((JL_STREAM*)STDERR_FILENO, "\n"); jlbacktrace(); // written to STDERR_FILENO } - src = NULL; + ci = NULL; +#ifndef JL_NDEBUG + abort(); +#endif } ct->world_age = last_age; ct->reentrant_timing -= 0b10; + ct->ptls->in_pure_callback = last_pure; mi->inInference = 0; #ifdef _OS_WINDOWS_ SetLastError(last_error); #endif errno = last_errno; - if (src && !jl_is_code_info(src)) { - src = NULL; + if (ci && !jl_is_code_instance(ci)) { + ci = NULL; } JL_GC_POP(); #endif - return src; + return ci; } JL_DLLEXPORT jl_value_t *jl_call_in_typeinf_world(jl_value_t **args, int nargs) @@ -436,63 +440,72 @@ JL_DLLEXPORT jl_value_t *jl_call_in_typeinf_world(jl_value_t **args, int nargs) return ret; } -JL_DLLEXPORT jl_value_t *jl_rettype_inferred(jl_method_instance_t *mi, size_t min_world, size_t max_world) JL_NOTSAFEPOINT +JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred( + jl_method_instance_t *mi JL_PROPAGATES_ROOT, jl_value_t *rettype, + size_t min_world, size_t max_world, jl_debuginfo_t *edges) { + jl_value_t *owner = jl_nothing; // TODO: owner should be arg jl_code_instance_t *codeinst = jl_atomic_load_relaxed(&mi->cache); while (codeinst) { - if (codeinst->min_world <= min_world && max_world <= codeinst->max_world) { - jl_value_t *code = jl_atomic_load_relaxed(&codeinst->inferred); - if (code && (code == jl_nothing || jl_ir_flag_inferred(code))) - return (jl_value_t*)codeinst; + if (jl_atomic_load_relaxed(&codeinst->min_world) == min_world && + jl_atomic_load_relaxed(&codeinst->max_world) == max_world && + jl_egal(codeinst->owner, owner) && + jl_egal(codeinst->rettype, rettype)) { + if (edges == NULL) + return codeinst; + jl_debuginfo_t *debuginfo = jl_atomic_load_relaxed(&codeinst->debuginfo); + if (edges == debuginfo) + return codeinst; + if (debuginfo == NULL && jl_atomic_cmpswap_relaxed(&codeinst->debuginfo, &debuginfo, edges)) + return codeinst; + if (debuginfo && jl_egal((jl_value_t*)debuginfo, (jl_value_t*)edges)) + return codeinst; } codeinst = jl_atomic_load_relaxed(&codeinst->next); } - return (jl_value_t*)jl_nothing; + codeinst = jl_new_codeinst( + mi, owner, rettype, (jl_value_t*)jl_any_type, NULL, NULL, + 0, min_world, max_world, 0, 0, jl_nothing, 0, edges); + jl_mi_cache_insert(mi, codeinst); + return codeinst; } -JL_DLLEXPORT jl_value_t *(*const jl_rettype_inferred_addr)(jl_method_instance_t *mi, size_t min_world, size_t max_world) JL_NOTSAFEPOINT = jl_rettype_inferred; - -JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred( - jl_method_instance_t *mi JL_PROPAGATES_ROOT, jl_value_t *rettype, - size_t min_world, size_t max_world) +JL_DLLEXPORT int jl_mi_cache_has_ci(jl_method_instance_t *mi, + jl_code_instance_t *ci) { jl_code_instance_t *codeinst = jl_atomic_load_relaxed(&mi->cache); while (codeinst) { - if (codeinst->min_world == min_world && - codeinst->max_world == max_world && - jl_egal(codeinst->rettype, rettype)) { - return codeinst; - } + if (codeinst == ci) + return 1; codeinst = jl_atomic_load_relaxed(&codeinst->next); } - codeinst = jl_new_codeinst( - mi, rettype, NULL, NULL, - 0, min_world, max_world, 0, 0, jl_nothing, 0); - jl_mi_cache_insert(mi, codeinst); - return codeinst; + return 0; } JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( - jl_method_instance_t *mi, jl_value_t *rettype, + jl_method_instance_t *mi, jl_value_t *owner, + jl_value_t *rettype, jl_value_t *exctype, jl_value_t *inferred_const, jl_value_t *inferred, int32_t const_flags, size_t min_world, size_t max_world, - uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, - uint8_t relocatability - /*, jl_array_t *edges, int absolute_max*/) + uint32_t ipo_effects, uint32_t effects, jl_value_t *analysis_results, + uint8_t relocatability, + jl_debuginfo_t *edges /* , int absolute_max*/) { jl_task_t *ct = jl_current_task; assert(min_world <= max_world && "attempting to set invalid world constraints"); jl_code_instance_t *codeinst = (jl_code_instance_t*)jl_gc_alloc(ct->ptls, sizeof(jl_code_instance_t), jl_code_instance_type); codeinst->def = mi; - codeinst->min_world = min_world; - codeinst->max_world = max_world; + codeinst->owner = owner; + jl_atomic_store_relaxed(&codeinst->min_world, min_world); + jl_atomic_store_relaxed(&codeinst->max_world, max_world); codeinst->rettype = rettype; + codeinst->exctype = exctype; jl_atomic_store_release(&codeinst->inferred, inferred); - //codeinst->edges = NULL; if ((const_flags & 2) == 0) inferred_const = NULL; codeinst->rettype_const = inferred_const; + jl_atomic_store_relaxed(&codeinst->debuginfo, edges); jl_atomic_store_relaxed(&codeinst->specptr.fptr, NULL); jl_atomic_store_relaxed(&codeinst->invoke, NULL); if ((const_flags & 1) != 0) { @@ -504,7 +517,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( jl_atomic_store_relaxed(&codeinst->next, NULL); codeinst->ipo_purity_bits = ipo_effects; jl_atomic_store_relaxed(&codeinst->purity_bits, effects); - codeinst->argescapes = argescapes; + codeinst->analysis_results = analysis_results; codeinst->relocatability = relocatability; return codeinst; } @@ -536,7 +549,7 @@ static int get_method_unspec_list(jl_typemap_entry_t *def, void *closure) if (!jl_is_svec(specializations)) { jl_method_instance_t *mi = (jl_method_instance_t*)specializations; assert(jl_is_method_instance(mi)); - if (jl_rettype_inferred(mi, world, world) == jl_nothing) + if (jl_rettype_inferred_native(mi, world, world) == jl_nothing) jl_array_ptr_1d_push((jl_array_t*)closure, (jl_value_t*)mi); return 1; } @@ -546,7 +559,7 @@ static int get_method_unspec_list(jl_typemap_entry_t *def, void *closure) jl_method_instance_t *mi = (jl_method_instance_t*)jl_svecref(specializations, i); if ((jl_value_t*)mi != jl_nothing) { assert(jl_is_method_instance(mi)); - if (jl_rettype_inferred(mi, world, world) == jl_nothing) + if (jl_rettype_inferred_native(mi, world, world) == jl_nothing) jl_array_ptr_1d_push((jl_array_t*)closure, (jl_value_t*)mi); } } @@ -561,7 +574,7 @@ int foreach_mtable_in_module( { jl_svec_t *table = jl_atomic_load_relaxed(&m->bindings); for (size_t i = 0; i < jl_svec_len(table); i++) { - jl_binding_t *b = (jl_binding_t*)jl_svec_ref(table, i); + jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i); if ((void*)b == jl_nothing) break; jl_sym_t *name = b->globalref->name; @@ -615,7 +628,7 @@ int jl_foreach_reachable_mtable(int (*visit)(jl_methtable_t *mt, void *env), voi if (mod_array) { JL_GC_PUSH1(&mod_array); int i; - for (i = 0; i < jl_array_len(mod_array); i++) { + for (i = 0; i < jl_array_nrows(mod_array); i++) { jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(mod_array, i); assert(jl_is_module(m)); if (m->parent == m) // some toplevel modules (really just Base) aren't actually @@ -640,7 +653,7 @@ static int reset_mt_caches(jl_methtable_t *mt, void *env) // removes all method caches // this might not be entirely safe (GC or MT), thus we only do it very early in bootstrapping if (!mt->frozen) { // make sure not to reset builtin functions - jl_atomic_store_release(&mt->leafcache, (jl_array_t*)jl_an_empty_vec_any); + jl_atomic_store_release(&mt->leafcache, (jl_genericmemory_t*)jl_an_empty_memory_any); jl_atomic_store_release(&mt->cache, jl_nothing); } jl_typemap_visitor(jl_atomic_load_relaxed(&mt->defs), get_method_unspec_list, env); @@ -664,10 +677,10 @@ JL_DLLEXPORT void jl_set_typeinf_func(jl_value_t *f) JL_GC_PUSH1(&unspec); jl_foreach_reachable_mtable(reset_mt_caches, (void*)unspec); size_t i, l; - for (i = 0, l = jl_array_len(unspec); i < l; i++) { + for (i = 0, l = jl_array_nrows(unspec); i < l; i++) { jl_method_instance_t *mi = (jl_method_instance_t*)jl_array_ptr_ref(unspec, i); - if (jl_rettype_inferred(mi, world, world) == jl_nothing) - jl_type_infer(mi, world, 1); + if (jl_rettype_inferred_native(mi, world, world) == jl_nothing) + jl_type_infer(mi, world, 1, SOURCE_MODE_NOT_REQUIRED); } JL_GC_POP(); } @@ -1213,12 +1226,12 @@ static int concretesig_equal(jl_value_t *tt, jl_value_t *simplesig) JL_NOTSAFEPO return 1; } -static inline jl_typemap_entry_t *lookup_leafcache(jl_array_t *leafcache JL_PROPAGATES_ROOT, jl_value_t *tt, size_t world) JL_NOTSAFEPOINT +static inline jl_typemap_entry_t *lookup_leafcache(jl_genericmemory_t *leafcache JL_PROPAGATES_ROOT, jl_value_t *tt, size_t world) JL_NOTSAFEPOINT { jl_typemap_entry_t *entry = (jl_typemap_entry_t*)jl_eqtable_get(leafcache, (jl_value_t*)tt, NULL); if (entry) { do { - if (entry->min_world <= world && world <= entry->max_world) { + if (jl_atomic_load_relaxed(&entry->min_world) <= world && world <= jl_atomic_load_relaxed(&entry->max_world)) { if (entry->simplesig == (void*)jl_nothing || concretesig_equal(tt, (jl_value_t*)entry->simplesig)) return entry; } @@ -1240,12 +1253,12 @@ static jl_method_instance_t *cache_method( int8_t offs = mt ? jl_cachearg_offset(mt) : 1; { // scope block if (mt) { - jl_array_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); + jl_genericmemory_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); jl_typemap_entry_t *entry = lookup_leafcache(leafcache, (jl_value_t*)tt, world); if (entry) return entry->func.linfo; } - struct jl_typemap_assoc search = {(jl_value_t*)tt, world, NULL, 0, ~(size_t)0}; + struct jl_typemap_assoc search = {(jl_value_t*)tt, world, NULL}; jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(jl_atomic_load_relaxed(cache), &search, offs, /*subtype*/1); if (entry && entry->func.value) return entry->func.linfo; @@ -1306,7 +1319,7 @@ static jl_method_instance_t *cache_method( } else { int unmatched_tvars = 0; - size_t i, l = jl_array_len(temp); + size_t i, l = jl_array_nrows(temp); for (i = 0; i < l; i++) { jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(temp, i); if (matc->method == definition) @@ -1339,7 +1352,7 @@ static jl_method_instance_t *cache_method( guardsigs = jl_alloc_svec(guards); temp3 = (jl_value_t*)guardsigs; guards = 0; - for (i = 0, l = jl_array_len(temp); i < l; i++) { + for (i = 0, l = jl_array_nrows(temp); i < l; i++) { jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(temp, i); jl_method_t *other = matc->method; if (other != definition) { @@ -1396,7 +1409,7 @@ static jl_method_instance_t *cache_method( // short-circuit if an existing entry is already present // that satisfies our requirements if (cachett != tt) { - struct jl_typemap_assoc search = {(jl_value_t*)cachett, world, NULL, 0, ~(size_t)0}; + struct jl_typemap_assoc search = {(jl_value_t*)cachett, world, NULL}; jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(jl_atomic_load_relaxed(cache), &search, offs, /*subtype*/1); if (entry && jl_egal((jl_value_t*)entry->simplesig, simplett ? (jl_value_t*)simplett : jl_nothing) && jl_egal((jl_value_t*)guardsigs, (jl_value_t*)entry->guardsigs)) { @@ -1420,11 +1433,11 @@ static jl_method_instance_t *cache_method( jl_cache_type_(tt); JL_UNLOCK(&typecache_lock); // Might GC } - jl_array_t *oldcache = jl_atomic_load_relaxed(&mt->leafcache); + jl_genericmemory_t *oldcache = jl_atomic_load_relaxed(&mt->leafcache); jl_typemap_entry_t *old = (jl_typemap_entry_t*)jl_eqtable_get(oldcache, (jl_value_t*)tt, jl_nothing); jl_atomic_store_relaxed(&newentry->next, old); jl_gc_wb(newentry, old); - jl_array_t *newcache = (jl_array_t*)jl_eqtable_put(jl_atomic_load_relaxed(&mt->leafcache), (jl_value_t*)tt, (jl_value_t*)newentry, NULL); + jl_genericmemory_t *newcache = jl_eqtable_put(jl_atomic_load_relaxed(&mt->leafcache), (jl_value_t*)tt, (jl_value_t*)newentry, NULL); if (newcache != oldcache) { jl_atomic_store_release(&mt->leafcache, newcache); jl_gc_wb(mt, newcache); @@ -1440,34 +1453,45 @@ static jl_method_instance_t *cache_method( static jl_method_match_t *_gf_invoke_lookup(jl_value_t *types JL_PROPAGATES_ROOT, jl_value_t *mt, size_t world, size_t *min_valid, size_t *max_valid); -static jl_method_instance_t *jl_mt_assoc_by_type(jl_methtable_t *mt JL_PROPAGATES_ROOT, jl_datatype_t *tt, size_t world) +static jl_method_instance_t *jl_mt_assoc_by_type(jl_methtable_t *mt JL_PROPAGATES_ROOT, jl_datatype_t *tt JL_MAYBE_UNROOTED, size_t world) { - // caller must hold the mt->writelock + jl_genericmemory_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); + jl_typemap_entry_t *entry = lookup_leafcache(leafcache, (jl_value_t*)tt, world); + if (entry) + return entry->func.linfo; + JL_TIMING(METHOD_LOOKUP_SLOW, METHOD_LOOKUP_SLOW); + jl_method_match_t *matc = NULL; + JL_GC_PUSH2(&tt, &matc); + JL_LOCK(&mt->writelock); assert(tt->isdispatchtuple || tt->hasfreetypevars); + jl_method_instance_t *mi = NULL; if (tt->isdispatchtuple) { - jl_array_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); + jl_genericmemory_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); jl_typemap_entry_t *entry = lookup_leafcache(leafcache, (jl_value_t*)tt, world); if (entry) - return entry->func.linfo; + mi = entry->func.linfo; } - struct jl_typemap_assoc search = {(jl_value_t*)tt, world, NULL, 0, ~(size_t)0}; - jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(jl_atomic_load_relaxed(&mt->cache), &search, jl_cachearg_offset(mt), /*subtype*/1); - if (entry) - return entry->func.linfo; + if (!mi) { + struct jl_typemap_assoc search = {(jl_value_t*)tt, world, NULL}; + jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(jl_atomic_load_relaxed(&mt->cache), &search, jl_cachearg_offset(mt), /*subtype*/1); + if (entry) + mi = entry->func.linfo; + } - size_t min_valid = 0; - size_t max_valid = ~(size_t)0; - jl_method_match_t *matc = _gf_invoke_lookup((jl_value_t*)tt, jl_nothing, world, &min_valid, &max_valid); - jl_method_instance_t *nf = NULL; - if (matc) { - JL_GC_PUSH1(&matc); - jl_method_t *m = matc->method; - jl_svec_t *env = matc->sparams; - nf = cache_method(mt, &mt->cache, (jl_value_t*)mt, tt, m, world, min_valid, max_valid, env); - JL_GC_POP(); + if (!mi) { + size_t min_valid = 0; + size_t max_valid = ~(size_t)0; + matc = _gf_invoke_lookup((jl_value_t*)tt, jl_nothing, world, &min_valid, &max_valid); + if (matc) { + jl_method_t *m = matc->method; + jl_svec_t *env = matc->sparams; + mi = cache_method(mt, &mt->cache, (jl_value_t*)mt, tt, m, world, min_valid, max_valid, env); + } } - return nf; + JL_UNLOCK(&mt->writelock); + JL_GC_POP(); + return mi; } @@ -1482,14 +1506,15 @@ static int get_intersect_visitor(jl_typemap_entry_t *oldentry, struct typemap_in { struct matches_env *closure = container_of(closure0, struct matches_env, match); assert(oldentry != closure->newentry && "entry already added"); - assert(oldentry->min_world <= closure->newentry->min_world && "old method cannot be newer than new method"); - assert(oldentry->max_world != closure->newentry->min_world && "method cannot be added at the same time as method deleted"); + assert(jl_atomic_load_relaxed(&oldentry->min_world) <= jl_atomic_load_relaxed(&closure->newentry->min_world) && "old method cannot be newer than new method"); + assert(jl_atomic_load_relaxed(&oldentry->max_world) != jl_atomic_load_relaxed(&closure->newentry->min_world) && "method cannot be added at the same time as method deleted"); // don't need to consider other similar methods if this oldentry will always fully intersect with them and dominates all of them typemap_slurp_search(oldentry, &closure->match); jl_method_t *oldmethod = oldentry->func.method; if (closure->match.issubty // e.g. jl_subtype(closure->newentry.sig, oldentry->sig) && jl_subtype(oldmethod->sig, (jl_value_t*)closure->newentry->sig)) { // e.g. jl_type_equal(closure->newentry->sig, oldentry->sig) - closure->replaced = oldentry; + if (closure->replaced == NULL || jl_atomic_load_relaxed(&closure->replaced->min_world) < jl_atomic_load_relaxed(&oldentry->min_world)) + closure->replaced = oldentry; // must pick the newest insertion (both are still valid) } if (closure->shadowed == NULL) closure->shadowed = (jl_value_t*)jl_alloc_vec_any(0); @@ -1533,14 +1558,6 @@ void print_func_loc(JL_STREAM *s, jl_method_t *m) } } -static int is_anonfn_typename(char *name) -{ - if (name[0] != '#' || name[1] == '#') - return 0; - char *other = strrchr(name, '#'); - return other > &name[1] && other[1] > '0' && other[1] <= '9'; -} - static void method_overwrite(jl_typemap_entry_t *newentry, jl_method_t *oldvalue) { // method overwritten @@ -1568,8 +1585,10 @@ static void method_overwrite(jl_typemap_entry_t *newentry, jl_method_t *oldvalue jl_printf(s, ".\n"); jl_uv_flush(s); } - if (jl_generating_output()) - jl_error("Method overwriting is not permitted during Module precompile."); + if (jl_generating_output()) { + jl_printf(JL_STDERR, "ERROR: Method overwriting is not permitted during Module precompilation. Use `__precompile__(false)` to opt-out of precompilation.\n"); + jl_throw(jl_precompilable_error); + } } static void update_max_args(jl_methtable_t *mt, jl_value_t *type) @@ -1602,45 +1621,10 @@ JL_DLLEXPORT jl_value_t *jl_debug_method_invalidation(int state) return jl_nothing; } -// call external callbacks registered with this method_instance -static void invalidate_external(jl_method_instance_t *mi, size_t max_world) { - jl_array_t *callbacks = mi->callbacks; - if (callbacks) { - // AbstractInterpreter allows for MethodInstances to be present in non-local caches - // inform those caches about the invalidation. - JL_TRY { - size_t i, l = jl_array_len(callbacks); - jl_value_t **args; - JL_GC_PUSHARGS(args, 3); - // these arguments are constant per call - args[1] = (jl_value_t*)mi; - args[2] = jl_box_uint32(max_world); - - jl_task_t *ct = jl_current_task; - size_t last_age = ct->world_age; - ct->world_age = jl_get_world_counter(); - - jl_value_t **cbs = (jl_value_t**)jl_array_ptr_data(callbacks); - for (i = 0; i < l; i++) { - args[0] = cbs[i]; - jl_apply(args, 3); - } - ct->world_age = last_age; - JL_GC_POP(); - } - JL_CATCH { - jl_printf((JL_STREAM*)STDERR_FILENO, "error in invalidation callback: "); - jl_static_show((JL_STREAM*)STDERR_FILENO, jl_current_exception()); - jl_printf((JL_STREAM*)STDERR_FILENO, "\n"); - jlbacktrace(); // written to STDERR_FILENO - } - } -} - -static void do_nothing_with_codeinst(jl_code_instance_t *ci) {} +static void _invalidate_backedges(jl_method_instance_t *replaced_mi, size_t max_world, int depth); // recursively invalidate cached methods that had an edge to a replaced method -static void invalidate_method_instance(void (*f)(jl_code_instance_t*), jl_method_instance_t *replaced, size_t max_world, int depth) +static void invalidate_method_instance(jl_method_instance_t *replaced, size_t max_world, int depth) { jl_timing_counter_inc(JL_TIMING_COUNTER_Invalidations, 1); if (_jl_debug_method_invalidation) { @@ -1657,50 +1641,41 @@ static void invalidate_method_instance(void (*f)(jl_code_instance_t*), jl_method JL_LOCK(&replaced->def.method->writelock); jl_code_instance_t *codeinst = jl_atomic_load_relaxed(&replaced->cache); while (codeinst) { - if (codeinst->max_world == ~(size_t)0) { - assert(codeinst->min_world - 1 <= max_world && "attempting to set illogical world constraints (probable race condition)"); - codeinst->max_world = max_world; + if (jl_atomic_load_relaxed(&codeinst->max_world) == ~(size_t)0) { + assert(jl_atomic_load_relaxed(&codeinst->min_world) - 1 <= max_world && "attempting to set illogical world constraints (probable race condition)"); + jl_atomic_store_release(&codeinst->max_world, max_world); } - assert(codeinst->max_world <= max_world); - JL_GC_PUSH1(&codeinst); - (*f)(codeinst); - JL_GC_POP(); + assert(jl_atomic_load_relaxed(&codeinst->max_world) <= max_world); codeinst = jl_atomic_load_relaxed(&codeinst->next); } + JL_GC_PUSH1(&replaced); // recurse to all backedges to update their valid range also - jl_array_t *backedges = replaced->backedges; - if (backedges) { - JL_GC_PUSH1(&backedges); - replaced->backedges = NULL; - size_t i = 0, l = jl_array_len(backedges); - jl_method_instance_t *replaced; - while (i < l) { - i = get_next_edge(backedges, i, NULL, &replaced); - invalidate_method_instance(f, replaced, max_world, depth + 1); - } - JL_GC_POP(); - } + _invalidate_backedges(replaced, max_world, depth + 1); + JL_GC_POP(); JL_UNLOCK(&replaced->def.method->writelock); } -// invalidate cached methods that overlap this definition -static void invalidate_backedges(void (*f)(jl_code_instance_t*), jl_method_instance_t *replaced_mi, size_t max_world, const char *why) -{ - JL_LOCK(&replaced_mi->def.method->writelock); +static void _invalidate_backedges(jl_method_instance_t *replaced_mi, size_t max_world, int depth) { jl_array_t *backedges = replaced_mi->backedges; - //jl_static_show(JL_STDERR, (jl_value_t*)replaced_mi); if (backedges) { // invalidate callers (if any) replaced_mi->backedges = NULL; JL_GC_PUSH1(&backedges); - size_t i = 0, l = jl_array_len(backedges); + size_t i = 0, l = jl_array_nrows(backedges); jl_method_instance_t *replaced; while (i < l) { i = get_next_edge(backedges, i, NULL, &replaced); - invalidate_method_instance(f, replaced, max_world, 1); + invalidate_method_instance(replaced, max_world, depth); } JL_GC_POP(); } +} + +// invalidate cached methods that overlap this definition +static void invalidate_backedges(jl_method_instance_t *replaced_mi, size_t max_world, const char *why) +{ + JL_LOCK(&replaced_mi->def.method->writelock); + _invalidate_backedges(replaced_mi, max_world, 1); JL_UNLOCK(&replaced_mi->def.method->writelock); if (why && _jl_debug_method_invalidation) { jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)replaced_mi); @@ -1725,7 +1700,7 @@ JL_DLLEXPORT void jl_method_instance_add_backedge(jl_method_instance_t *callee, jl_gc_wb(callee, callee->backedges); } else { - size_t i = 0, l = jl_array_len(callee->backedges); + size_t i = 0, l = jl_array_nrows(callee->backedges); for (i = 0; i < l; i++) { // optimized version of while (i < l) i = get_next_edge(callee->backedges, i, &invokeTypes, &mi); jl_value_t *mi = jl_array_ptr_ref(callee->backedges, i); @@ -1758,16 +1733,24 @@ JL_DLLEXPORT void jl_method_table_add_backedge(jl_methtable_t *mt, jl_value_t *t jl_array_ptr_set(mt->backedges, 1, caller); } else { - // TODO: use jl_cache_type_(tt) like cache_method does, instead of a linear scan - size_t i, l = jl_array_len(mt->backedges); + // check if the edge is already present and avoid adding a duplicate + size_t i, l = jl_array_nrows(mt->backedges); for (i = 1; i < l; i += 2) { - if (jl_types_equal(jl_array_ptr_ref(mt->backedges, i - 1), typ)) { - if (jl_array_ptr_ref(mt->backedges, i) == caller) { + if (jl_array_ptr_ref(mt->backedges, i) == caller) { + if (jl_types_equal(jl_array_ptr_ref(mt->backedges, i - 1), typ)) { JL_UNLOCK(&mt->writelock); return; } - // reuse the already cached instance of this type - typ = jl_array_ptr_ref(mt->backedges, i - 1); + } + } + // reuse an already cached instance of this type, if possible + // TODO: use jl_cache_type_(tt) like cache_method does, instead of this linear scan? + for (i = 1; i < l; i += 2) { + if (jl_array_ptr_ref(mt->backedges, i) != caller) { + if (jl_types_equal(jl_array_ptr_ref(mt->backedges, i - 1), typ)) { + typ = jl_array_ptr_ref(mt->backedges, i - 1); + break; + } } } jl_array_ptr_1d_push(mt->backedges, typ); @@ -1786,11 +1769,11 @@ static int invalidate_mt_cache(jl_typemap_entry_t *oldentry, void *closure0) { struct invalidate_mt_env *env = (struct invalidate_mt_env*)closure0; JL_GC_PROMISE_ROOTED(env->newentry); - if (oldentry->max_world == ~(size_t)0) { + if (jl_atomic_load_relaxed(&oldentry->max_world) == ~(size_t)0) { jl_method_instance_t *mi = oldentry->func.linfo; int intersects = 0; jl_method_instance_t **d = (jl_method_instance_t**)jl_array_ptr_data(env->shadowed); - size_t i, n = jl_array_len(env->shadowed); + size_t i, n = jl_array_nrows(env->shadowed); for (i = 0; i < n; i++) { if (mi == d[i]) { intersects = 1; @@ -1821,20 +1804,25 @@ static int invalidate_mt_cache(jl_typemap_entry_t *oldentry, void *closure0) jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); JL_GC_POP(); } - oldentry->max_world = env->max_world; + jl_atomic_store_relaxed(&oldentry->max_world, env->max_world); env->invalidated = 1; } } return 1; } + +struct disable_mt_env { + jl_method_t *replaced; + size_t max_world; +}; static int disable_mt_cache(jl_typemap_entry_t *oldentry, void *closure0) { - struct invalidate_mt_env *env = (struct invalidate_mt_env*)closure0; - if (oldentry->max_world < ~(size_t)0) + struct disable_mt_env *env = (struct disable_mt_env*)closure0; + if (jl_atomic_load_relaxed(&oldentry->max_world) < ~(size_t)0) return 1; jl_method_t *m = oldentry->func.linfo->def.method; - if (m == env->newentry->func.method) - oldentry->max_world = env->max_world; + if (m == env->replaced) + jl_atomic_store_relaxed(&oldentry->max_world, env->max_world); return 1; } @@ -1847,46 +1835,38 @@ static int typemap_search(jl_typemap_entry_t *entry, void *closure) return 1; } -static jl_typemap_entry_t *do_typemap_search(jl_methtable_t *mt JL_PROPAGATES_ROOT, jl_method_t *method) JL_NOTSAFEPOINT; - -#ifndef __clang_gcanalyzer__ /* in general, jl_typemap_visitor could be a safepoint, but not for typemap_search */ -static jl_typemap_entry_t *do_typemap_search(jl_methtable_t *mt JL_PROPAGATES_ROOT, jl_method_t *method) JL_NOTSAFEPOINT { +static jl_typemap_entry_t *do_typemap_search(jl_methtable_t *mt JL_PROPAGATES_ROOT, jl_method_t *method) { jl_value_t *closure = (jl_value_t*)(method); if (jl_typemap_visitor(jl_atomic_load_relaxed(&mt->defs), typemap_search, &closure)) jl_error("method not in method table"); return (jl_typemap_entry_t *)closure; } -#endif -static void jl_method_table_invalidate(jl_methtable_t *mt, jl_typemap_entry_t *methodentry, size_t max_world) +static void jl_method_table_invalidate(jl_methtable_t *mt, jl_method_t *replaced, size_t max_world) { if (jl_options.incremental && jl_generating_output()) jl_error("Method deletion is not possible during Module precompile."); - jl_method_t *method = methodentry->func.method; - assert(!method->is_for_opaque_closure); - method->deleted_world = methodentry->max_world = max_world; + assert(!replaced->is_for_opaque_closure); + assert(jl_atomic_load_relaxed(&jl_world_counter) == max_world); // drop this method from mt->cache - struct invalidate_mt_env mt_cache_env; + struct disable_mt_env mt_cache_env; mt_cache_env.max_world = max_world; - mt_cache_env.newentry = methodentry; - mt_cache_env.shadowed = NULL; - mt_cache_env.invalidated = 0; + mt_cache_env.replaced = replaced; jl_typemap_visitor(jl_atomic_load_relaxed(&mt->cache), disable_mt_cache, (void*)&mt_cache_env); - jl_array_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); - size_t i, l = jl_array_len(leafcache); + jl_genericmemory_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); + size_t i, l = leafcache->length; for (i = 1; i < l; i += 2) { - jl_typemap_entry_t *oldentry = (jl_typemap_entry_t*)jl_array_ptr_ref(leafcache, i); + jl_typemap_entry_t *oldentry = (jl_typemap_entry_t*)jl_genericmemory_ptr_ref(leafcache, i); if (oldentry) { while ((jl_value_t*)oldentry != jl_nothing) { - if (oldentry->max_world == ~(size_t)0) - oldentry->max_world = mt_cache_env.max_world; + disable_mt_cache(oldentry, (void*)&mt_cache_env); oldentry = jl_atomic_load_relaxed(&oldentry->next); } } } // Invalidate the backedges int invalidated = 0; - jl_value_t *specializations = jl_atomic_load_relaxed(&method->specializations); + jl_value_t *specializations = jl_atomic_load_relaxed(&replaced->specializations); JL_GC_PUSH1(&specializations); if (!jl_is_svec(specializations)) specializations = (jl_value_t*)jl_svec1(specializations); @@ -1895,15 +1875,14 @@ static void jl_method_table_invalidate(jl_methtable_t *mt, jl_typemap_entry_t *m jl_method_instance_t *mi = (jl_method_instance_t*)jl_svecref(specializations, i); if ((jl_value_t*)mi != jl_nothing) { invalidated = 1; - invalidate_external(mi, max_world); - invalidate_backedges(&do_nothing_with_codeinst, mi, max_world, "jl_method_table_disable"); + invalidate_backedges(mi, max_world, "jl_method_table_disable"); } } JL_GC_POP(); // XXX: this might have resolved an ambiguity, for which we have not tracked the edge here, // and thus now introduce a mistake into inference if (invalidated && _jl_debug_method_invalidation) { - jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)method); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)replaced); jl_value_t *loctag = jl_cstr_to_string("jl_method_table_disable"); JL_GC_PUSH1(&loctag); jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); @@ -1914,11 +1893,18 @@ static void jl_method_table_invalidate(jl_methtable_t *mt, jl_typemap_entry_t *m JL_DLLEXPORT void jl_method_table_disable(jl_methtable_t *mt, jl_method_t *method) { jl_typemap_entry_t *methodentry = do_typemap_search(mt, method); + JL_LOCK(&world_counter_lock); JL_LOCK(&mt->writelock); // Narrow the world age on the method to make it uncallable - size_t world = jl_atomic_fetch_add(&jl_world_counter, 1); - jl_method_table_invalidate(mt, methodentry, world); + size_t world = jl_atomic_load_relaxed(&jl_world_counter); + assert(method == methodentry->func.method); + assert(jl_atomic_load_relaxed(&method->deleted_world) == ~(size_t)0); + jl_atomic_store_relaxed(&method->deleted_world, world); + jl_atomic_store_relaxed(&methodentry->max_world, world); + jl_method_table_invalidate(mt, method, world); + jl_atomic_store_release(&jl_world_counter, world + 1); JL_UNLOCK(&mt->writelock); + JL_UNLOCK(&world_counter_lock); } static int jl_type_intersection2(jl_value_t *t1, jl_value_t *t2, jl_value_t **isect JL_REQUIRE_ROOTED_SLOT, jl_value_t **isect2 JL_REQUIRE_ROOTED_SLOT) @@ -1980,29 +1966,51 @@ static int is_replacing(char ambig, jl_value_t *type, jl_method_t *m, jl_method_ return 1; } -JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method, jl_tupletype_t *simpletype) +jl_typemap_entry_t *jl_method_table_add(jl_methtable_t *mt, jl_method_t *method, jl_tupletype_t *simpletype) { JL_TIMING(ADD_METHOD, ADD_METHOD); assert(jl_is_method(method)); assert(jl_is_mtable(mt)); jl_timing_show_method(method, JL_TIMING_DEFAULT_BLOCK); - jl_value_t *type = method->sig; + jl_typemap_entry_t *newentry = NULL; + JL_GC_PUSH1(&newentry); + JL_LOCK(&mt->writelock); + // add our new entry + assert(jl_atomic_load_relaxed(&method->primary_world) == ~(size_t)0); // min-world + assert(jl_atomic_load_relaxed(&method->deleted_world) == 1); // max-world + newentry = jl_typemap_alloc((jl_tupletype_t*)method->sig, simpletype, jl_emptysvec, (jl_value_t*)method, + jl_atomic_load_relaxed(&method->primary_world), jl_atomic_load_relaxed(&method->deleted_world)); + jl_typemap_insert(&mt->defs, (jl_value_t*)mt, newentry, jl_cachearg_offset(mt)); + update_max_args(mt, method->sig); + JL_UNLOCK(&mt->writelock); + JL_GC_POP(); + return newentry; +} + +void jl_method_table_activate(jl_methtable_t *mt, jl_typemap_entry_t *newentry) +{ + JL_TIMING(ADD_METHOD, ADD_METHOD); + jl_method_t *method = newentry->func.method; + assert(jl_is_mtable(mt)); + assert(jl_is_method(method)); + jl_timing_show_method(method, JL_TIMING_DEFAULT_BLOCK); + jl_value_t *type = (jl_value_t*)newentry->sig; jl_value_t *oldvalue = NULL; jl_array_t *oldmi = NULL; - if (method->primary_world == 1) - method->primary_world = jl_atomic_fetch_add(&jl_world_counter, 1) + 1; - size_t max_world = method->primary_world - 1; + JL_LOCK(&mt->writelock); + size_t world = jl_atomic_load_relaxed(&method->primary_world); + assert(world == jl_atomic_load_relaxed(&jl_world_counter) + 1); // min-world + assert(jl_atomic_load_relaxed(&method->deleted_world) == ~(size_t)0); // max-world + assert(jl_atomic_load_relaxed(&newentry->min_world) == ~(size_t)0); + assert(jl_atomic_load_relaxed(&newentry->max_world) == 1); + jl_atomic_store_relaxed(&newentry->min_world, world); + jl_atomic_store_relaxed(&method->primary_world, world); + size_t max_world = world - 1; jl_value_t *loctag = NULL; // debug info for invalidation jl_value_t *isect = NULL; jl_value_t *isect2 = NULL; jl_value_t *isect3 = NULL; - jl_typemap_entry_t *newentry = NULL; - JL_GC_PUSH7(&oldvalue, &oldmi, &newentry, &loctag, &isect, &isect2, &isect3); - JL_LOCK(&mt->writelock); - // add our new entry - newentry = jl_typemap_alloc((jl_tupletype_t*)type, simpletype, jl_emptysvec, - (jl_value_t*)method, method->primary_world, method->deleted_world); - jl_typemap_insert(&mt->defs, (jl_value_t*)mt, newentry, jl_cachearg_offset(mt)); + JL_GC_PUSH6(&oldvalue, &oldmi, &loctag, &isect, &isect2, &isect3); jl_typemap_entry_t *replaced = NULL; // then check what entries we replaced oldvalue = get_intersect_matches(jl_atomic_load_relaxed(&mt->defs), newentry, &replaced, jl_cachearg_offset(mt), max_world); @@ -2011,7 +2019,8 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method oldvalue = (jl_value_t*)replaced; invalidated = 1; method_overwrite(newentry, replaced->func.method); - jl_method_table_invalidate(mt, replaced, max_world); + // this is an optimized version of below, given we know the type-intersection is exact + jl_method_table_invalidate(mt, replaced->func.method, max_world); } else { jl_method_t *const *d; @@ -2023,11 +2032,11 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method else { assert(jl_is_array(oldvalue)); d = (jl_method_t**)jl_array_ptr_data(oldvalue); - n = jl_array_len(oldvalue); + n = jl_array_nrows(oldvalue); } if (mt->backedges) { jl_value_t **backedges = jl_array_ptr_data(mt->backedges); - size_t i, na = jl_array_len(mt->backedges); + size_t i, na = jl_array_nrows(mt->backedges); size_t ins = 0; for (i = 1; i < na; i += 2) { jl_value_t *backedgetyp = backedges[i - 1]; @@ -2060,8 +2069,7 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method } if (missing) { jl_method_instance_t *backedge = (jl_method_instance_t*)backedges[i]; - invalidate_external(backedge, max_world); - invalidate_method_instance(&do_nothing_with_codeinst, backedge, max_world, 0); + invalidate_method_instance(backedge, max_world, 0); invalidated = 1; if (_jl_debug_method_invalidation) jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)backedgetyp); @@ -2118,11 +2126,11 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method // over the intersection (not ambiguous) and the new method will be selected now (morespec_is) int replaced_dispatch = is_replacing(ambig, type, m, d, n, isect, isect2, morespec); // found that this specialization dispatch got replaced by m - // call invalidate_backedges(&do_nothing_with_codeinst, mi, max_world, "jl_method_table_insert"); + // call invalidate_backedges(mi, max_world, "jl_method_table_insert"); // but ignore invoke-type edges jl_array_t *backedges = mi->backedges; if (backedges) { - size_t ib = 0, insb = 0, nb = jl_array_len(backedges); + size_t ib = 0, insb = 0, nb = jl_array_nrows(backedges); jl_value_t *invokeTypes; jl_method_instance_t *caller; while (ib < nb) { @@ -2139,7 +2147,7 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method replaced_edge = replaced_dispatch; } if (replaced_edge) { - invalidate_method_instance(&do_nothing_with_codeinst, caller, max_world, 1); + invalidate_method_instance(caller, max_world, 1); invalidated = 1; } else { @@ -2149,7 +2157,6 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method jl_array_del_end(backedges, nb - insb); } jl_array_ptr_1d_push(oldmi, (jl_value_t*)mi); - invalidate_external(mi, max_world); if (_jl_debug_method_invalidation && invalidated) { jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)mi); loctag = jl_cstr_to_string("jl_method_table_insert"); @@ -2158,7 +2165,7 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method } } } - if (jl_array_len(oldmi)) { + if (jl_array_nrows(oldmi)) { // search mt->cache and leafcache and drop anything that might overlap with the new method // this is very cheap, so we don't mind being fairly conservative at over-approximating this struct invalidate_mt_env mt_cache_env; @@ -2168,10 +2175,10 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method mt_cache_env.invalidated = 0; jl_typemap_visitor(jl_atomic_load_relaxed(&mt->cache), invalidate_mt_cache, (void*)&mt_cache_env); - jl_array_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); - size_t i, l = jl_array_len(leafcache); + jl_genericmemory_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); + size_t i, l = leafcache->length; for (i = 1; i < l; i += 2) { - jl_value_t *entry = jl_array_ptr_ref(leafcache, i); + jl_value_t *entry = jl_genericmemory_ptr_ref(leafcache, i); if (entry) { while (entry != jl_nothing) { invalidate_mt_cache((jl_typemap_entry_t*)entry, (void*)&mt_cache_env); @@ -2187,11 +2194,25 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method loctag = jl_cstr_to_string("jl_method_table_insert"); jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); } - update_max_args(mt, type); + jl_atomic_store_relaxed(&newentry->max_world, jl_atomic_load_relaxed(&method->deleted_world)); JL_UNLOCK(&mt->writelock); JL_GC_POP(); } +JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method, jl_tupletype_t *simpletype) +{ + jl_typemap_entry_t *newentry = jl_method_table_add(mt, method, simpletype); + JL_GC_PUSH1(&newentry); + JL_LOCK(&world_counter_lock); + size_t world = jl_atomic_load_relaxed(&jl_world_counter) + 1; + jl_atomic_store_relaxed(&method->primary_world, world); + jl_atomic_store_relaxed(&method->deleted_world, ~(size_t)0); + jl_method_table_activate(mt, newentry); + jl_atomic_store_release(&jl_world_counter, world); + JL_UNLOCK(&world_counter_lock); + JL_GC_POP(); +} + static void JL_NORETURN jl_method_error_bare(jl_function_t *f, jl_value_t *args, size_t world) { if (jl_methoderror_type) { @@ -2235,7 +2256,22 @@ static jl_tupletype_t *lookup_arg_type_tuple(jl_value_t *arg1 JL_PROPAGATES_ROOT return jl_lookup_arg_tuple_type(arg1, args, nargs, 1); } -jl_method_instance_t *jl_method_lookup(jl_value_t **args, size_t nargs, size_t world) +JL_DLLEXPORT jl_value_t *jl_method_lookup_by_tt(jl_tupletype_t *tt, size_t world, jl_value_t *_mt) +{ + jl_methtable_t *mt = NULL; + if (_mt == jl_nothing) + mt = jl_gf_ft_mtable(jl_tparam0(tt)); + else { + assert(jl_isa(_mt, (jl_value_t*)jl_methtable_type)); + mt = (jl_methtable_t*) _mt; + } + jl_method_instance_t* mi = jl_mt_assoc_by_type(mt, tt, world); + if (!mi) + return jl_nothing; + return (jl_value_t*) mi; +} + +JL_DLLEXPORT jl_method_instance_t *jl_method_lookup(jl_value_t **args, size_t nargs, size_t world) { assert(nargs > 0 && "expected caller to handle this case"); jl_methtable_t *mt = jl_gf_mtable(args[0]); @@ -2244,16 +2280,7 @@ jl_method_instance_t *jl_method_lookup(jl_value_t **args, size_t nargs, size_t w if (entry) return entry->func.linfo; jl_tupletype_t *tt = arg_type_tuple(args[0], &args[1], nargs); - jl_array_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); - entry = lookup_leafcache(leafcache, (jl_value_t*)tt, world); - if (entry) - return entry->func.linfo; - JL_GC_PUSH1(&tt); - JL_LOCK(&mt->writelock); - jl_method_instance_t *sf = jl_mt_assoc_by_type(mt, tt, world); - JL_UNLOCK(&mt->writelock); - JL_GC_POP(); - return sf; + return jl_mt_assoc_by_type(mt, tt, world); } // return a Vector{Any} of svecs, each describing a method match: @@ -2282,16 +2309,6 @@ JL_DLLEXPORT jl_value_t *jl_matching_methods(jl_tupletype_t *types, jl_value_t * return ml_matches((jl_methtable_t*)mt, types, lim, include_ambiguous, 1, world, 1, min_valid, max_valid, ambig); } -jl_method_instance_t *jl_get_unspecialized_from_mi(jl_method_instance_t *method JL_PROPAGATES_ROOT) -{ - jl_method_t *def = method->def.method; - jl_method_instance_t *mi = jl_get_unspecialized(def); - if (mi == NULL) { - return method; - } - return mi; -} - jl_method_instance_t *jl_get_unspecialized(jl_method_t *def JL_PROPAGATES_ROOT) { // one unspecialized version of a function can be shared among all cached specializations @@ -2313,39 +2330,72 @@ jl_method_instance_t *jl_get_unspecialized(jl_method_t *def JL_PROPAGATES_ROOT) return unspec; } +STATIC_INLINE jl_value_t *_jl_rettype_inferred(jl_value_t *owner, jl_method_instance_t *mi, size_t min_world, size_t max_world) JL_NOTSAFEPOINT +{ + jl_code_instance_t *codeinst = jl_atomic_load_relaxed(&mi->cache); + while (codeinst) { + if (jl_atomic_load_relaxed(&codeinst->min_world) <= min_world && + max_world <= jl_atomic_load_relaxed(&codeinst->max_world) && + jl_egal(codeinst->owner, owner)) { + + jl_value_t *code = jl_atomic_load_relaxed(&codeinst->inferred); + if (code) + return (jl_value_t*)codeinst; + } + codeinst = jl_atomic_load_relaxed(&codeinst->next); + } + return (jl_value_t*)jl_nothing; +} + +JL_DLLEXPORT jl_value_t *jl_rettype_inferred(jl_value_t *owner, jl_method_instance_t *mi, size_t min_world, size_t max_world) JL_NOTSAFEPOINT +{ + return (jl_value_t*)_jl_rettype_inferred(owner, mi, min_world, max_world); +} + +JL_DLLEXPORT jl_value_t *jl_rettype_inferred_native(jl_method_instance_t *mi, size_t min_world, size_t max_world) JL_NOTSAFEPOINT +{ + return (jl_value_t*)_jl_rettype_inferred(jl_nothing, mi, min_world, max_world); +} + +JL_DLLEXPORT jl_value_t *(*const jl_rettype_inferred_addr)(jl_method_instance_t *mi, size_t min_world, size_t max_world) JL_NOTSAFEPOINT = jl_rettype_inferred_native; jl_code_instance_t *jl_method_compiled(jl_method_instance_t *mi, size_t world) { jl_code_instance_t *codeinst = jl_atomic_load_relaxed(&mi->cache); - while (codeinst) { - if (codeinst->min_world <= world && world <= codeinst->max_world) { + for (; codeinst; codeinst = jl_atomic_load_relaxed(&codeinst->next)) { + if (codeinst->owner != jl_nothing) + continue; + if (jl_atomic_load_relaxed(&codeinst->min_world) <= world && world <= jl_atomic_load_relaxed(&codeinst->max_world)) { if (jl_atomic_load_relaxed(&codeinst->invoke) != NULL) return codeinst; } - codeinst = jl_atomic_load_relaxed(&codeinst->next); } return NULL; } -jl_mutex_t precomp_statement_out_lock; -ios_t f_precompile; -JL_STREAM* s_precompile = NULL; - -static void init_precompile_output(void) +// Opportunistic SOURCE_MODE_ABI cache lookup. +jl_code_instance_t *jl_method_inferred_with_abi(jl_method_instance_t *mi JL_PROPAGATES_ROOT, size_t world) { - const char *t = jl_options.trace_compile; - if (!strncmp(t, "stderr", 6)) { - s_precompile = JL_STDERR; - } - else { - if (ios_file(&f_precompile, t, 1, 1, 1, 1) == NULL) - jl_errorf("cannot open precompile statement file \"%s\" for writing", t); - s_precompile = (JL_STREAM*) &f_precompile; + jl_code_instance_t *codeinst = jl_atomic_load_relaxed(&mi->cache); + for (; codeinst; codeinst = jl_atomic_load_relaxed(&codeinst->next)) { + if (codeinst->owner != jl_nothing) + continue; + + if (jl_atomic_load_relaxed(&codeinst->min_world) <= world && world <= jl_atomic_load_relaxed(&codeinst->max_world)) { + jl_value_t *code = jl_atomic_load_relaxed(&codeinst->inferred); + if (code && (code != jl_nothing || (jl_atomic_load_relaxed(&codeinst->invoke) != NULL))) + return codeinst; + } } + return NULL; } +jl_mutex_t precomp_statement_out_lock; + static void record_precompile_statement(jl_method_instance_t *mi) { + static ios_t f_precompile; + static JL_STREAM* s_precompile = NULL; jl_method_t *def = mi->def.method; if (jl_options.trace_compile == NULL) return; @@ -2354,7 +2404,15 @@ static void record_precompile_statement(jl_method_instance_t *mi) JL_LOCK(&precomp_statement_out_lock); if (s_precompile == NULL) { - init_precompile_output(); + const char *t = jl_options.trace_compile; + if (!strncmp(t, "stderr", 6)) { + s_precompile = JL_STDERR; + } + else { + if (ios_file(&f_precompile, t, 1, 1, 1, 1) == NULL) + jl_errorf("cannot open precompile statement file \"%s\" for writing", t); + s_precompile = (JL_STREAM*) &f_precompile; + } } if (!jl_has_free_typevars(mi->specTypes)) { jl_printf(s_precompile, "precompile("); @@ -2366,20 +2424,6 @@ static void record_precompile_statement(jl_method_instance_t *mi) JL_UNLOCK(&precomp_statement_out_lock); } -JL_DLLEXPORT void jl_write_precompile_statement(char* statement) -{ - if (jl_options.trace_compile == NULL) - return; - JL_LOCK(&precomp_statement_out_lock); - if (s_precompile == NULL) { - init_precompile_output(); - } - jl_printf(s_precompile, "%s\n", statement); - if (s_precompile != JL_STDERR) - ios_flush(&f_precompile); - JL_UNLOCK(&precomp_statement_out_lock); -} - jl_method_instance_t *jl_normalize_to_compilable_mi(jl_method_instance_t *mi JL_PROPAGATES_ROOT); jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t world) @@ -2396,9 +2440,11 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t jl_code_instance_t *codeinst2 = jl_compile_method_internal(mi2, world); jl_code_instance_t *codeinst = jl_get_method_inferred( mi, codeinst2->rettype, - codeinst2->min_world, codeinst2->max_world); + jl_atomic_load_relaxed(&codeinst2->min_world), jl_atomic_load_relaxed(&codeinst2->max_world), + jl_atomic_load_relaxed(&codeinst2->debuginfo)); if (jl_atomic_load_relaxed(&codeinst->invoke) == NULL) { codeinst->rettype_const = codeinst2->rettype_const; + jl_gc_wb(codeinst, codeinst->rettype_const); uint8_t specsigflags = jl_atomic_load_acquire(&codeinst2->specsigflags); jl_callptr_t invoke = jl_atomic_load_acquire(&codeinst2->invoke); void *fptr = jl_atomic_load_relaxed(&codeinst2->specptr.fptr); @@ -2444,7 +2490,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t // if compilation is disabled or source is unavailable, try calling unspecialized version if (compile_option == JL_OPTIONS_COMPILE_OFF || compile_option == JL_OPTIONS_COMPILE_MIN || - def->source == jl_nothing) { + (jl_is_method(def) && def->source == jl_nothing)) { // copy fptr from the template method definition if (jl_is_method(def)) { jl_method_instance_t *unspecmi = jl_atomic_load_relaxed(&def->unspecialized); @@ -2452,9 +2498,9 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t jl_code_instance_t *unspec = jl_atomic_load_relaxed(&unspecmi->cache); jl_callptr_t unspec_invoke = NULL; if (unspec && (unspec_invoke = jl_atomic_load_acquire(&unspec->invoke))) { - jl_code_instance_t *codeinst = jl_new_codeinst(mi, - (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); + jl_code_instance_t *codeinst = jl_new_codeinst(mi, jl_nothing, + (jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, NULL, NULL, + 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0, NULL); void *unspec_fptr = jl_atomic_load_relaxed(&unspec->specptr.fptr); if (unspec_fptr) { // wait until invoke and specsigflags are properly set @@ -2479,34 +2525,71 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t compile_option == JL_OPTIONS_COMPILE_MIN) { jl_code_info_t *src = jl_code_for_interpreter(mi, world); if (!jl_code_requires_compiler(src, 0)) { - jl_code_instance_t *codeinst = jl_new_codeinst(mi, - (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); + jl_code_instance_t *codeinst = jl_new_codeinst(mi, jl_nothing, + (jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, NULL, NULL, + 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0, NULL); jl_atomic_store_release(&codeinst->invoke, jl_fptr_interpret_call); jl_mi_cache_insert(mi, codeinst); record_precompile_statement(mi); return codeinst; } if (compile_option == JL_OPTIONS_COMPILE_OFF) { - jl_printf(JL_STDERR, "code missing for "); + jl_printf(JL_STDERR, "No compiled code available for "); jl_static_show(JL_STDERR, (jl_value_t*)mi); jl_printf(JL_STDERR, " : sysimg may not have been built with --compile=all\n"); } } - codeinst = jl_generate_fptr(mi, world); + // Ok, compilation is enabled. We'll need to try to compile something (probably). + // Try to find a codeinst we have already inferred (e.g. while we were compiling + // something else). + codeinst = jl_method_inferred_with_abi(mi, world); + + // Everything from here on is considered (user facing) compile time + uint64_t start = jl_typeinf_timing_begin(); + int is_recompile = jl_atomic_load_relaxed(&mi->cache) != NULL; + + // This codeinst hasn't been previously inferred do that now + if (!codeinst) { + // Don't bother inferring toplevel thunks or macros - the performance cost of inference is likely + // to significantly exceed the actual runtime. + int should_skip_inference = !jl_is_method(mi->def.method) || jl_symbol_name(mi->def.method->name)[0] == '@'; + + if (!should_skip_inference) { + codeinst = jl_type_infer(mi, world, 0, SOURCE_MODE_ABI); + } + } + + if (codeinst) { + if (jl_atomic_load_acquire(&codeinst->invoke) != NULL) { + jl_typeinf_timing_end(start, is_recompile); + // Already compiled - e.g. constabi, or compiled by a different thread while we were waiting. + return codeinst; + } + + JL_GC_PUSH1(&codeinst); + int did_compile = jl_compile_codeinst(codeinst); + + if (jl_atomic_load_relaxed(&codeinst->invoke) == NULL) { + // Something went wrong. Bail to the fallback path. + codeinst = NULL; + } else if (did_compile) { + record_precompile_statement(mi); + } + JL_GC_POP(); + } if (!codeinst) { - jl_method_instance_t *unspec = jl_get_unspecialized_from_mi(mi); - jl_code_instance_t *ucache = jl_get_method_inferred(unspec, (jl_value_t*)jl_any_type, 1, ~(size_t)0); + jl_method_instance_t *unspec = jl_get_unspecialized(def); + if (unspec == NULL) + unspec = mi; + jl_code_instance_t *ucache = jl_get_method_inferred(unspec, (jl_value_t*)jl_any_type, 1, ~(size_t)0, NULL); // ask codegen to make the fptr for unspec jl_callptr_t ucache_invoke = jl_atomic_load_acquire(&ucache->invoke); if (ucache_invoke == NULL) { - if (def->source == jl_nothing && (jl_atomic_load_relaxed(&ucache->def->uninferred) == jl_nothing || - jl_atomic_load_relaxed(&ucache->def->uninferred) == NULL)) { - jl_printf(JL_STDERR, "source not available for "); - jl_static_show(JL_STDERR, (jl_value_t*)mi); - jl_printf(JL_STDERR, "\n"); - jl_error("source missing for method that needs to be compiled"); + if ((!jl_is_method(def) || def->source == jl_nothing) && + (jl_atomic_load_relaxed(&ucache->def->uninferred) == jl_nothing || + jl_atomic_load_relaxed(&ucache->def->uninferred) == NULL)) { + jl_throw(jl_new_struct(jl_missingcodeerror_type, (jl_value_t*)mi)); } jl_generate_fptr_for_unspecialized(ucache); ucache_invoke = jl_atomic_load_acquire(&ucache->invoke); @@ -2515,10 +2598,12 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t if (ucache_invoke != jl_fptr_sparam && ucache_invoke != jl_fptr_interpret_call) { // only these care about the exact specTypes, otherwise we can use it directly + jl_typeinf_timing_end(start, is_recompile); return ucache; } - codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); + codeinst = jl_new_codeinst(mi, jl_nothing, + (jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, NULL, NULL, + 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0, NULL); void *unspec_fptr = jl_atomic_load_relaxed(&ucache->specptr.fptr); if (unspec_fptr) { // wait until invoke and specsigflags are properly set @@ -2534,10 +2619,8 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t jl_atomic_store_release(&codeinst->invoke, ucache_invoke); jl_mi_cache_insert(mi, codeinst); } - else { - record_precompile_statement(mi); - } jl_atomic_store_relaxed(&codeinst->precompile, 1); + jl_typeinf_timing_end(start, is_recompile); return codeinst; } @@ -2689,7 +2772,7 @@ jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types JL_PROPAGATES *min_valid = min_valid2; if (*max_valid > max_valid2) *max_valid = max_valid2; - if (matches == jl_nothing || jl_array_len(matches) != 1 || ambig) + if (matches == jl_nothing || jl_array_nrows(matches) != 1 || ambig) return NULL; JL_GC_PUSH1(&matches); jl_method_match_t *match = (jl_method_match_t*)jl_array_ptr_ref(matches, 0); @@ -2715,7 +2798,7 @@ static jl_method_instance_t *jl_get_compile_hint_specialization(jl_tupletype_t * *min_valid = min_valid2; if (*max_valid > max_valid2) *max_valid = max_valid2; - size_t i, n = jl_array_len(matches); + size_t i, n = jl_array_nrows(matches); if (n == 0) return NULL; JL_GC_PUSH1(&matches); @@ -2743,7 +2826,7 @@ static jl_method_instance_t *jl_get_compile_hint_specialization(jl_tupletype_t * exclude = 0; for (size_t j = n-1; j > i; j--) { // more general methods maybe more likely to be at end jl_method_match_t *match2 = (jl_method_match_t*)jl_array_ptr_ref(matches, j); - if (jl_type_morespecific(match1->method->sig, match2->method->sig)) { + if (jl_method_morespecific(match1->method, match2->method)) { exclude = 1; break; } @@ -2767,10 +2850,10 @@ static jl_method_instance_t *jl_get_compile_hint_specialization(jl_tupletype_t * static void _generate_from_hint(jl_method_instance_t *mi, size_t world) { - jl_value_t *codeinst = jl_rettype_inferred(mi, world, world); + jl_value_t *codeinst = jl_rettype_inferred_native(mi, world, world); if (codeinst == jl_nothing) { - (void)jl_type_infer(mi, world, 1); - codeinst = jl_rettype_inferred(mi, world, world); + (void)jl_type_infer(mi, world, 1, SOURCE_MODE_NOT_REQUIRED); + codeinst = jl_rettype_inferred_native(mi, world, world); } if (codeinst != jl_nothing) { if (jl_atomic_load_relaxed(&((jl_code_instance_t*)codeinst)->invoke) == jl_fptr_const_return) @@ -2784,7 +2867,7 @@ static void jl_compile_now(jl_method_instance_t *mi) size_t world = jl_atomic_load_acquire(&jl_world_counter); size_t tworld = jl_typeinf_world; _generate_from_hint(mi, world); - if (jl_typeinf_func && mi->def.method->primary_world <= tworld) { + if (jl_typeinf_func && jl_atomic_load_relaxed(&mi->def.method->primary_world) <= tworld) { // if it's part of the compiler, also attempt to compile for the compiler world too _generate_from_hint(mi, tworld); } @@ -2809,11 +2892,11 @@ JL_DLLEXPORT void jl_compile_method_instance(jl_method_instance_t *mi, jl_tuplet jl_method_instance_t *mi2 = jl_specializations_get_linfo(mi->def.method, (jl_value_t*)types2, tpenv2); JL_GC_POP(); jl_atomic_store_relaxed(&mi2->precompiled, 1); - if (jl_rettype_inferred(mi2, world, world) == jl_nothing) - (void)jl_type_infer(mi2, world, 1); - if (jl_typeinf_func && mi->def.method->primary_world <= tworld) { - if (jl_rettype_inferred(mi2, tworld, tworld) == jl_nothing) - (void)jl_type_infer(mi2, tworld, 1); + if (jl_rettype_inferred_native(mi2, world, world) == jl_nothing) + (void)jl_type_infer(mi2, world, 1, SOURCE_MODE_NOT_REQUIRED); + if (jl_typeinf_func && jl_atomic_load_relaxed(&mi->def.method->primary_world) <= tworld) { + if (jl_rettype_inferred_native(mi2, tworld, tworld) == jl_nothing) + (void)jl_type_infer(mi2, tworld, 1, SOURCE_MODE_NOT_REQUIRED); } } } @@ -2886,7 +2969,7 @@ STATIC_INLINE jl_value_t *_jl_invoke(jl_value_t *F, jl_value_t **args, uint32_t // manually inlined copy of jl_method_compiled jl_code_instance_t *codeinst = jl_atomic_load_relaxed(&mfunc->cache); while (codeinst) { - if (codeinst->min_world <= world && world <= codeinst->max_world) { + if (jl_atomic_load_relaxed(&codeinst->min_world) <= world && world <= jl_atomic_load_relaxed(&codeinst->max_world)) { jl_callptr_t invoke = jl_atomic_load_acquire(&codeinst->invoke); if (invoke != NULL) { jl_value_t *res = invoke(F, args, nargs, codeinst); @@ -3003,7 +3086,7 @@ STATIC_INLINE jl_method_instance_t *jl_lookup_generic_(jl_value_t *F, jl_value_t entry = jl_atomic_load_relaxed(&call_cache[cache_idx[i]]); \ if (entry && nargs == jl_svec_len(entry->sig->parameters) && \ sig_match_fast(FT, args, jl_svec_data(entry->sig->parameters), nargs) && \ - world >= entry->min_world && world <= entry->max_world) { \ + world >= jl_atomic_load_relaxed(&entry->min_world) && world <= jl_atomic_load_relaxed(&entry->max_world)) { \ goto have_entry; \ } \ } while (0); @@ -3019,9 +3102,9 @@ STATIC_INLINE jl_method_instance_t *jl_lookup_generic_(jl_value_t *F, jl_value_t // if no method was found in the associative cache, check the full cache JL_TIMING(METHOD_LOOKUP_FAST, METHOD_LOOKUP_FAST); mt = jl_gf_mtable(F); - jl_array_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); + jl_genericmemory_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); entry = NULL; - if (leafcache != (jl_array_t*)jl_an_empty_vec_any && + if (leafcache != (jl_genericmemory_t*)jl_an_empty_memory_any && jl_typetagis(jl_atomic_load_relaxed(&mt->cache), jl_typemap_level_type)) { // hashing args is expensive, but looking at mt->cache is probably even more expensive tt = lookup_arg_type_tuple(F, args, nargs); @@ -3056,14 +3139,9 @@ STATIC_INLINE jl_method_instance_t *jl_lookup_generic_(jl_value_t *F, jl_value_t mfunc = entry->func.linfo; } else { - JL_GC_PUSH1(&tt); assert(tt); - JL_LOCK(&mt->writelock); // cache miss case - JL_TIMING(METHOD_LOOKUP_SLOW, METHOD_LOOKUP_SLOW); mfunc = jl_mt_assoc_by_type(mt, tt, world); - JL_UNLOCK(&mt->writelock); - JL_GC_POP(); if (jl_options.malloc_log) jl_gc_sync_total_bytes(last_alloc); // discard allocation count from compilation if (mfunc == NULL) { @@ -3105,7 +3183,7 @@ static jl_method_match_t *_gf_invoke_lookup(jl_value_t *types JL_PROPAGATES_ROOT if (mt == jl_nothing) mt = NULL; jl_value_t *matches = ml_matches((jl_methtable_t*)mt, (jl_tupletype_t*)types, 1, 0, 0, world, 1, min_valid, max_valid, NULL); - if (matches == jl_nothing || jl_array_len(matches) != 1) + if (matches == jl_nothing || jl_array_nrows(matches) != 1) return NULL; jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(matches, 0); return matc; @@ -3220,7 +3298,8 @@ jl_function_t *jl_new_generic_function_with_supertype(jl_sym_t *name, jl_module_ jl_gc_wb(ftype->name->mt, name); jl_set_const(module, tname, (jl_value_t*)ftype); jl_value_t *f = jl_new_struct(ftype); - ftype->instance = f; jl_gc_wb(ftype, f); + ftype->instance = f; + jl_gc_wb(ftype, f); JL_GC_POP(); return (jl_function_t*)f; } @@ -3264,30 +3343,37 @@ static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersectio struct ml_matches_env *closure = container_of(closure0, struct ml_matches_env, match); if (closure->intersections == 0 && !closure0->issubty) return 1; - if (closure->world < ml->min_world) { + size_t min_world = jl_atomic_load_relaxed(&ml->min_world); + size_t max_world = jl_atomic_load_relaxed(&ml->max_world); + if (closure->world < min_world) { // ignore method table entries that are part of a later world - if (closure->match.max_valid >= ml->min_world) - closure->match.max_valid = ml->min_world - 1; + if (closure->match.max_valid >= min_world) + closure->match.max_valid = min_world - 1; return 1; } - else if (closure->world > ml->max_world) { + else if (closure->world > max_world) { // ignore method table entries that have been replaced in the current world - if (closure->match.min_valid <= ml->max_world) - closure->match.min_valid = ml->max_world + 1; + if (closure->match.min_valid <= max_world) + closure->match.min_valid = max_world + 1; return 1; } - else { - // intersect the env valid range with method's inclusive valid range - if (closure->match.min_valid < ml->min_world) - closure->match.min_valid = ml->min_world; - if (closure->match.max_valid > ml->max_world) - closure->match.max_valid = ml->max_world; - } jl_method_t *meth = ml->func.method; if (closure->lim >= 0 && jl_is_dispatch_tupletype(meth->sig)) { - if (closure->lim == 0) - return 0; - closure->lim--; + int replaced = 0; + // check if this is replaced, in which case we need to avoid double-counting it against the limit + // (although it will figure out later which one to keep and return) + size_t len = jl_array_nrows(closure->t); + for (int i = 0; i < len; i++) { + if (jl_types_equal(((jl_method_match_t*)jl_array_ptr_ref(closure->t, i))->method->sig, meth->sig)) { + replaced = 1; + break; + } + } + if (!replaced) { + if (closure->lim == 0) + return 0; + closure->lim--; + } } // don't need to consider other similar methods if this ml will always fully intersect with them and dominates all of them if (!closure->include_ambiguous || closure->lim != -1) @@ -3295,7 +3381,7 @@ static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersectio closure->matc = make_method_match((jl_tupletype_t*)closure->match.ti, closure->match.env, meth, closure->match.issubty ? FULLY_COVERS : NOT_FULLY_COVERS); - size_t len = jl_array_len(closure->t); + size_t len = jl_array_nrows(closure->t); if (len == 0) { closure->t = (jl_value_t*)jl_alloc_vec_any(1); jl_array_ptr_set(closure->t, 0, (jl_value_t*)closure->matc); @@ -3312,7 +3398,6 @@ static int ml_mtable_visitor(jl_methtable_t *mt, void *closure0) return jl_typemap_intersection_visitor(jl_atomic_load_relaxed(&mt->defs), jl_cachearg_offset(mt), env); } - // Visit the candidate methods, starting from t[idx], to determine a possible valid sort ordering, // where every morespecific method appears before any method which it has a common // intersection with but is not partly ambiguous with (ambiguity is transitive, particularly @@ -3363,7 +3448,7 @@ static int sort_mlmatches(jl_array_t *t, size_t idx, arraylist_t *visited, array jl_method_t *m2 = matc2->method; if (jl_subtype(ti, m2->sig)) { if (include_ambiguous) { - if (!jl_type_morespecific((jl_value_t*)m2->sig, (jl_value_t*)m->sig)) + if (!jl_method_morespecific(m2, m)) continue; } visited->items[idx] = (void*)1; @@ -3382,7 +3467,7 @@ static int sort_mlmatches(jl_array_t *t, size_t idx, arraylist_t *visited, array // First visit all "strong" edges where the child is definitely better. // This likely won't hit any cycles, but might (because morespecific is not transitive). // Along the way, record if we hit any ambiguities-we may need to track those later. - for (size_t childidx = 0; childidx < jl_array_len(t); childidx++) { + for (size_t childidx = 0; childidx < jl_array_nrows(t); childidx++) { if (childidx == idx) continue; int child_cycle = (size_t)visited->items[childidx]; @@ -3397,8 +3482,8 @@ static int sort_mlmatches(jl_array_t *t, size_t idx, arraylist_t *visited, array // since we only care about sorting of the intersections the user asked us about if (!subt2 && jl_has_empty_intersection(m2->sig, m->sig)) continue; - int msp = jl_type_morespecific((jl_value_t*)m->sig, (jl_value_t*)m2->sig); - int msp2 = !msp && jl_type_morespecific((jl_value_t*)m2->sig, (jl_value_t*)m->sig); + int msp = jl_method_morespecific(m, m2); + int msp2 = !msp && jl_method_morespecific(m2, m); if (!msp) { if (subt || !include_ambiguous || (lim != -1 && msp2)) { if (subt2 || jl_subtype((jl_value_t*)ti, m2->sig)) { @@ -3438,7 +3523,7 @@ static int sort_mlmatches(jl_array_t *t, size_t idx, arraylist_t *visited, array jl_method_t *m2 = matc2->method; if (jl_subtype(ti, m2->sig)) { if (include_ambiguous) { - if (!jl_type_morespecific((jl_value_t*)m2->sig, (jl_value_t*)m->sig)) + if (!jl_method_morespecific(m2, m)) continue; } visited->items[idx] = (void*)1; @@ -3514,7 +3599,7 @@ static int sort_mlmatches(jl_array_t *t, size_t idx, arraylist_t *visited, array jl_method_t *m2 = matc2->method; if (jl_subtype(ti, m2->sig)) { if (include_ambiguous) { - if (!jl_type_morespecific((jl_value_t*)m2->sig, (jl_value_t*)m->sig)) + if (!jl_method_morespecific(m2, m)) continue; } visited->items[childidx] = (void*)1; @@ -3545,8 +3630,7 @@ static int sort_mlmatches(jl_array_t *t, size_t idx, arraylist_t *visited, array // we don't consider that a third method might be // disrupting that ordering and just consider them // pairwise to keep this simple). - if (!jl_type_morespecific((jl_value_t*)m->sig, (jl_value_t*)m2->sig) && - !jl_type_morespecific((jl_value_t*)m2->sig, (jl_value_t*)m->sig)) { + if (!jl_method_morespecific(m, m2) && !jl_method_morespecific(m2, m)) { visited->items[childidx] = (void*)-1; break; } @@ -3584,7 +3668,6 @@ static int sort_mlmatches(jl_array_t *t, size_t idx, arraylist_t *visited, array } - // This is the collect form of calling jl_typemap_intersection_visitor // with optimizations to skip fully shadowed methods. // @@ -3620,14 +3703,14 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, /* .ti = */ NULL, /* .env = */ jl_emptysvec, /* .issubty = */ 0}, intersections, world, lim, include_ambiguous, /* .t = */ jl_an_empty_vec_any, /* .matc = */ NULL}; - struct jl_typemap_assoc search = {(jl_value_t*)type, world, jl_emptysvec, 1, ~(size_t)0}; + struct jl_typemap_assoc search = {(jl_value_t*)type, world, jl_emptysvec}; jl_value_t *isect2 = NULL; JL_GC_PUSH6(&env.t, &env.matc, &env.match.env, &search.env, &env.match.ti, &isect2); if (mt) { // check the leaf cache if this type can be in there if (((jl_datatype_t*)unw)->isdispatchtuple) { - jl_array_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); + jl_genericmemory_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); jl_typemap_entry_t *entry = lookup_leafcache(leafcache, (jl_value_t*)type, world); if (entry) { jl_method_instance_t *mi = entry->func.linfo; @@ -3648,10 +3731,12 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, env.match.env, meth, FULLY_COVERS); env.t = (jl_value_t*)jl_alloc_vec_any(1); jl_array_ptr_set(env.t, 0, env.matc); - if (*min_valid < entry->min_world) - *min_valid = entry->min_world; - if (*max_valid > entry->max_world) - *max_valid = entry->max_world; + size_t min_world = jl_atomic_load_relaxed(&entry->min_world); + size_t max_world = jl_atomic_load_relaxed(&entry->max_world); + if (*min_valid < min_world) + *min_valid = min_world; + if (*max_valid > max_world) + *max_valid = max_world; JL_GC_POP(); return env.t; } @@ -3674,16 +3759,21 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, env.match.env, meth, FULLY_COVERS); env.t = (jl_value_t*)jl_alloc_vec_any(1); jl_array_ptr_set(env.t, 0, env.matc); - if (*min_valid < entry->min_world) - *min_valid = entry->min_world; - if (*max_valid > entry->max_world) - *max_valid = entry->max_world; + size_t min_world = jl_atomic_load_relaxed(&entry->min_world); + size_t max_world = jl_atomic_load_relaxed(&entry->max_world); + if (*min_valid < min_world) + *min_valid = min_world; + if (*max_valid > max_world) + *max_valid = max_world; JL_GC_POP(); return env.t; } } if (!ml_mtable_visitor(mt, &env.match)) { JL_GC_POP(); + // if we return early, set only the min/max valid collected from matching + *min_valid = env.match.min_valid; + *max_valid = env.match.max_valid; return jl_nothing; } } @@ -3691,14 +3781,18 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, // else: scan everything if (!jl_foreach_reachable_mtable(ml_mtable_visitor, &env.match)) { JL_GC_POP(); + // if we return early, set only the min/max valid collected from matching + *min_valid = env.match.min_valid; + *max_valid = env.match.max_valid; return jl_nothing; } } + // if we return early, set only the min/max valid collected from matching *min_valid = env.match.min_valid; *max_valid = env.match.max_valid; // done with many of these values now env.match.ti = NULL; env.matc = NULL; env.match.env = NULL; search.env = NULL; - size_t i, j, len = jl_array_len(env.t); + size_t i, j, len = jl_array_nrows(env.t); jl_method_match_t *minmax = NULL; int minmax_ambig = 0; int all_subtypes = 1; @@ -3713,7 +3807,7 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_method_t *m = matc->method; if (minmax != NULL) { jl_method_t *minmaxm = minmax->method; - if (jl_type_morespecific((jl_value_t*)minmaxm->sig, (jl_value_t*)m->sig)) + if (jl_method_morespecific(minmaxm, m)) continue; } minmax = matc; @@ -3731,7 +3825,7 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, if (matc->fully_covers == FULLY_COVERS) { jl_method_t *m = matc->method; jl_method_t *minmaxm = minmax->method; - if (!jl_type_morespecific((jl_value_t*)minmaxm->sig, (jl_value_t*)m->sig)) { + if (!jl_method_morespecific(minmaxm, m)) { minmax_ambig = 1; minmax = NULL; has_ambiguity = 1; @@ -3756,7 +3850,7 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(env.t, i); if (matc->fully_covers != FULLY_COVERS) { jl_method_t *m = matc->method; - if (jl_type_morespecific((jl_value_t*)minmaxm->sig, (jl_value_t*)m->sig)) + if (jl_method_morespecific(minmaxm, m)) matc->fully_covers = SENTINEL; // put a sentinel value here for sorting else all_subtypes = 0; @@ -3844,7 +3938,7 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(env.t, idx); jl_method_t *m = matc->method; int subt = matc->fully_covers == FULLY_COVERS; // jl_subtype((jl_value_t*)type, (jl_value_t*)m->sig) - for (size_t idx2 = 0; idx2 < jl_array_len(env.t); idx2++) { + for (size_t idx2 = 0; idx2 < jl_array_nrows(env.t); idx2++) { if (idx2 == idx) continue; // laborious test, checking for existence and coverage of another method (m3) @@ -3875,8 +3969,7 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, if (ti == jl_bottom_type) continue; // and they aren't themselves simply ordered - if (jl_type_morespecific((jl_value_t*)m->sig, (jl_value_t*)m2->sig) || - jl_type_morespecific((jl_value_t*)m2->sig, (jl_value_t*)m->sig)) + if (jl_method_morespecific(m, m2) || jl_method_morespecific(m2, m)) continue; // now look for a third method m3 that dominated these and that fully covered this intersection already size_t k; @@ -3889,8 +3982,7 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_method_match_t *matc3 = (jl_method_match_t*)jl_array_ptr_ref(env.t, idx3); jl_method_t *m3 = matc3->method; if ((jl_subtype(ti, m3->sig) || (isect2 && jl_subtype(isect2, m3->sig))) - && jl_type_morespecific((jl_value_t*)m3->sig, (jl_value_t*)m->sig) - && jl_type_morespecific((jl_value_t*)m3->sig, (jl_value_t*)m2->sig)) { + && jl_method_morespecific(m3, m) && jl_method_morespecific(m3, m2)) { //if (jl_subtype(matc->spec_types, ti) || jl_subtype(matc->spec_types, matc3->m3->sig)) // // check if it covered not only this intersection, but all intersections with matc // // if so, we do not need to check all of them separately @@ -3926,12 +4018,24 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, arraylist_push(&result, minmax); j++; } - memcpy(jl_array_data(env.t), result.items, j * sizeof(jl_method_match_t*)); + memcpy(jl_array_data(env.t, jl_method_match_t*), result.items, j * sizeof(jl_method_match_t*)); arraylist_free(&result); if (j != len) jl_array_del_end((jl_array_t*)env.t, len - j); len = j; } + for (j = 0; j < len; j++) { + jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(env.t, j); + jl_method_t *m = matc->method; + // method applicability is the same as typemapentry applicability + size_t min_world = jl_atomic_load_relaxed(&m->primary_world); + size_t max_world = jl_atomic_load_relaxed(&m->deleted_world); + // intersect the env valid range with method lookup's inclusive valid range + if (env.match.min_valid < min_world) + env.match.min_valid = min_world; + if (env.match.max_valid > max_world) + env.match.max_valid = max_world; + } if (mt && cache_result && ((jl_datatype_t*)unw)->isdispatchtuple) { // cache_result parameter keeps this from being recursive if (len == 1 && !has_ambiguity) { env.matc = (jl_method_match_t*)jl_array_ptr_ref(env.t, 0); @@ -3942,6 +4046,8 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, JL_UNLOCK(&mt->writelock); } } + *min_valid = env.match.min_valid; + *max_valid = env.match.max_valid; if (ambig != NULL) *ambig = has_ambiguity; JL_GC_POP(); @@ -3974,7 +4080,7 @@ JL_DLLEXPORT uint64_t jl_typeinf_timing_begin(void) return jl_hrtime(); } -JL_DLLEXPORT void jl_typeinf_timing_end(uint64_t start) +JL_DLLEXPORT void jl_typeinf_timing_end(uint64_t start, int is_recompile) { if (!start) return; @@ -3983,6 +4089,9 @@ JL_DLLEXPORT void jl_typeinf_timing_end(uint64_t start) if (jl_atomic_load_relaxed(&jl_measure_compile_time_enabled)) { uint64_t inftime = jl_hrtime() - start; jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, inftime); + if (is_recompile) { + jl_atomic_fetch_add_relaxed(&jl_cumulative_recompile_time, inftime); + } } } diff --git a/src/iddict.c b/src/iddict.c index 1fa8a67d1ae96..0a0895d048c32 100644 --- a/src/iddict.c +++ b/src/iddict.c @@ -1,49 +1,48 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license -#define hash_size(h) (jl_array_len(h) / 2) +#define hash_size(h) (h->length / 2) // compute empirical max-probe for a given size #define max_probe(size) ((size) <= 1024 ? 16 : (size) >> 6) -#define keyhash(k) jl_object_id_(jl_typeof(k), k) +#define keyhash(k) jl_object_id_(jl_typetagof(k), k) #define h2index(hv, sz) (size_t)(((hv) & ((sz)-1)) * 2) -static inline int jl_table_assign_bp(jl_array_t **pa, jl_value_t *key, jl_value_t *val); +static inline int jl_table_assign_bp(jl_genericmemory_t **pa, jl_value_t *key, jl_value_t *val); -JL_DLLEXPORT jl_array_t *jl_idtable_rehash(jl_array_t *a, size_t newsz) +JL_DLLEXPORT jl_genericmemory_t *jl_idtable_rehash(jl_genericmemory_t *a, size_t newsz) { - size_t sz = jl_array_len(a); + size_t sz = a->length; size_t i; - jl_value_t **ol = (jl_value_t **)a->data; - jl_array_t *newa = jl_alloc_vec_any(newsz); - // keep the original array in the original slot since we need `ol` + jl_value_t **ol = (jl_value_t **) a->ptr; + jl_genericmemory_t *newa = NULL; + // keep the original memory in the original slot since we need `ol` // to be valid in the loop below. JL_GC_PUSH2(&newa, &a); + newa = jl_alloc_memory_any(newsz); for (i = 0; i < sz; i += 2) { if (ol[i + 1] != NULL) { jl_table_assign_bp(&newa, ol[i], ol[i + 1]); - // it is however necessary here because allocation - // can (and will) occur in a recursive call inside table_lookup_bp } } JL_GC_POP(); return newa; } -static inline int jl_table_assign_bp(jl_array_t **pa, jl_value_t *key, jl_value_t *val) +static inline int jl_table_assign_bp(jl_genericmemory_t **pa, jl_value_t *key, jl_value_t *val) { // pa points to a **un**rooted address uint_t hv; - jl_array_t *a = *pa; + jl_genericmemory_t *a = *pa; size_t orig, index, iter, empty_slot; size_t newsz, sz = hash_size(a); if (sz == 0) { - a = jl_alloc_vec_any(HT_N_INLINE); + a = jl_alloc_memory_any(HT_N_INLINE); sz = hash_size(a); *pa = a; } size_t maxprobe = max_probe(sz); - _Atomic(jl_value_t*) *tab = (_Atomic(jl_value_t*)*)a->data; + _Atomic(jl_value_t*) *tab = (_Atomic(jl_value_t*)*) a->ptr; hv = keyhash(key); while (1) { @@ -92,7 +91,7 @@ static inline int jl_table_assign_bp(jl_array_t **pa, jl_value_t *key, jl_value_ /* 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 = jl_array_len(a); + sz = a -> length; if (sz < HT_N_INLINE) newsz = HT_N_INLINE; else if (sz >= (1 << 19) || (sz <= (1 << 8))) @@ -102,20 +101,20 @@ static inline int jl_table_assign_bp(jl_array_t **pa, jl_value_t *key, jl_value_ *pa = jl_idtable_rehash(*pa, newsz); a = *pa; - tab = (_Atomic(jl_value_t*)*)a->data; + tab = (_Atomic(jl_value_t*)*) a->ptr; sz = hash_size(a); maxprobe = max_probe(sz); } } /* returns bp if key is in hash, otherwise NULL */ -inline _Atomic(jl_value_t*) *jl_table_peek_bp(jl_array_t *a, jl_value_t *key) JL_NOTSAFEPOINT +inline _Atomic(jl_value_t*) *jl_table_peek_bp(jl_genericmemory_t *a, jl_value_t *key) JL_NOTSAFEPOINT { size_t sz = hash_size(a); if (sz == 0) return NULL; size_t maxprobe = max_probe(sz); - _Atomic(jl_value_t*) *tab = (_Atomic(jl_value_t*)*)a->data; + _Atomic(jl_value_t*) *tab = (_Atomic(jl_value_t*)*) a->ptr; uint_t hv = keyhash(key); size_t index = h2index(hv, sz); sz *= 2; @@ -142,7 +141,7 @@ inline _Atomic(jl_value_t*) *jl_table_peek_bp(jl_array_t *a, jl_value_t *key) JL } JL_DLLEXPORT -jl_array_t *jl_eqtable_put(jl_array_t *h, jl_value_t *key, jl_value_t *val, int *p_inserted) +jl_genericmemory_t *jl_eqtable_put(jl_genericmemory_t *h, jl_value_t *key, jl_value_t *val, int *p_inserted) { int inserted = jl_table_assign_bp(&h, key, val); if (p_inserted) @@ -153,20 +152,20 @@ jl_array_t *jl_eqtable_put(jl_array_t *h, jl_value_t *key, jl_value_t *val, int // Note: lookup in the IdDict is permitted concurrently, if you avoid deletions, // and assuming you do use an external lock around all insertions JL_DLLEXPORT -jl_value_t *jl_eqtable_get(jl_array_t *h, jl_value_t *key, jl_value_t *deflt) JL_NOTSAFEPOINT +jl_value_t *jl_eqtable_get(jl_genericmemory_t *h, jl_value_t *key, jl_value_t *deflt) JL_NOTSAFEPOINT { _Atomic(jl_value_t*) *bp = jl_table_peek_bp(h, key); return (bp == NULL) ? deflt : jl_atomic_load_relaxed(bp); } -jl_value_t *jl_eqtable_getkey(jl_array_t *h, jl_value_t *key, jl_value_t *deflt) JL_NOTSAFEPOINT +jl_value_t *jl_eqtable_getkey(jl_genericmemory_t *h, jl_value_t *key, jl_value_t *deflt) JL_NOTSAFEPOINT { _Atomic(jl_value_t*) *bp = jl_table_peek_bp(h, key); return (bp == NULL) ? deflt : jl_atomic_load_relaxed(bp - 1); } JL_DLLEXPORT -jl_value_t *jl_eqtable_pop(jl_array_t *h, jl_value_t *key, jl_value_t *deflt, int *found) +jl_value_t *jl_eqtable_pop(jl_genericmemory_t *h, jl_value_t *key, jl_value_t *deflt, int *found) { _Atomic(jl_value_t*) *bp = jl_table_peek_bp(h, key); if (found) @@ -180,12 +179,12 @@ jl_value_t *jl_eqtable_pop(jl_array_t *h, jl_value_t *key, jl_value_t *deflt, in } JL_DLLEXPORT -size_t jl_eqtable_nextind(jl_array_t *t, size_t i) +size_t jl_eqtable_nextind(jl_genericmemory_t *t, size_t i) { if (i & 1) i++; - size_t alen = jl_array_dim0(t); - while (i < alen && ((void **)t->data)[i + 1] == NULL) + size_t alen = t->length; + while (i < alen && ((void**) t->ptr)[i + 1] == NULL) i += 2; if (i >= alen) return (size_t)-1; @@ -194,3 +193,4 @@ size_t jl_eqtable_nextind(jl_array_t *t, size_t i) #undef hash_size #undef max_probe +#undef h2index diff --git a/src/idset.c b/src/idset.c new file mode 100644 index 0000000000000..b9711ee17f021 --- /dev/null +++ b/src/idset.c @@ -0,0 +1,118 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + + +static uint_t idset_hash(size_t idx, jl_value_t *data) +{ + jl_value_t *x = jl_genericmemory_ptr_ref(data, idx); + // x should not be NULL, unless there was concurrent corruption + return x == NULL ? 0 : jl_object_id(x); +} + +static int idset_eq(size_t idx, const void *y, jl_value_t *data, uint_t hv) +{ + jl_value_t *x = jl_genericmemory_ptr_ref(data, idx); + // x should not be NULL, unless there was concurrent corruption + return x == NULL ? 0 : jl_egal(x, (jl_value_t*)y); +} + +jl_genericmemory_t *jl_idset_rehash(jl_genericmemory_t *keys, jl_genericmemory_t *idxs, size_t newsz) +{ + if (newsz == 0) + return idxs; + newsz = next_power_of_two(newsz); + //if (idxs->length == newsz) + // jl_idset_put_idx(keys, idxs, -newsz+1); + //else + return smallintset_rehash(idxs, idset_hash, (jl_value_t*)keys, newsz, 0); +} + +// Return idx if key is in hash, otherwise -1 +// Note: lookup in the IdSet is permitted concurrently, if you avoid deletions, +// and assuming you do use an external lock around all insertions +ssize_t jl_idset_peek_bp(jl_genericmemory_t *keys, jl_genericmemory_t *idxs, jl_value_t *key) JL_NOTSAFEPOINT +{ + uintptr_t hv = jl_object_id(key); + return jl_smallintset_lookup(idxs, idset_eq, key, (jl_value_t*)keys, hv, 0); +} + +jl_value_t *jl_idset_get(jl_genericmemory_t *keys, jl_genericmemory_t *idxs, jl_value_t *key) JL_NOTSAFEPOINT +{ + ssize_t idx = jl_idset_peek_bp(keys, idxs, key); + if (idx == -1) + return NULL; + return jl_genericmemory_ptr_ref(keys, idx); +} + + +static ssize_t idset_compact(jl_genericmemory_t *keys) +{ + // compact keys before rehashing idxs + ssize_t i, j; + ssize_t rehash = 0; + for (i = j = 0; i < keys->length; i++) { + jl_value_t *k = jl_genericmemory_ptr_ref(keys, i); + if (k != NULL) { + if (i != j) { + rehash = 1; + jl_genericmemory_ptr_set(keys, j, k); + jl_genericmemory_ptr_set(keys, i, NULL); + } + j++; + } + } + return rehash ? -j : j; +} + +jl_genericmemory_t *jl_idset_put_key(jl_genericmemory_t *keys, jl_value_t *key, ssize_t *newidx) +{ + ssize_t l = keys->length; + ssize_t i = l; + while (i > 0 && jl_genericmemory_ptr_ref(keys, i - 1) == NULL) + i--; + // i points to the place to insert + *newidx = i; + if (i == l) { + i = idset_compact(keys); + if (i < 0) { + *newidx = i - 1; + i = -i; + } + if (i >= l / 3 * 2) { + size_t nl = l < 4 ? 4 : (l * 3) >> 1; // grow space by 50% if less than 33% free after compacting + jl_genericmemory_t *nk = jl_alloc_genericmemory(jl_memory_any_type, nl); + if (i > 0) + memcpy(nk->ptr, keys->ptr, sizeof(void*) * i); + keys = nk; + } + } + assert(jl_genericmemory_ptr_ref(keys, i) == NULL); + jl_genericmemory_ptr_set(keys, i, key); + return keys; +} + +jl_genericmemory_t *jl_idset_put_idx(jl_genericmemory_t *keys, jl_genericmemory_t *idxs, ssize_t idx) +{ + _Atomic(jl_genericmemory_t*) newidxs = idxs; + JL_GC_PUSH1(&newidxs); + if (idx < 0) { // full rehash + smallintset_empty(idxs); + for (ssize_t i = 0; i < -idx; i++) + if (jl_genericmemory_ptr_ref(keys, i) != NULL) + jl_smallintset_insert(&newidxs, NULL, idset_hash, i, (jl_value_t*)keys); + } + else { + jl_smallintset_insert(&newidxs, NULL, idset_hash, idx, (jl_value_t*)keys); + } + JL_GC_POP(); + return jl_atomic_load_relaxed(&newidxs); +} + +/* returns idx if key is in hash, otherwise -1 */ +ssize_t jl_idset_pop(jl_genericmemory_t *keys, jl_genericmemory_t *idxs, jl_value_t *key) JL_NOTSAFEPOINT +{ + uintptr_t hv = jl_object_id(key); + ssize_t idx = jl_smallintset_lookup(idxs, idset_eq, key, (jl_value_t*)keys, hv, 1); + if (idx != -1) + jl_genericmemory_ptr_set(keys, idx, NULL); + return idx; +} diff --git a/src/init.c b/src/init.c index a046b4e6dcb21..41ec4c78b195d 100644 --- a/src/init.c +++ b/src/init.c @@ -246,25 +246,16 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode) JL_NOTSAFEPOINT_ENTER jl_task_t *ct = jl_get_current_task(); - if (ct) { - if (exitcode == 0) - jl_write_compiler_output(); + if (ct == NULL && jl_base_module) { + ct = container_of(jl_adopt_thread(), jl_task_t, gcstack); + } + else if (ct != NULL) { // we are about to start tearing everything down, so lets try not to get // upset by the local mess of things when we run the user's _atexit hooks // this also forces us into a GC-unsafe region without a safepoint jl_task_frame_noreturn(ct); - } - - if (ct == NULL && jl_base_module) - ct = container_of(jl_adopt_thread(), jl_task_t, gcstack); - else if (ct != NULL) jl_gc_safepoint_(ct->ptls); - - jl_print_gc_stats(JL_STDERR); - if (jl_options.code_coverage) - jl_write_coverage_data(jl_options.output_code_coverage); - if (jl_options.malloc_log) - jl_write_malloc_log(); + } if (jl_base_module) { jl_value_t *f = jl_get_global(jl_base_module, jl_symbol("_atexit")); @@ -282,7 +273,7 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode) JL_NOTSAFEPOINT_ENTER } JL_CATCH { jl_printf((JL_STREAM*)STDERR_FILENO, "\natexit hook threw an error: "); - jl_static_show((JL_STREAM*)STDERR_FILENO, jl_current_exception()); + jl_static_show((JL_STREAM*)STDERR_FILENO, jl_current_exception(ct)); jl_printf((JL_STREAM*)STDERR_FILENO, "\n"); jlbacktrace(); // written to STDERR_FILENO } @@ -290,6 +281,15 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode) JL_NOTSAFEPOINT_ENTER } } + if (ct && exitcode == 0) + jl_write_compiler_output(); + + jl_print_gc_stats(JL_STDERR); + if (jl_options.code_coverage) + jl_write_coverage_data(jl_options.output_code_coverage); + if (jl_options.malloc_log) + jl_write_malloc_log(); + // replace standard output streams with something that we can still print to // after the finalizers from base/stream.jl close the TTY JL_STDOUT = (uv_stream_t*) STDOUT_FILENO; @@ -317,7 +317,7 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode) JL_NOTSAFEPOINT_ENTER assert(item); uv_unref(item->h); jl_printf((JL_STREAM*)STDERR_FILENO, "error during exit cleanup: close: "); - jl_static_show((JL_STREAM*)STDERR_FILENO, jl_current_exception()); + jl_static_show((JL_STREAM*)STDERR_FILENO, jl_current_exception(ct)); jl_printf((JL_STREAM*)STDERR_FILENO, "\n"); jlbacktrace(); // written to STDERR_FILENO item = next_shutdown_queue_item(item); @@ -372,7 +372,7 @@ JL_DLLEXPORT void jl_postoutput_hook(void) } JL_CATCH { jl_printf((JL_STREAM*)STDERR_FILENO, "\npostoutput hook threw an error: "); - jl_static_show((JL_STREAM*)STDERR_FILENO, jl_current_exception()); + jl_static_show((JL_STREAM*)STDERR_FILENO, jl_current_exception(ct)); jl_printf((JL_STREAM*)STDERR_FILENO, "\n"); jlbacktrace(); // written to STDERR_FILENO } @@ -539,7 +539,7 @@ int jl_isabspath(const char *in) JL_NOTSAFEPOINT return 0; // relative path } -static char *abspath(const char *in, int nprefix) +static char *absrealpath(const char *in, int nprefix) { // compute an absolute realpath location, so that chdir doesn't change the file reference // ignores (copies directly over) nprefix characters at the start of abspath #ifndef _OS_WINDOWS_ @@ -575,6 +575,14 @@ static char *abspath(const char *in, int nprefix) } } #else + // GetFullPathName intentionally errors if given an empty string so manually insert `.` to invoke cwd + char *in2 = (char*)malloc_s(JL_PATH_MAX); + if (strlen(in) - nprefix == 0) { + memcpy(in2, in, nprefix); + in2[nprefix] = '.'; + in2[nprefix+1] = '\0'; + in = in2; + } DWORD n = GetFullPathName(in + nprefix, 0, NULL, NULL); if (n <= 0) { jl_error("fatal error: jl_options.image_file path too long or GetFullPathName failed"); @@ -585,6 +593,7 @@ static char *abspath(const char *in, int nprefix) jl_error("fatal error: jl_options.image_file path too long or GetFullPathName failed"); } memcpy(out, in, nprefix); + free(in2); #endif return out; } @@ -646,7 +655,7 @@ static void jl_resolve_sysimg_location(JL_IMAGE_SEARCH rel) } } if (jl_options.julia_bindir) - jl_options.julia_bindir = abspath(jl_options.julia_bindir, 0); + jl_options.julia_bindir = absrealpath(jl_options.julia_bindir, 0); free(free_path); free_path = NULL; if (jl_options.image_file) { @@ -661,33 +670,33 @@ static void jl_resolve_sysimg_location(JL_IMAGE_SEARCH rel) jl_options.image_file = free_path; } if (jl_options.image_file) - jl_options.image_file = abspath(jl_options.image_file, 0); + jl_options.image_file = absrealpath(jl_options.image_file, 0); if (free_path) { free(free_path); free_path = NULL; } } if (jl_options.outputo) - jl_options.outputo = abspath(jl_options.outputo, 0); + jl_options.outputo = absrealpath(jl_options.outputo, 0); if (jl_options.outputji) - jl_options.outputji = abspath(jl_options.outputji, 0); + jl_options.outputji = absrealpath(jl_options.outputji, 0); if (jl_options.outputbc) - jl_options.outputbc = abspath(jl_options.outputbc, 0); + jl_options.outputbc = absrealpath(jl_options.outputbc, 0); if (jl_options.outputasm) - jl_options.outputasm = abspath(jl_options.outputasm, 0); + jl_options.outputasm = absrealpath(jl_options.outputasm, 0); if (jl_options.machine_file) - jl_options.machine_file = abspath(jl_options.machine_file, 0); + jl_options.machine_file = absrealpath(jl_options.machine_file, 0); if (jl_options.output_code_coverage) jl_options.output_code_coverage = absformat(jl_options.output_code_coverage); if (jl_options.tracked_path) - jl_options.tracked_path = absformat(jl_options.tracked_path); + jl_options.tracked_path = absrealpath(jl_options.tracked_path, 0); const char **cmdp = jl_options.cmds; if (cmdp) { for (; *cmdp; cmdp++) { const char *cmd = *cmdp; if (cmd[0] == 'L') { - *cmdp = abspath(cmd, 1); + *cmdp = absrealpath(cmd, 1); } } } @@ -710,6 +719,7 @@ extern jl_mutex_t jl_modules_mutex; extern jl_mutex_t precomp_statement_out_lock; extern jl_mutex_t newly_inferred_mutex; extern jl_mutex_t global_roots_lock; +extern jl_mutex_t profile_show_peek_cond_lock; static void restore_fp_env(void) { @@ -729,6 +739,7 @@ static void init_global_mutexes(void) { JL_MUTEX_INIT(&global_roots_lock, "global_roots_lock"); JL_MUTEX_INIT(&jl_codegen_lock, "jl_codegen_lock"); JL_MUTEX_INIT(&typecache_lock, "typecache_lock"); + JL_MUTEX_INIT(&profile_show_peek_cond_lock, "profile_show_peek_cond_lock"); } JL_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel) @@ -803,11 +814,6 @@ JL_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel) #endif #endif - if ((jl_options.outputo || jl_options.outputbc || jl_options.outputasm) && - (jl_options.code_coverage || jl_options.malloc_log)) { - jl_error("cannot generate code-coverage or track allocation information while generating a .o, .bc, or .s output file"); - } - jl_init_rand(); jl_init_runtime_ccall(); jl_init_tasks(); @@ -820,6 +826,7 @@ JL_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel) arraylist_new(&jl_linkage_blobs, 0); arraylist_new(&jl_image_relocs, 0); + arraylist_new(&jl_top_mods, 0); arraylist_new(&eytzinger_image_tree, 0); arraylist_new(&eytzinger_idxs, 0); arraylist_push(&eytzinger_idxs, (void*)0); @@ -850,21 +857,19 @@ static NOINLINE void _finish_julia_init(JL_IMAGE_SEARCH rel, jl_ptls_t ptls, jl_ jl_options.cpu_target = "native"; jl_init_codegen(); + jl_init_common_symbols(); if (jl_options.image_file) { jl_restore_system_image(jl_options.image_file); } else { jl_init_types(); - jl_global_roots_table = jl_alloc_vec_any(0); + jl_global_roots_list = (jl_genericmemory_t*)jl_an_empty_memory_any; + jl_global_roots_keyset = (jl_genericmemory_t*)jl_an_empty_memory_any; } - jl_init_common_symbols(); jl_init_flisp(); jl_init_serializer(); if (!jl_options.image_file) { - jl_core_module = jl_new_module(jl_symbol("Core"), NULL); - jl_core_module->parent = jl_core_module; - jl_type_typename->mt->module = jl_core_module; jl_top_module = jl_core_module; jl_init_intrinsic_functions(); jl_init_primitives(); @@ -892,7 +897,7 @@ static NOINLINE void _finish_julia_init(JL_IMAGE_SEARCH rel, jl_ptls_t ptls, jl_ jl_array_t *init_order = jl_module_init_order; JL_GC_PUSH1(&init_order); jl_module_init_order = NULL; - int i, l = jl_array_len(init_order); + int i, l = jl_array_nrows(init_order); for (i = 0; i < l; i++) { jl_value_t *mod = jl_array_ptr_ref(init_order, i); jl_module_run_initializer((jl_module_t*)mod); diff --git a/src/interpreter.c b/src/interpreter.c index 4192ee9c9ca45..c7d7fa88ea447 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -147,7 +147,7 @@ jl_value_t *jl_eval_global_var(jl_module_t *m, jl_sym_t *e) { jl_value_t *v = jl_get_global(m, e); if (v == NULL) - jl_undefined_var_error(e); + jl_undefined_var_error(e, (jl_value_t*)m); return v; } @@ -155,18 +155,18 @@ jl_value_t *jl_eval_globalref(jl_globalref_t *g) { jl_value_t *v = jl_get_globalref_value(g); if (v == NULL) - jl_undefined_var_error(g->name); + jl_undefined_var_error(g->name, (jl_value_t*)g->mod); return v; } static int jl_source_nslots(jl_code_info_t *src) JL_NOTSAFEPOINT { - return jl_array_len(src->slotflags); + return jl_array_nrows(src->slotflags); } static int jl_source_nssavalues(jl_code_info_t *src) JL_NOTSAFEPOINT { - return jl_is_long(src->ssavaluetypes) ? jl_unbox_long(src->ssavaluetypes) : jl_array_len(src->ssavaluetypes); + return jl_is_long(src->ssavaluetypes) ? jl_unbox_long(src->ssavaluetypes) : jl_array_nrows(src->ssavaluetypes); } static void eval_stmt_value(jl_value_t *stmt, interpreter_state *s) @@ -191,7 +191,7 @@ static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s) jl_error("access to invalid slot number"); jl_value_t *v = s->locals[n - 1]; if (v == NULL) - jl_undefined_var_error((jl_sym_t*)jl_array_ptr_ref(src->slotnames, n - 1)); + jl_undefined_var_error((jl_sym_t*)jl_array_ptr_ref(src->slotnames, n - 1), (jl_value_t*)jl_local_sym); return v; } if (jl_is_quotenode(e)) { @@ -217,7 +217,7 @@ static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s) return e; jl_expr_t *ex = (jl_expr_t*)e; jl_value_t **args = jl_array_ptr_data(ex->args); - size_t nargs = jl_array_len(ex->args); + size_t nargs = jl_array_nrows(ex->args); jl_sym_t *head = ex->head; if (head == jl_call_sym) { return do_call(args, nargs, s); @@ -268,7 +268,7 @@ static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s) if (var == jl_getfield_undefref_sym) jl_throw(jl_undefref_exception); else - jl_undefined_var_error(var); + jl_undefined_var_error(var, (jl_value_t*)jl_local_sym); } return jl_nothing; } @@ -307,7 +307,7 @@ static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s) if (s->sparam_vals && n <= jl_svec_len(s->sparam_vals)) { jl_value_t *sp = jl_svecref(s->sparam_vals, n - 1); if (jl_is_typevar(sp) && !s->preevaluation) - jl_undefined_var_error(((jl_tvar_t*)sp)->name); + jl_undefined_var_error(((jl_tvar_t*)sp)->name, (jl_value_t*)jl_static_parameter_sym); return sp; } // static parameter val unknown needs to be an error for ccall @@ -317,7 +317,7 @@ static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s) return jl_copy_ast(eval_value(args[0], s)); } else if (head == jl_exc_sym) { - return jl_current_exception(); + return jl_current_exception(jl_current_task); } else if (head == jl_boundscheck_sym) { return jl_true; @@ -351,6 +351,7 @@ static size_t eval_phi(jl_array_t *stmts, interpreter_state *s, size_t ns, size_ size_t from = s->ip; size_t ip = to; unsigned nphiblockstmts = 0; + unsigned last_phi = 0; for (ip = to; ip < ns; ip++) { jl_value_t *e = jl_array_ptr_ref(stmts, ip); if (!jl_is_phinode(e)) { @@ -361,9 +362,16 @@ static size_t eval_phi(jl_array_t *stmts, interpreter_state *s, size_t ns, size_ } // Everything else is allowed in the phi-block for implementation // convenience - fall through. + } else { + last_phi = nphiblockstmts + 1; } nphiblockstmts += 1; } + // Cut off the phi block at the last phi node. For global refs that are not + // actually in the phi block, we want to evaluate them in the regular interpreter + // loop instead to make sure exception state is set up properly in case they throw. + nphiblockstmts = last_phi; + ip = to + last_phi; if (nphiblockstmts) { jl_value_t **dest = &s->locals[jl_source_nslots(s->src) + to]; jl_value_t **phis; // = (jl_value_t**)alloca(sizeof(jl_value_t*) * nphiblockstmts); @@ -386,8 +394,8 @@ static size_t eval_phi(jl_array_t *stmts, interpreter_state *s, size_t ns, size_ // %2 = phi ... // %3 = phi (1)[1 => %a], (2)[2 => %b] // from = 1, to = closest = 2, i = 1 --> edge = 2, edge_from = 2, from = 2 - for (unsigned j = 0; j < jl_array_len(edges); ++j) { - size_t edge_from = ((int32_t*)jl_array_data(edges))[j]; // 1-indexed + for (unsigned j = 0; j < jl_array_nrows(edges); ++j) { + size_t edge_from = jl_array_data(edges, int32_t)[j]; // 1-indexed if (edge_from == from + 1) { if (edge == -1) edge = j; @@ -444,7 +452,7 @@ static size_t eval_phi(jl_array_t *stmts, interpreter_state *s, size_t ns, size_ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, int toplevel) { jl_handler_t __eh; - size_t ns = jl_array_len(stmts); + size_t ns = jl_array_nrows(stmts); jl_task_t *ct = jl_current_task; while (1) { @@ -481,6 +489,71 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, ssize_t id = ((jl_ssavalue_t*)phic)->id - 1; s->locals[jl_source_nslots(s->src) + id] = val; } + else if (jl_is_enternode(stmt)) { + jl_enter_handler(ct, &__eh); + // This is a bit tricky, but supports the implementation of PhiC nodes. + // They are conceptually slots, but the slot to store to doesn't get explicitly + // mentioned in the store (aka the "UpsilonNode") (this makes them integrate more + // nicely with the rest of the SSA representation). In a compiler, we would figure + // out which slot to store to at compile time when we encounter the statement. We + // can't quite do that here, but we do something similar: We scan the catch entry + // block (the only place where PhiC nodes may occur) to find all the Upsilons we + // can possibly encounter. Then, we remember which slot they store to (we abuse the + // SSA value result array for this purpose). TODO: We could do this only the first + // time we encounter a given enter. + size_t catch_ip = jl_enternode_catch_dest(stmt); + if (catch_ip) { + catch_ip -= 1; + while (catch_ip < ns) { + jl_value_t *phicnode = jl_array_ptr_ref(stmts, catch_ip); + if (!jl_is_phicnode(phicnode)) + break; + jl_array_t *values = (jl_array_t*)jl_fieldref_noalloc(phicnode, 0); + for (size_t i = 0; i < jl_array_nrows(values); ++i) { + jl_value_t *val = jl_array_ptr_ref(values, i); + assert(jl_is_ssavalue(val)); + size_t upsilon = ((jl_ssavalue_t*)val)->id - 1; + assert(jl_is_upsilonnode(jl_array_ptr_ref(stmts, upsilon))); + s->locals[jl_source_nslots(s->src) + upsilon] = jl_box_ssavalue(catch_ip + 1); + } + s->locals[jl_source_nslots(s->src) + catch_ip] = NULL; + catch_ip += 1; + } + // store current top of exception stack for restore in pop_exception. + } + s->locals[jl_source_nslots(s->src) + ip] = jl_box_ulong(jl_excstack_state(ct)); + if (jl_enternode_scope(stmt)) { + jl_value_t *old_scope = ct->scope; + JL_GC_PUSH1(&old_scope); + jl_value_t *new_scope = eval_value(jl_enternode_scope(stmt), s); + ct->scope = new_scope; + if (!jl_setjmp(__eh.eh_ctx, 1)) { + eval_body(stmts, s, next_ip, toplevel); + jl_unreachable(); + } + ct->scope = old_scope; + JL_GC_POP(); + } + else { + if (!jl_setjmp(__eh.eh_ctx, 1)) { + eval_body(stmts, s, next_ip, toplevel); + jl_unreachable(); + } + } + + if (s->continue_at) { // means we reached a :leave expression + jl_eh_restore_state_noexcept(ct, &__eh); + ip = s->continue_at; + s->continue_at = 0; + continue; + } + else { // a real exception + jl_eh_restore_state(ct, &__eh); + ip = catch_ip; + assert(jl_enternode_catch_dest(stmt) != 0); + continue; + } + } else if (jl_is_expr(stmt)) { // Most exprs are allowed to end a BB by fall through jl_sym_t *head = ((jl_expr_t*)stmt)->head; @@ -510,49 +583,6 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, JL_GC_POP(); } } - else if (head == jl_enter_sym) { - jl_enter_handler(&__eh); - // This is a bit tricky, but supports the implementation of PhiC nodes. - // They are conceptually slots, but the slot to store to doesn't get explicitly - // mentioned in the store (aka the "UpsilonNode") (this makes them integrate more - // nicely with the rest of the SSA representation). In a compiler, we would figure - // out which slot to store to at compile time when we encounter the statement. We - // can't quite do that here, but we do something similar: We scan the catch entry - // block (the only place where PhiC nodes may occur) to find all the Upsilons we - // can possibly encounter. Then, we remember which slot they store to (we abuse the - // SSA value result array for this purpose). TODO: We could do this only the first - // time we encounter a given enter. - size_t catch_ip = jl_unbox_long(jl_exprarg(stmt, 0)) - 1; - while (catch_ip < ns) { - jl_value_t *phicnode = jl_array_ptr_ref(stmts, catch_ip); - if (!jl_is_phicnode(phicnode)) - break; - jl_array_t *values = (jl_array_t*)jl_fieldref_noalloc(phicnode, 0); - for (size_t i = 0; i < jl_array_len(values); ++i) { - jl_value_t *val = jl_array_ptr_ref(values, i); - assert(jl_is_ssavalue(val)); - size_t upsilon = ((jl_ssavalue_t*)val)->id - 1; - assert(jl_is_upsilonnode(jl_array_ptr_ref(stmts, upsilon))); - s->locals[jl_source_nslots(s->src) + upsilon] = jl_box_ssavalue(catch_ip + 1); - } - s->locals[jl_source_nslots(s->src) + catch_ip] = NULL; - catch_ip += 1; - } - // store current top of exception stack for restore in pop_exception. - s->locals[jl_source_nslots(s->src) + ip] = jl_box_ulong(jl_excstack_state()); - if (!jl_setjmp(__eh.eh_ctx, 1)) { - return eval_body(stmts, s, next_ip, toplevel); - } - else if (s->continue_at) { // means we reached a :leave expression - ip = s->continue_at; - s->continue_at = 0; - continue; - } - else { // a real exception - ip = catch_ip; - continue; - } - } else if (head == jl_leave_sym) { int hand_n_leave = 0; for (int i = 0; i < jl_expr_nargs(stmt); ++i) { @@ -567,11 +597,11 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, } if (hand_n_leave > 0) { assert(hand_n_leave > 0); - // equivalent to jl_pop_handler(hand_n_leave), but retaining eh for longjmp: + // equivalent to jl_pop_handler(hand_n_leave), longjmping + // to the :enter code above instead, which handles cleanup jl_handler_t *eh = ct->eh; while (--hand_n_leave > 0) eh = eh->prev; - jl_eh_restore_state(eh); // leave happens during normal control flow, but we must // longjmp to pop the eval_body call for each enter. s->continue_at = next_ip; @@ -581,7 +611,7 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, } else if (head == jl_pop_exception_sym) { size_t prev_state = jl_unbox_ulong(eval_value(jl_exprarg(stmt, 0), s)); - jl_restore_excstack(prev_state); + jl_restore_excstack(ct, prev_state); } else if (toplevel) { if (head == jl_method_sym && jl_expr_nargs(stmt) > 1) { @@ -675,7 +705,7 @@ jl_code_info_t *jl_code_for_interpreter(jl_method_instance_t *mi, size_t world) } } if (!src || !jl_is_code_info(src)) { - jl_error("source missing for method called in interpreter"); + jl_throw(jl_new_struct(jl_missingcodeerror_type, (jl_value_t*)mi)); } return src; } @@ -730,7 +760,19 @@ JL_DLLEXPORT const jl_callptr_t jl_fptr_interpret_call_addr = &jl_fptr_interpret jl_value_t *jl_interpret_opaque_closure(jl_opaque_closure_t *oc, jl_value_t **args, size_t nargs) { jl_method_t *source = oc->source; - jl_code_info_t *code = jl_uncompress_ir(source, NULL, (jl_value_t*)source->source); + jl_code_info_t *code = NULL; + if (source->source) { + code = jl_uncompress_ir(source, NULL, (jl_value_t*)source->source); + } + else { + // OC constructed from optimized IR. It'll have a single specialization with optimized code + // in it that we'll try to interpret. + jl_svec_t *specializations = (jl_svec_t*)jl_atomic_load_relaxed(&source->specializations); + assert(jl_is_method_instance(specializations)); + jl_method_instance_t *mi = (jl_method_instance_t *)specializations; + jl_code_instance_t *ci = jl_atomic_load_relaxed(&mi->cache); + code = jl_uncompress_ir(source, ci, jl_atomic_load_relaxed(&ci->inferred)); + } interpreter_state *s; unsigned nroots = jl_source_nslots(code) + jl_source_nssavalues(code) + 2; jl_task_t *ct = jl_current_task; diff --git a/src/intrinsics.cpp b/src/intrinsics.cpp index e8a7c0f333c12..c2450d359be45 100644 --- a/src/intrinsics.cpp +++ b/src/intrinsics.cpp @@ -15,9 +15,9 @@ STATISTIC(EmittedCoercedUnboxes, "Number of unbox coercions emitted"); STATISTIC(EmittedUnboxes, "Number of unboxes emitted"); STATISTIC(EmittedRuntimeCalls, "Number of runtime intrinsic calls emitted"); STATISTIC(EmittedIntrinsics, "Number of intrinsic calls emitted"); -STATISTIC(Emitted_arraylen, "Number of arraylen calls emitted"); STATISTIC(Emitted_pointerref, "Number of pointerref calls emitted"); STATISTIC(Emitted_pointerset, "Number of pointerset calls emitted"); +STATISTIC(Emitted_pointerarith, "Number of pointer arithmetic calls emitted"); STATISTIC(Emitted_atomic_fence, "Number of atomic_fence calls emitted"); STATISTIC(Emitted_atomic_pointerref, "Number of atomic_pointerref calls emitted"); STATISTIC(Emitted_atomic_pointerop, "Number of atomic_pointerop calls emitted"); @@ -174,12 +174,7 @@ static Type *INTT(Type *t, const DataLayout &DL) static Value *uint_cnvt(jl_codectx_t &ctx, Type *to, Value *x) { - Type *t = x->getType(); - if (t == to) - return x; - if (to->getPrimitiveSizeInBits() < x->getType()->getPrimitiveSizeInBits()) - return ctx.builder.CreateTrunc(x, to); - return ctx.builder.CreateZExt(x, to); + return ctx.builder.CreateZExtOrTrunc(x, to); } static Constant *julia_const_to_llvm(jl_codectx_t &ctx, const void *ptr, jl_datatype_t *bt) @@ -245,8 +240,8 @@ static Constant *julia_const_to_llvm(jl_codectx_t &ctx, const void *ptr, jl_data if (jl_is_uniontype(ft)) { // compute the same type layout as julia_struct_to_llvm size_t fsz = 0, al = 0; - (void)jl_islayout_inline(ft, &fsz, &al); - fsz = jl_field_size(bt, i); + (void)jl_islayout_inline(ft, &fsz, &al); // compute al + fsz = jl_field_size(bt, i); // get LLT_ALIGN(fsz+1,al) uint8_t sel = ((const uint8_t*)ptr)[offs + fsz - 1]; jl_value_t *active_ty = jl_nth_union_component(ft, sel); size_t active_sz = jl_datatype_size(active_ty); @@ -318,25 +313,90 @@ static Constant *julia_const_to_llvm(jl_codectx_t &ctx, jl_value_t *e) return julia_const_to_llvm(ctx, e, (jl_datatype_t*)bt); } +static Constant *undef_value_for_type(Type *T) { + auto tracked = CountTrackedPointers(T); + Constant *undef; + if (tracked.count) + // make sure gc pointers (including ptr_phi of union-split) are initialized to NULL + undef = Constant::getNullValue(T); + else + undef = UndefValue::get(T); + return undef; +} + +// rebuild a struct type with any i1 Bool (e.g. the llvmcall type) widened to i8 (the native size for memcpy) +static Type *zext_struct_type(Type *T) +{ + if (auto *AT = dyn_cast(T)) { + return ArrayType::get(AT->getElementType(), AT->getNumElements()); + } + else if (auto *ST = dyn_cast(T)) { + SmallVector Elements(ST->element_begin(), ST->element_end()); + for (size_t i = 0; i < Elements.size(); i++) { + Elements[i] = zext_struct_type(Elements[i]); + } + return StructType::get(ST->getContext(), Elements, ST->isPacked()); + } + else if (auto *VT = dyn_cast(T)) { + return VectorType::get(zext_struct_type(VT->getElementType()), VT); + } + else if (auto *IT = dyn_cast(T)) { + unsigned BitWidth = IT->getBitWidth(); + if (alignTo(BitWidth, 8) != BitWidth) + return IntegerType::get(IT->getContext(), alignTo(BitWidth, 8)); + } + return T; +} + +// rebuild a struct with any i1 Bool (e.g. the llvmcall type) widened to i8 (the native size for memcpy) +static Value *zext_struct_helper(jl_codectx_t &ctx, Value *V, Type *T2) +{ + Type *T = V->getType(); + if (T == T2) + return V; + if (auto *AT = dyn_cast(T2)) { + Value *V2 = undef_value_for_type(AT); + for (size_t i = 0; i < AT->getNumElements(); i++) { + Value *E = zext_struct_helper(ctx, ctx.builder.CreateExtractValue(V, i), AT->getElementType()); + V2 = ctx.builder.CreateInsertValue(V2, E, i); + } + return V2; + } + else if (auto *ST = dyn_cast(T2)) { + Value *V2 = undef_value_for_type(ST); + for (size_t i = 0; i < ST->getNumElements(); i++) { + Value *E = zext_struct_helper(ctx, ctx.builder.CreateExtractValue(V, i), ST->getElementType(i)); + V2 = ctx.builder.CreateInsertValue(V2, E, i); + } + return V2; + } + else if (T2->isIntegerTy() || T2->isVectorTy()) { + return ctx.builder.CreateZExt(V, T2); + } + return V; +} + +static Value *zext_struct(jl_codectx_t &ctx, Value *V) +{ + return zext_struct_helper(ctx, V, zext_struct_type(V->getType())); +} + static Value *emit_unboxed_coercion(jl_codectx_t &ctx, Type *to, Value *unboxed) { + if (unboxed->getType() == to) + return unboxed; + if (CastInst::castIsValid(Instruction::Trunc, unboxed, to)) + return ctx.builder.CreateTrunc(unboxed, to); + unboxed = zext_struct(ctx, unboxed); Type *ty = unboxed->getType(); if (ty == to) return unboxed; bool frompointer = ty->isPointerTy(); bool topointer = to->isPointerTy(); const DataLayout &DL = jl_Module->getDataLayout(); - if (ty->isIntegerTy(1) && to->isIntegerTy(8)) { - // bools may be stored internally as int8 - unboxed = ctx.builder.CreateZExt(unboxed, to); - } - else if (ty->isIntegerTy(8) && to->isIntegerTy(1)) { - // bools may be stored internally as int8 - unboxed = ctx.builder.CreateTrunc(unboxed, to); - } - else if (ty->isVoidTy() || DL.getTypeSizeInBits(ty) != DL.getTypeSizeInBits(to)) { + if (ty->isVoidTy() || DL.getTypeSizeInBits(ty) != DL.getTypeSizeInBits(to)) { // this can happen in dead code - //emit_unreachable(ctx); + CreateTrap(ctx.builder); return UndefValue::get(to); } if (frompointer && topointer) { @@ -381,7 +441,7 @@ static Value *emit_unbox(jl_codectx_t &ctx, Type *to, const jl_cgval_t &x, jl_va if (type_is_ghost(to)) { return NULL; } - //emit_unreachable(ctx); + CreateTrap(ctx.builder); return UndefValue::get(to); // type mismatch error } @@ -447,17 +507,9 @@ static void emit_unbox_store(jl_codectx_t &ctx, const jl_cgval_t &x, Value *dest return; } - Value *unboxed = nullptr; - if (!x.ispointer()) { // already unboxed, but sometimes need conversion - unboxed = x.V; - assert(unboxed); - } - - // bools stored as int8, but can be narrowed to int1 often - if (x.typ == (jl_value_t*)jl_bool_type) - unboxed = emit_unbox(ctx, getInt8Ty(ctx.builder.getContext()), x, (jl_value_t*)jl_bool_type); - - if (unboxed) { + if (!x.ispointer()) { // already unboxed, but sometimes need conversion (e.g. f32 -> i32) + assert(x.V); + Value *unboxed = zext_struct(ctx, x.V); Type *dest_ty = unboxed->getType()->getPointerTo(); if (dest->getType() != dest_ty) dest = emit_bitcast(ctx, dest, dest_ty); @@ -469,7 +521,7 @@ static void emit_unbox_store(jl_codectx_t &ctx, const jl_cgval_t &x, Value *dest } Value *src = data_pointer(ctx, x); - emit_memcpy(ctx, dest, jl_aliasinfo_t::fromTBAA(ctx, tbaa_dest), src, jl_aliasinfo_t::fromTBAA(ctx, x.tbaa), jl_datatype_size(x.typ), alignment, alignment, isVolatile); + emit_memcpy(ctx, dest, jl_aliasinfo_t::fromTBAA(ctx, tbaa_dest), src, jl_aliasinfo_t::fromTBAA(ctx, x.tbaa), jl_datatype_size(x.typ), alignment, julia_alignment(x.typ), isVolatile); } static jl_datatype_t *staticeval_bitstype(const jl_cgval_t &targ) @@ -484,7 +536,7 @@ static jl_datatype_t *staticeval_bitstype(const jl_cgval_t &targ) return NULL; } -static jl_cgval_t emit_runtime_call(jl_codectx_t &ctx, JL_I::intrinsic f, const jl_cgval_t *argv, size_t nargs) +static jl_cgval_t emit_runtime_call(jl_codectx_t &ctx, JL_I::intrinsic f, ArrayRef argv, size_t nargs) { Function *func = prepare_call(runtime_func()[f]); SmallVector argvalues(nargs); @@ -496,7 +548,7 @@ static jl_cgval_t emit_runtime_call(jl_codectx_t &ctx, JL_I::intrinsic f, const } // put a bits type tag on some value (despite the name, this doesn't necessarily actually change anything about the value however) -static jl_cgval_t generic_bitcast(jl_codectx_t &ctx, const jl_cgval_t *argv) +static jl_cgval_t generic_bitcast(jl_codectx_t &ctx, ArrayRef argv) { // Give the arguments names // const jl_cgval_t &bt_value = argv[0]; @@ -537,7 +589,7 @@ static jl_cgval_t generic_bitcast(jl_codectx_t &ctx, const jl_cgval_t *argv) } else { Value *size = emit_datatype_size(ctx, typ); - auto sizecheck = ctx.builder.CreateICmpEQ(size, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), nb)); + auto sizecheck = ctx.builder.CreateICmpEQ(size, ConstantInt::get(size->getType(), nb)); setName(ctx.emission_context, sizecheck, "sizecheck"); error_unless(ctx, sizecheck, @@ -574,10 +626,14 @@ static jl_cgval_t generic_bitcast(jl_codectx_t &ctx, const jl_cgval_t *argv) vx = ctx.builder.CreateZExt(vx, llvmt); } else if (vxt->isPointerTy() && !llvmt->isPointerTy()) { vx = ctx.builder.CreatePtrToInt(vx, llvmt); - setName(ctx.emission_context, vx, "bitcast_coercion"); + if (isa(vx) && !vx->hasName()) + // CreatePtrToInt may undo an IntToPtr + setName(ctx.emission_context, vx, "bitcast_coercion"); } else if (!vxt->isPointerTy() && llvmt->isPointerTy()) { vx = emit_inttoptr(ctx, vx, llvmt); - setName(ctx.emission_context, vx, "bitcast_coercion"); + if (isa(vx) && !vx->hasName()) + // emit_inttoptr may undo an PtrToInt + setName(ctx.emission_context, vx, "bitcast_coercion"); } else { vx = emit_bitcast(ctx, vx, llvmt); setName(ctx.emission_context, vx, "bitcast_coercion"); @@ -588,7 +644,8 @@ static jl_cgval_t generic_bitcast(jl_codectx_t &ctx, const jl_cgval_t *argv) return mark_julia_type(ctx, vx, false, bt); } else { - Value *box = emit_allocobj(ctx, nb, bt_value_rt); + unsigned align = sizeof(void*); // Allocations are at least pointer aligned + Value *box = emit_allocobj(ctx, nb, bt_value_rt, true, align); setName(ctx.emission_context, box, "bitcast_box"); init_bits_value(ctx, box, vx, ctx.tbaa().tbaa_immut); return mark_julia_type(ctx, box, true, bt->name->wrapper); @@ -598,7 +655,7 @@ static jl_cgval_t generic_bitcast(jl_codectx_t &ctx, const jl_cgval_t *argv) static jl_cgval_t generic_cast( jl_codectx_t &ctx, intrinsic f, Instruction::CastOps Op, - const jl_cgval_t *argv, bool toint, bool fromint) + ArrayRef argv, bool toint, bool fromint) { auto &TT = ctx.emission_context.TargetTriple; auto &DL = ctx.emission_context.DL; @@ -647,19 +704,20 @@ static jl_cgval_t generic_cast( else { Value *targ_rt = boxed(ctx, targ); emit_concretecheck(ctx, targ_rt, std::string(jl_intrinsic_name(f)) + ": target type not a leaf primitive type"); - Value *box = emit_allocobj(ctx, nb, targ_rt); + unsigned align = sizeof(void*); // Allocations are at least pointer aligned + Value *box = emit_allocobj(ctx, nb, targ_rt, true, align); setName(ctx.emission_context, box, "cast_box"); init_bits_value(ctx, box, ans, ctx.tbaa().tbaa_immut); return mark_julia_type(ctx, box, true, jlto->name->wrapper); } } -static jl_cgval_t emit_runtime_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) +static jl_cgval_t emit_runtime_pointerref(jl_codectx_t &ctx, ArrayRef argv) { return emit_runtime_call(ctx, pointerref, argv, 3); } -static jl_cgval_t emit_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) +static jl_cgval_t emit_pointerref(jl_codectx_t &ctx, ArrayRef argv) { const jl_cgval_t &e = argv[0]; const jl_cgval_t &i = argv[1]; @@ -688,7 +746,8 @@ static jl_cgval_t emit_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) if (ety == (jl_value_t*)jl_any_type) { Value *thePtr = emit_unbox(ctx, ctx.types().T_pprjlvalue, e, e.typ); - setName(ctx.emission_context, thePtr, "unbox_any_ptr"); + if (isa(thePtr) && !thePtr->hasName()) + setName(ctx.emission_context, thePtr, "unbox_any_ptr"); LoadInst *load = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, thePtr, im1), Align(align_nb)); setName(ctx.emission_context, load, "any_unbox"); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_data); @@ -698,7 +757,7 @@ static jl_cgval_t emit_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) else if (!deserves_stack(ety)) { assert(jl_is_datatype(ety)); uint64_t size = jl_datatype_size(ety); - Value *strct = emit_allocobj(ctx, (jl_datatype_t*)ety); + Value *strct = emit_allocobj(ctx, (jl_datatype_t*)ety, true); setName(ctx.emission_context, strct, "pointerref_box"); im1 = ctx.builder.CreateMul(im1, ConstantInt::get(ctx.types().T_size, LLT_ALIGN(size, jl_datatype_align(ety)))); @@ -716,7 +775,8 @@ static jl_cgval_t emit_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) assert(!isboxed); if (!type_is_ghost(ptrty)) { Value *thePtr = emit_unbox(ctx, ptrty->getPointerTo(), e, e.typ); - auto load = typed_load(ctx, thePtr, im1, ety, ctx.tbaa().tbaa_data, nullptr, isboxed, AtomicOrdering::NotAtomic, false, align_nb); + thePtr = ctx.builder.CreateInBoundsGEP(ptrty, thePtr, im1); + auto load = typed_load(ctx, thePtr, nullptr, ety, ctx.tbaa().tbaa_data, nullptr, isboxed, AtomicOrdering::NotAtomic, false, align_nb); setName(ctx.emission_context, load.V, "pointerref"); return load; } @@ -726,13 +786,13 @@ static jl_cgval_t emit_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) } } -static jl_cgval_t emit_runtime_pointerset(jl_codectx_t &ctx, jl_cgval_t *argv) +static jl_cgval_t emit_runtime_pointerset(jl_codectx_t &ctx, ArrayRef argv) { return emit_runtime_call(ctx, pointerset, argv, 4); } // e[i] = x -static jl_cgval_t emit_pointerset(jl_codectx_t &ctx, jl_cgval_t *argv) +static jl_cgval_t emit_pointerset(jl_codectx_t &ctx, ArrayRef argv) { const jl_cgval_t &e = argv[0]; const jl_cgval_t &x = argv[1]; @@ -791,14 +851,43 @@ static jl_cgval_t emit_pointerset(jl_codectx_t &ctx, jl_cgval_t *argv) assert(!isboxed); if (!type_is_ghost(ptrty)) { thePtr = emit_unbox(ctx, ptrty->getPointerTo(), e, e.typ); - typed_store(ctx, thePtr, im1, x, jl_cgval_t(), ety, ctx.tbaa().tbaa_data, nullptr, nullptr, isboxed, - AtomicOrdering::NotAtomic, AtomicOrdering::NotAtomic, align_nb, false, true, false, false, false, false, nullptr, ""); + thePtr = ctx.builder.CreateInBoundsGEP(ptrty, thePtr, im1); + typed_store(ctx, thePtr, x, jl_cgval_t(), ety, ctx.tbaa().tbaa_data, nullptr, nullptr, isboxed, + AtomicOrdering::NotAtomic, AtomicOrdering::NotAtomic, align_nb, nullptr, true, false, false, false, false, false, nullptr, "atomic_pointerset", nullptr, nullptr); } } return e; } -static jl_cgval_t emit_atomicfence(jl_codectx_t &ctx, jl_cgval_t *argv) +// ptr + offset +// ptr - offset +static jl_cgval_t emit_pointerarith(jl_codectx_t &ctx, intrinsic f, + ArrayRef argv) +{ + jl_value_t *ptrtyp = argv[0].typ; + jl_value_t *offtyp = argv[1].typ; + if (!jl_is_cpointer_type(ptrtyp) || offtyp != (jl_value_t *)jl_ulong_type) + return emit_runtime_call(ctx, f, argv, argv.size()); + assert(f == add_ptr || f == sub_ptr); + + Value *ptr = emit_unbox(ctx, ctx.types().T_ptr, argv[0], ptrtyp); + Value *off = emit_unbox(ctx, ctx.types().T_size, argv[1], offtyp); + if (f == sub_ptr) + off = ctx.builder.CreateNeg(off); + Value *ans = ctx.builder.CreateGEP(getInt8Ty(ctx.builder.getContext()), ptr, off); + + if (jl_is_concrete_type(ptrtyp)) { + return mark_julia_type(ctx, ans, false, ptrtyp); + } + else { + Value *box = emit_allocobj(ctx, (jl_datatype_t *)ptrtyp, true); + setName(ctx.emission_context, box, "ptr_box"); + init_bits_value(ctx, box, ans, ctx.tbaa().tbaa_immut); + return mark_julia_type(ctx, box, true, (jl_datatype_t *)ptrtyp); + } +} + +static jl_cgval_t emit_atomicfence(jl_codectx_t &ctx, ArrayRef argv) { const jl_cgval_t &ord = argv[0]; if (ord.constant && jl_is_symbol(ord.constant)) { @@ -814,7 +903,7 @@ static jl_cgval_t emit_atomicfence(jl_codectx_t &ctx, jl_cgval_t *argv) return emit_runtime_call(ctx, atomic_fence, argv, 1); } -static jl_cgval_t emit_atomic_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) +static jl_cgval_t emit_atomic_pointerref(jl_codectx_t &ctx, ArrayRef argv) { const jl_cgval_t &e = argv[0]; const jl_cgval_t &ord = argv[1]; @@ -854,7 +943,7 @@ static jl_cgval_t emit_atomic_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) if (!deserves_stack(ety)) { assert(jl_is_datatype(ety)); - Value *strct = emit_allocobj(ctx, (jl_datatype_t*)ety); + Value *strct = emit_allocobj(ctx, (jl_datatype_t*)ety, true); setName(ctx.emission_context, strct, "atomic_pointerref_box"); Value *thePtr = emit_unbox(ctx, getInt8PtrTy(ctx.builder.getContext()), e, e.typ); Type *loadT = Type::getIntNTy(ctx.builder.getContext(), nb * 8); @@ -892,7 +981,7 @@ static jl_cgval_t emit_atomic_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) // e[i] <= x (swap) // e[i] y => x (replace) // 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) +static jl_cgval_t emit_atomic_pointerop(jl_codectx_t &ctx, intrinsic f, ArrayRef argv, int nargs, const jl_cgval_t *modifyop) { bool issetfield = f == atomic_pointerset; bool isreplacefield = f == atomic_pointerreplace; @@ -929,8 +1018,8 @@ static jl_cgval_t emit_atomic_pointerop(jl_codectx_t &ctx, intrinsic f, const jl // n.b.: the expected value (y) must be rooted, but not the others Value *thePtr = emit_unbox(ctx, ctx.types().T_pprjlvalue, e, e.typ); bool isboxed = true; - jl_cgval_t ret = typed_store(ctx, thePtr, nullptr, x, y, ety, ctx.tbaa().tbaa_data, nullptr, nullptr, isboxed, - llvm_order, llvm_failorder, sizeof(jl_value_t*), false, issetfield, isreplacefield, isswapfield, ismodifyfield, false, modifyop, "atomic_pointermodify"); + jl_cgval_t ret = typed_store(ctx, thePtr, x, y, ety, ctx.tbaa().tbaa_data, nullptr, nullptr, isboxed, + llvm_order, llvm_failorder, sizeof(jl_value_t*), nullptr, issetfield, isreplacefield, isswapfield, ismodifyfield, false, false, modifyop, "atomic_pointermodify", nullptr, nullptr); if (issetfield) ret = e; return ret; @@ -968,8 +1057,8 @@ static jl_cgval_t emit_atomic_pointerop(jl_codectx_t &ctx, intrinsic f, const jl thePtr = emit_unbox(ctx, ptrty->getPointerTo(), e, e.typ); else thePtr = nullptr; // could use any value here, since typed_store will not use it - jl_cgval_t ret = typed_store(ctx, thePtr, nullptr, x, y, ety, ctx.tbaa().tbaa_data, nullptr, nullptr, isboxed, - llvm_order, llvm_failorder, nb, false, issetfield, isreplacefield, isswapfield, ismodifyfield, false, modifyop, "atomic_pointermodify"); + jl_cgval_t ret = typed_store(ctx, thePtr, x, y, ety, ctx.tbaa().tbaa_data, nullptr, nullptr, isboxed, + llvm_order, llvm_failorder, nb, nullptr, issetfield, isreplacefield, isswapfield, ismodifyfield, false, false, modifyop, "atomic_pointermodify", nullptr, nullptr); if (issetfield) ret = e; return ret; @@ -1029,7 +1118,7 @@ struct math_builder { } }; -static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **argvalues, size_t nargs, +static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, ArrayRef argvalues, size_t nargs, jl_datatype_t **newtyp, jl_value_t *xtyp); @@ -1197,7 +1286,7 @@ static jl_cgval_t emit_intrinsic(jl_codectx_t &ctx, intrinsic f, jl_value_t **ar for (size_t i = 0; i < nargs; ++i) { jl_cgval_t arg = emit_expr(ctx, args[i + 1]); if (arg.typ == jl_bottom_type) { - // intrinsics generally don't handle buttom values, so bail out early + // intrinsics generally don't handle bottom values, so bail out early return jl_cgval_t(); } argv[i] = arg; @@ -1207,84 +1296,82 @@ static jl_cgval_t emit_intrinsic(jl_codectx_t &ctx, intrinsic f, jl_value_t **ar // return emit_runtime_call(ctx, f, argv, nargs); switch (f) { - case arraylen: { - ++Emitted_arraylen; - assert(nargs == 1); - const jl_cgval_t &x = argv[0]; - jl_value_t *typ = jl_unwrap_unionall(x.typ); - if (!jl_is_datatype(typ) || ((jl_datatype_t*)typ)->name != jl_array_typename) - return emit_runtime_call(ctx, f, argv.data(), nargs); - return mark_julia_type(ctx, emit_arraylen(ctx, x), false, jl_long_type); - } case pointerref: ++Emitted_pointerref; assert(nargs == 3); - return emit_pointerref(ctx, argv.data()); + return emit_pointerref(ctx, argv); case pointerset: ++Emitted_pointerset; assert(nargs == 4); - return emit_pointerset(ctx, argv.data()); + return emit_pointerset(ctx, argv); + + case add_ptr: + case sub_ptr: + ++Emitted_pointerarith; + assert(nargs == 2); + return emit_pointerarith(ctx, f, argv); + case atomic_fence: ++Emitted_atomic_fence; assert(nargs == 1); - return emit_atomicfence(ctx, argv.data()); + return emit_atomicfence(ctx, argv); case atomic_pointerref: ++Emitted_atomic_pointerref; assert(nargs == 2); - return emit_atomic_pointerref(ctx, argv.data()); + return emit_atomic_pointerref(ctx, argv); case atomic_pointerset: case atomic_pointerswap: case atomic_pointermodify: case atomic_pointerreplace: ++Emitted_atomic_pointerop; - return emit_atomic_pointerop(ctx, f, argv.data(), nargs, nullptr); + return emit_atomic_pointerop(ctx, f, argv, nargs, nullptr); case bitcast: ++Emitted_bitcast; assert(nargs == 2); - return generic_bitcast(ctx, argv.data()); + return generic_bitcast(ctx, argv); case trunc_int: ++Emitted_trunc_int; assert(nargs == 2); - return generic_cast(ctx, f, Instruction::Trunc, argv.data(), true, true); + return generic_cast(ctx, f, Instruction::Trunc, argv, true, true); case sext_int: ++Emitted_sext_int; assert(nargs == 2); - return generic_cast(ctx, f, Instruction::SExt, argv.data(), true, true); + return generic_cast(ctx, f, Instruction::SExt, argv, true, true); case zext_int: ++Emitted_zext_int; assert(nargs == 2); - return generic_cast(ctx, f, Instruction::ZExt, argv.data(), true, true); + return generic_cast(ctx, f, Instruction::ZExt, argv, true, true); case uitofp: ++Emitted_uitofp; assert(nargs == 2); - return generic_cast(ctx, f, Instruction::UIToFP, argv.data(), false, true); + return generic_cast(ctx, f, Instruction::UIToFP, argv, false, true); case sitofp: ++Emitted_sitofp; assert(nargs == 2); - return generic_cast(ctx, f, Instruction::SIToFP, argv.data(), false, true); + return generic_cast(ctx, f, Instruction::SIToFP, argv, false, true); case fptoui: ++Emitted_fptoui; assert(nargs == 2); - return generic_cast(ctx, f, Instruction::FPToUI, argv.data(), true, false); + return generic_cast(ctx, f, Instruction::FPToUI, argv, true, false); case fptosi: ++Emitted_fptosi; assert(nargs == 2); - return generic_cast(ctx, f, Instruction::FPToSI, argv.data(), true, false); + return generic_cast(ctx, f, Instruction::FPToSI, argv, true, false); case fptrunc: ++Emitted_fptrunc; assert(nargs == 2); - return generic_cast(ctx, f, Instruction::FPTrunc, argv.data(), false, false); + return generic_cast(ctx, f, Instruction::FPTrunc, argv, false, false); case fpext: ++Emitted_fpext; assert(nargs == 2); - return generic_cast(ctx, f, Instruction::FPExt, argv.data(), false, false); + return generic_cast(ctx, f, Instruction::FPExt, argv, false, false); case not_int: { ++Emitted_not_int; assert(nargs == 1); const jl_cgval_t &x = argv[0]; if (!jl_is_primitivetype(x.typ)) - return emit_runtime_call(ctx, f, argv.data(), nargs); + return emit_runtime_call(ctx, f, argv, nargs); Type *xt = INTT(bitstype_to_llvm(x.typ, ctx.builder.getContext(), true), DL); Value *from = emit_unbox(ctx, xt, x, x.typ); Value *ans = ctx.builder.CreateNot(from); @@ -1296,7 +1383,7 @@ static jl_cgval_t emit_intrinsic(jl_codectx_t &ctx, intrinsic f, jl_value_t **ar assert(nargs == 1); const jl_cgval_t &x = argv[0]; if (!x.constant || !jl_is_datatype(x.constant)) - return emit_runtime_call(ctx, f, argv.data(), nargs); + return emit_runtime_call(ctx, f, argv, nargs); jl_datatype_t *dt = (jl_datatype_t*) x.constant; // select the appropriated overloaded intrinsic @@ -1306,7 +1393,7 @@ static jl_cgval_t emit_intrinsic(jl_codectx_t &ctx, intrinsic f, jl_value_t **ar else if (dt == jl_float64_type) intr_name += "f64"; else - return emit_runtime_call(ctx, f, argv.data(), nargs); + return emit_runtime_call(ctx, f, argv, nargs); FunctionCallee intr = jl_Module->getOrInsertFunction(intr_name, getInt1Ty(ctx.builder.getContext())); auto ret = ctx.builder.CreateCall(intr); @@ -1319,14 +1406,14 @@ static jl_cgval_t emit_intrinsic(jl_codectx_t &ctx, intrinsic f, jl_value_t **ar // verify argument types if (!jl_is_primitivetype(xinfo.typ)) - return emit_runtime_call(ctx, f, argv.data(), nargs); + return emit_runtime_call(ctx, f, argv, nargs); Type *xtyp = bitstype_to_llvm(xinfo.typ, ctx.builder.getContext(), true); if (float_func()[f]) xtyp = FLOATT(xtyp); else xtyp = INTT(xtyp, DL); if (!xtyp) - return emit_runtime_call(ctx, f, argv.data(), nargs); + return emit_runtime_call(ctx, f, argv, nargs); ////Bool are required to be in the range [0,1] ////so while they are represented as i8, ////the operations need to be done in mod 1 @@ -1342,13 +1429,13 @@ static jl_cgval_t emit_intrinsic(jl_codectx_t &ctx, intrinsic f, jl_value_t **ar if (f == shl_int || f == lshr_int || f == ashr_int) { if (!jl_is_primitivetype(argv[1].typ)) - return emit_runtime_call(ctx, f, argv.data(), nargs); + return emit_runtime_call(ctx, f, argv, nargs); argt[1] = INTT(bitstype_to_llvm(argv[1].typ, ctx.builder.getContext(), true), DL); } else { for (size_t i = 1; i < nargs; ++i) { if (xinfo.typ != argv[i].typ) - return emit_runtime_call(ctx, f, argv.data(), nargs); + return emit_runtime_call(ctx, f, argv, nargs); argt[i] = xtyp; } } @@ -1361,7 +1448,7 @@ static jl_cgval_t emit_intrinsic(jl_codectx_t &ctx, intrinsic f, jl_value_t **ar // call the intrinsic jl_value_t *newtyp = xinfo.typ; - Value *r = emit_untyped_intrinsic(ctx, f, argvalues.data(), nargs, (jl_datatype_t**)&newtyp, xinfo.typ); + Value *r = emit_untyped_intrinsic(ctx, f, argvalues, nargs, (jl_datatype_t**)&newtyp, xinfo.typ); // Turn Bool operations into mod 1 now, if needed if (newtyp == (jl_value_t*)jl_bool_type && !r->getType()->isIntegerTy(1)) r = ctx.builder.CreateTrunc(r, getInt1Ty(ctx.builder.getContext())); @@ -1371,7 +1458,7 @@ static jl_cgval_t emit_intrinsic(jl_codectx_t &ctx, intrinsic f, jl_value_t **ar assert(0 && "unreachable"); } -static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **argvalues, size_t nargs, +static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, ArrayRef argvalues, size_t nargs, jl_datatype_t **newtyp, jl_value_t *xtyp) { ++EmittedUntypedIntrinsics; @@ -1391,26 +1478,6 @@ static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **arg case srem_int: return ctx.builder.CreateSRem(x, y); case urem_int: return ctx.builder.CreateURem(x, y); - // LLVM will not fold ptrtoint+arithmetic+inttoptr to GEP. The reason for this - // has to do with alias analysis. When adding two integers, either one of them - // could be the pointer base. With getelementptr, it is clear which of the - // operands is the pointer base. We also have this information at the julia - // level. Thus, to not lose information, we need to have a separate intrinsic - // for pointer arithmetic which lowers to getelementptr. - case add_ptr: { - return ctx.builder.CreatePtrToInt( - ctx.builder.CreateGEP(getInt8Ty(ctx.builder.getContext()), - emit_inttoptr(ctx, x, getInt8PtrTy(ctx.builder.getContext())), y), t); - - } - - case sub_ptr: { - return ctx.builder.CreatePtrToInt( - ctx.builder.CreateGEP(getInt8Ty(ctx.builder.getContext()), - emit_inttoptr(ctx, x, getInt8PtrTy(ctx.builder.getContext())), ctx.builder.CreateNeg(y)), t); - - } - case neg_float: return math_builder(ctx)().CreateFNeg(x); case neg_float_fast: return math_builder(ctx, true)().CreateFNeg(x); case add_float: return math_builder(ctx)().CreateFAdd(x, y); @@ -1424,7 +1491,7 @@ static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **arg case fma_float: { assert(y->getType() == x->getType()); assert(z->getType() == y->getType()); - FunctionCallee fmaintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::fma, makeArrayRef(t)); + FunctionCallee fmaintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::fma, ArrayRef(t)); return ctx.builder.CreateCall(fmaintr, {x, y, z}); } case muladd_float: { @@ -1454,13 +1521,8 @@ static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **arg (f == checked_smul_int ? Intrinsic::smul_with_overflow : Intrinsic::umul_with_overflow))))); - FunctionCallee intr = Intrinsic::getDeclaration(jl_Module, intr_id, makeArrayRef(t)); - Value *res = ctx.builder.CreateCall(intr, {x, y}); - Value *val = ctx.builder.CreateExtractValue(res, ArrayRef(0)); - setName(ctx.emission_context, val, "checked"); - Value *obit = ctx.builder.CreateExtractValue(res, ArrayRef(1)); - setName(ctx.emission_context, obit, "overflow"); - Value *obyte = ctx.builder.CreateZExt(obit, getInt8Ty(ctx.builder.getContext())); + FunctionCallee intr = Intrinsic::getDeclaration(jl_Module, intr_id, ArrayRef(t)); + Value *tupval = ctx.builder.CreateCall(intr, {x, y}); jl_value_t *params[2]; params[0] = xtyp; @@ -1468,10 +1530,6 @@ static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **arg jl_datatype_t *tuptyp = (jl_datatype_t*)jl_apply_tuple_type_v(params, 2); *newtyp = tuptyp; - Value *tupval; - tupval = UndefValue::get(julia_type_to_llvm(ctx, (jl_value_t*)tuptyp)); - tupval = ctx.builder.CreateInsertValue(tupval, val, ArrayRef(0)); - tupval = ctx.builder.CreateInsertValue(tupval, obyte, ArrayRef(1)); return tupval; } @@ -1574,30 +1632,30 @@ static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **arg } } case bswap_int: { - FunctionCallee bswapintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::bswap, makeArrayRef(t)); - return ctx.builder.CreateCall(bswapintr, x); + FunctionCallee bswapintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::bswap, ArrayRef(t)); //TODO: Move to deduction guides + return ctx.builder.CreateCall(bswapintr, x); // when we drop LLVM 15 } case ctpop_int: { - FunctionCallee ctpopintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::ctpop, makeArrayRef(t)); + FunctionCallee ctpopintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::ctpop, ArrayRef(t)); return ctx.builder.CreateCall(ctpopintr, x); } case ctlz_int: { - FunctionCallee ctlz = Intrinsic::getDeclaration(jl_Module, Intrinsic::ctlz, makeArrayRef(t)); + FunctionCallee ctlz = Intrinsic::getDeclaration(jl_Module, Intrinsic::ctlz, ArrayRef(t)); y = ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0); return ctx.builder.CreateCall(ctlz, {x, y}); } case cttz_int: { - FunctionCallee cttz = Intrinsic::getDeclaration(jl_Module, Intrinsic::cttz, makeArrayRef(t)); + FunctionCallee cttz = Intrinsic::getDeclaration(jl_Module, Intrinsic::cttz, ArrayRef(t)); y = ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0); return ctx.builder.CreateCall(cttz, {x, y}); } case abs_float: { - FunctionCallee absintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::fabs, makeArrayRef(t)); + FunctionCallee absintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::fabs, ArrayRef(t)); return ctx.builder.CreateCall(absintr, x); } case copysign_float: { - FunctionCallee copyintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::copysign, makeArrayRef(t)); + FunctionCallee copyintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::copysign, ArrayRef(t)); return ctx.builder.CreateCall(copyintr, {x, y}); } case flipsign_int: { @@ -1616,27 +1674,27 @@ static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **arg return ctx.builder.CreateXor(ctx.builder.CreateAdd(x, tmp), tmp); } case ceil_llvm: { - FunctionCallee ceilintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::ceil, makeArrayRef(t)); + FunctionCallee ceilintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::ceil, ArrayRef(t)); return ctx.builder.CreateCall(ceilintr, x); } case floor_llvm: { - FunctionCallee floorintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::floor, makeArrayRef(t)); + FunctionCallee floorintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::floor, ArrayRef(t)); return ctx.builder.CreateCall(floorintr, x); } case trunc_llvm: { - FunctionCallee truncintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::trunc, makeArrayRef(t)); + FunctionCallee truncintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::trunc, ArrayRef(t)); return ctx.builder.CreateCall(truncintr, x); } case rint_llvm: { - FunctionCallee rintintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::rint, makeArrayRef(t)); + FunctionCallee rintintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::rint, ArrayRef(t)); return ctx.builder.CreateCall(rintintr, x); } case sqrt_llvm: { - FunctionCallee sqrtintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::sqrt, makeArrayRef(t)); + FunctionCallee sqrtintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::sqrt, ArrayRef(t)); return ctx.builder.CreateCall(sqrtintr, x); } case sqrt_llvm_fast: { - FunctionCallee sqrtintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::sqrt, makeArrayRef(t)); + FunctionCallee sqrtintr = Intrinsic::getDeclaration(jl_Module, Intrinsic::sqrt, ArrayRef(t)); return math_builder(ctx, true)().CreateCall(sqrtintr, x); } diff --git a/src/intrinsics.h b/src/intrinsics.h index 93747faa74160..5b463e3bafe28 100644 --- a/src/intrinsics.h +++ b/src/intrinsics.h @@ -12,8 +12,6 @@ ADD_I(udiv_int, 2) \ ADD_I(srem_int, 2) \ ADD_I(urem_int, 2) \ - ADD_I(add_ptr, 2) \ - ADD_I(sub_ptr, 2) \ ADD_I(neg_float, 1) \ ADD_I(add_float, 2) \ ADD_I(sub_float, 2) \ @@ -86,6 +84,9 @@ ADD_I(rint_llvm, 1) \ ADD_I(sqrt_llvm, 1) \ ADD_I(sqrt_llvm_fast, 1) \ + /* pointer arithmetic */ \ + ADD_I(add_ptr, 2) \ + ADD_I(sub_ptr, 2) \ /* pointer access */ \ ADD_I(pointerref, 3) \ ADD_I(pointerset, 4) \ @@ -99,8 +100,6 @@ /* c interface */ \ ADD_I(cglobal, 2) \ ALIAS(llvmcall, llvmcall) \ - /* object access */ \ - ADD_I(arraylen, 1) \ /* cpu feature tests */ \ ADD_I(have_fma, 1) \ /* hidden intrinsics */ \ diff --git a/src/ircode.c b/src/ircode.c index 508326c96314a..2e16d1b5b2420 100644 --- a/src/ircode.c +++ b/src/ircode.c @@ -59,6 +59,7 @@ jl_value_t *jl_deser_symbol(uint8_t tag) // --- encoding --- +static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) JL_GC_DISABLED; #define jl_encode_value(s, v) jl_encode_value_((s), (jl_value_t*)(v), 0) static void tagged_root(rle_reference *rr, jl_ircode_state *s, int i) @@ -70,7 +71,7 @@ static void tagged_root(rle_reference *rr, jl_ircode_state *s, int i) static void literal_val_id(rle_reference *rr, jl_ircode_state *s, jl_value_t *v) JL_GC_DISABLED { jl_array_t *rs = s->method->roots; - int i, l = jl_array_len(rs); + int i, l = jl_array_nrows(rs); if (jl_is_symbol(v) || jl_is_concrete_type(v)) { for (i = 0; i < l; i++) { if (jl_array_ptr_ref(rs, i) == v) @@ -84,7 +85,7 @@ static void literal_val_id(rle_reference *rr, jl_ircode_state *s, jl_value_t *v) } } jl_add_method_root(s->method, jl_precompile_toplevel_module, v); - return tagged_root(rr, s, jl_array_len(rs) - 1); + return tagged_root(rr, s, jl_array_nrows(rs) - 1); } static void jl_encode_int32(jl_ircode_state *s, int32_t x) @@ -103,6 +104,8 @@ static void jl_encode_as_indexed_root(jl_ircode_state *s, jl_value_t *v) { rle_reference rr; + if (jl_is_string(v)) + v = jl_as_global_root(v, 1); literal_val_id(&rr, s, v); int id = rr.index; assert(id >= 0); @@ -121,6 +124,44 @@ static void jl_encode_as_indexed_root(jl_ircode_state *s, jl_value_t *v) } } +static void jl_encode_memory_slice(jl_ircode_state *s, jl_genericmemory_t *mem, size_t offset, size_t len) JL_GC_DISABLED +{ + jl_datatype_t *t = (jl_datatype_t*)jl_typetagof(mem); + size_t i; + const jl_datatype_layout_t *layout = t->layout; + if (layout->flags.arrayelem_isboxed) { + for (i = 0; i < len; i++) { + jl_value_t *e = jl_genericmemory_ptr_ref(mem, offset + i); + jl_encode_value(s, e); + } + } + else if (layout->first_ptr >= 0) { + uint16_t elsz = layout->size; + size_t j, np = layout->npointers; + const char *data = (const char*)mem->ptr + offset * elsz; + for (i = 0; i < len; i++) { + const char *start = data; + for (j = 0; j < np; j++) { + uint32_t ptr = jl_ptr_offset(t, j); + const jl_value_t *const *fld = &((const jl_value_t *const *)data)[ptr]; + if ((const char*)fld != start) + ios_write(s->s, start, (const char*)fld - start); + JL_GC_PROMISE_ROOTED(*fld); + jl_encode_value(s, *fld); + start = (const char*)&fld[1]; + } + data += elsz; + if (data != start) + ios_write(s->s, start, data - start); + } + } + else { + ios_write(s->s, (char*)mem->ptr + offset * layout->size, len * layout->size); + if (layout->flags.arrayelem_isunion) + ios_write(s->s, jl_genericmemory_typetagdata(mem) + offset, len); + } +} + static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) JL_GC_DISABLED { size_t i; @@ -203,7 +244,7 @@ static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) } else if (jl_is_expr(v)) { jl_expr_t *e = (jl_expr_t*)v; - size_t l = jl_array_len(e->args); + size_t l = jl_array_nrows(e->args); if (e->head == jl_call_sym) { if (l == 2) { write_uint8(s->s, TAG_CALL1); @@ -235,31 +276,31 @@ static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) else if (jl_is_phinode(v)) { jl_array_t *edges = (jl_array_t*)jl_fieldref_noalloc(v, 0); jl_array_t *values = (jl_array_t*)jl_fieldref_noalloc(v, 1); - size_t l = jl_array_len(edges); - if (l <= 255 && jl_array_len(values) == l) { + size_t l = jl_array_nrows(edges); + if (l <= 255 && jl_array_nrows(values) == l) { write_uint8(s->s, TAG_PHINODE); write_uint8(s->s, (uint8_t)l); } else { write_uint8(s->s, TAG_LONG_PHINODE); write_int32(s->s, l); - write_int32(s->s, jl_array_len(values)); + write_int32(s->s, jl_array_nrows(values)); } for (i = 0; i < l; i++) { - int32_t e = ((int32_t*)jl_array_data(edges))[i]; + int32_t e = jl_array_data(edges, int32_t)[i]; if (e <= 20) jl_encode_value(s, jl_box_int32(e)); else jl_encode_int32(s, e); } - l = jl_array_len(values); + l = jl_array_nrows(values); for (i = 0; i < l; i++) { jl_encode_value(s, jl_array_ptr_ref(values, i)); } } else if (jl_is_phicnode(v)) { jl_array_t *values = (jl_array_t*)jl_fieldref_noalloc(v, 0); - size_t l = jl_array_len(values); + size_t l = jl_array_nrows(values); if (l <= 255) { write_uint8(s->s, TAG_PHICNODE); write_uint8(s->s, (uint8_t)l); @@ -281,6 +322,11 @@ static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) jl_encode_value(s, jl_get_nth_field(v, 0)); jl_encode_value(s, jl_get_nth_field(v, 1)); } + else if (jl_is_enternode(v)) { + write_uint8(s->s, TAG_ENTERNODE); + jl_encode_value(s, jl_get_nth_field(v, 0)); + jl_encode_value(s, jl_get_nth_field(v, 1)); + } else if (jl_is_argument(v)) { write_uint8(s->s, TAG_ARGUMENT); jl_encode_value(s, jl_get_nth_field(v, 0)); @@ -321,11 +367,6 @@ static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) write_uint8(s->s, TAG_UINT8); write_int8(s->s, *(int8_t*)jl_data_ptr(v)); } - else if (jl_typetagis(v, jl_lineinfonode_type)) { - write_uint8(s->s, TAG_LINEINFO); - for (i = 0; i < jl_datatype_nfields(jl_lineinfonode_type); i++) - jl_encode_value(s, jl_get_nth_field(v, i)); - } else if (((jl_datatype_t*)jl_typeof(v))->instance == v) { write_uint8(s->s, TAG_SINGLETON); jl_encode_value(s, jl_typeof(v)); @@ -337,52 +378,34 @@ static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) } else if (as_literal && jl_is_array(v)) { jl_array_t *ar = (jl_array_t*)v; - jl_value_t *et = jl_tparam0(jl_typeof(ar)); - int isunion = jl_is_uniontype(et); - if (ar->flags.ndims == 1 && ar->elsize <= 0x1f) { + if (jl_array_ndims(ar) == 1) { write_uint8(s->s, TAG_ARRAY1D); - write_uint8(s->s, (ar->flags.ptrarray << 7) | (ar->flags.hasptr << 6) | (isunion << 5) | (ar->elsize & 0x1f)); } else { write_uint8(s->s, TAG_ARRAY); - write_uint16(s->s, ar->flags.ndims); - write_uint16(s->s, (ar->flags.ptrarray << 15) | (ar->flags.hasptr << 14) | (isunion << 13) | (ar->elsize & 0x1fff)); + write_uint16(s->s, jl_array_ndims(ar)); } - for (i = 0; i < ar->flags.ndims; i++) - jl_encode_value(s, jl_box_long(jl_array_dim(ar,i))); + for (i = 0; i < jl_array_ndims(ar); i++) + jl_encode_value(s, jl_box_long(jl_array_dim(ar, i))); jl_encode_value(s, jl_typeof(ar)); size_t l = jl_array_len(ar); - if (ar->flags.ptrarray) { - for (i = 0; i < l; i++) { - jl_value_t *e = jl_array_ptr_ref(v, i); - jl_encode_value(s, e); - } - } - else if (ar->flags.hasptr) { - const char *data = (const char*)jl_array_data(ar); - uint16_t elsz = ar->elsize; - size_t j, np = ((jl_datatype_t*)et)->layout->npointers; - for (i = 0; i < l; i++) { - const char *start = data; - for (j = 0; j < np; j++) { - uint32_t ptr = jl_ptr_offset((jl_datatype_t*)et, j); - const jl_value_t *const *fld = &((const jl_value_t *const *)data)[ptr]; - if ((const char*)fld != start) - ios_write(s->s, start, (const char*)fld - start); - JL_GC_PROMISE_ROOTED(*fld); - jl_encode_value(s, *fld); - start = (const char*)&fld[1]; - } - data += elsz; - if (data != start) - ios_write(s->s, start, data - start); - } - } - else { - ios_write(s->s, (char*)jl_array_data(ar), l * ar->elsize); - if (jl_array_isbitsunion(ar)) - ios_write(s->s, jl_array_typetagdata(ar), l); - } + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(ar->ref.mem))->layout; + size_t offset; + if (layout->flags.arrayelem_isunion || layout->size == 0) + offset = (uintptr_t)ar->ref.ptr_or_offset; + else + offset = ((char*)ar->ref.ptr_or_offset - (char*)ar->ref.mem->ptr) / layout->size; + jl_encode_memory_slice(s, ar->ref.mem, offset, l); + } + else if (as_literal && jl_is_genericmemory(v)) { + jl_genericmemory_t* m = (jl_genericmemory_t*)v; + write_uint8(s->s, TAG_MEMORYT); + jl_encode_value(s, (jl_datatype_t*)jl_typetagof(v)); + jl_encode_value(s, jl_box_long(m->length)); + jl_encode_memory_slice(s, m, 0, m->length); + } + else if (as_literal && jl_is_layout_opaque(((jl_datatype_t*)jl_typeof(v))->layout)) { + assert(0 && "not legal to store this as literal"); } else if (as_literal || jl_is_uniontype(v) || jl_is_newvarnode(v) || jl_is_linenode(v) || jl_is_upsilonnode(v) || jl_is_pinode(v) || jl_is_slotnumber(v) || jl_is_ssavalue(v) || @@ -434,11 +457,10 @@ static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) } } -static jl_code_info_flags_t code_info_flags(uint8_t inferred, uint8_t propagate_inbounds, uint8_t has_fcall, +static jl_code_info_flags_t code_info_flags(uint8_t propagate_inbounds, uint8_t has_fcall, uint8_t nospecializeinfer, uint8_t inlining, uint8_t constprop) { jl_code_info_flags_t flags; - flags.bits.inferred = inferred; flags.bits.propagate_inbounds = propagate_inbounds; flags.bits.has_fcall = has_fcall; flags.bits.nospecializeinfer = nospecializeinfer; @@ -466,52 +488,27 @@ static jl_value_t *jl_decode_value_svec(jl_ircode_state *s, uint8_t tag) JL_GC_D return (jl_value_t*)sv; } -static jl_value_t *jl_decode_value_array(jl_ircode_state *s, uint8_t tag) JL_GC_DISABLED +static jl_value_t *jl_decode_value_memory(jl_ircode_state *s, jl_value_t *mty, size_t nel) JL_GC_DISABLED { - int16_t i, ndims; - int isptr, isunion, hasptr, elsize; - if (tag == TAG_ARRAY1D) { - ndims = 1; - elsize = read_uint8(s->s); - isptr = (elsize >> 7) & 1; - hasptr = (elsize >> 6) & 1; - isunion = (elsize >> 5) & 1; - elsize = elsize & 0x1f; - } - else { - ndims = read_uint16(s->s); - elsize = read_uint16(s->s); - isptr = (elsize >> 15) & 1; - hasptr = (elsize >> 14) & 1; - isunion = (elsize >> 13) & 1; - elsize = elsize & 0x1fff; - } - size_t *dims = (size_t*)alloca(ndims * sizeof(size_t)); - for (i = 0; i < ndims; i++) { - dims[i] = jl_unbox_long(jl_decode_value(s)); - } - jl_array_t *a = jl_new_array_for_deserialization( - (jl_value_t*)NULL, ndims, dims, !isptr, hasptr, isunion, elsize); - jl_value_t *aty = jl_decode_value(s); - jl_set_typeof(a, aty); - if (a->flags.ptrarray) { - jl_value_t **data = (jl_value_t**)jl_array_data(a); - size_t i, numel = jl_array_len(a); + jl_genericmemory_t *m = jl_alloc_genericmemory(mty, nel); + const jl_datatype_layout_t *layout = ((jl_datatype_t*)mty)->layout; + if (layout->flags.arrayelem_isboxed) { + jl_value_t **data = (jl_value_t**)m->ptr; + size_t i, numel = m->length; for (i = 0; i < numel; i++) { data[i] = jl_decode_value(s); } - assert(jl_astaggedvalue(a)->bits.gc == GC_CLEAN); // gc is disabled - } - else if (a->flags.hasptr) { - size_t i, numel = jl_array_len(a); - char *data = (char*)jl_array_data(a); - uint16_t elsz = a->elsize; - jl_datatype_t *et = (jl_datatype_t*)jl_tparam0(jl_typeof(a)); - size_t j, np = et->layout->npointers; + assert(jl_astaggedvalue(m)->bits.gc == GC_CLEAN); // gc is disabled + } + else if (layout->first_ptr >= 0) { + size_t i, numel = m->length; + char *data = (char*)m->ptr; + uint16_t elsz = layout->size; + size_t j, np = layout->npointers; for (i = 0; i < numel; i++) { char *start = data; for (j = 0; j < np; j++) { - uint32_t ptr = jl_ptr_offset(et, j); + uint32_t ptr = jl_ptr_offset((jl_datatype_t*)mty, j); jl_value_t **fld = &((jl_value_t**)data)[ptr]; if ((char*)fld != start) ios_readall(s->s, start, (const char*)fld - start); @@ -522,13 +519,39 @@ static jl_value_t *jl_decode_value_array(jl_ircode_state *s, uint8_t tag) JL_GC_ if (data != start) ios_readall(s->s, start, data - start); } - assert(jl_astaggedvalue(a)->bits.gc == GC_CLEAN); // gc is disabled + assert(jl_astaggedvalue(m)->bits.gc == GC_CLEAN); // gc is disabled } else { - size_t extra = jl_array_isbitsunion(a) ? jl_array_len(a) : 0; - size_t tot = jl_array_len(a) * a->elsize + extra; - ios_readall(s->s, (char*)jl_array_data(a), tot); + size_t extra = jl_genericmemory_isbitsunion(m) ? m->length : 0; + size_t tot = m->length * layout->size + extra; + ios_readall(s->s, (char*)m->ptr, tot); } + return (jl_value_t*)m; +} + +JL_DLLEXPORT jl_array_t *jl_alloc_array_nd(jl_value_t *atype, size_t *dims, size_t ndims); + +static jl_value_t *jl_decode_value_array(jl_ircode_state *s, uint8_t tag) JL_GC_DISABLED +{ + int16_t i, ndims; + if (tag == TAG_ARRAY1D) + ndims = 1; + else + ndims = read_uint16(s->s); + size_t *dims = (size_t*)alloca(ndims * sizeof(size_t)); + size_t len = 1; + for (i = 0; i < ndims; i++) { + dims[i] = jl_unbox_long(jl_decode_value(s)); + len *= dims[i]; + } + jl_value_t *aty = jl_decode_value(s); + jl_array_t *a = jl_alloc_array_nd(aty, dims, ndims); + a->ref.mem = (jl_genericmemory_t*)jl_decode_value_memory(s, jl_field_type_concrete((jl_datatype_t*)jl_field_type_concrete((jl_datatype_t*)aty, 0), 1), len); + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(a->ref.mem))->layout; + if (layout->flags.arrayelem_isunion || layout->size == 0) + a->ref.ptr_or_offset = (void*)0; + else + a->ref.ptr_or_offset = a->ref.mem->ptr; return (jl_value_t*)a; } @@ -553,7 +576,7 @@ static jl_value_t *jl_decode_value_expr(jl_ircode_state *s, uint8_t tag) JL_GC_D if (head == NULL) head = (jl_sym_t*)jl_decode_value(s); jl_expr_t *e = jl_exprn(head, len); - jl_value_t **data = (jl_value_t**)(e->args->data); + jl_value_t **data = jl_array_ptr_data(e->args); for (i = 0; i < len; i++) { data[i] = jl_decode_value(s); } @@ -573,11 +596,11 @@ static jl_value_t *jl_decode_value_phi(jl_ircode_state *s, uint8_t tag) JL_GC_DI jl_array_t *e = jl_alloc_array_1d(jl_array_int32_type, len_e); jl_array_t *v = jl_alloc_vec_any(len_v); jl_value_t *phi = jl_new_struct(jl_phinode_type, e, v); - int32_t *data_e = (int32_t*)(e->data); + int32_t *data_e = jl_array_data(e, int32_t); for (i = 0; i < len_e; i++) { data_e[i] = jl_unbox_int32(jl_decode_value(s)); } - jl_value_t **data_v = (jl_value_t**)(v->data); + jl_value_t **data_v = jl_array_ptr_data(v); for (i = 0; i < len_v; i++) { data_v[i] = jl_decode_value(s); } @@ -593,7 +616,7 @@ static jl_value_t *jl_decode_value_phic(jl_ircode_state *s, uint8_t tag) JL_GC_D len = read_int32(s->s); jl_array_t *v = jl_alloc_vec_any(len); jl_value_t *phic = jl_new_struct(jl_phicnode_type, v); - jl_value_t **data = (jl_value_t**)(v->data); + jl_value_t **data = jl_array_ptr_data(v); for (i = 0; i < len; i++) { data[i] = jl_decode_value(s); } @@ -638,7 +661,7 @@ static jl_value_t *jl_decode_value(jl_ircode_state *s) JL_GC_DISABLED { assert(!ios_eof(s->s)); jl_value_t *v; - size_t i, n; + size_t n; uint64_t key; uint8_t tag = read_uint8(s->s); if (tag > LAST_TAG) @@ -678,6 +701,8 @@ static jl_value_t *jl_decode_value(jl_ircode_state *s) JL_GC_DISABLED return v; case TAG_ARRAY: JL_FALLTHROUGH; case TAG_ARRAY1D: return jl_decode_value_array(s, tag); + case TAG_MEMORYT: + return jl_decode_value_memory(s, jl_decode_value(s), jl_unbox_long(jl_decode_value(s))); case TAG_EXPR: JL_FALLTHROUGH; case TAG_LONG_EXPR: JL_FALLTHROUGH; case TAG_CALL1: JL_FALLTHROUGH; @@ -696,6 +721,11 @@ static jl_value_t *jl_decode_value(jl_ircode_state *s) JL_GC_DISABLED set_nth_field(jl_gotoifnot_type, v, 0, jl_decode_value(s), 0); set_nth_field(jl_gotoifnot_type, v, 1, jl_decode_value(s), 0); return v; + case TAG_ENTERNODE: + v = jl_new_struct_uninit(jl_enternode_type); + set_nth_field(jl_enternode_type, v, 0, jl_decode_value(s), 0); + set_nth_field(jl_enternode_type, v, 1, jl_decode_value(s), 0); + return v; case TAG_ARGUMENT: v = jl_new_struct_uninit(jl_argument_type); set_nth_field(jl_argument_type, v, 0, jl_decode_value(s), 0); @@ -747,13 +777,6 @@ static jl_value_t *jl_decode_value(jl_ircode_state *s) JL_GC_DISABLED v = jl_alloc_string(n); ios_readall(s->s, jl_string_data(v), n); return v; - case TAG_LINEINFO: - v = jl_new_struct_uninit(jl_lineinfonode_type); - for (i = 0; i < jl_datatype_nfields(jl_lineinfonode_type); i++) { - //size_t offs = jl_field_offset(jl_lineinfonode_type, i); - set_nth_field(jl_lineinfonode_type, v, i, jl_decode_value(s), 0); - } - return v; default: assert(tag == TAG_GENERAL || tag == TAG_SHORT_GENERAL); return jl_decode_value_any(s, tag); @@ -764,12 +787,62 @@ static jl_value_t *jl_decode_value(jl_ircode_state *s) JL_GC_DISABLED typedef jl_value_t jl_string_t; // for local expressibility +static size_t codelocs_parseheader(jl_string_t *cl, int *line_offset, int *line_bytes, int *to_bytes) JL_NOTSAFEPOINT +{ + if (jl_string_len(cl) == 0) { + *line_offset = *line_bytes = *to_bytes = 0; + return 0; + } + int32_t header[3]; + memcpy(&header, (char*)jl_string_data(cl), sizeof(header)); + *line_offset = header[0]; + if (header[1] < 255) + *line_bytes = 1; + else if (header[1] < 65535) + *line_bytes = 2; + else + *line_bytes = 4; + if (header[2] == 0) + *to_bytes = 0; + else if (header[2] < 255) + *to_bytes = 1; + else if (header[2] < 65535) + *to_bytes = 2; + else + *to_bytes = 4; + assert(jl_string_len(cl) >= sizeof(header) + *line_bytes); + return (jl_string_len(cl) - sizeof(header) - *line_bytes) / (*line_bytes + *to_bytes * 2); // compute nstmts +} +#ifndef NDEBUG +static int codelocs_nstmts(jl_string_t *cl) JL_NOTSAFEPOINT +{ + int line_offset, line_bytes, to_bytes; + return codelocs_parseheader(cl, &line_offset, &line_bytes, &to_bytes); +} +#endif + +#define IR_DATASIZE_FLAGS sizeof(uint8_t) +#define IR_DATASIZE_PURITY sizeof(uint16_t) +#define IR_DATASIZE_INLINING_COST sizeof(uint16_t) +#define IR_DATASIZE_NSLOTS sizeof(int32_t) +typedef enum { + ir_offset_flags = 0, + ir_offset_purity = 0 + IR_DATASIZE_FLAGS, + ir_offset_inlining_cost = 0 + IR_DATASIZE_FLAGS + IR_DATASIZE_PURITY, + ir_offset_nslots = 0 + IR_DATASIZE_FLAGS + IR_DATASIZE_PURITY + IR_DATASIZE_INLINING_COST, + ir_offset_slotflags = 0 + IR_DATASIZE_FLAGS + IR_DATASIZE_PURITY + IR_DATASIZE_INLINING_COST + IR_DATASIZE_NSLOTS +} ir_offset; + JL_DLLEXPORT jl_string_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) { JL_TIMING(AST_COMPRESS, AST_COMPRESS); JL_LOCK(&m->writelock); // protect the roots array (Might GC) + int isdef = code == NULL; + if (isdef) + code = (jl_code_info_t*)m->source; assert(jl_is_method(m)); assert(jl_is_code_info(code)); + assert(jl_array_nrows(code->code) == codelocs_nstmts(code->debuginfo->codelocs) || jl_string_len(code->debuginfo->codelocs) == 0); ios_t dest; ios_mem(&dest, 0); int en = jl_gc_enable(0); // Might GC @@ -786,31 +859,31 @@ JL_DLLEXPORT jl_string_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) 1 }; - jl_code_info_flags_t flags = code_info_flags(code->inferred, code->propagate_inbounds, code->has_fcall, + jl_code_info_flags_t flags = code_info_flags(code->propagate_inbounds, code->has_fcall, code->nospecializeinfer, code->inlining, code->constprop); write_uint8(s.s, flags.packed); - write_uint8(s.s, code->purity.bits); + static_assert(sizeof(flags.packed) == IR_DATASIZE_FLAGS, "ir_datasize_flags is mismatched with the actual size"); + write_uint16(s.s, code->purity.bits); + static_assert(sizeof(code->purity.bits) == IR_DATASIZE_PURITY, "ir_datasize_purity is mismatched with the actual size"); write_uint16(s.s, code->inlining_cost); + static_assert(sizeof(code->inlining_cost) == IR_DATASIZE_INLINING_COST, "ir_datasize_inlining_cost is mismatched with the actual size"); - size_t nslots = jl_array_len(code->slotflags); + int32_t nslots = jl_array_nrows(code->slotflags); assert(nslots >= m->nargs && nslots < INT32_MAX); // required by generated functions write_int32(s.s, nslots); - ios_write(s.s, (char*)jl_array_data(code->slotflags), nslots); + static_assert(sizeof(nslots) == IR_DATASIZE_NSLOTS, "ir_datasize_nslots is mismatched with the actual size"); + ios_write(s.s, jl_array_data(code->slotflags, const char), nslots); // N.B.: The layout of everything before this point is explicitly referenced // by the various jl_ir_ accessors. Make sure to adjust those if you change // the data layout. - for (i = 0; i < 6; i++) { + for (i = 0; i < 5; i++) { int copy = 1; - if (i == 1) { // skip codelocs - assert(jl_field_offset(jl_code_info_type, i) == offsetof(jl_code_info_t, codelocs)); + if (i == 1) { // skip debuginfo + assert(jl_field_offset(jl_code_info_type, i) == offsetof(jl_code_info_t, debuginfo)); continue; } - if (i == 4) { // don't copy contents of method_for_inference_limit_heuristics field - assert(jl_field_offset(jl_code_info_type, i) == offsetof(jl_code_info_t, method_for_inference_limit_heuristics)); - copy = 0; - } jl_encode_value_(&s, jl_get_nth_field((jl_value_t*)code, i), copy); } @@ -825,28 +898,12 @@ JL_DLLEXPORT jl_string_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) else jl_encode_value(&s, jl_nothing); - size_t nstmt = jl_array_len(code->code); - assert(nstmt == jl_array_len(code->codelocs)); - if (jl_array_len(code->linetable) < 256) { - for (i = 0; i < nstmt; i++) { - write_uint8(s.s, ((int32_t*)jl_array_data(code->codelocs))[i]); - } - } - else if (jl_array_len(code->linetable) < 65536) { - for (i = 0; i < nstmt; i++) { - write_uint16(s.s, ((int32_t*)jl_array_data(code->codelocs))[i]); - } - } - else { - ios_write(s.s, (char*)jl_array_data(code->codelocs), nstmt * sizeof(int32_t)); - } - write_uint8(s.s, s.relocatability); ios_flush(s.s); jl_string_t *v = jl_pchar_to_string(s.s->buf, s.s->size); ios_close(s.s); - if (jl_array_len(m->roots) == 0) { + if (jl_array_nrows(m->roots) == 0) { m->roots = NULL; } JL_GC_PUSH1(&v); @@ -883,19 +940,18 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t flags.packed = read_uint8(s.s); code->inlining = flags.bits.inlining; code->constprop = flags.bits.constprop; - code->inferred = flags.bits.inferred; code->propagate_inbounds = flags.bits.propagate_inbounds; code->has_fcall = flags.bits.has_fcall; code->nospecializeinfer = flags.bits.nospecializeinfer; - code->purity.bits = read_uint8(s.s); + code->purity.bits = read_uint16(s.s); code->inlining_cost = read_uint16(s.s); size_t nslots = read_int32(&src); code->slotflags = jl_alloc_array_1d(jl_array_uint8_type, nslots); - ios_readall(s.s, (char*)jl_array_data(code->slotflags), nslots); + ios_readall(s.s, jl_array_data(code->slotflags, char), nslots); - for (i = 0; i < 6; i++) { - if (i == 1) // skip codelocs + for (i = 0; i < 5; i++) { + if (i == 1) // skip debuginfo continue; assert(jl_field_isptr(jl_code_info_type, i)); jl_value_t **fld = (jl_value_t**)((char*)jl_data_ptr(code) + jl_field_offset(jl_code_info_type, i)); @@ -909,57 +965,36 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t slotnames = m->slot_syms; code->slotnames = jl_uncompress_argnames(slotnames); - size_t nstmt = jl_array_len(code->code); - code->codelocs = (jl_value_t*)jl_alloc_array_1d(jl_array_int32_type, nstmt); - if (jl_array_len(code->linetable) < 256) { - for (i = 0; i < nstmt; i++) { - ((int32_t*)jl_array_data(code->codelocs))[i] = read_uint8(s.s); - } - } - else if (jl_array_len(code->linetable) < 65536) { - for (i = 0; i < nstmt; i++) { - ((int32_t*)jl_array_data(code->codelocs))[i] = read_uint16(s.s); - } - } - else { - ios_readall(s.s, (char*)jl_array_data(code->codelocs), nstmt * sizeof(int32_t)); - } + if (metadata) + code->debuginfo = jl_atomic_load_relaxed(&metadata->debuginfo); + else + code->debuginfo = m->debuginfo; + assert(code->debuginfo); + assert(jl_array_nrows(code->code) == codelocs_nstmts(code->debuginfo->codelocs) || jl_string_len(code->debuginfo->codelocs) == 0); (void) read_uint8(s.s); // relocatability - + assert(!ios_eof(s.s)); assert(ios_getc(s.s) == -1); + ios_close(s.s); JL_GC_PUSH1(&code); jl_gc_enable(en); JL_UNLOCK(&m->writelock); // Might GC JL_GC_POP(); if (metadata) { - code->min_world = metadata->min_world; - code->max_world = metadata->max_world; - code->rettype = metadata->rettype; code->parent = metadata->def; } return code; } -JL_DLLEXPORT uint8_t jl_ir_flag_inferred(jl_string_t *data) -{ - if (jl_is_code_info(data)) - return ((jl_code_info_t*)data)->inferred; - assert(jl_is_string(data)); - jl_code_info_flags_t flags; - flags.packed = jl_string_data(data)[0]; - return flags.bits.inferred; -} - JL_DLLEXPORT uint8_t jl_ir_flag_inlining(jl_string_t *data) { if (jl_is_code_info(data)) return ((jl_code_info_t*)data)->inlining; assert(jl_is_string(data)); jl_code_info_flags_t flags; - flags.packed = jl_string_data(data)[0]; + flags.packed = jl_string_data(data)[ir_offset_flags]; return flags.bits.inlining; } @@ -969,7 +1004,7 @@ JL_DLLEXPORT uint8_t jl_ir_flag_has_fcall(jl_string_t *data) return ((jl_code_info_t*)data)->has_fcall; assert(jl_is_string(data)); jl_code_info_flags_t flags; - flags.packed = jl_string_data(data)[0]; + flags.packed = jl_string_data(data)[ir_offset_flags]; return flags.bits.has_fcall; } @@ -978,13 +1013,13 @@ JL_DLLEXPORT uint16_t jl_ir_inlining_cost(jl_string_t *data) if (jl_is_code_info(data)) return ((jl_code_info_t*)data)->inlining_cost; assert(jl_is_string(data)); - uint16_t res = jl_load_unaligned_i16(jl_string_data(data) + 2); + uint16_t res = jl_load_unaligned_i16(jl_string_data(data) + ir_offset_inlining_cost); return res; } JL_DLLEXPORT jl_value_t *jl_compress_argnames(jl_array_t *syms) { - size_t nsyms = jl_array_len(syms); + size_t nsyms = jl_array_nrows(syms); size_t i, len = 0; for (i = 0; i < nsyms; i++) { jl_sym_t *name = (jl_sym_t*)jl_array_ptr_ref(syms, i); @@ -1012,11 +1047,11 @@ JL_DLLEXPORT ssize_t jl_ir_nslots(jl_value_t *data) { if (jl_is_code_info(data)) { jl_code_info_t *func = (jl_code_info_t*)data; - return jl_array_len(func->slotnames); + return jl_array_nrows(func->slotnames); } else { assert(jl_is_string(data)); - int nslots = jl_load_unaligned_i32(jl_string_data(data) + 2 + sizeof(uint16_t)); + int nslots = jl_load_unaligned_i32(jl_string_data(data) + ir_offset_nslots); return nslots; } } @@ -1024,10 +1059,12 @@ JL_DLLEXPORT ssize_t jl_ir_nslots(jl_value_t *data) JL_DLLEXPORT uint8_t jl_ir_slotflag(jl_string_t *data, size_t i) { assert(i < jl_ir_nslots(data)); - if (jl_is_code_info(data)) - return ((uint8_t*)((jl_code_info_t*)data)->slotflags->data)[i]; + if (jl_is_code_info(data)) { + jl_array_t *slotflags = ((jl_code_info_t*)data)->slotflags; + return jl_array_data(slotflags, uint8_t)[i]; + } assert(jl_is_string(data)); - return jl_string_data(data)[2 + sizeof(uint16_t) + sizeof(int32_t) + i]; + return jl_string_data(data)[ir_offset_slotflags + i]; } JL_DLLEXPORT jl_array_t *jl_uncompress_argnames(jl_value_t *syms) @@ -1073,6 +1110,244 @@ JL_DLLEXPORT jl_value_t *jl_uncompress_argname_n(jl_value_t *syms, size_t i) return jl_nothing; } +// codelocs are compressed as follows: +// The input vector is a NTuple{3,UInt32} (struct jl_codeloc_t) +// The vector is scanned for min and max of the values for each element +// The output is then allocated to hold (min-line, max-line, max-at) first, then line - min (in the smallest space), then the remainder (in the smallest space) +static inline struct jl_codeloc_t unpack_codeloc(jl_string_t *cl, size_t pc, int line_offset, int line_bytes, int to_bytes) JL_NOTSAFEPOINT +{ + const char *ptr = jl_string_data(cl) + sizeof(int32_t[3]); + if (pc == 0) + to_bytes = 0; + else + ptr += line_bytes + (pc - 1) * (line_bytes + to_bytes * 2); + uint8_t int8; + uint16_t int16; + uint32_t int32; + struct jl_codeloc_t codeloc; + switch (line_bytes) { + case 0: + codeloc.line = 0; + break; + case 1: + memcpy(&int8, ptr, 1); + codeloc.line = int8; + break; + case 2: + memcpy(&int16, ptr, 2); + codeloc.line = int16; + break; + case 4: + memcpy(&int32, ptr, 4); + codeloc.line = int32; + break; + } + if (codeloc.line > 0) + codeloc.line += line_offset - 1; + ptr += line_bytes; + switch (to_bytes) { + case 0: + codeloc.to = 0; + break; + case 1: + memcpy(&int8, ptr, 1); + codeloc.to = int8; + break; + case 2: + memcpy(&int16, ptr, 2); + codeloc.to = int16; + break; + case 4: + memcpy(&int32, ptr, 4); + codeloc.to = int32; + break; + } + ptr += to_bytes; + switch (to_bytes) { + case 0: + codeloc.pc = 0; + break; + case 1: + memcpy(&int8, ptr, 1); + codeloc.pc = int8; + break; + case 2: + memcpy(&int16, ptr, 2); + codeloc.pc = int16; + break; + case 3: + memcpy(&int32, ptr, 4); + codeloc.pc = int32; + break; + } + ptr += to_bytes; + return codeloc; +} + + +static const struct jl_codeloc_t badloc = {-1, 0, 0}; + +JL_DLLEXPORT struct jl_codeloc_t jl_uncompress1_codeloc(jl_string_t *cl, size_t pc) JL_NOTSAFEPOINT +{ + assert(jl_is_string(cl)); + int line_offset, line_bytes, to_bytes; + size_t nstmts = codelocs_parseheader(cl, &line_offset, &line_bytes, &to_bytes); + if (pc > nstmts) + return badloc; + return unpack_codeloc(cl, pc, line_offset, line_bytes, to_bytes); +} + +static int allzero(jl_value_t *codelocs) JL_NOTSAFEPOINT +{ + int32_t *p = jl_array_data(codelocs,int32_t); + int32_t *pend = p + jl_array_nrows(codelocs); + do { + if (*p) + return 0; + } while (++p < pend); + return 1; +} + +JL_DLLEXPORT jl_string_t *jl_compress_codelocs(int32_t firstline, jl_value_t *codelocs, size_t nstmts) // firstline+Vector{Int32} => Memory{UInt8} +{ + assert(jl_typeis(codelocs, jl_array_int32_type)); + if (jl_array_nrows(codelocs) == 0) + nstmts = 0; + assert(nstmts * 3 == jl_array_nrows(codelocs)); + if (allzero(codelocs)) + return jl_an_empty_string; + struct jl_codeloc_t codeloc, min, max; + size_t i; + min.line = min.to = min.pc = firstline <= 0 ? INT32_MAX : firstline; + max.line = max.to = max.pc = 0; + for (i = 0; i < nstmts; i++) { + memcpy(&codeloc, jl_array_data(codelocs,int32_t) + 3 * i, sizeof(codeloc)); +#define SETMIN(x) if (codeloc.x < min.x) min.x = codeloc.x +#define SETMAX(x) if (codeloc.x > max.x) max.x = codeloc.x + if (codeloc.line > 0) + SETMIN(line); + SETMAX(line); + SETMIN(to); + SETMAX(to); + SETMIN(pc); + SETMAX(pc); +#undef SETMIN +#undef SETMAX + } + int32_t header[3]; + header[0] = min.line > max.line ? 0 : min.line; + header[1] = min.line > max.line ? 0 : max.line - min.line; + header[2] = max.to > max.pc ? max.to : max.pc; + size_t line_bytes; + if (header[1] < 255) + line_bytes = 1; + else if (header[1] < 65535) + line_bytes = 2; + else + line_bytes = 4; + size_t to_bytes; + if (header[2] == 0) + to_bytes = 0; + else if (header[2] < 255) + to_bytes = 1; + else if (header[2] < 65535) + to_bytes = 2; + else + to_bytes = 4; + jl_string_t *cl = jl_alloc_string(sizeof(header) + line_bytes + nstmts * (line_bytes + to_bytes * 2)); + // store header structure + memcpy(jl_string_data(cl), &header, sizeof(header)); + // pack bytes + char *ptr = jl_string_data(cl) + sizeof(header); + uint8_t int8; + uint16_t int16; + uint32_t int32; + { // store firstline value + int8 = int16 = int32 = firstline > 0 ? firstline - header[0] + 1 : 0; + switch (line_bytes) { + case 0: + break; + case 1: + memcpy(ptr, &int8, 1); + break; + case 2: + memcpy(ptr, &int16, 2); + break; + case 4: + memcpy(ptr, &int32, 4); + break; + } + ptr += line_bytes; + } + for (i = 0; i < nstmts; i++) { + memcpy(&codeloc, jl_array_data(codelocs,int32_t) + 3 * i, sizeof(codeloc)); + int8 = int16 = int32 = codeloc.line > 0 ? codeloc.line - header[0] + 1 : 0; + switch (line_bytes) { + case 0: + break; + case 1: + memcpy(ptr, &int8, 1); + break; + case 2: + memcpy(ptr, &int16, 2); + break; + case 4: + memcpy(ptr, &int32, 4); + break; + } + ptr += line_bytes; + int8 = int16 = int32 = codeloc.to; + switch (to_bytes) { + case 0: + break; + case 1: + memcpy(ptr, &int8, 1); + break; + case 2: + memcpy(ptr, &int16, 2); + break; + case 4: + memcpy(ptr, &int32, 4); + break; + } + ptr += to_bytes; + int8 = int16 = int32 = codeloc.pc; + switch (to_bytes) { + case 0: + break; + case 1: + memcpy(ptr, &int8, 1); + break; + case 2: + memcpy(ptr, &int16, 2); + break; + case 4: + memcpy(ptr, &int32, 4); + break; + } + ptr += to_bytes; + } + return cl; +} + +JL_DLLEXPORT jl_value_t *jl_uncompress_codelocs(jl_string_t *cl, size_t nstmts) // Memory{UInt8} => Vector{Int32} +{ + assert(jl_is_string(cl)); + int line_offset, line_bytes, to_bytes; + size_t nlocs = codelocs_parseheader(cl, &line_offset, &line_bytes, &to_bytes); + assert(nlocs == 0 || nlocs == nstmts); + jl_value_t *codelocs = (jl_value_t*)jl_alloc_array_1d(jl_array_int32_type, nstmts * 3); + size_t i; + for (i = 0; i < nlocs; i++) { + struct jl_codeloc_t codeloc = unpack_codeloc(cl, i + 1, line_offset, line_bytes, to_bytes);; + memcpy(jl_array_data(codelocs,int32_t) + i * 3, &codeloc, sizeof(codeloc)); + } + if (nlocs == 0) { + memset(jl_array_data(codelocs,int32_t), 0, nstmts * sizeof(struct jl_codeloc_t)); + } + return codelocs; +} + void jl_init_serializer(void) { jl_task_t *ct = jl_current_task; @@ -1118,6 +1393,8 @@ void jl_init_serializer(void) jl_uint32_type, jl_uint64_type, jl_type_type_mt, jl_nonfunction_mt, jl_opaque_closure_type, + jl_memory_any_type, + jl_memory_uint8_type, ct->ptls->root_task, @@ -1135,6 +1412,7 @@ void jl_init_serializer(void) deser_tag[TAG_SLOTNUMBER] = (jl_value_t*)jl_slotnumber_type; deser_tag[TAG_SVEC] = (jl_value_t*)jl_simplevector_type; deser_tag[TAG_ARRAY] = (jl_value_t*)jl_array_type; + deser_tag[TAG_MEMORYT] = (jl_value_t*)jl_genericmemory_type; deser_tag[TAG_EXPR] = (jl_value_t*)jl_expr_type; deser_tag[TAG_PHINODE] = (jl_value_t*)jl_phinode_type; deser_tag[TAG_PHICNODE] = (jl_value_t*)jl_phicnode_type; @@ -1148,7 +1426,6 @@ void jl_init_serializer(void) deser_tag[TAG_INT32] = (jl_value_t*)jl_int32_type; deser_tag[TAG_INT64] = (jl_value_t*)jl_int64_type; deser_tag[TAG_UINT8] = (jl_value_t*)jl_uint8_type; - deser_tag[TAG_LINEINFO] = (jl_value_t*)jl_lineinfonode_type; deser_tag[TAG_UNIONALL] = (jl_value_t*)jl_unionall_type; deser_tag[TAG_GOTONODE] = (jl_value_t*)jl_gotonode_type; deser_tag[TAG_QUOTENODE] = (jl_value_t*)jl_quotenode_type; diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index 62d1a8612ce60..eee2879ef0ed7 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -34,7 +34,6 @@ using namespace llvm; -#include "llvm-codegen-shared.h" #include "jitlayers.h" #include "julia_assert.h" #include "processor.h" @@ -42,9 +41,7 @@ using namespace llvm; # include # include # include -# if JL_LLVM_VERSION >= 150000 # include -# endif # include #define DEBUG_TYPE "julia_jitlayers" @@ -116,6 +113,15 @@ static void *getTLSAddress(void *control) } #endif +#ifdef _OS_OPENBSD_ +extern "C" { + __int128 __divti3(__int128, __int128); + __int128 __modti3(__int128, __int128); + unsigned __int128 __udivti3(unsigned __int128, unsigned __int128); + unsigned __int128 __umodti3(unsigned __int128, unsigned __int128); +} +#endif + // Snooping on which functions are being compiled, and how long it takes extern "C" JL_DLLEXPORT_CODEGEN void jl_dump_compiles_impl(void *s) @@ -180,6 +186,8 @@ static orc::ThreadSafeModule jl_get_globals_module(orc::ThreadSafeContext &ctx, return GTSM; } +extern jl_value_t *jl_fptr_wait_for_compiled(jl_value_t *f, jl_value_t **args, uint32_t nargs, jl_code_instance_t *m); + // this generates llvm code for the lambda info // and adds the result to the jitlayers // (and the shadow module), @@ -187,9 +195,7 @@ static orc::ThreadSafeModule jl_get_globals_module(orc::ThreadSafeContext &ctx, static jl_callptr_t _jl_compile_codeinst( jl_code_instance_t *codeinst, jl_code_info_t *src, - size_t world, - orc::ThreadSafeContext context, - bool is_recompile) + orc::ThreadSafeContext context) { // caller must hold codegen_lock // and have disabled finalizers @@ -199,20 +205,12 @@ static jl_callptr_t _jl_compile_codeinst( start_time = jl_hrtime(); assert(jl_is_code_instance(codeinst)); - assert(codeinst->min_world <= world && (codeinst->max_world >= world || codeinst->max_world == 0) && - "invalid world for method-instance"); JL_TIMING(CODEINST_COMPILE, CODEINST_COMPILE); -#ifdef USE_TRACY - if (is_recompile) { - TracyCZoneColor(JL_TIMING_DEFAULT_BLOCK->tracy_ctx, 0xFFA500); - } -#endif jl_callptr_t fptr = NULL; // emit the code in LLVM IR form jl_codegen_params_t params(std::move(context), jl_ExecutionEngine->getDataLayout(), jl_ExecutionEngine->getTargetTriple()); // Locks the context params.cache = true; - params.world = world; params.imaging_mode = imaging_default(); params.debug_level = jl_options.debug_level; { @@ -324,10 +322,13 @@ static jl_callptr_t _jl_compile_codeinst( } } else { jl_callptr_t prev_invoke = NULL; + // Allow replacing addr if it is either NULL or our special waiting placeholder. if (!jl_atomic_cmpswap_acqrel(&this_code->invoke, &prev_invoke, addr)) { - addr = prev_invoke; - //TODO do we want to potentially promote invoke anyways? (e.g. invoke is jl_interpret_call or some other - //known lesser function) + if (prev_invoke == &jl_fptr_wait_for_compiled && !jl_atomic_cmpswap_acqrel(&this_code->invoke, &prev_invoke, addr)) { + addr = prev_invoke; + //TODO do we want to potentially promote invoke anyways? (e.g. invoke is jl_interpret_call or some other + //known lesser function) + } } } if (this_code == codeinst) @@ -436,7 +437,7 @@ void jl_extern_c_impl(jl_value_t *declrt, jl_tupletype_t *sigt) jl_type_error("@ccallable", (jl_value_t*)jl_anytuple_type_type, (jl_value_t*)sigt); // check that f is a guaranteed singleton type jl_datatype_t *ft = (jl_datatype_t*)jl_tparam0(sigt); - if (!jl_is_datatype(ft) || ft->instance == NULL) + if (!jl_is_datatype(ft) || !jl_is_datatype_singleton(ft)) jl_error("@ccallable: function object must be a singleton"); // compute / validate return type @@ -468,93 +469,23 @@ void jl_extern_c_impl(jl_value_t *declrt, jl_tupletype_t *sigt) jl_error("@ccallable was already defined for this method name"); } -// this compiles li and emits fptr extern "C" JL_DLLEXPORT_CODEGEN -jl_code_instance_t *jl_generate_fptr_impl(jl_method_instance_t *mi JL_PROPAGATES_ROOT, size_t world) +int jl_compile_codeinst_impl(jl_code_instance_t *ci) { - auto ct = jl_current_task; - bool timed = (ct->reentrant_timing & 1) == 0; - if (timed) - ct->reentrant_timing |= 1; - uint64_t compiler_start_time = 0; - uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); - bool is_recompile = false; - 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; - jl_code_instance_t *codeinst = NULL; - JL_GC_PUSH2(&src, &codeinst); - JL_LOCK(&jl_codegen_lock); // also disables finalizers, to prevent any unexpected recursion - jl_value_t *ci = jl_rettype_inferred_addr(mi, world, world); - if (ci != jl_nothing) - codeinst = (jl_code_instance_t*)ci; - if (codeinst) { - src = (jl_code_info_t*)jl_atomic_load_relaxed(&codeinst->inferred); - if ((jl_value_t*)src == jl_nothing) - src = NULL; - else if (jl_is_method(mi->def.method)) - src = jl_uncompress_ir(mi->def.method, codeinst, (jl_value_t*)src); - } - else { - // identify whether this is an invalidated method that is being recompiled - is_recompile = jl_atomic_load_relaxed(&mi->cache) != NULL; - } - if (src == NULL && jl_is_method(mi->def.method) && - jl_symbol_name(mi->def.method->name)[0] != '@') { - if (mi->def.method->source != jl_nothing) { - // If the caller didn't provide the source and IR is available, - // see if it is inferred, or try to infer it for ourself. - // (but don't bother with typeinf on macros or toplevel thunks) - src = jl_type_infer(mi, world, 0); - } - } - jl_code_instance_t *compiled = jl_method_compiled(mi, world); - if (compiled) { - codeinst = compiled; - } - else if (src && jl_is_code_info(src)) { - if (!codeinst) { - codeinst = jl_get_method_inferred(mi, src->rettype, src->min_world, src->max_world); - if (src->inferred) { - jl_value_t *null = nullptr; - jl_atomic_cmpswap_relaxed(&codeinst->inferred, &null, jl_nothing); - } - } - ++SpecFPtrCount; - _jl_compile_codeinst(codeinst, src, world, *jl_ExecutionEngine->getContext(), is_recompile); - if (jl_atomic_load_relaxed(&codeinst->invoke) == NULL) - codeinst = NULL; - } - else { - codeinst = NULL; - } - JL_UNLOCK(&jl_codegen_lock); - if (timed) { - if (measure_compile_time_enabled) { - uint64_t t_comp = jl_hrtime() - compiler_start_time; - if (is_recompile) { - jl_atomic_fetch_add_relaxed(&jl_cumulative_recompile_time, t_comp); - } - jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, t_comp); - } - ct->reentrant_timing &= ~1ull; - } - JL_GC_POP(); - return codeinst; -} - -extern "C" JL_DLLEXPORT_CODEGEN -void jl_generate_fptr_for_oc_wrapper_impl(jl_code_instance_t *oc_wrap) -{ - if (jl_atomic_load_relaxed(&oc_wrap->invoke) != NULL) { - return; + int newly_compiled = 0; + if (jl_atomic_load_relaxed(&ci->invoke) != NULL) { + return newly_compiled; } JL_LOCK(&jl_codegen_lock); - if (jl_atomic_load_relaxed(&oc_wrap->invoke) == NULL) { - _jl_compile_codeinst(oc_wrap, NULL, 1, *jl_ExecutionEngine->getContext(), 0); + if (jl_atomic_load_relaxed(&ci->invoke) == NULL) { + ++SpecFPtrCount; + uint64_t start = jl_typeinf_timing_begin(); + _jl_compile_codeinst(ci, NULL, *jl_ExecutionEngine->getContext()); + jl_typeinf_timing_end(start, 0); + newly_compiled = 1; } JL_UNLOCK(&jl_codegen_lock); // Might GC + return newly_compiled; } extern "C" JL_DLLEXPORT_CODEGEN @@ -588,7 +519,10 @@ void jl_generate_fptr_for_unspecialized_impl(jl_code_instance_t *unspec) if (src) { assert(jl_is_code_info(src)); ++UnspecFPtrCount; - _jl_compile_codeinst(unspec, src, unspec->min_world, *jl_ExecutionEngine->getContext(), 0); + jl_debuginfo_t *debuginfo = src->debuginfo; + jl_atomic_store_release(&unspec->debuginfo, debuginfo); // n.b. this assumes the field was previously NULL, which is not entirely true + jl_gc_wb(unspec, debuginfo); + _jl_compile_codeinst(unspec, src, *jl_ExecutionEngine->getContext()); } jl_callptr_t null = nullptr; // if we hit a codegen bug (or ran into a broken generated function or llvmcall), fall back to the interpreter as a last resort @@ -612,7 +546,7 @@ jl_value_t *jl_dump_method_asm_impl(jl_method_instance_t *mi, size_t world, char emit_mc, char getwrapper, const char* asm_variant, const char *debuginfo, char binary) { // printing via disassembly - jl_code_instance_t *codeinst = jl_generate_fptr(mi, world); + jl_code_instance_t *codeinst = jl_compile_method_internal(mi, world); if (codeinst) { uintptr_t fptr = (uintptr_t)jl_atomic_load_acquire(&codeinst->invoke); if (getwrapper) @@ -633,24 +567,14 @@ jl_value_t *jl_dump_method_asm_impl(jl_method_instance_t *mi, size_t world, JL_LOCK(&jl_codegen_lock); // also disables finalizers, to prevent any unexpected recursion specfptr = (uintptr_t)jl_atomic_load_relaxed(&codeinst->specptr.fptr); if (specfptr == 0) { - jl_code_info_t *src = jl_type_infer(mi, world, 0); - JL_GC_PUSH1(&src); - jl_method_t *def = mi->def.method; - if (jl_is_method(def)) { - if (!src) { - // TODO: jl_code_for_staged can throw - src = def->generator ? jl_code_for_staged(mi, world) : (jl_code_info_t*)def->source; - } - if (src && (jl_value_t*)src != jl_nothing) - src = jl_uncompress_ir(mi->def.method, codeinst, (jl_value_t*)src); - } - fptr = (uintptr_t)jl_atomic_load_acquire(&codeinst->invoke); - specfptr = (uintptr_t)jl_atomic_load_relaxed(&codeinst->specptr.fptr); - if (src && jl_is_code_info(src)) { - if (fptr == (uintptr_t)jl_fptr_const_return_addr && specfptr == 0) { - fptr = (uintptr_t)_jl_compile_codeinst(codeinst, src, world, *jl_ExecutionEngine->getContext(), 0); - specfptr = (uintptr_t)jl_atomic_load_relaxed(&codeinst->specptr.fptr); - } + // Doesn't need SOURCE_MODE_FORCE_SOURCE_UNCACHED, because the codegen lock is held, + // so there's no concern that the ->inferred field will be deleted. + jl_code_instance_t *forced_ci = jl_type_infer(mi, world, 0, SOURCE_MODE_FORCE_SOURCE); + JL_GC_PUSH1(&forced_ci); + if (forced_ci) { + // Force compile of this codeinst even though it already has an ->invoke + _jl_compile_codeinst(forced_ci, NULL, *jl_ExecutionEngine->getContext()); + specfptr = (uintptr_t)jl_atomic_load_relaxed(&forced_ci->specptr.fptr); } JL_GC_POP(); } @@ -794,7 +718,7 @@ struct JITObjectInfo { class JLDebuginfoPlugin : public ObjectLinkingLayer::Plugin { std::mutex PluginMutex; std::map> PendingObjs; - // Resources from distinct MaterializationResponsibilitys can get merged + // Resources from distinct `MaterializationResponsibility`s can get merged // after emission, so we can have multiple debug objects per resource key. std::map, 0>> RegisteredObjs; @@ -860,8 +784,11 @@ class JLDebuginfoPlugin : public ObjectLinkingLayer::Plugin { PendingObjs.erase(&MR); return Error::success(); } - +#if JL_LLVM_VERSION >= 160000 + Error notifyRemovingResources(JITDylib &JD, orc::ResourceKey K) override +#else Error notifyRemovingResources(ResourceKey K) override +#endif { std::lock_guard lock(PluginMutex); RegisteredObjs.erase(K); @@ -869,7 +796,11 @@ class JLDebuginfoPlugin : public ObjectLinkingLayer::Plugin { return Error::success(); } +#if JL_LLVM_VERSION >= 160000 + void notifyTransferringResources(JITDylib &JD, ResourceKey DstKey, ResourceKey SrcKey) override +#else void notifyTransferringResources(ResourceKey DstKey, ResourceKey SrcKey) override +#endif { std::lock_guard lock(PluginMutex); auto SrcIt = RegisteredObjs.find(SrcKey); @@ -928,11 +859,21 @@ class JLMemoryUsagePlugin : public ObjectLinkingLayer::Plugin { Error notifyFailed(orc::MaterializationResponsibility &MR) override { return Error::success(); } - Error notifyRemovingResources(orc::ResourceKey K) override { +#if JL_LLVM_VERSION >= 160000 + Error notifyRemovingResources(JITDylib &JD, orc::ResourceKey K) override +#else + Error notifyRemovingResources(orc::ResourceKey K) override +#endif + { return Error::success(); } +#if JL_LLVM_VERSION >= 160000 + void notifyTransferringResources(JITDylib &JD, orc::ResourceKey DstKey, + orc::ResourceKey SrcKey) override {} +#else void notifyTransferringResources(orc::ResourceKey DstKey, orc::ResourceKey SrcKey) override {} +#endif void modifyPassConfig(orc::MaterializationResponsibility &, jitlink::LinkGraph &, @@ -949,7 +890,11 @@ class JLMemoryUsagePlugin : public ObjectLinkingLayer::Plugin { for (auto block : section.blocks()) { secsize += block->getSize(); } +#if JL_LLVM_VERSION >= 160000 + if ((section.getMemProt() & orc::MemProt::Exec) == orc::MemProt::None) { +#else if ((section.getMemProt() & jitlink::MemProt::Exec) == jitlink::MemProt::None) { +#endif data_size += secsize; } else { code_size += secsize; @@ -981,10 +926,10 @@ class JLMemoryUsagePlugin : public ObjectLinkingLayer::Plugin { // TODO: Port our memory management optimisations to JITLink instead of using the // default InProcessMemoryManager. std::unique_ptr createJITLinkMemoryManager() { -#if JL_LLVM_VERSION < 150000 - return cantFail(jitlink::InProcessMemoryManager::Create()); -#else +#if JL_LLVM_VERSION < 160000 return cantFail(orc::MapperJITLinkMemoryManager::CreateWithMapper()); +#else + return cantFail(orc::MapperJITLinkMemoryManager::CreateWithMapper(/*Reservation Granularity*/ 16 * 1024 * 1024)); #endif } @@ -1031,6 +976,13 @@ class ForwardingMemoryManager : public RuntimeDyld::MemoryManager { bool IsReadOnly) override { return MemMgr->allocateDataSection(Size, Alignment, SectionID, SectionName, IsReadOnly); } +#if JL_LLVM_VERSION >= 160000 + virtual void reserveAllocationSpace(uintptr_t CodeSize, Align CodeAlign, + uintptr_t RODataSize, Align RODataAlign, + uintptr_t RWDataSize, Align RWDataAlign) override { + return MemMgr->reserveAllocationSpace(CodeSize, CodeAlign, RODataSize, RODataAlign, RWDataSize, RWDataAlign); + } +#else virtual void reserveAllocationSpace(uintptr_t CodeSize, uint32_t CodeAlign, uintptr_t RODataSize, uint32_t RODataAlign, @@ -1038,6 +990,7 @@ class ForwardingMemoryManager : public RuntimeDyld::MemoryManager { uint32_t RWDataAlign) override { return MemMgr->reserveAllocationSpace(CodeSize, CodeAlign, RODataSize, RODataAlign, RWDataSize, RWDataAlign); } +#endif virtual bool needsToReserveAllocationSpace() override { return MemMgr->needsToReserveAllocationSpace(); } @@ -1168,6 +1121,8 @@ namespace { #else None; #endif + if (TheTriple.isAArch64()) + codemodel = CodeModel::Small; auto optlevel = CodeGenOptLevelFor(jl_options.opt_level); auto TM = TheTarget->createTargetMachine( TheTriple.getTriple(), TheCPU, FeaturesStr, @@ -1179,11 +1134,7 @@ namespace { ); assert(TM && "Failed to select target machine -" " Is the LLVM backend for this CPU enabled?"); - if (!TheTriple.isARM() && !TheTriple.isPPC64()) { - // FastISel seems to be buggy for ARM. Ref #13321 - if (jl_options.opt_level < 2) - TM->setFastISel(true); - } + fixupTM(*TM); return std::unique_ptr(TM); } } // namespace @@ -1209,7 +1160,9 @@ namespace { : JTMB(createJTMBFromTM(TM, optlevel)) {} std::unique_ptr operator()() JL_NOTSAFEPOINT { - return cantFail(JTMB.createTargetMachine()); + auto TM = cantFail(JTMB.createTargetMachine()); + fixupTM(*TM); + return TM; } }; @@ -1217,24 +1170,30 @@ namespace { orc::JITTargetMachineBuilder JTMB; OptimizationLevel O; SmallVector, 0> &printers; - PMCreator(TargetMachine &TM, int optlevel, SmallVector, 0> &printers) JL_NOTSAFEPOINT - : JTMB(createJTMBFromTM(TM, optlevel)), O(getOptLevel(optlevel)), printers(printers) {} + std::mutex &llvm_printing_mutex; + PMCreator(TargetMachine &TM, int optlevel, SmallVector, 0> &printers, std::mutex &llvm_printing_mutex) JL_NOTSAFEPOINT + : JTMB(createJTMBFromTM(TM, optlevel)), O(getOptLevel(optlevel)), printers(printers), llvm_printing_mutex(llvm_printing_mutex) {} auto operator()() JL_NOTSAFEPOINT { - auto NPM = std::make_unique(cantFail(JTMB.createTargetMachine()), O); + auto TM = cantFail(JTMB.createTargetMachine()); + fixupTM(*TM); + auto NPM = std::make_unique(std::move(TM), O); // TODO this needs to be locked, as different resource pools may add to the printer vector at the same time - printers.push_back([NPM = NPM.get()]() JL_NOTSAFEPOINT { - NPM->printTimers(); - }); + { + std::lock_guard lock(llvm_printing_mutex); + printers.push_back([NPM = NPM.get()]() JL_NOTSAFEPOINT { + NPM->printTimers(); + }); + } return NPM; } }; template struct OptimizerT { - OptimizerT(TargetMachine &TM, SmallVector, 0> &printers) JL_NOTSAFEPOINT { + OptimizerT(TargetMachine &TM, SmallVector, 0> &printers, std::mutex &llvm_printing_mutex) JL_NOTSAFEPOINT { for (size_t i = 0; i < N; i++) { - PMs[i] = std::make_unique>>(PMCreator(TM, i, printers)); + PMs[i] = std::make_unique>>(PMCreator(TM, i, printers, llvm_printing_mutex)); } } @@ -1324,6 +1283,7 @@ namespace { } } } + ++ModulesOptimized; switch (PoolIdx) { case 0: ++OptO0; @@ -1375,10 +1335,12 @@ namespace { struct JITPointersT { - JITPointersT(orc::ExecutionSession &ES) JL_NOTSAFEPOINT : ES(ES) {} + JITPointersT(SharedBytesT &SharedBytes, std::mutex &Lock) JL_NOTSAFEPOINT + : SharedBytes(SharedBytes), Lock(Lock) {} Expected operator()(orc::ThreadSafeModule TSM, orc::MaterializationResponsibility &R) JL_NOTSAFEPOINT { TSM.withModuleDo([&](Module &M) JL_NOTSAFEPOINT { + std::lock_guard locked(Lock); for (auto &GV : make_early_inc_range(M.globals())) { if (auto *Shared = getSharedBytes(GV)) { ++InternedGlobals; @@ -1394,10 +1356,11 @@ namespace { return std::move(TSM); } + private: // optimize memory by turning long strings into memoized copies, instead of // making a copy per object file of output. - // we memoize them using the ExecutionSession's string pool; - // this makes it unsafe to call clearDeadEntries() on the pool. + // we memoize them using a StringSet with a custom-alignment allocator + // to ensure they are properly aligned Constant *getSharedBytes(GlobalVariable &GV) JL_NOTSAFEPOINT { // We could probably technically get away with // interning even external linkage globals, @@ -1423,11 +1386,17 @@ namespace { // Cutoff, since we don't want to intern small strings return nullptr; } - auto Interned = *ES.intern(Data); + Align Required = GV.getAlign().valueOrOne(); + Align Preferred = MaxAlignedAlloc::alignment(Data.size()); + if (Required > Preferred) + return nullptr; + StringRef Interned = SharedBytes.insert(Data).first->getKey(); + assert(llvm::isAddrAligned(Preferred, Interned.data())); return literal_static_pointer_val(Interned.data(), GV.getType()); } - orc::ExecutionSession &ES; + SharedBytesT &SharedBytes; + std::mutex &Lock; }; } @@ -1607,6 +1576,27 @@ void optimizeDLSyms(Module &M) { JuliaOJIT::DLSymOptimizer(true)(M); } +void fixupTM(TargetMachine &TM) { + auto TheTriple = TM.getTargetTriple(); + if (jl_options.opt_level < 2) { + if (!TheTriple.isARM() && !TheTriple.isPPC64() && !TheTriple.isAArch64()) + TM.setFastISel(true); + else // FastISel seems to be buggy Ref #13321 + TM.setFastISel(false); + } +} + +extern int jl_opaque_ptrs_set; +void SetOpaquePointer(LLVMContext &ctx) { + if (jl_opaque_ptrs_set) + return; +#ifndef JL_LLVM_OPAQUE_POINTERS + ctx.setOpaquePointers(false); +#else + ctx.setOpaquePointers(true); +#endif +} + llvm::DataLayout jl_create_datalayout(TargetMachine &TM) { // Mark our address spaces as non-integral auto jl_data_layout = TM.createDataLayout(); @@ -1628,6 +1618,7 @@ JuliaOJIT::JuliaOJIT() DLSymOpt(std::make_unique(false)), ContextPool([](){ auto ctx = std::make_unique(); + SetOpaquePointer(*ctx); return orc::ThreadSafeContext(std::move(ctx)); }), #ifdef JL_USE_JITLINK @@ -1645,8 +1636,8 @@ JuliaOJIT::JuliaOJIT() #endif LockLayer(ObjectLayer), CompileLayer(ES, LockLayer, std::make_unique>(orc::irManglingOptionsFromTargetOptions(TM->Options), *TM)), - JITPointersLayer(ES, CompileLayer, orc::IRTransformLayer::TransformFunction(JITPointersT(ES))), - OptimizeLayer(ES, JITPointersLayer, orc::IRTransformLayer::TransformFunction(OptimizerT(*TM, PrintLLVMTimers))), + JITPointersLayer(ES, CompileLayer, orc::IRTransformLayer::TransformFunction(JITPointersT(SharedBytes, RLST_mutex))), + OptimizeLayer(ES, JITPointersLayer, orc::IRTransformLayer::TransformFunction(OptimizerT(*TM, PrintLLVMTimers, llvm_printing_mutex))), OptSelLayer(ES, OptimizeLayer, orc::IRTransformLayer::TransformFunction(selectOptLevel)), DepsVerifyLayer(ES, OptSelLayer, orc::IRTransformLayer::TransformFunction(validateExternRelocations)), ExternalCompileLayer(ES, LockLayer, @@ -1728,18 +1719,39 @@ JuliaOJIT::JuliaOJIT() ExternalJD.addToLinkOrder(JD, orc::JITDylibLookupFlags::MatchExportedSymbolsOnly); orc::SymbolAliasMap jl_crt = { -#if JULIA_FLOAT16_ABI == 1 - { mangle("__gnu_h2f_ieee"), { mangle("julia__gnu_h2f_ieee"), JITSymbolFlags::Exported } }, - { mangle("__extendhfsf2"), { mangle("julia__gnu_h2f_ieee"), JITSymbolFlags::Exported } }, - { mangle("__gnu_f2h_ieee"), { mangle("julia__gnu_f2h_ieee"), JITSymbolFlags::Exported } }, - { mangle("__truncsfhf2"), { mangle("julia__gnu_f2h_ieee"), JITSymbolFlags::Exported } }, - { mangle("__truncdfhf2"), { mangle("julia__truncdfhf2"), JITSymbolFlags::Exported } }, + // Float16 conversion routines +#if defined(_CPU_X86_64_) && defined(_OS_DARWIN_) && JL_LLVM_VERSION >= 160000 + // LLVM 16 reverted to soft-float ABI for passing half on x86_64 Darwin + // https://github.com/llvm/llvm-project/commit/2bcf51c7f82ca7752d1bba390a2e0cb5fdd05ca9 + { mangle("__gnu_h2f_ieee"), { mangle("julia_half_to_float"), JITSymbolFlags::Exported } }, + { mangle("__extendhfsf2"), { mangle("julia_half_to_float"), JITSymbolFlags::Exported } }, + { mangle("__gnu_f2h_ieee"), { mangle("julia_float_to_half"), JITSymbolFlags::Exported } }, + { mangle("__truncsfhf2"), { mangle("julia_float_to_half"), JITSymbolFlags::Exported } }, + { mangle("__truncdfhf2"), { mangle("julia_double_to_half"), JITSymbolFlags::Exported } }, +#else + { mangle("__gnu_h2f_ieee"), { mangle("julia__gnu_h2f_ieee"), JITSymbolFlags::Exported } }, + { mangle("__extendhfsf2"), { mangle("julia__gnu_h2f_ieee"), JITSymbolFlags::Exported } }, + { mangle("__gnu_f2h_ieee"), { mangle("julia__gnu_f2h_ieee"), JITSymbolFlags::Exported } }, + { mangle("__truncsfhf2"), { mangle("julia__gnu_f2h_ieee"), JITSymbolFlags::Exported } }, + { mangle("__truncdfhf2"), { mangle("julia__truncdfhf2"), JITSymbolFlags::Exported } }, #endif - { mangle("__truncsfbf2"), { mangle("julia__truncsfbf2"), JITSymbolFlags::Exported } }, - { mangle("__truncdfbf2"), { mangle("julia__truncdfbf2"), JITSymbolFlags::Exported } }, + // BFloat16 conversion routines + { mangle("__truncsfbf2"), { mangle("julia__truncsfbf2"), JITSymbolFlags::Exported } }, + { mangle("__truncdfbf2"), { mangle("julia__truncdfbf2"), JITSymbolFlags::Exported } }, }; cantFail(GlobalJD.define(orc::symbolAliases(jl_crt))); +#ifdef _OS_OPENBSD_ + orc::SymbolMap i128_crt; + + i128_crt[mangle("__divti3")] = JITEvaluatedSymbol::fromPointer(&__divti3, JITSymbolFlags::Exported); + i128_crt[mangle("__modti3")] = JITEvaluatedSymbol::fromPointer(&__modti3, JITSymbolFlags::Exported); + i128_crt[mangle("__udivti3")] = JITEvaluatedSymbol::fromPointer(&__udivti3, JITSymbolFlags::Exported); + i128_crt[mangle("__umodti3")] = JITEvaluatedSymbol::fromPointer(&__umodti3, JITSymbolFlags::Exported); + + cantFail(GlobalJD.define(orc::absoluteSymbols(i128_crt))); +#endif + #ifdef MSAN_EMUTLS_WORKAROUND orc::SymbolMap msan_crt; msan_crt[mangle("__emutls_get_address")] = JITEvaluatedSymbol::fromPointer(msan_workaround::getTLSAddress, JITSymbolFlags::Exported); @@ -1816,7 +1828,7 @@ void JuliaOJIT::addModule(orc::ThreadSafeModule TSM) auto Lookups = ES.lookup({{&JD, orc::JITDylibLookupFlags::MatchExportedSymbolsOnly}}, NewExports); if (!Lookups) { ES.reportError(Lookups.takeError()); - errs() << "Failed to lookup symbols in module!"; + errs() << "Failed to lookup symbols in module!\n"; if (CurrentlyCompiling) { CurrentlyCompiling.withModuleDo([](Module &M) JL_NOTSAFEPOINT { errs() << "Dumping failing module\n" << M << "\n"; }); } else { @@ -1856,7 +1868,7 @@ Error JuliaOJIT::addObjectFile(orc::JITDylib &JD, std::unique_ptr JL_JITSymbol JuliaOJIT::findSymbol(StringRef Name, bool ExportedSymbolsOnly) { orc::JITDylib* SearchOrders[3] = {&JD, &GlobalJD, &ExternalJD}; - ArrayRef SearchOrder = makeArrayRef(&SearchOrders[0], ExportedSymbolsOnly ? 3 : 1); + ArrayRef SearchOrder = ArrayRef(&SearchOrders[0], ExportedSymbolsOnly ? 3 : 1); auto Sym = ES.lookup(SearchOrder, Name); if (Sym) return *Sym; @@ -1871,7 +1883,7 @@ JL_JITSymbol JuliaOJIT::findUnmangledSymbol(StringRef Name) Expected JuliaOJIT::findExternalJDSymbol(StringRef Name, bool ExternalJDOnly) { orc::JITDylib* SearchOrders[3] = {&ExternalJD, &GlobalJD, &JD}; - ArrayRef SearchOrder = makeArrayRef(&SearchOrders[0], ExternalJDOnly ? 1 : 3); + ArrayRef SearchOrder = ArrayRef(&SearchOrders[0], ExternalJDOnly ? 1 : 3); auto Sym = ES.lookup(SearchOrder, getMangledName(Name)); return Sym; } @@ -1899,6 +1911,7 @@ uint64_t JuliaOJIT::getFunctionAddress(StringRef Name) StringRef JuliaOJIT::getFunctionAtAddress(uint64_t Addr, jl_code_instance_t *codeinst) { std::lock_guard lock(RLST_mutex); + assert(Addr != (uint64_t)&jl_fptr_wait_for_compiled); std::string *fname = &ReverseLocalSymbolTable[(void*)(uintptr_t)Addr]; if (fname->empty()) { std::string string_fname; @@ -1938,9 +1951,11 @@ void JuliaOJIT::enableJITDebuggingSupport() cantFail(JD.define(orc::absoluteSymbols(GDBFunctions))); if (TM->getTargetTriple().isOSBinFormatMachO()) ObjectLayer.addPlugin(cantFail(orc::GDBJITDebugInfoRegistrationPlugin::Create(ES, JD, TM->getTargetTriple()))); +#ifndef _COMPILER_ASAN_ENABLED_ // TODO: Fix duplicated sections spam #51794 else if (TM->getTargetTriple().isOSBinFormatELF()) //EPCDebugObjectRegistrar doesn't take a JITDylib, so we have to directly provide the call address ObjectLayer.addPlugin(std::make_unique(ES, std::make_unique(ES, orc::ExecutorAddr::fromPtr(&llvm_orc_registerJITLoaderGDBWrapper)))); +#endif } #else void JuliaOJIT::enableJITDebuggingSupport() @@ -2125,7 +2140,7 @@ void jl_merge_module(orc::ThreadSafeModule &destTSM, orc::ThreadSafeModule srcTS std::unique_ptr JuliaOJIT::cloneTargetMachine() const { - return std::unique_ptr(getTarget() + auto NewTM = std::unique_ptr(getTarget() .createTargetMachine( getTargetTriple().str(), getTargetCPU(), @@ -2134,6 +2149,8 @@ std::unique_ptr JuliaOJIT::cloneTargetMachine() const TM->getRelocationModel(), TM->getCodeModel(), TM->getOptLevel())); + fixupTM(*NewTM); + return NewTM; } const Triple& JuliaOJIT::getTargetTriple() const { diff --git a/src/jitlayers.h b/src/jitlayers.h index a120df8148502..931f0070042ac 100644 --- a/src/jitlayers.h +++ b/src/jitlayers.h @@ -1,6 +1,8 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license #include +#include +#include #include #include @@ -8,6 +10,7 @@ #include #include #include +#include #include #include @@ -22,10 +25,11 @@ #include "julia.h" #include "julia_internal.h" #include "platform.h" - +#include "llvm-codegen-shared.h" #include #include + // As of LLVM 13, there are two runtime JIT linker implementations, the older // RuntimeDyld (used via orc::RTDyldObjectLinkingLayer) and the newer JITLink // (used via orc::ObjectLinkingLayer). @@ -49,16 +53,8 @@ #endif // The sanitizers don't play well with our memory manager -#if defined(JL_FORCE_JITLINK) || JL_LLVM_VERSION >= 150000 && defined(HAS_SANITIZER) +#if defined(JL_FORCE_JITLINK) || defined(_CPU_AARCH64_) || defined(HAS_SANITIZER) # define JL_USE_JITLINK -#else -# if defined(_CPU_AARCH64_) -# if defined(_OS_LINUX_) && JL_LLVM_VERSION < 150000 -# pragma message("On aarch64-gnu-linux, LLVM version >= 15 is required for JITLink; fallback suffers from occasional segfaults") -# else -# define JL_USE_JITLINK -# endif -# endif #endif # include @@ -86,24 +82,41 @@ struct OptimizationOptions { bool dump_native; bool external_use; bool llvm_only; + bool always_inline; + bool enable_early_simplifications; + bool enable_early_optimizations; + bool enable_scalar_optimizations; + bool enable_loop_optimizations; + bool enable_vector_pipeline; + bool remove_ni; + bool cleanup; static constexpr OptimizationOptions defaults( bool lower_intrinsics=true, bool dump_native=false, bool external_use=false, - bool llvm_only=false) { - return {lower_intrinsics, dump_native, external_use, llvm_only}; + bool llvm_only=false, + bool always_inline=true, + bool enable_early_simplifications=true, + bool enable_early_optimizations=true, + bool enable_scalar_optimizations=true, + bool enable_loop_optimizations=true, + bool enable_vector_pipeline=true, + bool remove_ni=true, + bool cleanup=true) { + return {lower_intrinsics, dump_native, external_use, llvm_only, + always_inline, enable_early_simplifications, + enable_early_optimizations, enable_scalar_optimizations, + enable_loop_optimizations, enable_vector_pipeline, + remove_ni, cleanup}; } }; struct NewPM { std::unique_ptr TM; - StandardInstrumentations SI; - std::unique_ptr PIC; - PassBuilder PB; - ModulePassManager MPM; OptimizationLevel O; - + OptimizationOptions options; + TimePassesHandler TimePasses; NewPM(std::unique_ptr TM, OptimizationLevel O, OptimizationOptions options = OptimizationOptions::defaults()) JL_NOTSAFEPOINT; ~NewPM() JL_NOTSAFEPOINT; @@ -234,7 +247,6 @@ struct jl_codegen_params_t { std::unique_ptr _shared_module; inline Module &shared_module(); // inputs - size_t world = 0; const jl_cgparams_t *params = &jl_default_cgparams; bool cache = false; bool external_linkage = false; @@ -250,7 +262,8 @@ jl_llvm_functions_t jl_emit_code( jl_method_instance_t *mi, jl_code_info_t *src, jl_value_t *jlrettype, - jl_codegen_params_t ¶ms); + jl_codegen_params_t ¶ms, + size_t min_world, size_t max_world); jl_llvm_functions_t jl_emit_codeinst( orc::ThreadSafeModule &M, @@ -289,6 +302,46 @@ static const inline char *name_from_method_instance(jl_method_instance_t *li) JL return jl_is_method(li->def.method) ? jl_symbol_name(li->def.method->name) : "top-level scope"; } +template +class MaxAlignedAllocImpl + : public AllocatorBase> { + +public: + MaxAlignedAllocImpl() JL_NOTSAFEPOINT = default; + + static Align alignment(size_t Size) JL_NOTSAFEPOINT { + // Define the maximum alignment we expect to require, from offset bytes off + // the returned pointer, this is >= alignof(std::max_align_t), which is too + // small often to actually use. + const size_t MaxAlignment = JL_CACHE_BYTE_ALIGNMENT; + if (Size <= offset) + return Align(1); + return Align(std::min((size_t)llvm::PowerOf2Ceil(Size - offset), MaxAlignment)); + } + + LLVM_ATTRIBUTE_RETURNS_NONNULL void *Allocate(size_t Size, Align Alignment) { + Align MaxAlign = alignment(Size); + assert(Alignment < MaxAlign); (void)Alignment; + return jl_gc_perm_alloc(Size, 0, MaxAlign.value(), offset); + } + + inline LLVM_ATTRIBUTE_RETURNS_NONNULL + void * Allocate(size_t Size, size_t Alignment) { + return Allocate(Size, Align(Alignment)); + } + + // Pull in base class overloads. + using AllocatorBase::Allocate; + + void Deallocate(const void *Ptr, size_t Size, size_t /*Alignment*/) { abort(); } + + // Pull in base class overloads. + using AllocatorBase::Deallocate; + +private: +}; +using MaxAlignedAlloc = MaxAlignedAllocImpl<>; + typedef JITSymbol JL_JITSymbol; // The type that is similar to SymbolInfo on LLVM 4.0 is actually // `JITEvaluatedSymbol`. However, we only use this type when a JITSymbol @@ -297,6 +350,7 @@ typedef JITSymbol JL_SymbolInfo; using CompilerResultT = Expected>; using OptimizerResultT = Expected; +using SharedBytesT = StringSet::MapEntryTy)>>; class JuliaOJIT { public: @@ -386,7 +440,7 @@ class JuliaOJIT { } private: ResourcePool &pool; - llvm::Optional resource; + Optional resource; }; OwningResource operator*() JL_NOTSAFEPOINT { @@ -513,6 +567,7 @@ class JuliaOJIT { // Note that this is a safepoint due to jl_get_library_ and jl_dlsym calls void optimizeDLSyms(Module &M); + private: const std::unique_ptr TM; @@ -526,6 +581,7 @@ class JuliaOJIT { std::mutex RLST_mutex{}; int RLST_inc = 0; DenseMap ReverseLocalSymbolTable; + SharedBytesT SharedBytes; std::unique_ptr DLSymOpt; @@ -534,6 +590,7 @@ class JuliaOJIT { jl_locked_stream dump_compiles_stream; jl_locked_stream dump_llvm_opt_stream; + std::mutex llvm_printing_mutex{}; SmallVector, 0> PrintLLVMTimers; ResourcePool> ContextPool; @@ -566,6 +623,8 @@ Module &jl_codegen_params_t::shared_module() JL_NOTSAFEPOINT { } return *_shared_module; } +void fixupTM(TargetMachine &TM) JL_NOTSAFEPOINT; +void SetOpaquePointer(LLVMContext &ctx) JL_NOTSAFEPOINT; void optimizeDLSyms(Module &M); diff --git a/src/jl_exported_data.inc b/src/jl_exported_data.inc index aa23b9d7b8205..79ff437841879 100644 --- a/src/jl_exported_data.inc +++ b/src/jl_exported_data.inc @@ -4,8 +4,12 @@ #define JL_EXPORTED_DATA_POINTERS(XX) \ XX(jl_abstractarray_type) \ XX(jl_abstractstring_type) \ + XX(jl_addrspace_type) \ + XX(jl_addrspace_typename) \ + XX(jl_addrspacecore_type) \ XX(jl_an_empty_string) \ XX(jl_an_empty_vec_any) \ + XX(jl_an_empty_memory_any) \ XX(jl_anytuple_type) \ XX(jl_anytuple_type_type) \ XX(jl_any_type) \ @@ -31,6 +35,7 @@ XX(jl_const_type) \ XX(jl_core_module) \ XX(jl_datatype_type) \ + XX(jl_debuginfo_type) \ XX(jl_densearray_type) \ XX(jl_diverror_exception) \ XX(jl_emptysvec) \ @@ -48,6 +53,7 @@ XX(jl_binding_type) \ XX(jl_globalref_type) \ XX(jl_gotoifnot_type) \ + XX(jl_enternode_type) \ XX(jl_gotonode_type) \ XX(jl_initerror_type) \ XX(jl_int16_type) \ @@ -64,12 +70,24 @@ XX(jl_llvmpointer_typename) \ XX(jl_loaderror_type) \ XX(jl_main_module) \ + XX(jl_memory_any_type) \ XX(jl_memory_exception) \ + XX(jl_genericmemory_type) \ + XX(jl_genericmemory_typename) \ + XX(jl_memory_uint8_type) \ + XX(jl_memory_uint16_type) \ + XX(jl_memory_uint32_type) \ + XX(jl_memory_uint64_type) \ + XX(jl_memoryref_any_type) \ + XX(jl_genericmemoryref_type) \ + XX(jl_genericmemoryref_typename) \ + XX(jl_memoryref_uint8_type) \ XX(jl_methoderror_type) \ XX(jl_method_instance_type) \ XX(jl_method_match_type) \ XX(jl_method_type) \ XX(jl_methtable_type) \ + XX(jl_missingcodeerror_type) \ XX(jl_module_type) \ XX(jl_n_threads_per_pool) \ XX(jl_namedtuple_type) \ @@ -130,6 +148,7 @@ XX(jl_weakref_type) \ XX(jl_libdl_module) \ XX(jl_libdl_dlopen_func) \ + XX(jl_precompilable_error) \ // Data symbols that are defined inside the public libjulia #define JL_EXPORTED_DATA_SYMBOLS(XX) \ diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index c503eeb4407b3..e2527e3d7aeab 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -8,6 +8,7 @@ XX(jl_alloc_array_1d) \ XX(jl_alloc_array_2d) \ XX(jl_alloc_array_3d) \ + XX(jl_alloc_array_nd) \ XX(jl_alloc_string) \ XX(jl_alloc_svec) \ XX(jl_alloc_svec_uninit) \ @@ -21,35 +22,23 @@ XX(jl_apply_type1) \ XX(jl_apply_type2) \ XX(jl_argument_datatype) \ - XX(jl_arraylen) \ - XX(jl_arrayref) \ - XX(jl_arrayset) \ - XX(jl_arrayunset) \ - XX(jl_array_copy) \ - XX(jl_array_del_at) \ - XX(jl_array_del_beg) \ XX(jl_array_del_end) \ XX(jl_array_eltype) \ - XX(jl_array_grow_at) \ - XX(jl_array_grow_beg) \ XX(jl_array_grow_end) \ - XX(jl_array_isassigned) \ XX(jl_array_ptr) \ XX(jl_array_ptr_1d_append) \ XX(jl_array_ptr_1d_push) \ - XX(jl_array_ptr_copy) \ + XX(jl_genericmemory_owner) \ + XX(jl_genericmemoryref) \ XX(jl_array_rank) \ - XX(jl_array_size) \ - XX(jl_array_sizehint) \ XX(jl_array_to_string) \ - XX(jl_array_typetagdata) \ - XX(jl_array_validate_dims) \ XX(jl_atexit_hook) \ XX(jl_atomic_bool_cmpswap_bits) \ XX(jl_atomic_cmpswap_bits) \ XX(jl_atomic_error) \ XX(jl_atomic_new_bits) \ XX(jl_atomic_store_bits) \ + XX(jl_atomic_storeonce_bits) \ XX(jl_atomic_swap_bits) \ XX(jl_backtrace_from_here) \ XX(jl_base_relative_to) \ @@ -120,10 +109,10 @@ XX(jl_egal__bits) \ XX(jl_egal__bitstag) \ XX(jl_eh_restore_state) \ + XX(jl_eh_restore_state_noexcept) \ XX(jl_enter_handler) \ XX(jl_enter_threaded_region) \ XX(jl_environ) \ - XX(jl_eof_error) \ XX(jl_eqtable_get) \ XX(jl_eqtable_pop) \ XX(jl_eqtable_put) \ @@ -216,6 +205,7 @@ XX(jl_get_binding_wr) \ XX(jl_get_cpu_name) \ XX(jl_get_cpu_features) \ + XX(jl_cpu_has_fma) \ XX(jl_get_current_task) \ XX(jl_get_default_sysimg_path) \ XX(jl_get_excstack) \ @@ -226,7 +216,6 @@ XX(jl_get_JIT) \ XX(jl_get_julia_bin) \ XX(jl_get_julia_bindir) \ - XX(jl_get_method_inferred) \ XX(jl_get_module_compile) \ XX(jl_get_module_infer) \ XX(jl_get_module_of_binding) \ @@ -247,9 +236,9 @@ XX(jl_get_world_counter) \ XX(jl_get_zero_subnormals) \ XX(jl_gf_invoke_lookup) \ + XX(jl_method_lookup_by_tt) \ + XX(jl_method_lookup) \ XX(jl_gf_invoke_lookup_worlds) \ - XX(jl_git_branch) \ - XX(jl_git_commit) \ XX(jl_global_event_loop) \ XX(jl_has_empty_intersection) \ XX(jl_has_free_typevars) \ @@ -278,7 +267,6 @@ XX(jl_ios_buffer_n) \ XX(jl_ios_fd) \ XX(jl_ios_get_nbyte_int) \ - XX(jl_ir_flag_inferred) \ XX(jl_ir_flag_has_fcall) \ XX(jl_ir_flag_inlining) \ XX(jl_ir_inlining_cost) \ @@ -291,6 +279,7 @@ XX(jl_is_binding_deprecated) \ XX(jl_is_char_signed) \ XX(jl_is_const) \ + XX(jl_is_assertsbuild) \ XX(jl_is_debugbuild) \ XX(jl_is_foreign_type) \ XX(jl_is_identifier) \ @@ -342,7 +331,6 @@ XX(jl_module_uuid) \ XX(jl_native_alignment) \ XX(jl_nb_available) \ - XX(jl_new_array) \ XX(jl_new_bits) \ XX(jl_new_codeinst) \ XX(jl_new_code_info_uninit) \ @@ -363,7 +351,6 @@ XX(jl_new_typevar) \ XX(jl_next_from_addrinfo) \ XX(jl_normalize_to_compilable_sig) \ - XX(jl_write_precompile_statement) \ XX(jl_no_exc_handler) \ XX(jl_object_id) \ XX(jl_object_id_) \ @@ -380,6 +367,7 @@ XX(jl_pointerref) \ XX(jl_pointerset) \ XX(jl_pop_handler) \ + XX(jl_pop_handler_noexcept) \ XX(jl_preload_sysimg_so) \ XX(jl_prepend_cwd) \ XX(jl_printf) \ @@ -394,18 +382,17 @@ XX(jl_profile_maxlen_data) \ XX(jl_profile_start_timer) \ XX(jl_profile_stop_timer) \ - XX(jl_ptrarrayref) \ XX(jl_ptr_to_array) \ XX(jl_ptr_to_array_1d) \ XX(jl_queue_work) \ XX(jl_raise_debugger) \ XX(jl_readuntil) \ XX(jl_cache_flags) \ + XX(jl_match_cache_flags_current) \ XX(jl_match_cache_flags) \ XX(jl_read_verify_header) \ XX(jl_realloc) \ XX(jl_register_newmeth_tracer) \ - XX(jl_reshape_array) \ XX(jl_resolve_globals_in_ir) \ XX(jl_restore_excstack) \ XX(jl_restore_incremental) \ @@ -448,11 +435,9 @@ XX(jl_stderr_obj) \ XX(jl_stderr_stream) \ XX(jl_stdin_stream) \ - XX(jl_stdout_obj) \ XX(jl_stdout_stream) \ XX(jl_stored_inline) \ XX(jl_string_ptr) \ - XX(jl_string_to_array) \ XX(jl_subtype) \ XX(jl_subtype_env) \ XX(jl_subtype_env_size) \ @@ -461,7 +446,6 @@ XX(jl_svec2) \ XX(jl_svec_copy) \ XX(jl_svec_fill) \ - XX(jl_svec_ref) \ XX(jl_switch) \ XX(jl_switchto) \ XX(jl_symbol) \ @@ -497,7 +481,7 @@ XX(jl_type_intersection) \ XX(jl_type_intersection_with_env) \ XX(jl_type_morespecific) \ - XX(jl_type_morespecific_no_subtype) \ + XX(jl_method_morespecific) \ XX(jl_type_union) \ XX(jl_type_unionall) \ XX(jl_unbox_bool) \ @@ -517,6 +501,7 @@ XX(jl_uncompress_argname_n) \ XX(jl_uncompress_ir) \ XX(jl_undefined_var_error) \ + XX(jl_unwrap_unionall) \ XX(jl_has_no_field_error) \ XX(jl_value_ptr) \ XX(jl_ver_is_release) \ @@ -528,7 +513,6 @@ XX(jl_vprintf) \ XX(jl_wakeup_thread) \ XX(jl_write_compiler_output) \ - XX(jl_yield) \ #define JL_RUNTIME_EXPORTED_FUNCS_WIN(XX) \ XX(jl_setjmp) \ @@ -551,9 +535,8 @@ YY(jl_init_codegen) \ YY(jl_getFunctionInfo) \ YY(jl_register_fptrs) \ - YY(jl_generate_fptr) \ YY(jl_generate_fptr_for_unspecialized) \ - YY(jl_generate_fptr_for_oc_wrapper) \ + YY(jl_compile_codeinst) \ YY(jl_compile_extern_c) \ YY(jl_teardown_codegen) \ YY(jl_jit_total_bytes) \ diff --git a/src/jl_uv.c b/src/jl_uv.c index 281dd798dbb36..4da23a5937770 100644 --- a/src/jl_uv.c +++ b/src/jl_uv.c @@ -39,21 +39,24 @@ static void walk_print_cb(uv_handle_t *h, void *arg) const char *type = uv_handle_type_name(h->type); if (!type) type = ""; + size_t resource_id; // fits an int or pid_t on Unix, HANDLE or PID on Windows uv_os_fd_t fd; if (h->type == UV_PROCESS) - fd = uv_process_get_pid((uv_process_t*)h); - else if (uv_fileno(h, &fd)) - fd = (uv_os_fd_t)-1; + resource_id = (size_t)uv_process_get_pid((uv_process_t*)h); + else if (uv_fileno(h, &fd) == 0) + resource_id = (size_t)fd; + else + resource_id = -1; const char *pad = " "; // 16 spaces - int npad = fd == -1 ? 0 : snprintf(NULL, 0, "%zd", (size_t)fd); + int npad = resource_id == -1 ? 0 : snprintf(NULL, 0, "%zd", resource_id); if (npad < 0) npad = 0; npad += strlen(type); pad += npad < strlen(pad) ? npad : strlen(pad); - if (fd == -1) - jl_safe_printf(" %s %s@%p->%p\n", type, pad, (void*)h, (void*)h->data); + if (resource_id == -1) + jl_safe_printf(" %s %s%p->%p\n", type, pad, (void*)h, (void*)h->data); else - jl_safe_printf(" %s[%zd] %s@%p->%p\n", type, (size_t)fd, pad, (void*)h, (void*)h->data); + jl_safe_printf(" %s[%zd] %s%p->%p\n", type, resource_id, pad, (void*)h, (void*)h->data); } static void wait_empty_func(uv_timer_t *t) @@ -63,32 +66,38 @@ static void wait_empty_func(uv_timer_t *t) if (!uv_loop_alive(t->loop)) return; jl_safe_printf("\n[pid %zd] waiting for IO to finish:\n" - " TYPE[FD/PID] @UV_HANDLE_T->DATA\n", + " Handle type uv_handle_t->data\n", (size_t)uv_os_getpid()); uv_walk(jl_io_loop, walk_print_cb, NULL); + if (jl_generating_output() && jl_options.incremental) { + jl_safe_printf("This means that a package has started a background task or event source that has not finished running. For precompilation to complete successfully, the event source needs to be closed explicitly. See the developer documentation on fixing precompilation hangs for more help.\n"); + } jl_gc_collect(JL_GC_FULL); } void jl_wait_empty_begin(void) { JL_UV_LOCK(); - if (wait_empty_worker.type != UV_TIMER && jl_io_loop) { - // try to purge anything that is just waiting for cleanup - jl_io_loop->stop_flag = 0; - uv_run(jl_io_loop, UV_RUN_NOWAIT); - uv_timer_init(jl_io_loop, &wait_empty_worker); + if (jl_io_loop) { + if (wait_empty_worker.type != UV_TIMER) { + // try to purge anything that is just waiting for cleanup + jl_io_loop->stop_flag = 0; + uv_run(jl_io_loop, UV_RUN_NOWAIT); + uv_timer_init(jl_io_loop, &wait_empty_worker); + uv_unref((uv_handle_t*)&wait_empty_worker); + } + // make sure this is running uv_update_time(jl_io_loop); uv_timer_start(&wait_empty_worker, wait_empty_func, 10, 15000); - uv_unref((uv_handle_t*)&wait_empty_worker); } JL_UV_UNLOCK(); } - void jl_wait_empty_end(void) { - JL_UV_LOCK(); - uv_close((uv_handle_t*)&wait_empty_worker, NULL); - JL_UV_UNLOCK(); + // n.b. caller must be holding jl_uv_mutex + if (wait_empty_worker.type == UV_TIMER) + // make sure this timer is stopped, but not destroyed in case the user calls jl_wait_empty_begin again + uv_timer_stop(&wait_empty_worker); } @@ -130,11 +139,17 @@ void JL_UV_LOCK(void) } } +/** + * @brief Begin an IO lock. + */ JL_DLLEXPORT void jl_iolock_begin(void) { JL_UV_LOCK(); } +/** + * @brief End an IO lock. + */ JL_DLLEXPORT void jl_iolock_end(void) { JL_UV_UNLOCK(); @@ -173,9 +188,12 @@ static void jl_uv_closeHandle(uv_handle_t *handle) ct->world_age = last_age; return; } - if (handle == (uv_handle_t*)&signal_async || handle == (uv_handle_t*)&wait_empty_worker) + if (handle == (uv_handle_t*)&wait_empty_worker) + handle->type = UV_UNKNOWN_HANDLE; + else if (handle == (uv_handle_t*)&signal_async) return; - free(handle); + else + free(handle); } static void jl_uv_flush_close_callback(uv_write_t *req, int status) @@ -218,9 +236,16 @@ static void uv_flush_callback(uv_write_t *req, int status) free(req); } -// Turn a normal write into a blocking write (primarily for use from C and gdb). -// Warning: This calls uv_run, so it can have unbounded side-effects. -// Be care where you call it from! - the libuv loop is also not reentrant. +/** + * @brief Flush a UV stream. + * + * Primarily used from C and gdb to convert a normal write operation on a UV stream + * into a blocking write. It calls uv_run, which can have unbounded side-effects. + * Caution is advised as the location from where this function is called is critical + * due to the non-reentrancy of the libuv loop. + * + * @param stream A pointer to `uv_stream_t` representing the stream to flush. + */ JL_DLLEXPORT void jl_uv_flush(uv_stream_t *stream) { if (stream == (void*)STDIN_FILENO || @@ -252,27 +277,115 @@ JL_DLLEXPORT void jl_uv_flush(uv_stream_t *stream) // getters and setters // TODO: check if whoever calls these is thread-safe +/** + * @brief Get the process ID of a UV process. + * + * @param p A pointer to `uv_process_t` representing the UV process. + * @return The process ID. + */ JL_DLLEXPORT int jl_uv_process_pid(uv_process_t *p) { return p->pid; } + +/** + * @brief Get the data associated with a UV process. + * + * @param p A pointer to `uv_process_t` representing the UV process. + * @return A pointer to the process data. + */ JL_DLLEXPORT void *jl_uv_process_data(uv_process_t *p) { return p->data; } + +/** + * @brief Get the base pointer of a UV buffer. + * + * @param buf A constant pointer to `uv_buf_t` representing the UV buffer. + * @return A pointer to the base of the buffer. + */ JL_DLLEXPORT void *jl_uv_buf_base(const uv_buf_t *buf) { return buf->base; } + +/** + * @brief Get the length of a UV buffer. + * + * @param buf A constant pointer to `uv_buf_t` representing the UV buffer. + * @return The length of the buffer as `size_t`. + */ JL_DLLEXPORT size_t jl_uv_buf_len(const uv_buf_t *buf) { return buf->len; } + +/** + * @brief Set the base pointer of a UV buffer. + * + * @param buf A pointer to `uv_buf_t` representing the UV buffer. + * @param b A pointer to `char` representing the new base of the buffer. + */ JL_DLLEXPORT void jl_uv_buf_set_base(uv_buf_t *buf, char *b) { buf->base = b; } + +/** + * @brief Set the length of a UV buffer. + * + * @param buf A pointer to `uv_buf_t` representing the UV buffer. + * @param n The new length of the buffer as `size_t`. + */ JL_DLLEXPORT void jl_uv_buf_set_len(uv_buf_t *buf, size_t n) { buf->len = n; } + +/** + * @brief Get the handle associated with a UV connect request. + * + * @param connect A pointer to `uv_connect_t` representing the connect request. + * @return A pointer to the associated handle. + */ JL_DLLEXPORT void *jl_uv_connect_handle(uv_connect_t *connect) { return connect->handle; } + +/** + * @brief Get the file descriptor from a UV file structure. + * + * @param f A pointer to `jl_uv_file_t` representing the UV file. + * @return The file descriptor as `uv_os_fd_t`. + */ JL_DLLEXPORT uv_os_fd_t jl_uv_file_handle(jl_uv_file_t *f) { return f->file; } + +/** + * @brief Get the data field from a UV request. + * + * @param req A pointer to `uv_req_t` representing the request. + * @return A pointer to the data associated with the request. + */ JL_DLLEXPORT void *jl_uv_req_data(uv_req_t *req) { return req->data; } + +/** + * @brief Set the data field of a UV request. + * + * @param req A pointer to `uv_req_t` representing the request. + * @param data A pointer to the data to be associated with the request. + */ JL_DLLEXPORT void jl_uv_req_set_data(uv_req_t *req, void *data) { req->data = data; } + +/** + * @brief Get the data field from a UV handle. + * + * @param handle A pointer to `uv_handle_t` representing the handle. + * @return A pointer to the data associated with the handle. + */ JL_DLLEXPORT void *jl_uv_handle_data(uv_handle_t *handle) { return handle->data; } -JL_DLLEXPORT void *jl_uv_write_handle(uv_write_t *req) { return req->handle; } -extern _Atomic(unsigned) _threadedregion; +/** + * @brief Get the handle associated with a UV write request. + * + * @param req A pointer to `uv_write_t` representing the write request. + * @return A pointer to the handle associated with the write request. + */ +JL_DLLEXPORT void *jl_uv_write_handle(uv_write_t *req) { return req->handle; } +/** + * @brief Process pending UV events. + * + * See also `uv_run` in the libuv documentation for status code enumeration. + * + * @return An integer indicating the status of the event processing. + */ JL_DLLEXPORT int jl_process_events(void) { jl_task_t *ct = jl_current_task; uv_loop_t *loop = jl_io_loop; jl_gc_safepoint_(ct->ptls); - if (loop && (jl_atomic_load_relaxed(&_threadedregion) || jl_atomic_load_relaxed(&ct->tid) == 0)) { + if (loop && (jl_atomic_load_relaxed(&_threadedregion) || jl_atomic_load_relaxed(&ct->tid) == jl_atomic_load_relaxed(&io_loop_tid))) { if (jl_atomic_load_relaxed(&jl_uv_n_waiters) == 0 && jl_mutex_trylock(&jl_uv_mutex)) { JL_PROBE_RT_START_PROCESS_EVENTS(ct); loop->stop_flag = 0; @@ -293,6 +406,11 @@ static void jl_proc_exit_cleanup_cb(uv_process_t *process, int64_t exit_status, uv_close((uv_handle_t*)process, (uv_close_cb)&free); } +/** + * @brief Close a UV handle. + * + * @param handle A pointer to `uv_handle_t` that needs to be closed. + */ JL_DLLEXPORT void jl_close_uv(uv_handle_t *handle) { JL_UV_LOCK(); @@ -326,6 +444,11 @@ JL_DLLEXPORT void jl_close_uv(uv_handle_t *handle) JL_UV_UNLOCK(); } +/** + * @brief Forcefully close a UV handle. + * + * @param handle A pointer to `uv_handle_t` to be forcefully closed. + */ JL_DLLEXPORT void jl_forceclose_uv(uv_handle_t *handle) { if (!uv_is_closing(handle)) { // avoid double-closing the stream @@ -337,12 +460,23 @@ JL_DLLEXPORT void jl_forceclose_uv(uv_handle_t *handle) } } +/** + * @brief Associate a Julia structure with a UV handle. + * + * @param handle A pointer to `uv_handle_t` to be associated with a Julia structure. + * @param data Additional parameters representing the Julia structure to be associated. + */ JL_DLLEXPORT void jl_uv_associate_julia_struct(uv_handle_t *handle, jl_value_t *data) { handle->data = data; } +/** + * @brief Disassociate a Julia structure from a UV handle. + * + * @param handle A pointer to `uv_handle_t` from which the Julia structure will be disassociated. + */ JL_DLLEXPORT void jl_uv_disassociate_julia_struct(uv_handle_t *handle) { handle->data = NULL; @@ -350,6 +484,29 @@ JL_DLLEXPORT void jl_uv_disassociate_julia_struct(uv_handle_t *handle) #define UV_HANDLE_CLOSED 0x02 +/** + * @brief Spawn a new process. + * + * Spawns a new process to execute external programs or scripts within the context of the Julia application. + * + * @param name A C string representing the name or path of the executable to spawn. + * @param argv An array of C strings representing the arguments for the process. The array should be null-terminated. + * @param loop A pointer to `uv_loop_t` representing the event loop where the process is registered. + * @param proc A pointer to `uv_process_t` where the details of the spawned process are stored. + * @param stdio An array of `uv_stdio_container_t` representing the file descriptors for standard input, output, and error. + * @param nstdio An integer representing the number of elements in the stdio array. + * @param flags A uint32_t representing process creation flags. + See also `enum uv_process_flags` in the libuv documentation. + * @param env An array of C strings for setting environment variables. The array should be null-terminated. + * @param cwd A C string representing the current working directory for the process. + * @param cpumask A C string representing the CPU affinity mask for the process. + See also the `cpumask` field of the `uv_process_options_t` structure in the libuv documentation. + * @param cpumask_size The size of the cpumask. + * @param cb A function pointer to `uv_exit_cb` which is the callback function to be called upon process exit. + * + * @return An integer indicating the success or failure of the spawn operation. A return value of 0 indicates success, + * while a non-zero value indicates an error. + */ JL_DLLEXPORT int jl_spawn(char *name, char **argv, uv_loop_t *loop, uv_process_t *proc, uv_stdio_container_t *stdio, int nstdio, @@ -478,7 +635,7 @@ JL_DLLEXPORT int jl_fs_write(uv_os_fd_t handle, const char *data, size_t len, { jl_task_t *ct = jl_get_current_task(); // TODO: fix this cheating - if (jl_get_safe_restore() || ct == NULL || jl_atomic_load_relaxed(&ct->tid) != 0) + if (jl_get_safe_restore() || ct == NULL || jl_atomic_load_relaxed(&ct->tid) != jl_atomic_load_relaxed(&io_loop_tid)) #ifdef _OS_WINDOWS_ return WriteFile(handle, data, len, NULL, NULL); #else @@ -506,25 +663,6 @@ JL_DLLEXPORT int jl_fs_read(uv_os_fd_t handle, char *data, size_t len) return ret; } -JL_DLLEXPORT int jl_fs_read_byte(uv_os_fd_t handle) -{ - uv_fs_t req; - unsigned char c; - uv_buf_t buf[1]; - buf[0].base = (char*)&c; - buf[0].len = 1; - int ret = uv_fs_read(unused_uv_loop_arg, &req, handle, buf, 1, -1, NULL); - uv_fs_req_cleanup(&req); - switch (ret) { - case -1: return ret; - case 0: jl_eof_error(); - case 1: return (int)c; - default: - assert(0 && "jl_fs_read_byte: Invalid return value from uv_fs_read"); - return -1; - } -} - JL_DLLEXPORT int jl_fs_close(uv_os_fd_t handle) { uv_fs_t req; @@ -578,7 +716,7 @@ JL_DLLEXPORT void jl_uv_puts(uv_stream_t *stream, const char *str, size_t n) // TODO: Hack to make CoreIO thread-safer jl_task_t *ct = jl_get_current_task(); - if (ct == NULL || jl_atomic_load_relaxed(&ct->tid) != 0) { + if (ct == NULL || jl_atomic_load_relaxed(&ct->tid) != jl_atomic_load_relaxed(&io_loop_tid)) { if (stream == JL_STDOUT) { fd = UV_STDOUT_FD; } @@ -973,31 +1111,39 @@ static inline int ishexchar(char c) JL_DLLEXPORT int jl_ispty(uv_pipe_t *pipe) { - if (pipe->type != UV_NAMED_PIPE) return 0; + char namebuf[0]; size_t len = 0; - if (uv_pipe_getpeername(pipe, NULL, &len) != UV_ENOBUFS) return 0; + if (pipe->type != UV_NAMED_PIPE) + return 0; + if (uv_pipe_getpeername(pipe, namebuf, &len) != UV_ENOBUFS) + return 0; char *name = (char*)alloca(len + 1); - if (uv_pipe_getpeername(pipe, name, &len)) return 0; + if (uv_pipe_getpeername(pipe, name, &len)) + return 0; name[len] = '\0'; // return true if name matches regex: // ^\\\\?\\pipe\\(msys|cygwin)-[0-9a-z]{16}-[pt]ty[1-9][0-9]*- //jl_printf(JL_STDERR,"pipe_name: %s\n", name); int n = 0; - if (!strncmp(name,"\\\\?\\pipe\\msys-",14)) + if (!strncmp(name, "\\\\?\\pipe\\msys-", 14)) n = 14; - else if (!strncmp(name,"\\\\?\\pipe\\cygwin-",16)) + else if (!strncmp(name, "\\\\?\\pipe\\cygwin-", 16)) n = 16; else return 0; //jl_printf(JL_STDERR,"prefix pass\n"); name += n; for (int n = 0; n < 16; n++) - if (!ishexchar(*name++)) return 0; + if (!ishexchar(*name++)) + return 0; //jl_printf(JL_STDERR,"hex pass\n"); - if ((*name++)!='-') return 0; - if (*name != 'p' && *name != 't') return 0; + if ((*name++)!='-') + return 0; + if (*name != 'p' && *name != 't') + return 0; name++; - if (*name++ != 't' || *name++ != 'y') return 0; + if (*name++ != 't' || *name++ != 'y') + return 0; //jl_printf(JL_STDERR,"tty pass\n"); return 1; } diff --git a/src/jlapi.c b/src/jlapi.c index 29be3b9e6179c..a3621385a437e 100644 --- a/src/jlapi.c +++ b/src/jlapi.c @@ -26,11 +26,28 @@ extern "C" { #include #endif +/** + * @brief Check if Julia is already initialized. + * + * Determine if Julia has been previously initialized + * via `jl_init` or `jl_init_with_image`. + * + * @return Returns 1 if Julia is initialized, 0 otherwise. + */ JL_DLLEXPORT int jl_is_initialized(void) { return jl_main_module != NULL; } +/** + * @brief Set Julia command line arguments. + * + * Allows setting the command line arguments for Julia, + * similar to arguments passed in the main function of a C program. + * + * @param argc The number of command line arguments. + * @param argv Array of command line arguments. + */ JL_DLLEXPORT void jl_set_ARGS(int argc, char **argv) { if (jl_core_module != NULL) { @@ -41,21 +58,30 @@ JL_DLLEXPORT void jl_set_ARGS(int argc, char **argv) jl_set_const(jl_core_module, jl_symbol("ARGS"), (jl_value_t*)args); JL_GC_POP(); } - assert(jl_array_len(args) == 0); + assert(jl_array_nrows(args) == 0); jl_array_grow_end(args, argc); int i; for (i = 0; i < argc; i++) { jl_value_t *s = (jl_value_t*)jl_cstr_to_string(argv[i]); - jl_arrayset(args, s, i); + jl_array_ptr_set(args, i, s); } } } -// First argument is the usr/bin directory where the julia binary is, or NULL to guess. -// Second argument is the path of a system image file (*.so). -// A non-absolute path is interpreted as relative to the first argument path, or -// relative to the default julia home dir. -// The default is something like ../lib/julia/sys.so +/** + * @brief Initialize Julia with a specified system image file. + * + * Initializes Julia by specifying the usr/bin directory where the Julia binary is + * and the path of a system image file (*.so). If the julia_bindir is NULL, the function + * attempts to guess the directory. The image_path is interpreted as a path to the system image + * file. A non-absolute path for the system image is considered relative to julia_bindir, or + * relative to the default Julia home directory. The default system image is typically + * something like ../lib/julia/sys.so. + * + * @param julia_bindir The usr/bin directory where the Julia binary is located, or NULL to guess. + * @param image_path The path of a system image file (*.so). Interpreted as relative to julia_bindir + * or the default Julia home directory if not an absolute path. + */ JL_DLLEXPORT void jl_init_with_image(const char *julia_bindir, const char *image_path) { @@ -71,6 +97,12 @@ JL_DLLEXPORT void jl_init_with_image(const char *julia_bindir, jl_exception_clear(); } +/** + * @brief Initialize the Julia runtime. + * + * Initializes the Julia runtime without any specific system image. + * It must be called before any other Julia API functions. + */ JL_DLLEXPORT void jl_init(void) { char *libbindir = NULL; @@ -105,6 +137,13 @@ static void _jl_exception_clear(jl_task_t *ct) JL_NOTSAFEPOINT ct->ptls->previous_exception = NULL; } +/** + * @brief Evaluate a Julia expression from a string. + * + * @param str A C string containing the Julia expression to be evaluated. + * @return A pointer to `jl_value_t` representing the result of the evaluation. + * Returns `NULL` if an error occurs during parsing or evaluation. + */ JL_DLLEXPORT jl_value_t *jl_eval_string(const char *str) { jl_value_t *r; @@ -119,29 +158,50 @@ JL_DLLEXPORT jl_value_t *jl_eval_string(const char *str) _jl_exception_clear(ct); } JL_CATCH { - ct->ptls->previous_exception = jl_current_exception(); + ct->ptls->previous_exception = jl_current_exception(ct); r = NULL; } return r; } -JL_DLLEXPORT jl_value_t *jl_current_exception(void) JL_GLOBALLY_ROOTED JL_NOTSAFEPOINT +/** + * @brief Get the current exception in the Julia context. + * + * @return A pointer to `jl_value_t` representing the current exception. + * Returns `NULL` if no exception is currently thrown. + */ +JL_DLLEXPORT jl_value_t *jl_current_exception(jl_task_t *ct) JL_GLOBALLY_ROOTED JL_NOTSAFEPOINT { - jl_excstack_t *s = jl_current_task->excstack; + jl_excstack_t *s = ct->excstack; return s && s->top != 0 ? jl_excstack_exception(s, s->top) : jl_nothing; } +/** + * @brief Check if an exception has occurred in the Julia context. + * + * @return A pointer to `jl_value_t` representing the exception that occurred. + * Returns `NULL` if no exception has occurred. + */ JL_DLLEXPORT jl_value_t *jl_exception_occurred(void) { return jl_current_task->ptls->previous_exception; } +/** + * @brief Clear the current exception in the Julia context. + * + */ JL_DLLEXPORT void jl_exception_clear(void) { _jl_exception_clear(jl_current_task); } -// get the name of a type as a string +/** + * @brief Get the type name of a Julia value. + * + * @param v A pointer to `jl_value_t` representing the Julia value. + * @return A C string containing the name of the type. + */ JL_DLLEXPORT const char *jl_typename_str(jl_value_t *v) { if (!jl_is_datatype(v)) @@ -149,32 +209,78 @@ JL_DLLEXPORT const char *jl_typename_str(jl_value_t *v) return jl_symbol_name(((jl_datatype_t*)v)->name->name); } -// get the name of typeof(v) as a string +/** + * @brief Get the string representation of a Julia value's type. + * + * @param v A pointer to `jl_value_t` representing the Julia value. + * @return A C string describing the type of the value. + */ JL_DLLEXPORT const char *jl_typeof_str(jl_value_t *v) { return jl_typename_str((jl_value_t*)jl_typeof(v)); } +/** + * @brief Get the element type of a Julia array. + * + * @param a A pointer to `jl_value_t` representing the Julia array. + * @return A pointer to the type of the array elements. + */ JL_DLLEXPORT void *jl_array_eltype(jl_value_t *a) { return jl_tparam0(jl_typeof(a)); } +/** + * @brief Get the number of dimensions of a Julia array. + * + * Returns the rank (number of dimensions) of a Julia array. + * + * @param a A pointer to `jl_value_t` representing the Julia array. + * @return An integer representing the number of dimensions of the array. + */ JL_DLLEXPORT int jl_array_rank(jl_value_t *a) { return jl_array_ndims(a); } -JL_DLLEXPORT size_t jl_array_size(jl_value_t *a, int d) +/** + * @brief Get the size of a specific dimension of a Julia array. + * + * Returns the size (number of elements) of a specific dimension + * of a Julia array. + * + * @param a A pointer to `jl_array_t` representing the Julia array. + * @param d The dimension for which the size is requested. + * @return The size of the specified dimension of the array. + */ +JL_DLLEXPORT size_t jl_array_size(jl_array_t *a, int d) { + // n.b this functions only use was to violate the vector abstraction, so we have to continue to emulate that + if (d >= jl_array_ndims(a)) + return a->ref.mem->length; return jl_array_dim(a, d); } +/** + * @brief Get the C string pointer from a Julia string. + * + * @param s A pointer to `jl_value_t` representing the Julia string. + * @return A C string pointer containing the contents of the Julia string. + */ JL_DLLEXPORT const char *jl_string_ptr(jl_value_t *s) { return jl_string_data(s); } +/** + * @brief Call a Julia function with a specified number of arguments. + * + * @param f A pointer to `jl_function_t` representing the Julia function to call. + * @param args An array of pointers to `jl_value_t` representing the arguments. + * @param nargs The number of arguments in the array. + * @return A pointer to `jl_value_t` representing the result of the function call. + */ JL_DLLEXPORT jl_value_t *jl_call(jl_function_t *f, jl_value_t **args, uint32_t nargs) { jl_value_t *v; @@ -194,12 +300,20 @@ JL_DLLEXPORT jl_value_t *jl_call(jl_function_t *f, jl_value_t **args, uint32_t n _jl_exception_clear(ct); } JL_CATCH { - ct->ptls->previous_exception = jl_current_exception(); + ct->ptls->previous_exception = jl_current_exception(ct); v = NULL; } return v; } +/** + * @brief Call a Julia function with no arguments. + * + * A specialized case of `jl_call` for simpler scenarios. + * + * @param f A pointer to `jl_function_t` representing the Julia function to call. + * @return A pointer to `jl_value_t` representing the result of the function call. + */ JL_DLLEXPORT jl_value_t *jl_call0(jl_function_t *f) { jl_value_t *v; @@ -214,12 +328,21 @@ JL_DLLEXPORT jl_value_t *jl_call0(jl_function_t *f) _jl_exception_clear(ct); } JL_CATCH { - ct->ptls->previous_exception = jl_current_exception(); + ct->ptls->previous_exception = jl_current_exception(ct); v = NULL; } return v; } +/** + * @brief Call a Julia function with one argument. + * + * A specialized case of `jl_call` for simpler scenarios. + * + * @param f A pointer to `jl_function_t` representing the Julia function to call. + * @param a A pointer to `jl_value_t` representing the argument to the function. + * @return A pointer to `jl_value_t` representing the result of the function call. + */ JL_DLLEXPORT jl_value_t *jl_call1(jl_function_t *f, jl_value_t *a) { jl_value_t *v; @@ -237,12 +360,22 @@ JL_DLLEXPORT jl_value_t *jl_call1(jl_function_t *f, jl_value_t *a) _jl_exception_clear(ct); } JL_CATCH { - ct->ptls->previous_exception = jl_current_exception(); + ct->ptls->previous_exception = jl_current_exception(ct); v = NULL; } return v; } +/** + * @brief Call a Julia function with two arguments. + * + * A specialized case of `jl_call` for simpler scenarios. + * + * @param f A pointer to `jl_function_t` representing the Julia function to call. + * @param a A pointer to `jl_value_t` representing the first argument. + * @param b A pointer to `jl_value_t` representing the second argument. + * @return A pointer to `jl_value_t` representing the result of the function call. + */ JL_DLLEXPORT jl_value_t *jl_call2(jl_function_t *f, jl_value_t *a, jl_value_t *b) { jl_value_t *v; @@ -261,12 +394,23 @@ JL_DLLEXPORT jl_value_t *jl_call2(jl_function_t *f, jl_value_t *a, jl_value_t *b _jl_exception_clear(ct); } JL_CATCH { - ct->ptls->previous_exception = jl_current_exception(); + ct->ptls->previous_exception = jl_current_exception(ct); v = NULL; } return v; } +/** + * @brief Call a Julia function with three arguments. + * + * A specialized case of `jl_call` for simpler scenarios. + * + * @param f A pointer to `jl_function_t` representing the Julia function to call. + * @param a A pointer to `jl_value_t` representing the first argument. + * @param b A pointer to `jl_value_t` representing the second argument. + * @param c A pointer to `jl_value_t` representing the third argument. + * @return A pointer to `jl_value_t` representing the result of the function call. + */ JL_DLLEXPORT jl_value_t *jl_call3(jl_function_t *f, jl_value_t *a, jl_value_t *b, jl_value_t *c) { @@ -287,24 +431,23 @@ JL_DLLEXPORT jl_value_t *jl_call3(jl_function_t *f, jl_value_t *a, _jl_exception_clear(ct); } JL_CATCH { - ct->ptls->previous_exception = jl_current_exception(); + ct->ptls->previous_exception = jl_current_exception(ct); v = NULL; } return v; } -JL_DLLEXPORT void jl_yield(void) -{ - static jl_function_t *yieldfunc = NULL; - if (yieldfunc == NULL) - yieldfunc = (jl_function_t*)jl_get_global(jl_base_module, jl_symbol("yield")); - if (yieldfunc != NULL) - jl_call0(yieldfunc); -} - +/** + * @brief Get a field from a Julia object. + * + * @param o A pointer to `jl_value_t` representing the Julia object. + * @param fld A C string representing the name of the field to retrieve. + * @return A pointer to `jl_value_t` representing the value of the field. + */ JL_DLLEXPORT jl_value_t *jl_get_field(jl_value_t *o, const char *fld) { jl_value_t *v; + jl_task_t *ct = jl_current_task; JL_TRY { jl_value_t *s = (jl_value_t*)jl_symbol(fld); int i = jl_field_index((jl_datatype_t*)jl_typeof(o), (jl_sym_t*)s, 1); @@ -312,17 +455,29 @@ JL_DLLEXPORT jl_value_t *jl_get_field(jl_value_t *o, const char *fld) jl_exception_clear(); } JL_CATCH { - jl_current_task->ptls->previous_exception = jl_current_exception(); + ct->ptls->previous_exception = jl_current_exception(ct); v = NULL; } return v; } +/** + * @brief Begin an atomic signal-protected region. + * + * Marks the start of a region of code that should be protected + * from interruption by asynchronous signals. + */ JL_DLLEXPORT void jl_sigatomic_begin(void) { JL_SIGATOMIC_BEGIN(); } +/** + * @brief End an atomic signal-protected region. + * + * Marks the end of a region of code protected from asynchronous signals. + * It should be used in conjunction with `jl_sigatomic_begin` to define signal-protected regions. + */ JL_DLLEXPORT void jl_sigatomic_end(void) { jl_task_t *ct = jl_current_task; @@ -331,6 +486,11 @@ JL_DLLEXPORT void jl_sigatomic_end(void) JL_SIGATOMIC_END(); } +/** + * @brief Check if Julia is running in debug build mode. + * + * @return Returns 1 if Julia is in debug build mode, 0 otherwise. + */ JL_DLLEXPORT int jl_is_debugbuild(void) JL_NOTSAFEPOINT { #ifdef JL_DEBUG_BUILD @@ -340,6 +500,24 @@ JL_DLLEXPORT int jl_is_debugbuild(void) JL_NOTSAFEPOINT #endif } +/** + * @brief Check if Julia has been build with assertions enabled. + * + * @return Returns 1 if assertions are enabled, 0 otherwise. + */ +JL_DLLEXPORT int8_t jl_is_assertsbuild(void) JL_NOTSAFEPOINT { +#ifndef JL_NDEBUG + return 1; +#else + return 0; +#endif +} + +/** + * @brief Check if Julia's memory debugging is enabled. + * + * @return Returns 1 if memory debugging is enabled, 0 otherwise. + */ JL_DLLEXPORT int8_t jl_is_memdebug(void) JL_NOTSAFEPOINT { #ifdef MEMDEBUG return 1; @@ -348,92 +526,143 @@ JL_DLLEXPORT int8_t jl_is_memdebug(void) JL_NOTSAFEPOINT { #endif } +/** + * @brief Get the directory path of the Julia binary. + * + * @return A pointer to `jl_value_t` representing the directory path as a Julia string. + */ JL_DLLEXPORT jl_value_t *jl_get_julia_bindir(void) { return jl_cstr_to_string(jl_options.julia_bindir); } +/** + * @brief Get the path to the Julia binary. + * + * @return A pointer to `jl_value_t` representing the full path as a Julia string. + */ JL_DLLEXPORT jl_value_t *jl_get_julia_bin(void) { return jl_cstr_to_string(jl_options.julia_bin); } +/** + * @brief Get the path to the Julia system image file. + * + * @return A pointer to `jl_value_t` representing the system image file path as a Julia string. + */ JL_DLLEXPORT jl_value_t *jl_get_image_file(void) { return jl_cstr_to_string(jl_options.image_file); } +/** + * @brief Get the major version number of Julia. + * + * @return The major version number as an integer. + */ JL_DLLEXPORT int jl_ver_major(void) { return JULIA_VERSION_MAJOR; } +/** + * @brief Get the minor version number of Julia. + * + * @return The minor version number as an integer. + */ JL_DLLEXPORT int jl_ver_minor(void) { return JULIA_VERSION_MINOR; } +/** + * @brief Get the patch version number of Julia. + * + * @return The patch version number as an integer. + */ JL_DLLEXPORT int jl_ver_patch(void) { return JULIA_VERSION_PATCH; } +/** + * @brief Check if the current Julia version is a release version. + * + * @return Returns 1 if it is a release version, 0 otherwise. + */ JL_DLLEXPORT int jl_ver_is_release(void) { return JULIA_VERSION_IS_RELEASE; } +/** + * @brief Get the Julia version as a string. + * + * @return A C string containing the version information. + */ JL_DLLEXPORT const char *jl_ver_string(void) { return JULIA_VERSION_STRING; } -// return char* from String field in Base.GIT_VERSION_INFO -static const char *git_info_string(const char *fld) -{ - static jl_value_t *GIT_VERSION_INFO = NULL; - if (!GIT_VERSION_INFO) - GIT_VERSION_INFO = jl_get_global(jl_base_module, jl_symbol("GIT_VERSION_INFO")); - jl_value_t *f = jl_get_field(GIT_VERSION_INFO, fld); - assert(jl_is_string(f)); - return jl_string_data(f); -} - -JL_DLLEXPORT const char *jl_git_branch(void) -{ - static const char *branch = NULL; - if (!branch) branch = git_info_string("branch"); - return branch; -} - -JL_DLLEXPORT const char *jl_git_commit(void) -{ - static const char *commit = NULL; - if (!commit) commit = git_info_string("commit"); - return commit; -} - -// Create function versions of some useful macros for GDB or FFI use +/** + * @brief Convert a Julia value to a tagged value. + * + * Converts a Julia value into its corresponding tagged value representation. + * Tagged values include additional metadata used internally by the Julia runtime. + * + * @param v A pointer to `jl_value_t` representing the Julia value. + * @return A pointer to `jl_taggedvalue_t` representing the tagged value. + */ JL_DLLEXPORT jl_taggedvalue_t *(jl_astaggedvalue)(jl_value_t *v) { return jl_astaggedvalue(v); } +/** + * @brief Convert a tagged value back to a Julia value. + * + * Converts a tagged value back into its original Julia value. + * It's the inverse operation of `jl_astaggedvalue`. + * + * @param v A pointer to `jl_taggedvalue_t` representing the tagged value. + * @return A pointer to `jl_value_t` representing the original Julia value. + */ JL_DLLEXPORT jl_value_t *(jl_valueof)(jl_taggedvalue_t *v) { return jl_valueof(v); } +/** + * @brief Get the type of a Julia value. + * + * @param v A pointer to `jl_value_t` representing the Julia value. + * @return A pointer to `jl_value_t` representing the type of the value. + */ JL_DLLEXPORT jl_value_t *(jl_typeof)(jl_value_t *v) { return jl_typeof(v); } +/** + * @brief Get the field types of a Julia value. + * + * @param v A pointer to `jl_value_t` representing the Julia value. + * @return A pointer to `jl_value_t` representing the field types. + */ JL_DLLEXPORT jl_value_t *(jl_get_fieldtypes)(jl_value_t *v) { return (jl_value_t*)jl_get_fieldtypes((jl_datatype_t*)v); } +/** + * @brief Check equality of two Julia values. + * + * @param a A pointer to `jl_value_t` representing the first Julia value. + * @param b A pointer to `jl_value_t` representing the second Julia value. + * @return Returns 1 if the values are equal, 0 otherwise. + */ JL_DLLEXPORT int ijl_egal(jl_value_t *a, jl_value_t *b) { return jl_egal(a, b); @@ -441,24 +670,56 @@ JL_DLLEXPORT int ijl_egal(jl_value_t *a, jl_value_t *b) #ifndef __clang_gcanalyzer__ +/** + * @brief Enter a state where concurrent garbage collection (GC) is considered unsafe. + * + * Marks the beginning of a code region where garbage collection operations are unsafe. + * Used to make it legal to access GC-managed state (almost anything) + * + * @return An `int8_t` state value representing the previous GC state. + */ JL_DLLEXPORT int8_t (jl_gc_unsafe_enter)(void) { jl_task_t *ct = jl_current_task; return jl_gc_unsafe_enter(ct->ptls); } +/** + * @brief Leave the state where garbage collection is considered unsafe. + * + * Ends a code region where garbage collection was marked as unsafe. + * It restores the previous GC state using the state value returned by `jl_gc_unsafe_enter`. + * + * @param state The state value returned by `jl_gc_unsafe_enter` to restore the previous GC state. + */ JL_DLLEXPORT void (jl_gc_unsafe_leave)(int8_t state) { jl_task_t *ct = jl_current_task; jl_gc_unsafe_leave(ct->ptls, state); } +/** + * @brief Enter a state where garbage collection (GC) is considered safe. + * + * Marks the beginning of a code region where garbage collection operations are safe. + * Used to enable GC in sections of code where it was previously marked as unsafe. + * + * @return An `int8_t` state value representing the previous GC state. + */ JL_DLLEXPORT int8_t (jl_gc_safe_enter)(void) { jl_task_t *ct = jl_current_task; return jl_gc_safe_enter(ct->ptls); } +/** + * @brief Leave the state where garbage collection is considered safe. + * + * Ends a code region where garbage collection was marked as safe. + * It restores the previous GC state using the state value returned by `jl_gc_safe_enter`. + * + * @param state The state value returned by `jl_gc_safe_enter` to restore the previous GC state. + */ JL_DLLEXPORT void (jl_gc_safe_leave)(int8_t state) { jl_task_t *ct = jl_current_task; @@ -466,49 +727,96 @@ JL_DLLEXPORT void (jl_gc_safe_leave)(int8_t state) } #endif +/** + * @brief Trigger a garbage collection safepoint in a GC-unsafe region. + * + * Triggers a safepoint for garbage collection. Used to + * ensure that the garbage collector can run at specific points in the code, + * particularly in long-running operations or loops. + */ JL_DLLEXPORT void jl_gc_safepoint(void) { jl_task_t *ct = jl_current_task; jl_gc_safepoint_(ct->ptls); } +/** + * @brief Pause CPU execution for a brief moment. + * + * Used to pause the CPU briefly, typically to reduce power consumption + * or manage CPU resources more effectively in a tight loop or busy wait scenario. + */ JL_DLLEXPORT void (jl_cpu_pause)(void) { jl_cpu_pause(); } +/** + * @brief Suspend CPU execution. + * + * Suspends CPU execution until a specific condition or event occurs. + */ JL_DLLEXPORT void (jl_cpu_suspend)(void) { jl_cpu_suspend(); } +/** + * @brief Wake the CPU from a suspended state. + * + * Used to resume CPU execution after it has been suspended using `jl_cpu_suspend`. + */ JL_DLLEXPORT void (jl_cpu_wake)(void) { jl_cpu_wake(); } +/** + * @brief Enable cumulative compile timing. + */ JL_DLLEXPORT void jl_cumulative_compile_timing_enable(void) { // Increment the flag to allow reentrant callers to `@time`. jl_atomic_fetch_add(&jl_measure_compile_time_enabled, 1); } +/** + * @brief Disable cumulative compile timing. + */ JL_DLLEXPORT void jl_cumulative_compile_timing_disable(void) { // Decrement the flag when done measuring, allowing other callers to continue measuring. jl_atomic_fetch_add(&jl_measure_compile_time_enabled, -1); } +/** + * @brief Get the cumulative compilation time in nanoseconds. + * + * @return The cumulative compilation time in nanoseconds. + */ JL_DLLEXPORT uint64_t jl_cumulative_compile_time_ns(void) { return jl_atomic_load_relaxed(&jl_cumulative_compile_time); } +/** + * @brief Get the cumulative recompilation time in nanoseconds. + * + * @return The cumulative recompilation time in nanoseconds. + */ JL_DLLEXPORT uint64_t jl_cumulative_recompile_time_ns(void) { return jl_atomic_load_relaxed(&jl_cumulative_recompile_time); } +/** + * @brief Retrieve floating-point environment constants. + * + * Populates an array with constants related to the floating-point environment, + * such as rounding modes and exception flags. + * + * @param ret An array of integers to be populated with floating-point environment constants. + */ JL_DLLEXPORT void jl_get_fenv_consts(int *ret) { ret[0] = FE_INEXACT; @@ -530,6 +838,14 @@ JL_DLLEXPORT int jl_get_fenv_rounding(void) { return fegetround(); } + +/** + * @brief Set the floating-point rounding mode. + * + * @param i An integer representing the desired floating-point rounding mode. + See also "floating-point rounding" macros in ``. + * @return An integer indicating the success or failure of setting the rounding mode. + */ JL_DLLEXPORT int jl_set_fenv_rounding(int i) { return fesetround(i); @@ -537,6 +853,7 @@ JL_DLLEXPORT int jl_set_fenv_rounding(int i) static int exec_program(char *program) { + jl_task_t *ct = jl_current_task; JL_TRY { jl_load(jl_main_module, program); } @@ -545,7 +862,7 @@ static int exec_program(char *program) // printing directly to STDERR_FILENO. int shown_err = 0; jl_printf(JL_STDERR, "error during bootstrap:\n"); - jl_value_t *exc = jl_current_exception(); + jl_value_t *exc = jl_current_exception(ct); jl_value_t *showf = jl_base_module ? jl_get_function(jl_base_module, "show") : NULL; if (showf) { jl_value_t *errs = jl_stderr_obj(); @@ -574,8 +891,8 @@ static NOINLINE int true_main(int argc, char *argv[]) jl_function_t *start_client = jl_base_module ? (jl_function_t*)jl_get_global(jl_base_module, jl_symbol("_start")) : NULL; + jl_task_t *ct = jl_current_task; if (start_client) { - jl_task_t *ct = jl_current_task; int ret = 1; JL_TRY { size_t last_age = ct->world_age; @@ -587,7 +904,7 @@ static NOINLINE int true_main(int argc, char *argv[]) ct->world_age = last_age; } JL_CATCH { - jl_no_exc_handler(jl_current_exception(), ct); + jl_no_exc_handler(jl_current_exception(ct), ct); } return ret; } @@ -631,7 +948,7 @@ static NOINLINE int true_main(int argc, char *argv[]) line = NULL; } jl_printf((JL_STREAM*)STDERR_FILENO, "\nparser error:\n"); - jl_static_show((JL_STREAM*)STDERR_FILENO, jl_current_exception()); + jl_static_show((JL_STREAM*)STDERR_FILENO, jl_current_exception(ct)); jl_printf((JL_STREAM*)STDERR_FILENO, "\n"); jl_print_backtrace(); // written to STDERR_FILENO } @@ -691,6 +1008,13 @@ static void rr_detach_teleport(void) { } #endif +/** + * @brief Entry point for the Julia REPL (Read-Eval-Print Loop). + * + * @param argc The number of command-line arguments. + * @param argv Array of command-line arguments. + * @return An integer indicating the exit status of the REPL session. + */ JL_DLLEXPORT int jl_repl_entrypoint(int argc, char *argv[]) { #ifdef USE_TRACY diff --git a/src/jlfrontend.scm b/src/jlfrontend.scm index 23c3acf7973c7..2c5f42eda5ce8 100644 --- a/src/jlfrontend.scm +++ b/src/jlfrontend.scm @@ -31,8 +31,7 @@ ;; this is overwritten when we run in actual julia (define (defined-julia-global v) #f) -(define (julia-current-file) 'none) -(define (julia-current-line) 0) +(define (nothrow-julia-global v) #f) ;; parser entry points @@ -140,7 +139,7 @@ (define (toplevel-only-expr? e) (and (pair? e) - (or (memq (car e) '(toplevel line module import using export + (or (memq (car e) '(toplevel line module import using export public error incomplete)) (and (memq (car e) '(global const)) (every symbol? (cdr e)))))) @@ -181,7 +180,10 @@ ;; Abuse scm_to_julia here to convert arguments to warn. This is meant for ;; `Expr`s but should be good enough provided we're only passing simple ;; numbers, symbols and strings. - ((lowering-warning (lambda lst (set! warnings (cons (cons 'warn lst) warnings))))) + ((lowering-warning (lambda (level group warn_file warn_line . lst) + (let ((line (if (= warn_line 0) line warn_line)) + (file (if (eq? warn_file 'none) file warn_file))) + (set! warnings (cons (list* 'warn level group (symbol (string file line)) file line lst) warnings)))))) (let ((thunk (if stmt (expand-to-thunk-stmt- expr file line) (expand-to-thunk- expr file line)))) diff --git a/src/jloptions.c b/src/jloptions.c index f9443ed8f5704..3a2c26165e63e 100644 --- a/src/jloptions.c +++ b/src/jloptions.c @@ -8,6 +8,7 @@ #include #include + #include "julia_assert.h" #ifdef _OS_WINDOWS_ @@ -18,6 +19,15 @@ char *shlib_ext = ".dylib"; char *shlib_ext = ".so"; #endif +/* This simple hand-crafted tolower exists to avoid locale-dependent effects in + * behaviors (and utf8proc_tolower wasn't linking properly on all platforms) */ +static char ascii_tolower(char c) +{ + if ('A' <= c && c <= 'Z') + return c - 'A' + 'a'; + return c; +} + static const char system_image_path[256] = "\0" JL_SYSTEM_IMAGE_PATH; JL_DLLEXPORT const char *jl_get_default_sysimg_path(void) { @@ -96,124 +106,159 @@ JL_DLLEXPORT void jl_init_options(void) static const char usage[] = "\n julia [switches] -- [programfile] [args...]\n\n"; static const char opts[] = - "Switches (a '*' marks the default value, if applicable; settings marked '($)' may trigger package precompilation):\n\n" - " -v, --version Display version information\n" - " -h, --help Print this message (--help-hidden for more)\n" - " --help-hidden Uncommon options not shown by `-h`\n\n" + "Switches (a '*' marks the default value, if applicable; settings marked '($)' may trigger package\n" + "precompilation):\n\n" + " Option Description\n" + " ---------------------------------------------------------------------------------------------------\n" + " -v, --version Display version information\n" + " -h, --help Print command-line options (this message)\n" + " --help-hidden Print uncommon options not shown by `-h`\n\n" // startup options - " --project[={|@.}] Set as the active project/environment\n" - " -J, --sysimage Start up with the given system image file\n" - " -H, --home Set location of `julia` executable\n" - " --startup-file={yes*|no} Load `JULIA_DEPOT_PATH/config/startup.jl`; if `JULIA_DEPOT_PATH`\n" - " environment variable is unset, load `~/.julia/config/startup.jl`\n" - " --handle-signals={yes*|no} Enable or disable Julia's default signal handlers\n" - " --sysimage-native-code={yes*|no}\n" - " Use native code from system image if available\n" - " --compiled-modules={yes*|no|existing}\n" - " Enable or disable incremental precompilation of modules\n" - " --pkgimages={yes*|no}\n" - " Enable or disable usage of native code caching in the form of pkgimages ($)\n\n" + " --project[={|@.}] Set as the active project/environment.\n" + " The default @. option will search through parent\n" + " directories until a Project.toml or JuliaProject.toml\n" + " file is found.\n" + " -J, --sysimage Start up with the given system image file\n" + " -H, --home Set location of `julia` executable\n" + " --startup-file={yes*|no} Load `JULIA_DEPOT_PATH/config/startup.jl`; \n" + " if `JULIA_DEPOT_PATH` environment variable is unset,\n" + " load `~/.julia/config/startup.jl`\n" + " --handle-signals={yes*|no} Enable or disable Julia's default signal handlers\n" + " --sysimage-native-code={yes*|no} Use native code from system image if available\n" + " --compiled-modules={yes*|no|existing|strict} Enable or disable incremental precompilation of\n" + " modules. The `existing` option allows use of existing\n" + " compiled modules that were previously precompiled,\n" + " but disallows creation of new precompile files.\n" + " The `strict` option is similar, but will error if no\n" + " precompile file is found.\n" + " --pkgimages={yes*|no|existing} Enable or disable usage of native code caching in the\n" + " form of pkgimages. The `existing` option allows use\n" + " of existing pkgimages but disallows creation of new\n" + " ones ($)\n\n" // actions - " -e, --eval Evaluate \n" - " -E, --print Evaluate and display the result\n" - " -L, --load Load immediately on all processors\n\n" + " -e, --eval Evaluate \n" + " -E, --print Evaluate and display the result\n" + " -m, --module [args] Run entry point of `Package` (`@main` function) with\n" + " `args'.\n" + " -L, --load Load immediately on all processors\n\n" // parallel options - " -t, --threads {auto|N[,auto|M]}\n" - " Enable N[+M] threads; N threads are assigned to the `default`\n" - " threadpool, and if M is specified, M threads are assigned to the\n" - " `interactive` threadpool; \"auto\" tries to infer a useful\n" - " default number of threads to use but the exact behavior might change\n" - " in the future. Currently sets N to the number of CPUs assigned to\n" - " this Julia process based on the OS-specific affinity assignment\n" - " interface if supported (Linux and Windows) or to the number of CPU\n" - " threads if not supported (MacOS) or if process affinity is not\n" - " configured, and sets M to 1.\n" - " --gcthreads=N[,M] Use N threads for the mark phase of GC and M (0 or 1) threads for the concurrent sweeping phase of GC.\n" - " N is set to half of the number of compute threads and M is set to 0 if unspecified.\n" - " -p, --procs {N|auto} Integer value N launches N additional local worker processes\n" - " \"auto\" launches as many workers as the number of local CPU threads (logical cores)\n" - " --machine-file Run processes on hosts listed in \n\n" + " -t, --threads {auto|N[,auto|M]} Enable N[+M] threads; N threads are assigned to the\n" + " `default` threadpool, and if M is specified, M\n" + " threads are assigned to the `interactive`\n" + " threadpool; `auto` tries to infer a useful\n" + " default number of threads to use but the exact\n" + " behavior might change in the future. Currently sets\n" + " N to the number of CPUs assigned to this Julia\n" + " process based on the OS-specific affinity assignment\n" + " interface if supported (Linux and Windows) or to the\n" + " number of CPU threads if not supported (MacOS) or if\n" + " process affinity is not configured, and sets M to 1.\n" + " --gcthreads=N[,M] Use N threads for the mark phase of GC and M (0 or 1)\n" + " threads for the concurrent sweeping phase of GC.\n" + " N is set to half of the number of compute threads and\n" + " M is set to 0 if unspecified.\n" + " -p, --procs {N|auto} Integer value N launches N additional local worker\n" + " processes `auto` launches as many workers as the\n" + " number of local CPU threads (logical cores).\n" + " --machine-file Run processes on hosts listed in \n\n" // interactive options - " -i, --interactive Interactive mode; REPL runs and `isinteractive()` is true\n" - " -q, --quiet Quiet startup: no banner, suppress REPL warnings\n" - " --banner={yes|no|short|auto*}\n" - " Enable or disable startup banner\n" - " --color={yes|no|auto*} Enable or disable color text\n" - " --history-file={yes*|no} Load or save history\n\n" + " -i, --interactive Interactive mode; REPL runs and\n" + " `isinteractive()` is true.\n" + " -q, --quiet Quiet startup: no banner, suppress REPL warnings\n" + " --banner={yes|no|short|auto*} Enable or disable startup banner\n" + " --color={yes|no|auto*} Enable or disable color text\n" + " --history-file={yes*|no} Load or save history\n\n" // error and warning options - " --depwarn={yes|no*|error} Enable or disable syntax and method deprecation warnings (`error` turns warnings into errors)\n" - " --warn-overwrite={yes|no*} Enable or disable method overwrite warnings\n" - " --warn-scope={yes*|no} Enable or disable warning for ambiguous top-level scope\n\n" + " --depwarn={yes|no*|error} Enable or disable syntax and method deprecation\n" + " warnings (`error` turns warnings into errors)\n" + " --warn-overwrite={yes|no*} Enable or disable method overwrite warnings\n" + " --warn-scope={yes*|no} Enable or disable warning for ambiguous top-level\n" + " scope\n\n" // code generation options - " -C, --cpu-target Limit usage of CPU features up to ; set to `help` to see the available options\n" - " -O, --optimize={0,1,2*,3} Set the optimization level (level 3 if `-O` is used without a level) ($)\n" - " --min-optlevel={0*,1,2,3} Set a lower bound on the optimization level\n" + " -C, --cpu-target Limit usage of CPU features up to ; set to\n" + " `help` to see the available options\n" + " -O, --optimize={0|1|2*|3} Set the optimization level (level 3 if `-O` is used\n" + " without a level) ($)\n" + " --min-optlevel={0*|1|2|3} Set a lower bound on the optimization level\n" #ifdef JL_DEBUG_BUILD - " -g, --debug-info=[{0,1,2*}] Set the level of debug info generation in the julia-debug build ($)\n" + " -g, --debug-info=[{0|1|2*}] Set the level of debug info generation in the\n" + " julia-debug build ($)\n" #else - " -g, --debug-info=[{0,1*,2}] Set the level of debug info generation (level 2 if `-g` is used without a level) ($)\n" + " -g, --debug-info=[{0|1*|2}] Set the level of debug info generation (level 2 if\n" + " `-g` is used without a level) ($)\n" #endif - " --inline={yes*|no} Control whether inlining is permitted, including overriding @inline declarations\n" - " --check-bounds={yes|no|auto*}\n" - " Emit bounds checks always, never, or respect @inbounds declarations ($)\n" + " --inline={yes*|no} Control whether inlining is permitted, including\n" + " overriding @inline declarations\n" + " --check-bounds={yes|no|auto*} Emit bounds checks always, never, or respect\n" + " @inbounds declarations ($)\n" + " --math-mode={ieee|user*} Always follow `ieee` floating point semantics or\n" + " respect `@fastmath` declarations\n\n" #ifdef USE_POLLY - " --polly={yes*|no} Enable or disable the polyhedral optimizer Polly (overrides @polly declaration)\n" + " --polly={yes*|no} Enable or disable the polyhedral optimizer Polly\n" + " (overrides @polly declaration)\n" #endif // instrumentation options - " --code-coverage[={none*|user|all}]\n" - " Count executions of source lines (omitting setting is equivalent to `user`)\n" - " --code-coverage=@\n" - " Count executions but only in files that fall under the given file path/directory.\n" - " The `@` prefix is required to select this option. A `@` with no path will track the\n" - " current directory.\n" + " --code-coverage[={none*|user|all}] Count executions of source lines (omitting setting is\n" + " equivalent to `user`)\n" + " --code-coverage=@ Count executions but only in files that fall under\n" + " the given file path/directory. The `@` prefix is\n" + " required to select this option. A `@` with no path\n" + " will track the current directory.\n" - " --code-coverage=tracefile.info\n" - " Append coverage information to the LCOV tracefile (filename supports format tokens)\n" + " --code-coverage=tracefile.info Append coverage information to the LCOV tracefile\n" + " (filename supports format tokens)\n" // TODO: These TOKENS are defined in `runtime_ccall.cpp`. A more verbose `--help` should include that list here. - " --track-allocation[={none*|user|all}]\n" - " Count bytes allocated by each source line (omitting setting is equivalent to `user`)\n" - " --track-allocation=@\n" - " Count bytes but only in files that fall under the given file path/directory.\n" - " The `@` prefix is required to select this option. A `@` with no path will track the\n" - " current directory.\n" - " --bug-report=KIND Launch a bug report session. It can be used to start a REPL, run a script, or evaluate\n" - " expressions. It first tries to use BugReporting.jl installed in current environment and\n" - " fallbacks to the latest compatible BugReporting.jl if not. For more information, see\n" - " --bug-report=help.\n\n" - - " --heap-size-hint= Forces garbage collection if memory usage is higher than that value.\n" - " The memory hint might be specified in megabytes(500M) or gigabytes(1G)\n\n" + " --track-allocation[={none*|user|all}] Count bytes allocated by each source line (omitting\n" + " setting is equivalent to `user`)\n" + " --track-allocation=@ Count bytes but only in files that fall under the\n" + " given file path/directory. The `@` prefix is required\n" + " to select this option. A `@` with no path will track\n" + " the current directory.\n" + " --bug-report=KIND Launch a bug report session. It can be used to start\n" + " a REPL, run a script, or evaluate expressions. It\n" + " first tries to use BugReporting.jl installed in\n" + " current environment and fallbacks to the latest\n" + " compatible BugReporting.jl if not. For more\n" + " information, see --bug-report=help.\n\n" + " --heap-size-hint= Forces garbage collection if memory usage is higher\n" + " than the given value. The value may be specified as a\n" + " number of bytes, optionally in units of KB, MB, GB,\n" + " or TB, or as a percentage of physical memory with %.\n\n" ; static const char opts_hidden[] = "Switches (a '*' marks the default value, if applicable):\n\n" + " Option Description\n" + " ---------------------------------------------------------------------------------------------------\n" // code generation options - " --compile={yes*|no|all|min}\n" - " Enable or disable JIT compiler, or request exhaustive or minimal compilation\n\n" + " --compile={yes*|no|all|min} Enable or disable JIT compiler, or request exhaustive\n" + " or minimal compilation\n\n" // compiler output options - " --output-o Generate an object file (including system image data)\n" - " --output-ji Generate a system image data file (.ji)\n" - " --strip-metadata Remove docstrings and source location info from system image\n" - " --strip-ir Remove IR (intermediate representation) of compiled functions\n\n" + " --output-o Generate an object file (including system image data)\n" + " --output-ji Generate a system image data file (.ji)\n" + " --strip-metadata Remove docstrings and source location info from\n" + " system image\n" + " --strip-ir Remove IR (intermediate representation) of compiled\n" + " functions\n\n" // compiler debugging (see the devdocs for tips on using these options) - " --output-unopt-bc Generate unoptimized LLVM bitcode (.bc)\n" - " --output-bc Generate LLVM bitcode (.bc)\n" - " --output-asm Generate an assembly file (.s)\n" - " --output-incremental={yes|no*}\n" - " Generate an incremental output file (rather than complete)\n" - " --trace-compile={stderr,name}\n" - " Print precompile statements for methods compiled during execution or save to a path\n" - " --image-codegen Force generate code in imaging mode\n" - " --permalloc-pkgimg={yes|no*} Copy the data section of package images into memory\n" + " --output-unopt-bc Generate unoptimized LLVM bitcode (.bc)\n" + " --output-bc Generate LLVM bitcode (.bc)\n" + " --output-asm Generate an assembly file (.s)\n" + " --output-incremental={yes|no*} Generate an incremental output file (rather than\n" + " complete)\n" + " --trace-compile={stderr|name} Print precompile statements for methods compiled\n" + " during execution or save to a path\n" + " --image-codegen Force generate code in imaging mode\n" + " --permalloc-pkgimg={yes|no*} Copy the data section of package images into memory\n" ; JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) @@ -261,7 +306,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) opt_gc_threads, opt_permalloc_pkgimg }; - static const char* const shortopts = "+vhqH:e:E:L:J:C:it:p:O:g:"; + static const char* const shortopts = "+vhqH:e:E:L:J:C:it:p:O:g:m:"; static const struct option longopts[] = { // exposed command line options // NOTE: This set of required arguments need to be kept in sync @@ -274,6 +319,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) { "banner", required_argument, 0, opt_banner }, { "home", required_argument, 0, 'H' }, { "eval", required_argument, 0, 'e' }, + { "module", required_argument, 0, 'm' }, { "print", required_argument, 0, 'E' }, { "load", required_argument, 0, 'L' }, { "bug-report", required_argument, 0, opt_bug_report }, @@ -333,7 +379,6 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) const char **cmds = NULL; int codecov = JL_LOG_NONE; int malloclog = JL_LOG_NONE; - int pkgimage_explicit = 0; int argc = *argcp; char **argv = *argvp; char *endptr; @@ -412,6 +457,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) case 'e': // eval case 'E': // print case 'L': // load + case 'm': // module case opt_bug_report: // bug { size_t sz = strlen(optarg) + 1; @@ -425,6 +471,10 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) ncmds++; cmds[ncmds] = 0; jl_options.cmds = cmds; + if (c == 'm') { + optind -= 1; + goto parsing_args_done; + } break; } case 'J': // sysimage @@ -465,17 +515,20 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) jl_options.use_compiled_modules = JL_OPTIONS_USE_COMPILED_MODULES_NO; else if (!strcmp(optarg,"existing")) jl_options.use_compiled_modules = JL_OPTIONS_USE_COMPILED_MODULES_EXISTING; + else if (!strcmp(optarg,"strict")) + jl_options.use_compiled_modules = JL_OPTIONS_USE_COMPILED_MODULES_STRICT; else - jl_errorf("julia: invalid argument to --compiled-modules={yes|no|existing} (%s)", optarg); + jl_errorf("julia: invalid argument to --compiled-modules={yes|no|existing|strict} (%s)", optarg); break; case opt_pkgimages: - pkgimage_explicit = 1; if (!strcmp(optarg,"yes")) jl_options.use_pkgimages = JL_OPTIONS_USE_PKGIMAGES_YES; else if (!strcmp(optarg,"no")) jl_options.use_pkgimages = JL_OPTIONS_USE_PKGIMAGES_NO; + else if (!strcmp(optarg,"existing")) + jl_options.use_pkgimages = JL_OPTIONS_USE_PKGIMAGES_EXISTING; else - jl_errorf("julia: invalid argument to --pkgimage={yes|no} (%s)", optarg); + jl_errorf("julia: invalid argument to --pkgimages={yes|no} (%s)", optarg); break; case 'C': // cpu-target jl_options.cpu_target = strdup(optarg); @@ -762,7 +815,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) else if (!strcmp(optarg,"user")) jl_options.fast_math = JL_OPTIONS_FAST_MATH_DEFAULT; else - jl_errorf("julia: invalid argument to --math-mode (%s)", optarg); + jl_errorf("julia: invalid argument to --math-mode={ieee|user} (%s)", optarg); break; case opt_worker: jl_options.worker = 1; @@ -799,36 +852,51 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) break; case opt_heap_size_hint: if (optarg != NULL) { - size_t endof = strlen(optarg); long double value = 0.0; - if (sscanf(optarg, "%Lf", &value) == 1 && value > 1e-7) { - char unit = optarg[endof - 1]; - uint64_t multiplier = 1ull; - switch (unit) { - case 'k': - case 'K': - multiplier <<= 10; - break; - case 'm': - case 'M': - multiplier <<= 20; - break; - case 'g': - case 'G': - multiplier <<= 30; - break; - case 't': - case 'T': - multiplier <<= 40; - break; - default: - break; - } - jl_options.heap_size_hint = (uint64_t)(value * multiplier); + char unit[4] = {0}; + int nparsed = sscanf(optarg, "%Lf%3s", &value, unit); + if (nparsed == 0 || strlen(unit) > 2 || (strlen(unit) == 2 && ascii_tolower(unit[1]) != 'b')) { + jl_errorf("julia: invalid argument to --heap-size-hint (%s)", optarg); + } + uint64_t multiplier = 1ull; + switch (ascii_tolower(unit[0])) { + case '\0': + case 'b': + break; + case 'k': + multiplier <<= 10; + break; + case 'm': + multiplier <<= 20; + break; + case 'g': + multiplier <<= 30; + break; + case 't': + multiplier <<= 40; + break; + case '%': + if (value > 100) + jl_errorf("julia: invalid percentage specified in --heap-size-hint"); + uint64_t mem = uv_get_total_memory(); + uint64_t cmem = uv_get_constrained_memory(); + if (cmem > 0 && cmem < mem) + mem = cmem; + multiplier = mem/100; + break; + default: + jl_errorf("julia: invalid argument to --heap-size-hint (%s)", optarg); + break; } + long double sz = value * multiplier; + if (isnan(sz) || sz < 0) { + jl_errorf("julia: invalid argument to --heap-size-hint (%s)", optarg); + } + const long double limit = ldexpl(1.0, 64); // UINT64_MAX + 1 + jl_options.heap_size_hint = sz < limit ? (uint64_t)sz : UINT64_MAX; } if (jl_options.heap_size_hint == 0) - jl_errorf("julia: invalid argument to --heap-size-hint without memory size specified"); + jl_errorf("julia: invalid memory size specified in --heap-size-hint"); break; case opt_gc_threads: @@ -860,13 +928,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) "This is a bug, please report it.", c); } } - if (codecov || malloclog) { - if (pkgimage_explicit && jl_options.use_pkgimages) { - jl_errorf("julia: Can't use --pkgimages=yes together " - "with --track-allocation or --code-coverage."); - } - jl_options.use_pkgimages = 0; - } + parsing_args_done: jl_options.code_coverage = codecov; jl_options.malloc_log = malloclog; int proc_args = *argcp < optind ? *argcp : optind; diff --git a/src/jltypes.c b/src/jltypes.c index 33b52158488a3..2ea432a7a5cc3 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -65,7 +65,9 @@ static int layout_uses_free_typevars(jl_value_t *v, jl_typeenv_t *env) } if (jl_is_datatype(v)) { jl_datatype_t *dt = (jl_datatype_t*)v; - if (dt->layout || dt->isconcretetype || !dt->name->mayinlinealloc) + if (dt->isconcretetype) + return 0; + if (dt->layout || !dt->name->mayinlinealloc) return 0; if (dt->name == jl_namedtuple_typename) return layout_uses_free_typevars(jl_tparam0(dt), env) || layout_uses_free_typevars(jl_tparam1(dt), env); @@ -215,7 +217,7 @@ JL_DLLEXPORT jl_array_t *jl_find_free_typevars(jl_value_t *v) } // test whether a type has vars bound by the given environment -static int jl_has_bound_typevars(jl_value_t *v, jl_typeenv_t *env) JL_NOTSAFEPOINT +int jl_has_bound_typevars(jl_value_t *v, jl_typeenv_t *env) JL_NOTSAFEPOINT { while (1) { if (jl_is_typevar(v)) { @@ -290,7 +292,13 @@ JL_DLLEXPORT int jl_has_typevar_from_unionall(jl_value_t *t, jl_unionall_t *ua) int jl_has_fixed_layout(jl_datatype_t *dt) { - if (dt->layout || dt->isconcretetype) + if (dt->isconcretetype) + return 1; + if (jl_is_genericmemory_type(dt)) { // GenericMemory{kind,addrspace,T} uses T for final layout, which is a parameter not a field however + // optionally: return !layout_uses_free_typevars(jl_tparam1(dt), env); + return 0; + } + if (dt->layout) return 1; if (dt->name->abstract) return 0; @@ -313,15 +321,15 @@ int jl_has_fixed_layout(jl_datatype_t *dt) int jl_type_mappable_to_c(jl_value_t *ty) { assert(!jl_is_typevar(ty) && jl_is_type(ty)); + if (jl_is_array_type(ty) || jl_is_genericmemory_type(ty) || + (jl_is_datatype(ty) && ((jl_datatype_t*)ty)->layout != NULL && + jl_is_layout_opaque(((jl_datatype_t*)ty)->layout))) + return 1; // as boxed if (jl_is_structtype(ty)) return jl_has_fixed_layout((jl_datatype_t*)ty) && ((jl_datatype_t*)ty)->name->atomicfields == NULL; if (jl_is_primitivetype(ty)) - return 1; - if (ty == (jl_value_t*)jl_any_type || ty == (jl_value_t*)jl_bottom_type) - return 1; // as boxed - if (jl_is_abstract_ref_type(ty) || jl_is_array_type(ty) || - (jl_is_datatype(ty) && ((jl_datatype_t*)ty)->layout != NULL && - jl_is_layout_opaque(((jl_datatype_t*)ty)->layout))) + return 1; // as isbits + if (ty == (jl_value_t*)jl_any_type || ty == (jl_value_t*)jl_bottom_type || jl_is_abstract_ref_type(ty)) return 1; // as boxed return 0; // refuse to map Union and UnionAll to C } @@ -548,6 +556,44 @@ static void isort_union(jl_value_t **a, size_t len) JL_NOTSAFEPOINT } } +static int simple_subtype(jl_value_t *a, jl_value_t *b, int hasfree, int isUnion) +{ + assert(hasfree == (jl_has_free_typevars(a) | (jl_has_free_typevars(b) << 1))); + if (a == jl_bottom_type || b == (jl_value_t*)jl_any_type) + return 1; + if (jl_egal(a, b)) + return 1; + if (hasfree == 0) { + int mergeable = isUnion; + if (!mergeable) // issue #24521: don't merge Type{T} where typeof(T) varies + mergeable = !(jl_is_type_type(a) && jl_is_type_type(b) && + jl_typeof(jl_tparam0(a)) != jl_typeof(jl_tparam0(b))); + return mergeable && jl_subtype(a, b); + } + if (jl_is_typevar(a)) { + jl_value_t *na = ((jl_tvar_t*)a)->ub; + hasfree &= (jl_has_free_typevars(na) | 2); + return simple_subtype(na, b, hasfree, isUnion); + } + if (jl_is_typevar(b)) { + jl_value_t *nb = ((jl_tvar_t*)b)->lb; + // This branch is not valid if `b` obeys diagonal rule, + // as it might normalize `Union` into a single `TypeVar`, e.g. + // Tuple{Union{Int,T},T} where {T>:Int} != Tuple{T,T} where {T>:Int} + if (is_leaf_bound(nb)) + return 0; + hasfree &= ((jl_has_free_typevars(nb) << 1) | 1); + return simple_subtype(a, nb, hasfree, isUnion); + } + if (b==(jl_value_t*)jl_datatype_type || b==(jl_value_t*)jl_typeofbottom_type) { + // This branch is not valid for `Union`/`UnionAll`, e.g. + // (Type{Union{Int,T2} where {T2<:T1}} where {T1}){Int} == Type{Int64} + // (Type{Union{Int,T1}} where {T1}){Int} == Type{Int64} + return jl_is_type_type(a) && jl_typeof(jl_tparam0(a)) == b; + } + return 0; +} + JL_DLLEXPORT jl_value_t *jl_type_union(jl_value_t **ts, size_t n) { if (n == 0) @@ -572,13 +618,9 @@ JL_DLLEXPORT jl_value_t *jl_type_union(jl_value_t **ts, size_t n) int has_free = temp[i] != NULL && jl_has_free_typevars(temp[i]); for (j = 0; j < nt; j++) { if (j != i && temp[i] && temp[j]) { - if (temp[i] == jl_bottom_type || - temp[j] == (jl_value_t*)jl_any_type || - jl_egal(temp[i], temp[j]) || - (!has_free && !jl_has_free_typevars(temp[j]) && - jl_subtype(temp[i], temp[j]))) { + int has_free2 = has_free | (jl_has_free_typevars(temp[j]) << 1); + if (simple_subtype(temp[i], temp[j], has_free2, 1)) temp[i] = NULL; - } } } } @@ -600,18 +642,9 @@ JL_DLLEXPORT jl_value_t *jl_type_union(jl_value_t **ts, size_t n) return tu; } -// note: this is turned off as `Union` doesn't do such normalization. -// static int simple_subtype(jl_value_t *a, jl_value_t *b) -// { -// if (jl_is_kind(b) && jl_is_type_type(a) && jl_typeof(jl_tparam0(a)) == b) -// return 1; -// if (jl_is_typevar(b) && obviously_egal(a, ((jl_tvar_t*)b)->lb)) -// return 1; -// return 0; -// } - -static int simple_subtype2(jl_value_t *a, jl_value_t *b, int hasfree) +static int simple_subtype2(jl_value_t *a, jl_value_t *b, int hasfree, int isUnion) { + assert(hasfree == (jl_has_free_typevars(a) | (jl_has_free_typevars(b) << 1))); int subab = 0, subba = 0; if (jl_egal(a, b)) { subab = subba = 1; @@ -622,9 +655,9 @@ static int simple_subtype2(jl_value_t *a, jl_value_t *b, int hasfree) else if (b == jl_bottom_type || a == (jl_value_t*)jl_any_type) { subba = 1; } - else if (hasfree) { - // subab = simple_subtype(a, b); - // subba = simple_subtype(b, a); + else if (hasfree != 0) { + subab = simple_subtype(a, b, hasfree, isUnion); + subba = simple_subtype(b, a, ((hasfree & 2) >> 1) | ((hasfree & 1) << 1), isUnion); } else if (jl_is_type_type(a) && jl_is_type_type(b) && jl_typeof(jl_tparam0(a)) != jl_typeof(jl_tparam0(b))) { @@ -656,10 +689,11 @@ jl_value_t *simple_union(jl_value_t *a, jl_value_t *b) // first remove cross-redundancy and check if `a >: b` or `a <: b`. for (i = 0; i < nta; i++) { if (temp[i] == NULL) continue; - int hasfree = jl_has_free_typevars(temp[i]); + int has_free = jl_has_free_typevars(temp[i]); for (j = nta; j < nt; j++) { if (temp[j] == NULL) continue; - int subs = simple_subtype2(temp[i], temp[j], hasfree || jl_has_free_typevars(temp[j])); + int has_free2 = has_free | (jl_has_free_typevars(temp[j]) << 1); + int subs = simple_subtype2(temp[i], temp[j], has_free2, 0); int subab = subs & 1, subba = subs >> 1; if (subab) { temp[i] = NULL; @@ -689,15 +723,9 @@ jl_value_t *simple_union(jl_value_t *a, jl_value_t *b) size_t jmax = i < nta ? nta : nt; for (j = jmin; j < jmax; j++) { if (j != i && temp[i] && temp[j]) { - if (temp[i] == jl_bottom_type || - temp[j] == (jl_value_t*)jl_any_type || - jl_egal(temp[i], temp[j]) || - (!has_free && !jl_has_free_typevars(temp[j]) && - // issue #24521: don't merge Type{T} where typeof(T) varies - !(jl_is_type_type(temp[i]) && jl_is_type_type(temp[j]) && jl_typeof(jl_tparam0(temp[i])) != jl_typeof(jl_tparam0(temp[j]))) && - jl_subtype(temp[i], temp[j]))) { + int has_free2 = has_free | (jl_has_free_typevars(temp[j]) << 1); + if (simple_subtype(temp[i], temp[j], has_free2, 0)) temp[i] = NULL; - } } } } @@ -758,10 +786,11 @@ jl_value_t *simple_intersect(jl_value_t *a, jl_value_t *b, int overesi) for (i = 0; i < nta; i++) { if (temp[i] == NULL) continue; all_disjoint = 0; - int hasfree = jl_has_free_typevars(temp[i]); + int has_free = jl_has_free_typevars(temp[i]); for (j = nta; j < nt; j++) { if (temp[j] == NULL) continue; - int subs = simple_subtype2(temp[i], temp[j], hasfree || jl_has_free_typevars(temp[j])); + int has_free2 = has_free | (jl_has_free_typevars(temp[j]) << 1); + int subs = simple_subtype2(temp[i], temp[j], has_free2, 0); int subab = subs & 1, subba = subs >> 1; if (subba && !subab) { stemp[i] = -1; @@ -1125,6 +1154,7 @@ static void cache_insert_type_set(jl_datatype_t *val, uint_t hv) jl_svec_t *cache_rehash_set(jl_svec_t *a, size_t newsz) { + newsz = newsz ? next_power_of_two(newsz) : 0; jl_value_t **ol = jl_svec_data(a); size_t sz = jl_svec_len(a); while (1) { @@ -1158,7 +1188,6 @@ static void cache_insert_type_linear(jl_datatype_t *type, ssize_t insert_at) jl_atomic_store_release(&type->name->linearcache, nc); jl_gc_wb(type->name, nc); cache = nc; - n = jl_svec_len(nc); } assert(jl_svecref(cache, insert_at) == jl_nothing); jl_svecset(cache, insert_at, (jl_value_t*)type); // todo: make this an atomic-store @@ -1408,6 +1437,15 @@ JL_DLLEXPORT jl_value_t *jl_apply_type2(jl_value_t *tc, jl_value_t *p1, jl_value return jl_apply_type(tc, args, 2); } +JL_DLLEXPORT jl_value_t *jl_apply_type3(jl_value_t *tc, jl_value_t *p1, jl_value_t *p2, jl_value_t *p3) +{ + jl_value_t *args[3]; + args[0] = p1; + args[1] = p2; + args[2] = p3; + return jl_apply_type(tc, args, 3); +} + 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); @@ -1441,7 +1479,7 @@ JL_EXTENSION struct _jl_typestack_t { }; static jl_value_t *inst_type_w_(jl_value_t *t, jl_typeenv_t *env, jl_typestack_t *stack, int check); -static jl_svec_t *inst_ftypes(jl_svec_t *p, jl_typeenv_t *env, jl_typestack_t *stack); +static jl_svec_t *inst_ftypes(jl_svec_t *p, jl_typeenv_t *env, jl_typestack_t *stack, int cacheable); JL_DLLEXPORT jl_value_t *jl_instantiate_unionall(jl_unionall_t *u, jl_value_t *p) { @@ -1713,7 +1751,7 @@ static void check_datatype_parameters(jl_typename_t *tn, jl_value_t **params, si JL_GC_POP(); } -jl_value_t *extract_wrapper(jl_value_t *t JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT JL_GLOBALLY_ROOTED +static jl_value_t *extract_wrapper(jl_value_t *t JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT JL_GLOBALLY_ROOTED { t = jl_unwrap_unionall(t); if (jl_is_datatype(t)) @@ -1728,7 +1766,7 @@ jl_value_t *extract_wrapper(jl_value_t *t JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT JL return NULL; } -int _may_substitute_ub(jl_value_t *v, jl_tvar_t *var, int inside_inv, int *cov_count) JL_NOTSAFEPOINT +static int _may_substitute_ub(jl_value_t *v, jl_tvar_t *var, int inside_inv, int *cov_count) JL_NOTSAFEPOINT { while (1) { if (v == (jl_value_t*)var) { @@ -1808,7 +1846,7 @@ static jl_value_t *normalize_unionalls(jl_value_t *t) else if (jl_is_unionall(t)) { jl_unionall_t *u = (jl_unionall_t*)t; jl_value_t *body = normalize_unionalls(u->body); - JL_GC_PUSH1(&body); + JL_GC_PUSH2(&body, &t); if (body != u->body) { t = jl_new_struct(jl_unionall_type, u->var, body); u = (jl_unionall_t*)t; @@ -1992,24 +2030,18 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value } } - // move array of instantiated parameters to heap; we need to keep it - if (p == NULL) { - p = jl_alloc_svec_uninit(ntp); - for (size_t i = 0; i < ntp; i++) - jl_svecset(p, i, iparams[i]); - } - // try to simplify some type parameters if (check && tn != jl_type_typename) { - size_t i; int changed = 0; if (istuple) // normalization might change Tuple's, but not other types's, cacheable status cacheable = 1; + size_t i; for (i = 0; i < ntp; i++) { - jl_value_t *newp = normalize_unionalls(iparams[i]); - if (newp != iparams[i]) { + jl_value_t *pi = iparams[i]; + jl_value_t *newp = normalize_unionalls(pi); + if (newp != pi) { iparams[i] = newp; - jl_svecset(p, i, newp); + if (p) jl_gc_wb(p, newp); changed = 1; } if (istuple && cacheable && !jl_is_concrete_type(newp)) @@ -2035,12 +2067,39 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value } } + // try to reduce duplication in objects (if the caller didn't already check) by + // comparing them against a list of objects already known to be globally rooted and + // swapping them as possible + if (check && jl_global_roots_list != NULL) { + for (size_t i = 0; i < ntp; i++) { + jl_value_t *pi = iparams[i]; + if (cacheable || !jl_has_free_typevars(pi)) { + pi = jl_as_global_root(pi, cacheable); + if (pi != NULL) { + iparams[i] = pi; + if (p) jl_gc_wb(p, pi); + } + } + } + } + + // move array of instantiated parameters to heap; we need to keep it + if (p == NULL) { + p = jl_alloc_svec_uninit(ntp); + for (size_t i = 0; i < ntp; i++) { + jl_svecset(p, i, iparams[i]); + } + } + + ndt = jl_new_uninitialized_datatype(); + + // now that most allocations are done // acquire the write lock now that we know we need a new object // since we're going to immediately leak it globally via the instantiation stack if (cacheable) { JL_LOCK(&typecache_lock); // Might GC jl_value_t *lkup = (jl_value_t*)lookup_type(tn, iparams, ntp); - if (lkup != NULL) { + if (lkup) { JL_UNLOCK(&typecache_lock); // Might GC JL_GC_POP(); return lkup; @@ -2048,7 +2107,6 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value } // create and initialize new type - ndt = jl_new_uninitialized_datatype(); ndt->isprimitivetype = dt->isprimitivetype; // Usually dt won't have ismutationfree set at this point, but it is // overridden for `Type`, which we handle here. @@ -2083,17 +2141,30 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value jl_errorf("duplicate field name in NamedTuple: \"%s\" is not unique", jl_symbol_name((jl_sym_t*)ni)); } } - if (!jl_is_datatype(values_tt)) - jl_error("NamedTuple field type must be a tuple type"); - if (jl_is_va_tuple((jl_datatype_t*)values_tt) || jl_nparams(values_tt) != nf) - jl_error("NamedTuple names and field types must have matching lengths"); - ndt->types = ((jl_datatype_t*)values_tt)->parameters; + if (values_tt == jl_bottom_type && nf > 0) { + ndt->types = jl_svec_fill(nf, jl_bottom_type); + } + else { + if (!jl_is_datatype(values_tt)) + jl_error("NamedTuple field type must be a tuple datatype"); + if (jl_is_va_tuple((jl_datatype_t*)values_tt) || jl_nparams(values_tt) != nf) + jl_error("NamedTuple names and field types must have matching lengths"); + ndt->types = ((jl_datatype_t*)values_tt)->parameters; + } jl_gc_wb(ndt, ndt->types); } else { - ndt->types = jl_emptysvec; // XXX: this is essentially always false + ndt->types = jl_emptysvec; // XXX: this is essentially always incorrect } } + else if (tn == jl_genericmemoryref_typename || tn == jl_genericmemory_typename) { + jl_value_t *isatomic = jl_svecref(p, 0); + if (!jl_is_typevar(isatomic) && !jl_is_symbol(isatomic)) + jl_type_error_rt("GenericMemory", "isatomic parameter", (jl_value_t*)jl_symbol_type, isatomic); + jl_value_t *addrspace = jl_svecref(p, 2); + if (!jl_is_typevar(addrspace) && !jl_is_addrspace(addrspace)) + jl_type_error_rt("GenericMemory", "addrspace parameter", (jl_value_t*)jl_addrspace_type, addrspace); + } jl_datatype_t *primarydt = ((jl_datatype_t*)jl_unwrap_unionall(tn->wrapper)); jl_precompute_memoized_dt(ndt, cacheable); @@ -2128,9 +2199,9 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value else if (cacheable) { // recursively instantiate the types of the fields if (dt->types == NULL) - ndt->types = jl_compute_fieldtypes(ndt, stack); + ndt->types = jl_compute_fieldtypes(ndt, stack, cacheable); else - ndt->types = inst_ftypes(ftypes, env, stack); + ndt->types = inst_ftypes(ftypes, env, stack, cacheable); jl_gc_wb(ndt, ndt->types); } } @@ -2197,14 +2268,15 @@ jl_tupletype_t *jl_inst_arg_tuple_type(jl_value_t *arg1, jl_value_t **args, size return tt; } -static jl_svec_t *inst_ftypes(jl_svec_t *p, jl_typeenv_t *env, jl_typestack_t *stack) +static jl_svec_t *inst_ftypes(jl_svec_t *p, jl_typeenv_t *env, jl_typestack_t *stack, int cacheable) { size_t i; size_t lp = jl_svec_len(p); jl_svec_t *np = jl_alloc_svec(lp); - JL_GC_PUSH1(&np); + jl_value_t *pi = NULL; + JL_GC_PUSH2(&np, &pi); for (i = 0; i < lp; i++) { - jl_value_t *pi = jl_svecref(p, i); + pi = jl_svecref(p, i); JL_TRY { pi = inst_type_w_(pi, env, stack, 1); if (!jl_is_type(pi) && !jl_is_typevar(pi)) { @@ -2214,7 +2286,8 @@ static jl_svec_t *inst_ftypes(jl_svec_t *p, jl_typeenv_t *env, jl_typestack_t *s JL_CATCH { pi = jl_bottom_type; } - jl_svecset(np, i, pi); + jl_value_t *globalpi = jl_as_global_root(pi, cacheable); + jl_svecset(np, i, globalpi ? globalpi : pi); } JL_GC_POP(); return np; @@ -2461,7 +2534,7 @@ jl_vararg_t *jl_wrap_vararg(jl_value_t *t, jl_value_t *n, int check) return vm; } -JL_DLLEXPORT jl_svec_t *jl_compute_fieldtypes(jl_datatype_t *st JL_PROPAGATES_ROOT, void *stack) +JL_DLLEXPORT jl_svec_t *jl_compute_fieldtypes(jl_datatype_t *st JL_PROPAGATES_ROOT, void *stack, int cacheable) { assert(st->name != jl_namedtuple_typename && st->name != jl_tuple_typename); jl_datatype_t *wt = (jl_datatype_t*)jl_unwrap_unionall(st->name->wrapper); @@ -2481,7 +2554,7 @@ JL_DLLEXPORT jl_svec_t *jl_compute_fieldtypes(jl_datatype_t *st JL_PROPAGATES_RO jl_typestack_t top; top.tt = st; top.prev = (jl_typestack_t*)stack; - st->types = inst_ftypes(wt->types, &env[n - 1], &top); + st->types = inst_ftypes(wt->types, &env[n - 1], &top, cacheable); jl_gc_wb(st, st->types); return st->types; } @@ -2498,7 +2571,7 @@ void jl_reinstantiate_inner_types(jl_datatype_t *t) // can throw! if (partial == NULL) return; if (n == 0) { - assert(jl_array_len(partial) == 0); + assert(jl_array_nrows(partial) == 0); return; } @@ -2509,7 +2582,7 @@ void jl_reinstantiate_inner_types(jl_datatype_t *t) // can throw! env[i].prev = i == 0 ? NULL : &env[i - 1]; } - for (j = 0; j < jl_array_len(partial); j++) { + for (j = 0; j < jl_array_nrows(partial); j++) { jl_datatype_t *ndt = (jl_datatype_t*)jl_array_ptr_ref(partial, j); if (ndt == NULL) continue; @@ -2522,14 +2595,14 @@ void jl_reinstantiate_inner_types(jl_datatype_t *t) // can throw! } if (t->types != jl_emptysvec) { - for (j = 0; j < jl_array_len(partial); j++) { + for (j = 0; j < jl_array_nrows(partial); j++) { jl_datatype_t *ndt = (jl_datatype_t*)jl_array_ptr_ref(partial, j); if (ndt == NULL) continue; for (i = 0; i < n; i++) env[i].val = jl_svecref(ndt->parameters, i); assert(ndt->types == NULL); - ndt->types = inst_ftypes(t->types, &env[n - 1], &top); + ndt->types = inst_ftypes(t->types, &env[n - 1], &top, 1); jl_gc_wb(ndt, ndt->types); if (ndt->isconcretetype) { // cacheable jl_compute_field_offsets(ndt); @@ -2739,6 +2812,7 @@ void jl_init_types(void) JL_GC_DISABLED XX(vararg); // It seems like we probably usually end up needing the box for kinds (often used in an Any context), so force it to exist jl_vararg_type->name->mayinlinealloc = 0; + jl_vararg_type->ismutationfree = 1; jl_svec_t *anytuple_params = jl_svec(1, jl_wrap_vararg((jl_value_t*)jl_any_type, (jl_value_t*)NULL, 0)); jl_anytuple_type = jl_new_datatype(jl_symbol("Tuple"), core, jl_any_type, anytuple_params, @@ -2855,8 +2929,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type), jl_emptysvec, 0, 1, 4); - const static uint32_t typemap_entry_constfields[1] = { 0x000003fe }; // (1<<1)|(1<<2)|(1<<3)|(1<<4)|(1<<5)|(1<<6)|(1<<7)|(1<<8)|(1<<9) - const static uint32_t typemap_entry_atomicfields[1] = { 0x00000001 }; // (1<<0) + const static uint32_t typemap_entry_constfields[1] = { 0x000003ce }; // (1<<1)|(1<<2)|(1<<3)|(1<<6)|(1<<7)|(1<<8)|(1<<9) + const static uint32_t typemap_entry_atomicfields[1] = { 0x00000031 }; // (1<<0)|(1<<4)|(1<<5) jl_typemap_entry_type->name->constfields = typemap_entry_constfields; jl_typemap_entry_type->name->atomicfields = typemap_entry_atomicfields; @@ -2865,7 +2939,59 @@ void jl_init_types(void) JL_GC_DISABLED jl_function_type->name->mt = NULL; // subtypes of Function have independent method tables jl_builtin_type->name->mt = NULL; // so they don't share the Any type table - jl_svec_t *tv = jl_svec2(tvar("T"), tvar("N")); + jl_svec_t *tv; + + jl_module_type = + jl_new_datatype(jl_symbol("Module"), core, jl_any_type, jl_emptysvec, + jl_emptysvec, jl_emptysvec, jl_emptysvec, 0, 1, 0); + XX(module); + assert(jl_module_type->instance == NULL); + jl_compute_field_offsets(jl_module_type); + + jl_binding_type = + jl_new_datatype(jl_symbol("Binding"), core, jl_any_type, jl_emptysvec, + jl_perm_symsvec(5, "value", "globalref", "owner", "ty", "flags"), + jl_svec(5, jl_any_type, jl_any_type/*jl_globalref_type*/, jl_any_type/*jl_binding_type*/, jl_type_type, jl_uint8_type), + jl_emptysvec, 0, 1, 0); + const static uint32_t binding_atomicfields[] = { 0x0015 }; // Set fields 1, 3, 4 as atomic + jl_binding_type->name->atomicfields = binding_atomicfields; + const static uint32_t binding_constfields[] = { 0x0002 }; // Set fields 2 as constant + jl_binding_type->name->constfields = binding_constfields; + + jl_globalref_type = + jl_new_datatype(jl_symbol("GlobalRef"), core, jl_any_type, jl_emptysvec, + jl_perm_symsvec(3, "mod", "name", "binding"), + jl_svec(3, jl_module_type, jl_symbol_type, jl_binding_type), + jl_emptysvec, 0, 0, 3); + + core = jl_new_module(jl_symbol("Core"), NULL); + core->parent = core; + jl_type_typename->mt->module = core; + jl_core_module = core; + core = NULL; // not ready yet to use + + tv = jl_svec1(tvar("Backend")); + jl_addrspace_typename = + jl_new_primitivetype((jl_value_t*)jl_symbol("AddrSpace"), core, jl_any_type, tv, 8)->name; + jl_addrspace_type = (jl_unionall_t*)jl_addrspace_typename->wrapper; + jl_addrspacecore_type = (jl_datatype_t*)jl_apply_type1((jl_value_t*)jl_addrspace_type, (jl_value_t*)jl_core_module); + jl_value_t *cpumem = jl_permbox8(jl_addrspacecore_type, 0, 0); + + tv = jl_svec1(tvar("T")); + jl_ref_type = (jl_unionall_t*) + jl_new_abstracttype((jl_value_t*)jl_symbol("Ref"), core, jl_any_type, tv)->name->wrapper; + + tv = jl_svec1(tvar("T")); + jl_pointer_typename = + jl_new_primitivetype((jl_value_t*)jl_symbol("Ptr"), core, + (jl_datatype_t*)jl_apply_type((jl_value_t*)jl_ref_type, jl_svec_data(tv), 1), + tv, + sizeof(void*) * 8)->name; + jl_pointer_type = (jl_unionall_t*)jl_pointer_typename->wrapper; + jl_value_t *pointer_void = jl_apply_type1((jl_value_t*)jl_pointer_type, (jl_value_t*)jl_nothing_type); + jl_voidpointer_type = (jl_datatype_t*)pointer_void; + + tv = jl_svec2(tvar("T"), tvar("N")); jl_abstractarray_type = (jl_unionall_t*) jl_new_abstracttype((jl_value_t*)jl_symbol("AbstractArray"), core, jl_any_type, tv)->name->wrapper; @@ -2876,13 +3002,47 @@ void jl_init_types(void) JL_GC_DISABLED (jl_datatype_t*)jl_apply_type((jl_value_t*)jl_abstractarray_type, jl_svec_data(tv), 2), tv)->name->wrapper; + tv = jl_svec(3, tvar("isatomic"), tvar("T"), tvar("addrspace")); + jl_datatype_t *jl_memory_supertype = (jl_datatype_t*)jl_apply_type2((jl_value_t*)jl_densearray_type, jl_svecref(tv, 1), jl_box_long(1)); + jl_datatype_t *memory_datatype = + jl_new_datatype(jl_symbol("GenericMemory"), core, jl_memory_supertype, tv, + jl_perm_symsvec(2, "length", "ptr"), + jl_svec(2, jl_long_type, pointer_void), + jl_emptysvec, 0, 1, 2); + jl_genericmemory_typename = memory_datatype->name; + jl_genericmemory_type = (jl_unionall_t*)jl_genericmemory_typename->wrapper; + const static uint32_t memory_constfields[1] = { 0x00000003 }; // (1<<1)|(1<<0) + memory_datatype->name->constfields = memory_constfields; + memory_datatype->ismutationfree = 0; + + jl_datatype_t *jl_memoryref_supertype = (jl_datatype_t*)jl_apply_type1((jl_value_t*)jl_ref_type, jl_svecref(tv, 1)); + jl_datatype_t *memoryref_datatype = + jl_new_datatype(jl_symbol("GenericMemoryRef"), core, jl_memoryref_supertype, tv, + jl_perm_symsvec(2, "ptr_or_offset", "mem"), + jl_svec(2, pointer_void, memory_datatype), + jl_emptysvec, 0, 0, 2); + jl_genericmemoryref_typename = memoryref_datatype->name; + jl_genericmemoryref_type = (jl_unionall_t*)jl_genericmemoryref_typename->wrapper; + memoryref_datatype->ismutationfree = 0; + + jl_memory_any_type = jl_apply_type3((jl_value_t*)jl_genericmemory_type, (jl_value_t*)jl_not_atomic_sym, (jl_value_t*)jl_any_type, cpumem); + jl_memory_uint8_type = jl_apply_type3((jl_value_t*)jl_genericmemory_type, (jl_value_t*)jl_not_atomic_sym, (jl_value_t*)jl_uint8_type, cpumem); + jl_memory_uint16_type = jl_apply_type3((jl_value_t*)jl_genericmemory_type, (jl_value_t*)jl_not_atomic_sym, (jl_value_t*)jl_uint16_type, cpumem); + jl_memory_uint32_type = jl_apply_type3((jl_value_t*)jl_genericmemory_type, (jl_value_t*)jl_not_atomic_sym, (jl_value_t*)jl_uint32_type, cpumem); + jl_memory_uint64_type = jl_apply_type3((jl_value_t*)jl_genericmemory_type, (jl_value_t*)jl_not_atomic_sym, (jl_value_t*)jl_uint64_type, cpumem); + jl_memoryref_any_type = jl_apply_type3((jl_value_t*)jl_genericmemoryref_type, (jl_value_t*)jl_not_atomic_sym, (jl_value_t*)jl_any_type, cpumem); + jl_memoryref_uint8_type = jl_apply_type3((jl_value_t*)jl_genericmemoryref_type, (jl_value_t*)jl_not_atomic_sym, (jl_value_t*)jl_uint8_type, cpumem); + tv = jl_svec2(tvar("T"), tvar("N")); - jl_array_type = (jl_unionall_t*) - jl_new_datatype(jl_symbol("Array"), core, + jl_array_typename = jl_new_datatype(jl_symbol("Array"), core, (jl_datatype_t*)jl_apply_type((jl_value_t*)jl_densearray_type, jl_svec_data(tv), 2), - tv, jl_emptysvec, jl_emptysvec, jl_emptysvec, 0, 1, 0)->name->wrapper; - jl_array_typename = ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_array_type))->name; - jl_compute_field_offsets((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_array_type)); + tv, + jl_perm_symsvec(2, "ref", "size"), + jl_svec(2, + jl_apply_type3((jl_value_t*)jl_genericmemoryref_type, (jl_value_t*)jl_not_atomic_sym, jl_svecref(tv, 0), cpumem), + jl_apply_type1((jl_value_t*)jl_tuple_type, (jl_value_t*)jl_wrap_vararg((jl_value_t*)jl_long_type, jl_svecref(tv, 1), 0))), + jl_emptysvec, 0, 1, 2)->name; + jl_array_type = (jl_unionall_t*)jl_array_typename->wrapper; jl_array_any_type = jl_apply_type2((jl_value_t*)jl_array_type, (jl_value_t*)jl_any_type, jl_box_long(1)); jl_array_symbol_type = jl_apply_type2((jl_value_t*)jl_array_type, (jl_value_t*)jl_symbol_type, jl_box_long(1)); @@ -2891,8 +3051,18 @@ void jl_init_types(void) JL_GC_DISABLED jl_array_int32_type = jl_apply_type2((jl_value_t*)jl_array_type, (jl_value_t*)jl_int32_type, jl_box_long(1)); jl_array_uint64_type = jl_apply_type2((jl_value_t*)jl_array_type, (jl_value_t*)jl_uint64_type, jl_box_long(1)); jl_an_empty_vec_any = (jl_value_t*)jl_alloc_vec_any(0); // used internally - jl_atomic_store_relaxed(&jl_nonfunction_mt->leafcache, (jl_array_t*)jl_an_empty_vec_any); - jl_atomic_store_relaxed(&jl_type_type_mt->leafcache, (jl_array_t*)jl_an_empty_vec_any); + jl_an_empty_memory_any = (jl_value_t*)jl_alloc_memory_any(0); // used internally + jl_atomic_store_relaxed(&jl_nonfunction_mt->leafcache, (jl_genericmemory_t*)jl_an_empty_memory_any); + jl_atomic_store_relaxed(&jl_type_type_mt->leafcache, (jl_genericmemory_t*)jl_an_empty_memory_any); + + // finish initializing module Core + core = jl_core_module; + jl_atomic_store_relaxed(&core->bindingkeyset, (jl_genericmemory_t*)jl_an_empty_memory_any); + // export own name, so "using Foo" makes "Foo" itself visible + jl_set_const(core, core->name, (jl_value_t*)core); + jl_module_public(core, core->name, 1); + jl_set_const(core, jl_symbol("CPU"), (jl_value_t*)cpumem); + core = NULL; jl_expr_type = jl_new_datatype(jl_symbol("Expr"), core, @@ -2901,13 +3071,6 @@ void jl_init_types(void) JL_GC_DISABLED jl_svec(2, jl_symbol_type, jl_array_any_type), jl_emptysvec, 0, 1, 2); - jl_module_type = - jl_new_datatype(jl_symbol("Module"), core, jl_any_type, jl_emptysvec, - jl_emptysvec, jl_emptysvec, jl_emptysvec, 0, 1, 0); - XX(module); - jl_module_type->instance = NULL; - jl_compute_field_offsets(jl_module_type); - jl_value_t *symornothing[2] = { (jl_value_t*)jl_symbol_type, (jl_value_t*)jl_void_type }; jl_linenumbernode_type = jl_new_datatype(jl_symbol("LineNumberNode"), core, jl_any_type, jl_emptysvec, @@ -2916,10 +3079,10 @@ void jl_init_types(void) JL_GC_DISABLED jl_emptysvec, 0, 0, 2); jl_lineinfonode_type = - jl_new_datatype(jl_symbol("LineInfoNode"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(5, "module", "method", "file", "line", "inlined_at"), - jl_svec(5, jl_module_type, jl_any_type, jl_symbol_type, jl_int32_type, jl_int32_type), - jl_emptysvec, 0, 0, 5); + jl_new_datatype(jl_symbol("LegacyLineInfoNode"), core, jl_any_type, jl_emptysvec, + jl_perm_symsvec(3, "file", "line", "inlined_at"), + jl_svec(3, jl_symbol_type, jl_int32_type, jl_int32_type), + jl_emptysvec, 0, 0, 3); jl_gotonode_type = jl_new_datatype(jl_symbol("GotoNode"), core, jl_any_type, jl_emptysvec, @@ -2933,6 +3096,12 @@ void jl_init_types(void) JL_GC_DISABLED jl_svec(2, jl_any_type, jl_long_type), jl_emptysvec, 0, 0, 2); + jl_enternode_type = + jl_new_datatype(jl_symbol("EnterNode"), core, jl_any_type, jl_emptysvec, + jl_perm_symsvec(2, "catch_dest", "scope"), + jl_svec(2, jl_long_type, jl_any_type), + jl_emptysvec, 0, 0, 1); + jl_returnnode_type = jl_new_datatype(jl_symbol("ReturnNode"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(1, "val"), @@ -2975,25 +3144,38 @@ void jl_init_types(void) JL_GC_DISABLED jl_svec(1, jl_slotnumber_type), jl_emptysvec, 0, 0, 1); + jl_debuginfo_type = + jl_new_datatype(jl_symbol("DebugInfo"), core, + jl_any_type, jl_emptysvec, + jl_perm_symsvec(4, + "def", + "linetable", + "edges", + "codelocs"), + jl_svec(4, + jl_any_type, // union(jl_method_instance_type, jl_method_type, jl_symbol_type), + jl_any_type, // union(jl_nothing, jl_debuginfo_type) + jl_simplevector_type, // memory{debuginfo} + jl_string_type), + jl_emptysvec, 0, 0, 4); + jl_debuginfo_type->name->mayinlinealloc = 0; + jl_code_info_type = jl_new_datatype(jl_symbol("CodeInfo"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(22, + jl_perm_symsvec(19, "code", - "codelocs", + "debuginfo", "ssavaluetypes", "ssaflags", - "method_for_inference_limit_heuristics", - "linetable", "slotnames", "slotflags", "slottypes", - "rettype", "parent", + "method_for_inference_limit_heuristics", "edges", "min_world", "max_world", - "inferred", "propagate_inbounds", "has_fcall", "nospecializeinfer", @@ -3001,13 +3183,11 @@ void jl_init_types(void) JL_GC_DISABLED "constprop", "purity", "inlining_cost"), - jl_svec(22, + jl_svec(19, jl_array_any_type, - jl_array_int32_type, + jl_debuginfo_type, jl_any_type, jl_array_uint32_type, - jl_any_type, - jl_any_type, jl_array_symbol_type, jl_array_uint8_type, jl_any_type, @@ -3019,30 +3199,30 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type, jl_bool_type, jl_bool_type, - jl_bool_type, - jl_uint8_type, jl_uint8_type, jl_uint8_type, + jl_uint16_type, jl_uint16_type), jl_emptysvec, - 0, 1, 22); + 0, 1, 19); jl_method_type = jl_new_datatype(jl_symbol("Method"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(30, + jl_perm_symsvec(31, "name", "module", "file", "line", - "primary_world", - "deleted_world", // !const + "primary_world", // atomic + "deleted_world", // atomic "sig", "specializations", // !const "speckeyset", // !const "slot_syms", "external_mt", "source", // !const + "debuginfo", // !const "unspecialized", // !const "generator", // !const "roots", // !const @@ -3061,7 +3241,7 @@ void jl_init_types(void) JL_GC_DISABLED "constprop", "max_varargs", "purity"), - jl_svec(30, + jl_svec(31, jl_symbol_type, jl_module_type, jl_symbol_type, @@ -3070,10 +3250,11 @@ void jl_init_types(void) JL_GC_DISABLED jl_ulong_type, jl_type_type, jl_any_type, // union(jl_simplevector_type, jl_method_instance_type), - jl_array_type, + jl_genericmemory_type, // union(jl_memory_uint8_type, jl_memory_uint16_type, jl_memory_uint32_type, jl_memory_uint64_type, jl_memory_any_type) jl_string_type, jl_any_type, jl_any_type, + jl_debuginfo_type, jl_any_type, // jl_method_instance_type jl_any_type, jl_array_any_type, @@ -3091,34 +3272,34 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type, jl_uint8_type, jl_uint8_type, - jl_uint8_type), + jl_uint16_type), jl_emptysvec, 0, 1, 10); - //const static uint32_t method_constfields[1] = { 0x03fc065f }; // (1<<0)|(1<<1)|(1<<2)|(1<<3)|(1<<4)|(1<<6)|(1<<9)|(1<<10)|(1<<18)|(1<<19)|(1<<20)|(1<<21)|(1<<22)|(1<<23)|(1<<24)|(1<<25); + //const static uint32_t method_constfields[1] = { 0b0 }; // (1<<0)|(1<<1)|(1<<2)|(1<<3)|(1<<6)|(1<<9)|(1<<10)|(1<<17)|(1<<21)|(1<<22)|(1<<23)|(1<<24)|(1<<25)|(1<<26)|(1<<27)|(1<<28)|(1<<29)|(1<<30); //jl_method_type->name->constfields = method_constfields; + const static uint32_t method_atomicfields[1] = { 0x00000030 }; // (1<<4)|(1<<5) + jl_method_type->name->atomicfields = method_atomicfields; jl_method_instance_type = jl_new_datatype(jl_symbol("MethodInstance"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(10, + jl_perm_symsvec(9, "def", "specTypes", "sparam_vals", "uninferred", "backedges", - "callbacks", "cache", "inInference", "cache_with_orig", "precompiled"), - jl_svec(10, + jl_svec(9, jl_new_struct(jl_uniontype_type, jl_method_type, jl_module_type), jl_any_type, jl_simplevector_type, jl_any_type, jl_array_any_type, - jl_any_type, - jl_any_type, + jl_any_type/*jl_code_instance_type*/, jl_bool_type, jl_bool_type, jl_bool_type), @@ -3126,7 +3307,7 @@ void jl_init_types(void) JL_GC_DISABLED 0, 1, 3); // These fields should be constant, but Serialization wants to mutate them in initialization //const static uint32_t method_instance_constfields[1] = { 0x00000007 }; // (1<<0)|(1<<1)|(1<<2); - const static uint32_t method_instance_atomicfields[1] = { 0x00000248 }; // (1<<3)|(1<<6)|(1<<9); + const static uint32_t method_instance_atomicfields[1] = { 0x00000128 }; // (1<<3)|(1<<5)|(1<<8); //Fields 4 and 5 must be protected by method->write_lock, and thus all operations on jl_method_instance_t are threadsafe. TODO: except inInference //jl_method_instance_type->name->constfields = method_instance_constfields; jl_method_instance_type->name->atomicfields = method_instance_atomicfields; @@ -3134,29 +3315,33 @@ void jl_init_types(void) JL_GC_DISABLED jl_code_instance_type = jl_new_datatype(jl_symbol("CodeInstance"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(15, + jl_perm_symsvec(18, "def", + "owner", "next", "min_world", "max_world", "rettype", + "exctype", "rettype_const", "inferred", - //"edges", + "debuginfo", // TODO: rename to edges? //"absolute_max", "ipo_purity_bits", "purity_bits", - "argescapes", - "isspecsig", "precompile", "relocatability", + "analysis_results", + "specsigflags", "precompile", "relocatability", "invoke", "specptr"), // function object decls - jl_svec(15, + jl_svec(18, jl_method_instance_type, jl_any_type, + jl_any_type, jl_ulong_type, jl_ulong_type, jl_any_type, jl_any_type, jl_any_type, - //jl_any_type, + jl_any_type, + jl_debuginfo_type, //jl_bool_type, jl_uint32_type, jl_uint32_type, jl_any_type, @@ -3166,11 +3351,12 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type, jl_any_type), // fptrs jl_emptysvec, 0, 1, 1); - jl_svecset(jl_code_instance_type->types, 1, jl_code_instance_type); - const static uint32_t code_instance_constfields[1] = { 0b000001010110001 }; // Set fields 1, 5-6, 8, 10 as const - const static uint32_t code_instance_atomicfields[1] = { 0b110100101000010 }; // Set fields 2, 7, 9, 12, 14-15 as atomic - //Fields 3-4 are only operated on by construction and deserialization, so are const at runtime - //Fields 11 and 15 must be protected by locks, and thus all operations on jl_code_instance_t are threadsafe + jl_svecset(jl_code_instance_type->types, 2, jl_code_instance_type); + const static uint32_t code_instance_constfields[1] = { 0b000001010011100011 }; // Set fields 1, 2, 6-8, 11, 13 as const + const static uint32_t code_instance_atomicfields[1] = { 0b110110101100011100 }; // Set fields 3-5, 9, 10, 12, 14-15, 17-18 as atomic + //Fields 4-5 are only operated on by construction and deserialization, so are const at runtime + //Fields 13 and 17 must be protected by locks, and thus all operations on jl_code_instance_t are threadsafe + //Except for field 9 (inferred), which is volatile unless you know which other places are currently using it jl_code_instance_type->name->constfields = code_instance_constfields; jl_code_instance_type->name->atomicfields = code_instance_atomicfields; @@ -3201,17 +3387,6 @@ void jl_init_types(void) JL_GC_DISABLED jl_intrinsic_type = jl_new_primitivetype((jl_value_t*)jl_symbol("IntrinsicFunction"), core, jl_builtin_type, jl_emptysvec, 32); - tv = jl_svec1(tvar("T")); - jl_ref_type = (jl_unionall_t*) - jl_new_abstracttype((jl_value_t*)jl_symbol("Ref"), core, jl_any_type, tv)->name->wrapper; - - tv = jl_svec1(tvar("T")); - jl_pointer_type = (jl_unionall_t*) - jl_new_primitivetype((jl_value_t*)jl_symbol("Ptr"), core, - (jl_datatype_t*)jl_apply_type((jl_value_t*)jl_ref_type, jl_svec_data(tv), 1), tv, - sizeof(void*)*8)->name->wrapper; - jl_pointer_typename = ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_pointer_type))->name; - // LLVMPtr{T, AS} where {T, AS} jl_tvar_t *elvar = tvar("T"); tv = jl_svec2(elvar, tvar("AS")); @@ -3283,24 +3458,6 @@ void jl_init_types(void) JL_GC_DISABLED jl_value_t *listt = jl_new_struct(jl_uniontype_type, jl_task_type, jl_nothing_type); jl_svecset(jl_task_type->types, 0, listt); - jl_binding_type = - jl_new_datatype(jl_symbol("Binding"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(5, "value", "globalref", "owner", "ty", "flags"), - jl_svec(5, jl_any_type, jl_any_type/*jl_globalref_type*/, jl_any_type/*jl_binding_type*/, jl_type_type, jl_uint8_type), - jl_emptysvec, 0, 1, 0); - const static uint32_t binding_atomicfields[] = { 0x0015 }; // Set fields 1, 3, 4 as atomic - jl_binding_type->name->atomicfields = binding_atomicfields; - const static uint32_t binding_constfields[] = { 0x0002 }; // Set fields 2 as constant - jl_binding_type->name->constfields = binding_constfields; - - jl_globalref_type = - jl_new_datatype(jl_symbol("GlobalRef"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(3, "mod", "name", "binding"), - jl_svec(3, jl_module_type, jl_symbol_type, jl_binding_type), - jl_emptysvec, 0, 0, 3); - - jl_value_t *pointer_void = jl_apply_type1((jl_value_t*)jl_pointer_type, (jl_value_t*)jl_nothing_type); - jl_voidpointer_type = (jl_datatype_t*)pointer_void; tv = jl_svec2(tvar("A"), tvar("R")); jl_opaque_closure_type = (jl_unionall_t*)jl_new_datatype(jl_symbol("OpaqueClosure"), core, jl_function_type, tv, // N.B.: OpaqueClosure call code relies on specptr being field 5. @@ -3337,10 +3494,11 @@ void jl_init_types(void) JL_GC_DISABLED jl_svecset(jl_methtable_type->types, 8, jl_long_type); // uint32_t plus alignment jl_svecset(jl_methtable_type->types, 9, jl_uint8_type); jl_svecset(jl_methtable_type->types, 10, jl_uint8_type); - jl_svecset(jl_method_type->types, 12, jl_method_instance_type); - jl_svecset(jl_method_instance_type->types, 6, jl_code_instance_type); - jl_svecset(jl_code_instance_type->types, 13, jl_voidpointer_type); - jl_svecset(jl_code_instance_type->types, 14, jl_voidpointer_type); + jl_svecset(jl_method_type->types, 13, jl_method_instance_type); + //jl_svecset(jl_debuginfo_type->types, 0, jl_method_instance_type); // union(jl_method_instance_type, jl_method_type, jl_symbol_type) + jl_svecset(jl_method_instance_type->types, 5, jl_code_instance_type); + jl_svecset(jl_code_instance_type->types, 16, jl_voidpointer_type); + jl_svecset(jl_code_instance_type->types, 17, jl_voidpointer_type); jl_svecset(jl_binding_type->types, 1, jl_globalref_type); jl_svecset(jl_binding_type->types, 2, jl_binding_type); @@ -3349,7 +3507,6 @@ void jl_init_types(void) JL_GC_DISABLED jl_compute_field_offsets(jl_uniontype_type); jl_compute_field_offsets(jl_tvar_type); jl_compute_field_offsets(jl_methtable_type); - jl_compute_field_offsets(jl_module_type); jl_compute_field_offsets(jl_method_instance_type); jl_compute_field_offsets(jl_code_instance_type); jl_compute_field_offsets(jl_unionall_type); @@ -3361,6 +3518,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_symbol_type->ismutationfree = jl_symbol_type->isidentityfree = 1; jl_simplevector_type->ismutationfree = jl_simplevector_type->isidentityfree = 1; jl_datatype_type->ismutationfree = 1; + assert(((jl_datatype_t*)jl_array_any_type)->ismutationfree == 0); + assert(((jl_datatype_t*)jl_array_uint8_type)->ismutationfree == 0); // Technically not ismutationfree, but there's a separate system to deal // with mutations for global state. @@ -3368,18 +3527,6 @@ void jl_init_types(void) JL_GC_DISABLED // Module object identity is determined by its name and parent name. jl_module_type->isidentityfree = 1; - // Array's mutable data is hidden, so we need to override it - ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_array_type))->ismutationfree = 0; - ((jl_datatype_t*)jl_array_any_type)->ismutationfree = 0; - ((jl_datatype_t*)jl_array_symbol_type)->ismutationfree = 0; - ((jl_datatype_t*)jl_array_uint8_type)->ismutationfree = 0; - ((jl_datatype_t*)jl_array_uint32_type)->ismutationfree = 0; - ((jl_datatype_t*)jl_array_int32_type)->ismutationfree = 0; - ((jl_datatype_t*)jl_array_uint64_type)->ismutationfree = 0; - - // override the preferred layout for a couple types - jl_lineinfonode_type->name->mayinlinealloc = 0; // FIXME: assumed to be a pointer by codegen - export_jl_small_typeof(); } @@ -3434,6 +3581,8 @@ 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_missingcodeerror_type = (jl_datatype_t*)core("MissingCodeError"); + jl_precompilable_error = jl_new_struct_uninit((jl_datatype_t*)core("PrecompilableError")); jl_pair_type = core("Pair"); jl_kwcall_func = core("kwcall"); jl_kwcall_mt = ((jl_datatype_t*)jl_typeof(jl_kwcall_func))->name->mt; diff --git a/src/jsvm-emscripten/asyncify_setup.js b/src/jsvm-emscripten/asyncify_setup.js deleted file mode 100644 index 6783206602fd0..0000000000000 --- a/src/jsvm-emscripten/asyncify_setup.js +++ /dev/null @@ -1,144 +0,0 @@ -Module.preRun.push(function() { - if (typeof Asyncify !== "undefined") { - Asyncify.instrumentWasmExports = function (exports) { return exports; }; - Asyncify.handleSleep = function (startAsync) { - if (ABORT) return; - Module['noExitRuntime'] = true; - if (Asyncify.state === Asyncify.State.Normal) { - // Prepare to sleep. Call startAsync, and see what happens: - // if the code decided to call our callback synchronously, - // then no async operation was in fact begun, and we don't - // need to do anything. - var reachedCallback = false; - var reachedAfterCallback = false; - var task = get_current_task(); - startAsync(function(returnValue) { - assert(!returnValue || typeof returnValue === 'number'); // old emterpretify API supported other stuff - if (ABORT) return; - Asyncify.returnValue = returnValue || 0; - reachedCallback = true; - if (!reachedAfterCallback) { - // We are happening synchronously, so no need for async. - return; - } - schedule_and_wait(task); - }); - reachedAfterCallback = true; - if (!reachedCallback) { - Module['_jl_task_wait'](); - } - } else if (Asyncify.state === Asyncify.State.Rewinding) { - // Stop a resume. - finish_schedule_task(); - } else { - abort('invalid state: ' + Asyncify.state); - } - return Asyncify.returnValue; - }; - } -}); - -function get_current_task() { - return Module['_jl_get_current_task'](); -} - -function get_root_task() { - return Module['_jl_get_root_task'](); -} - -function task_ctx_ptr(task) { - return Module["_task_ctx_ptr"](task); -} - -function ctx_save(ctx) { - var stackPtr = stackSave(); - - // Save the bottom of the C stack in the task context. It simultaneously - // serves as the top of the asyncify stack. - HEAP32[ctx + 4 >> 2] = stackPtr; - - Asyncify.state = Asyncify.State.Unwinding; - Module['_asyncify_start_unwind'](ctx); - if (Browser.mainLoop.func) { - Browser.mainLoop.pause(); - } -} - -function do_start_task(old_stack) -{ - try { - // start_task is always the entry point for any task - Module['_start_task'](); - } catch(e) { - stackRestore(old_stack) - if (e !== e+0 && e !== 'killed') throw e; - maybe_schedule_next(); - return; - } - // Either unwind or normal exit. In either case, we're back at the main task - if (Asyncify.state === Asyncify.State.Unwinding) { - // We just finished unwinding for a sleep. - Asyncify.state = Asyncify.State.Normal; - Module['_asyncify_stop_unwind'](); - } - stackRestore(old_stack); - maybe_schedule_next(); -} - -function schedule_and_wait(task) { - Module['_jl_schedule_task'](task); - Module['_jl_task_wait'](); -} - -function finish_schedule_task() { - Asyncify.state = Asyncify.State.Normal; - Module['_asyncify_stop_rewind'](); -} - -next_ctx = 0; -next_need_start = true; -function set_next_ctx(ctx, needs_start) { - next_ctx = ctx; - next_need_start = needs_start; -} - -function root_ctx() { - return task_ctx_ptr(get_root_task()) -} - -function ctx_switch(lastt_ctx) { - if (lastt_ctx == root_ctx()) { - // If we're in the root context, switch to - // the new ctx now, else we'll get there after - // unwinding. - return schedule_next() - } else if (lastt_ctx == 0) { - throw 'killed'; - } else { - return ctx_save(lastt_ctx); - } -} - -function schedule_next() -{ - old_stack = stackSave(); - var next_task_stack = HEAP32[next_ctx + 4 >> 2]; - if (!next_need_start) { - Asyncify.state = Asyncify.State.Rewinding; - Module['_asyncify_start_rewind'](next_ctx); - if (Browser.mainLoop.func) { - Browser.mainLoop.resume(); - } - } - next_ctx = -1; - stackRestore(next_task_stack); - do_start_task(old_stack) -} - -function maybe_schedule_next() { - assert(next_ctx != -1); - if (next_ctx == root_ctx() || next_ctx == 0) { - return; - } - schedule_next() -} diff --git a/src/jsvm-emscripten/task.js b/src/jsvm-emscripten/task.js deleted file mode 100644 index ba695a5a40052..0000000000000 --- a/src/jsvm-emscripten/task.js +++ /dev/null @@ -1,15 +0,0 @@ -mergeInto(LibraryManager.library, { - jl_set_fiber: function(ctx) { - set_next_ctx(ctx, false); - return ctx_switch(0) - }, - jl_swap_fiber: function(lastt_ctx, ctx) { - set_next_ctx(ctx, false); - return ctx_switch(lastt_ctx) - }, - jl_start_fiber: function(lastt_ctx, ctx) { - set_next_ctx(ctx, true); - return ctx_switch(lastt_ctx) - } -}); - diff --git a/src/julia-parser.scm b/src/julia-parser.scm index 210ba8f0ae07b..891a26bb0ea49 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -716,7 +716,7 @@ ;; ";" at the top level produces a sequence of top level expressions (define (parse-stmts s) - (let ((ex (parse-Nary s (lambda (s) (parse-docstring s parse-eq)) + (let ((ex (parse-Nary s (lambda (s) (parse-public s parse-eq)) '(#\;) 'toplevel (lambda (x) (eqv? x #\newline)) #f))) ;; check for unparsed junk after an expression (let ((t (peek-token s))) @@ -1608,18 +1608,18 @@ ((module baremodule) (let* ((name (parse-unary-prefix s)) (loc (line-number-node s)) - (body (parse-block s (lambda (s) (parse-docstring s parse-eq))))) + (body (parse-block s (lambda (s) (parse-public s parse-eq))))) (if (reserved-word? name) (error (string "invalid module name \"" name "\""))) (expect-end s word) (list 'module (if (eq? word 'module) '(true) '(false)) name `(block ,loc ,@(cdr body))))) - ((export) + ((export public) (let ((es (map macrocall-to-atsym (parse-comma-separated s parse-unary-prefix)))) (if (not (every symbol-or-interpolate? es)) - (error "invalid \"export\" statement")) - `(export ,@es))) + (error (string "invalid \"" word "\" statement"))) + `(,word ,@es))) ((import using) (parse-imports s word)) ((do) @@ -2610,15 +2610,23 @@ (define (valid-modref? e) (and (length= e 3) (eq? (car e) '|.|) (pair? (caddr e)) - (eq? (car (caddr e)) 'quote) (symbol? (cadr (caddr e))) + (or (eq? (car (caddr e)) 'quote) + (eq? (car (caddr e)) 'inert)) + (symbol? (cadr (caddr e))) (or (symbol? (cadr e)) (valid-modref? (cadr e))))) (define (macroify-name e . suffixes) (cond ((symbol? e) (symbol (apply string #\@ e suffixes))) + ((and (pair? e) (eq? (car e) 'quote)) + `(quote ,(apply macroify-name (cadr e) suffixes))) + ((and (pair? e) (eq? (car e) 'inert)) + `(inert ,(apply macroify-name (cadr e) suffixes))) + ((globalref? e) + `(globalref ,(cadr e) ,(apply macroify-name (caddr e) suffixes))) ((valid-modref? e) `(|.| ,(cadr e) - (quote ,(apply macroify-name (cadr (caddr e)) suffixes)))) + ,(apply macroify-name (caddr e) suffixes))) (else (error (string "invalid macro usage \"@(" (deparse e) ")\"" ))))) (define (macroify-call s call startloc) @@ -2664,6 +2672,17 @@ ;; string interpolation (eq? (car e) 'string)))) +(define (parse-public s production) + (if (eq? (peek-token s) 'public) + (let ((spc (ts:space? s))) + (take-token s) + (if (memv (peek-token s) '(#\( = #\[)) + (begin ;; TODO: deprecation warning here + (ts:put-back! s 'public spc) + (parse-docstring s production)) + (parse-resword s 'public))) + (parse-docstring s production))) + (define (parse-docstring s production) (let ((startloc (line-number-node s)) ; be sure to use the line number from the head of the docstring (ex (production s))) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 8c3dce77bc6fc..899476afc093a 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -509,7 +509,7 @@ sparams)) (kw (gensy)) (kwdecl `(|::| ,kw (core NamedTuple))) - (rkw (if (null? restkw) (make-ssavalue) (symbol (string (car restkw) "...")))) + (rkw (if (null? restkw) '() (symbol (string (car restkw) "...")))) (restkw (map (lambda (v) `(|::| ,v (call (top pairs) (core NamedTuple)))) restkw)) (mangled (let ((und (and name (undot-name name)))) (symbol (string (if (and name (= (string.char (string name) 0) #\#)) @@ -608,16 +608,18 @@ ,tempslot))) vars vals) `(block - (= ,rkw (call (top pairs) - ,(if (null? keynames) - kw - `(call (top structdiff) ,kw (curly (core NamedTuple) - (tuple ,@(map quotify keynames))))))) - ,@(if (null? restkw) - `((if (call (top isempty) ,rkw) + ,(if (null? restkw) + `(if (call (top isempty) + (call (top diff_names) + (call (top keys) ,kw) + (tuple ,@(map quotify keynames)))) (null) - (call (top kwerr) ,kw ,@(map arg-name pargl) ,@splatted-vararg))) - '()) + (call (top kwerr) ,kw ,@(map arg-name pargl) ,@splatted-vararg)) + `(= ,rkw (call (top pairs) + ,(if (null? keynames) + kw + `(call (top structdiff) ,kw (curly (core NamedTuple) + (tuple ,@(map quotify keynames)))))))) (return (call ,mangled ;; finally, call the core function ,@keyvars ,@(if (null? restkw) '() (list rkw)) @@ -1345,15 +1347,18 @@ (else (error "invalid let syntax")))) (else (error "invalid let syntax"))))))))) +(define (valid-macro-def-name? e) + (or (symbol? e) (valid-modref? e) (globalref? e))) + (define (expand-macro-def e) (cond ((and (pair? (cadr e)) (eq? (car (cadr e)) 'call) - (symbol? (cadr (cadr e)))) + (valid-macro-def-name? (cadr (cadr e)))) (let ((anames (remove-empty-parameters (cddr (cadr e))))) (if (has-parameters? anames) (error "macros cannot accept keyword arguments")) (expand-forms - `(function (call ,(symbol (string #\@ (cadr (cadr e)))) + `(function (call ,(macroify-name (cadr (cadr e))) (|::| __source__ (core LineNumberNode)) (|::| __module__ (core Module)) ,@(map (lambda (v) @@ -1362,8 +1367,8 @@ v)) anames)) ,@(cddr e))))) - ((and (length= e 2) (symbol? (cadr e))) - (expand-forms `(function ,(symbol (string #\@ (cadr e)))))) + ((and (length= e 2) (valid-macro-def-name? (cadr e))) + (expand-forms `(function ,(macroify-name (cadr e))))) (else (error "invalid macro definition")))) @@ -1424,7 +1429,7 @@ (scope-block ,finalb))))) ((length> e 3) (and (length> e 6) (error "invalid \"try\" form")) - (let ((elseb (if (length= e 6) (cdddddr e) '()))) + (let ((elseb (if (length= e 6) `((scope-block ,@(cdddddr e))) '()))) (expand-forms `(,(if (null? elseb) 'trycatch 'trycatchelse) (scope-block ,tryb) @@ -1651,8 +1656,10 @@ (let ((g (make-ssavalue))) (begin (set! a (cons `(= ,g ,x) a)) g))))) - (cons (cons (car e) (map arg-to-temp (cdr e))) - (reverse a))))) + (if (eq? (car e) 'let) + (cons (arg-to-temp e) (reverse a)) + (cons (cons (car e) (map arg-to-temp (cdr e))) + (reverse a)))))) (define (lower-kw-call f args) (let* ((para (if (has-parameters? args) (cdar args) '())) @@ -3234,11 +3241,9 @@ (warn-var?! (cadr e) scope) (= *scopewarn-opt* 1)) (let* ((v (cadr e)) - (loc (extract-line-file loc)) - (line (if (= (car loc) 0) (julia-current-line) (car loc))) - (file (if (eq? (cadr loc) 'none) (julia-current-file) (cadr loc)))) + (loc (extract-line-file loc))) (lowering-warning - 1000 'warn (symbol (string file line)) file line + 1000 'warn (cadr loc) (car loc) (string "Assignment to `" v "` in soft scope is ambiguous " "because a global variable by the same name exists: " "`" v "` will be treated as a new local. " @@ -3426,8 +3431,7 @@ f(x) = yt(x) (define (type-for-closure-parameterized name P names fields types super) (let ((n (length P)) (s (make-ssavalue))) - `((thunk - (lambda () + `((thunk ,(linearize `(lambda () (() () 0 ()) (block (global ,name) (const ,name) ,@(map (lambda (p n) `(= ,p (call (core TypeVar) ',n (core Any)))) P names) @@ -3438,22 +3442,22 @@ f(x) = yt(x) (call (core _setsuper!) ,s ,super) (= (outerref ,name) ,s) (call (core _typebody!) ,s (call (core svec) ,@types)) - (return (null)))))))) + (return (null))))))))) (define (type-for-closure name fields super) (let ((s (make-ssavalue))) - `((thunk (lambda () - (() () 0 ()) - (block (global ,name) (const ,name) - (= ,s (call (core _structtype) (thismodule) (inert ,name) (call (core svec)) - (call (core svec) ,@(map quotify fields)) - (call (core svec)) - (false) ,(length fields))) - (call (core _setsuper!) ,s ,super) - (= (outerref ,name) ,s) - (call (core _typebody!) ,s - (call (core svec) ,@(map (lambda (v) '(core Box)) fields))) - (return (null)))))))) + `((thunk ,(linearize `(lambda () + (() () 0 ()) + (block (global ,name) (const ,name) + (= ,s (call (core _structtype) (thismodule) (inert ,name) (call (core svec)) + (call (core svec) ,@(map quotify fields)) + (call (core svec)) + (false) ,(length fields))) + (call (core _setsuper!) ,s ,super) + (= (outerref ,name) ,s) + (call (core _typebody!) ,s + (call (core svec) ,@(map (lambda (v) '(core Box)) fields))) + (return (null))))))))) ;; better versions of above, but they get handled wrong in many places ;; need to fix that in order to handle #265 fully (and use the definitions) @@ -3481,7 +3485,7 @@ f(x) = yt(x) (define (vinfo:not-capt vi) - (list (car vi) (cadr vi) (logand (caddr vi) (lognot 5)))) + (list (car vi) (cadr vi) (logand (caddr vi) (lognot 1)))) (define (clear-capture-bits vinfos) (map vinfo:not-capt vinfos)) @@ -3708,7 +3712,7 @@ f(x) = yt(x) thunk with-static-parameters toplevel-only global globalref outerref const-if-global thismodule const atomic null true false ssavalue isdefined toplevel module lambda - error gc_preserve_begin gc_preserve_end import using export public inline noinline))) + error gc_preserve_begin gc_preserve_end import using export public inline noinline purity))) (define (local-in? s lam (tab #f)) (or (and tab (has? tab s)) @@ -4252,22 +4256,37 @@ f(x) = yt(x) (else (for-each linearize (cdr e)))) e) +;; N.B.: This assumes that resolve-scopes has run, so outerref is equivalent to +;; a global in the current scope. (define (valid-ir-argument? e) - (or (simple-atom? e) (symbol? e) + (or (simple-atom? e) + (and (outerref? e) (nothrow-julia-global (cadr e))) + (and (globalref? e) (nothrow-julia-global (cadr e) (caddr e))) (and (pair? e) - (memq (car e) '(quote inert top core globalref outerref + (memq (car e) '(quote inert top core slot static_parameter))))) (define (valid-ir-rvalue? lhs e) (or (ssavalue? lhs) (valid-ir-argument? e) (and (symbol? lhs) (pair? e) - (memq (car e) '(new splatnew the_exception isdefined call invoke foreigncall cfunction gc_preserve_begin copyast new_opaque_closure))))) + (memq (car e) '(new splatnew the_exception isdefined call invoke foreigncall cfunction gc_preserve_begin copyast new_opaque_closure globalref outerref))))) (define (valid-ir-return? e) ;; returning lambda directly is needed for @generated (or (valid-ir-argument? e) (and (pair? e) (memq (car e) '(lambda))))) +(define (code-trivially-effect-free? e) + ;; determine whether the execution of this code can be observed. + ;; If not it may be deleted. In general, the only thing we can detect here + ;; is empty blocks that only have metadata in them. + (if (pair? e) + (case (car e) + ((block) (every code-trivially-effect-free? (cdr e))) + ((line null) #t) + (else #f)) + #t)) + ;; this pass behaves like an interpreter on the given code. ;; to perform stateful operations, it calls `emit` to record that something ;; needs to be done. in value position, it returns an expression computing @@ -4353,7 +4372,7 @@ f(x) = yt(x) (if (eq? (cdr s) dest-tokens) (cons (car s) l) (loop (cdr s) (cons (car s) l)))))) - (define (emit-return x) + (define (emit-return tail x) (define (emit- x) (let* ((tmp (if ((if (null? catch-token-stack) valid-ir-return? simple-atom?) x) #f @@ -4362,8 +4381,12 @@ f(x) = yt(x) (begin (emit `(= ,tmp ,x)) tmp) x))) (define (actually-return x) - (let* ((x (if rett - (compile (convert-for-type-decl (emit- x) rett #t lam) '() #t #f) + (let* ((x (begin0 (emit- x) + ;; if we are adding an implicit return then mark it as having no location + (if (not (eq? tail 'explicit)) + (emit '(line #f))))) + (x (if rett + (compile (convert-for-type-decl x rett #t lam) '() #t #f) x)) (x (emit- x))) (let ((pexc (pop-exc-expr catch-token-stack '()))) @@ -4408,48 +4431,59 @@ f(x) = yt(x) (else (string "\"" h "\" expression")))) (if (not (null? (cadr lam))) (error (string (head-to-text (car e)) " not at top level")))) + (define (valid-body-ir-argument? aval) + (or (valid-ir-argument? aval) + (and (symbol? aval) ; Arguments are always defined slots + (or (memq aval (lam:args lam)) + (let ((vi (get vinfo-table aval #f))) + (and vi (vinfo:never-undef vi))))))) + (define (single-assign-var? aval) + (and (symbol? aval) ; Arguments are always sa + (or (memq aval (lam:args lam)) + (let ((vi (get vinfo-table aval #f))) + (and vi (vinfo:sa vi)))))) + ;; TODO: We could also allow const globals here + (define (const-read-arg? x) + ;; Even if we have side effects, we know that singly-assigned + ;; locals cannot be affected them, so we can inline them anyway. + (or (simple-atom? x) (single-assign-var? x) + (and (pair? x) + (memq (car x) '(quote inert top core))))) ;; evaluate the arguments of a call, creating temporary locations as needed (define (compile-args lst break-labels) (if (null? lst) '() - (let ((simple? (every (lambda (x) (or (simple-atom? x) (symbol? x) - (and (pair? x) - (memq (car x) '(quote inert top core globalref outerref))))) - lst))) - (let loop ((lst lst) - (vals '())) - (if (null? lst) - (reverse! vals) - (let* ((arg (car lst)) - (aval (or (compile arg break-labels #t #f) - ;; TODO: argument exprs that don't yield a value? - '(null)))) - (loop (cdr lst) - (cons (if (and (not simple?) - (not (simple-atom? arg)) - (not (simple-atom? aval)) - (not (and (pair? arg) - (memq (car arg) '(quote inert top core)))) - (not (and (symbol? aval) ;; function args are immutable and always assigned - (memq aval (lam:args lam)))) - (not (and (or (symbol? arg) - (and (pair? arg) - (memq (car arg) '(globalref outerref)))) - (or (null? (cdr lst)) - (null? vals))))) - (let ((tmp (make-ssavalue))) - (emit `(= ,tmp ,aval)) - tmp) - aval) - vals)))))))) + ;; First check if all the arguments as simple (and therefore side-effect free). + ;; Otherwise, we need to use ssa values for all arguments to ensure proper + ;; left-to-right evaluation semantics. + (let ((simple? (every (lambda (x) (or (simple-atom? x) (symbol? x) + (and (pair? x) + (memq (car x) '(quote inert top core globalref outerref))))) + lst))) + (let loop ((lst lst) + (vals '())) + (if (null? lst) + (reverse! vals) + (let* ((arg (car lst)) + (aval (or (compile arg break-labels #t #f) + ;; TODO: argument exprs that don't yield a value? + '(null)))) + (loop (cdr lst) + (cons (if (and + (or simple? (const-read-arg? aval)) + (valid-body-ir-argument? aval)) + aval + (let ((tmp (make-ssavalue))) + (emit `(= ,tmp ,aval)) + tmp)) + vals)))))))) (define (compile-cond ex break-labels) (let ((cnd (or (compile ex break-labels #t #f) ;; TODO: condition exprs that don't yield a value? '(null)))) - (if (not (valid-ir-argument? cnd)) + (if (valid-body-ir-argument? cnd) cnd (let ((tmp (make-ssavalue))) (emit `(= ,tmp ,cnd)) - tmp) - cnd))) + tmp)))) (define (emit-cond cnd break-labels endl) (let* ((cnd (if (and (pair? cnd) (eq? (car cnd) 'block)) (flatten-ex 'block cnd) @@ -4502,7 +4536,7 @@ f(x) = yt(x) (eq? (car e) 'globalref)) (underscore-symbol? (cadr e))))) (error (string "all-underscore identifiers are write-only and their values cannot be used in expressions" (format-loc current-loc)))) - (cond (tail (emit-return e1)) + (cond (tail (emit-return tail e1)) (value e1) ((symbol? e1) (emit e1) #f) ;; keep symbols for undefined-var checking ((and (pair? e1) (eq? (car e1) 'outerref)) (emit e1) #f) ;; keep globals for undefined-var checking @@ -4548,7 +4582,7 @@ f(x) = yt(x) (else (compile-args (cdr e) break-labels)))) (callex (cons (car e) args))) - (cond (tail (emit-return callex)) + (cond (tail (emit-return tail callex)) (value callex) (else (emit callex))))) ((=) @@ -4565,7 +4599,7 @@ f(x) = yt(x) (if (not (eq? rr rhs)) (emit `(= ,rr ,rhs))) (emit `(= ,lhs ,rr)) - (if tail (emit-return rr)) + (if tail (emit-return tail rr)) rr) (emit-assignment lhs rhs)))))) ((block) @@ -4618,7 +4652,7 @@ f(x) = yt(x) (if file-diff (set! filename last-fname)) v))) ((return) - (compile (cadr e) break-labels #t #t) + (compile (cadr e) break-labels #t 'explicit) #f) ((unnecessary) ;; `unnecessary` marks expressions generated by lowering that @@ -4633,7 +4667,8 @@ f(x) = yt(x) (let ((v1 (compile (caddr e) break-labels value tail))) (if val (emit-assignment val v1)) (if (and (not tail) (or (length> e 3) val)) - (emit end-jump)) + (begin (emit `(line #f)) + (emit end-jump))) (let ((elselabel (make&mark-label))) (for-each (lambda (test) (set-car! (cddr test) elselabel)) @@ -4645,7 +4680,7 @@ f(x) = yt(x) (if (not tail) (set-car! (cdr end-jump) (make&mark-label)) (if (length= e 3) - (emit-return v2))) + (emit-return tail v2))) val)))) ((_while) (let* ((endl (make-label)) @@ -4687,7 +4722,7 @@ f(x) = yt(x) (emit `(label ,m)) (put! label-map (cadr e) (make&mark-label))) (if tail - (emit-return '(null)) + (emit-return tail '(null)) (if value (error "misplaced label"))))) ((symbolicgoto) (let* ((m (get label-map (cadr e) #f)) @@ -4701,24 +4736,30 @@ f(x) = yt(x) #f)) ;; exception handlers are lowered using - ;; (= tok (enter L)) - push handler with catch block at label L, yielding token + ;; (= tok (enter L scope)) + ;; push handler with catch block at label L and scope `scope`, yielding token + ;; `scope` is only recognized for tryfinally and may be omitted in the lowering ;; (leave n) - pop N exception handlers ;; (pop_exception tok) - pop exception stack back to state of associated enter ((trycatch tryfinally trycatchelse) (let ((handler-token (make-ssavalue)) (catch (make-label)) + (catchcode (if (eq? (car e) 'tryfinally) '(call (top rethrow)) (caddr e))) (els (and (eq? (car e) 'trycatchelse) (make-label))) (endl (make-label)) (last-finally-handler finally-handler) - (finally (if (eq? (car e) 'tryfinally) (new-mutable-var) #f)) + ;; Special case optimization: If the finally block is trivially empty, don't perform finally + ;; lowering, just lower this as a try/catch block with rethrow and scope hnadling. + (finally (if (and (eq? (car e) 'tryfinally) (not (code-trivially-effect-free? (caddr e)))) (new-mutable-var) #f)) + (scope (if (eq? (car e) 'tryfinally) (cdddr e) '())) (my-finally-handler #f)) ;; handler block entry - (emit `(= ,handler-token (enter ,catch))) + (emit `(= ,handler-token (enter ,catch ,@(compile-args scope break-labels)))) (set! handler-token-stack (cons handler-token handler-token-stack)) (if finally (begin (set! my-finally-handler (list finally endl '() handler-token-stack catch-token-stack)) (set! finally-handler my-finally-handler) (emit `(= ,finally -1)))) - (let* ((v1 (compile (cadr e) break-labels value #f)) ;; emit try block code + (let* ((v1 (compile (cadr e) break-labels value #f)) ;; emit try block code (val (if (and value (not tail)) (new-mutable-var) #f))) ;; handler block postfix @@ -4727,7 +4768,7 @@ f(x) = yt(x) (begin (if els (begin (if (and (not val) v1) (emit v1)) (emit `(leave ,handler-token))) - (if v1 (emit-return v1))) + (if v1 (emit-return tail v1))) (if (not finally) (set! endl #f))) (begin (emit `(leave ,handler-token)) (emit `(goto ,(or els endl))))) @@ -4738,11 +4779,11 @@ f(x) = yt(x) (let ((v3 (compile (cadddr e) break-labels value tail))) ;; emit else block code (if val (emit-assignment val v3))) (if endl (emit `(goto ,endl))))) - ;; emit either catch or finally block + ;; emit either catch or finally block. A combined try/catch/finally block was split into + ;; separate trycatch and tryfinally blocks earlier. (mark-label catch) - (emit `(leave ,handler-token)) (if finally - (begin (enter-finally-block '(call (top rethrow)) #f) ;; enter block via exception + (begin (enter-finally-block catchcode #f) ;; enter block via exception (mark-label endl) ;; non-exceptional control flow enters here (set! finally-handler last-finally-handler) (compile (caddr e) break-labels #f #f) @@ -4759,14 +4800,14 @@ f(x) = yt(x) (emit `(= ,tmp (call (core ===) ,finally ,(caar actions)))) (emit `(gotoifnot ,tmp ,skip)))) (let ((ac (cdar actions))) - (cond ((eq? (car ac) 'return) (emit-return (cadr ac))) + (cond ((eq? (car ac) 'return) (emit-return tail (cadr ac))) ((eq? (car ac) 'break) (emit-break (cadr ac))) (else ;; assumed to be a rethrow (emit ac)))) (if skip (mark-label skip)) (loop (cdr actions)))))) (begin (set! catch-token-stack (cons handler-token catch-token-stack)) - (let ((v2 (compile (caddr e) break-labels value tail))) + (let ((v2 (compile catchcode break-labels value tail))) (if val (emit-assignment val v2)) (if (not tail) (emit `(pop_exception ,handler-token))) ;; else done in emit-return from compile @@ -4798,8 +4839,8 @@ f(x) = yt(x) (set! global-const-error current-loc)) (emit e)))) ((atomic) (error "misplaced atomic declaration")) - ((isdefined) (if tail (emit-return e) e)) - ((boundscheck) (if tail (emit-return e) e)) + ((isdefined throw_undef_if_not) (if tail (emit-return tail e) e)) + ((boundscheck) (if tail (emit-return tail e) e)) ((method) (if (not (null? (cadr lam))) @@ -4820,12 +4861,12 @@ f(x) = yt(x) l)))) (emit `(method ,(or (cadr e) '(false)) ,sig ,lam)) (if value (compile '(null) break-labels value tail))) - (cond (tail (emit-return e)) + (cond (tail (emit-return tail e)) (value e) (else (emit e))))) ((lambda) (let ((temp (linearize e))) - (cond (tail (emit-return temp)) + (cond (tail (emit-return tail temp)) (value temp) (else (emit temp))))) @@ -4833,7 +4874,7 @@ f(x) = yt(x) ((thunk module) (check-top-level e) (emit e) - (if tail (emit-return '(null))) + (if tail (emit-return tail '(null))) '(null)) ((toplevel-only) (check-top-level (cdr e)) @@ -4843,7 +4884,7 @@ f(x) = yt(x) (check-top-level e) (let ((val (make-ssavalue))) (emit `(= ,val ,e)) - (if tail (emit-return val)) + (if tail (emit-return tail val)) val)) ;; other top level expressions @@ -4852,7 +4893,7 @@ f(x) = yt(x) (emit e) (let ((have-ret? (and (pair? code) (pair? (car code)) (eq? (caar code) 'return)))) (if (and tail (not have-ret?)) - (emit-return '(null)))) + (emit-return tail '(null)))) '(null)) ((gc_preserve_begin) @@ -4860,7 +4901,7 @@ f(x) = yt(x) (cons (car e) args))) ;; metadata expressions - ((lineinfo line meta inbounds loopinfo gc_preserve_end aliasscope popaliasscope inline noinline) + ((lineinfo line meta inbounds loopinfo gc_preserve_end aliasscope popaliasscope inline noinline purity) (let ((have-ret? (and (pair? code) (pair? (car code)) (eq? (caar code) 'return)))) (cond ((eq? (car e) 'line) (set! current-loc e) @@ -4876,7 +4917,7 @@ f(x) = yt(x) (else (emit e))) (if (and tail (not have-ret?)) - (emit-return '(null))) + (emit-return tail '(null))) '(null))) ;; unsupported assignment operators @@ -4978,13 +5019,13 @@ f(x) = yt(x) (list ,@(cadr vi)) ,(caddr vi) (list ,@(cadddr vi))) ,@(cdddr lam)))) -(define (make-lineinfo name file line (inlined-at #f)) - `(lineinfo (thismodule) ,(if inlined-at '|macro expansion| name) ,file ,line ,(or inlined-at 0))) +(define (make-lineinfo file line (inlined-at #f)) + `(lineinfo ,file ,line ,(or inlined-at 0))) (define (set-lineno! lineinfo num) - (set-car! (cddddr lineinfo) num)) + (set-car! (cddr lineinfo) num)) -(define (compact-ir body name file line) +(define (compact-ir body file line) (let ((code '(block)) (locs '(list)) (linetable '(list)) @@ -4992,6 +5033,7 @@ f(x) = yt(x) (labltable (table)) (ssavtable (table)) (current-loc 0) + (nowhere #f) (current-file file) (current-line line) (locstack '()) @@ -5000,36 +5042,43 @@ f(x) = yt(x) (or e (raise "missing value in IR")) (if (and (null? (cdr linetable)) (not (and (pair? e) (eq? (car e) 'meta)))) - (begin (set! linetable (cons (make-lineinfo name file line) linetable)) + (begin (set! linetable (cons (make-lineinfo file line) linetable)) (set! linetablelen (+ linetablelen 1)) (set! current-loc 1))) (set! code (cons e code)) (set! i (+ i 1)) - (set! locs (cons current-loc locs))) + (set! locs (cons (if nowhere 0 current-loc) locs)) + (set! nowhere #f)) (let loop ((stmts (cdr body))) (if (pair? stmts) (let ((e (car stmts))) (cond ((atom? e) (emit e)) ((eq? (car e) 'line) - (if (and (= current-line 0) (length= e 2) (pair? linetable)) - ;; (line n) after push_loc just updates the line for the new file - (begin (set-lineno! (car linetable) (cadr e)) - (set! current-line (cadr e))) - (begin - (set! current-line (cadr e)) - (if (pair? (cddr e)) - (set! current-file (caddr e))) - (set! linetable (cons (if (null? locstack) - (make-lineinfo name current-file current-line) - (make-lineinfo name current-file current-line (caar locstack))) - linetable)) - (set! linetablelen (+ linetablelen 1)) - (set! current-loc linetablelen)))) + (cond ((and (length= e 2) (not (cadr e))) + ;; (line #f) marks that we are entering a generated statement + ;; that should not be counted as belonging to the previous marked location, + ;; for example `return` after a not-executed `if` arm in tail position. + (set! nowhere #t)) + ((and (= current-line 0) (length= e 2) (pair? linetable)) + ;; (line n) after push_loc just updates the line for the new file + (begin (set-lineno! (car linetable) (cadr e)) + (set! current-line (cadr e)))) + (else + (begin + (set! current-line (cadr e)) + (if (pair? (cddr e)) + (set! current-file (caddr e))) + (set! linetable (cons (if (null? locstack) + (make-lineinfo current-file current-line) + (make-lineinfo current-file current-line (caar locstack))) + linetable)) + (set! linetablelen (+ linetablelen 1)) + (set! current-loc linetablelen))))) ((and (length> e 2) (eq? (car e) 'meta) (eq? (cadr e) 'push_loc)) (set! locstack (cons (list current-loc current-line current-file) locstack)) (set! current-file (caddr e)) (set! current-line 0) - (set! linetable (cons (make-lineinfo name current-file current-line current-loc) linetable)) + (set! linetable (cons (make-lineinfo current-file current-line current-loc) linetable)) (set! linetablelen (+ linetablelen 1)) (set! current-loc linetablelen)) ((and (length= e 2) (eq? (car e) 'meta) (eq? (cadr e) 'pop_loc)) @@ -5055,7 +5104,6 @@ f(x) = yt(x) (define (renumber-lambda lam file line) (let* ((stuff (compact-ir (lam:body lam) - (if (null? (cadr lam)) '|top-level scope| 'none) file line)) (code (aref stuff 0)) (locs (aref stuff 1)) @@ -5081,14 +5129,16 @@ f(x) = yt(x) ((nospecialize-meta? e) ;; convert nospecialize vars to slot numbers `(meta ,(cadr e) ,@(map renumber-stuff (cddr e)))) - ((or (atom? e) (quoted? e) (eq? (car e) 'global)) + ((or (atom? e) (quoted? e) (eq? (car e) 'global) (eq? (car e) 'toplevel)) e) ((ssavalue? e) (let ((idx (get ssavalue-table (cadr e) #f))) (if (not idx) (begin (prn e) (prn lam) (error "ssavalue with no def"))) `(ssavalue ,idx))) - ((memq (car e) '(goto enter)) - (list* (car e) (get label-table (cadr e)) (cddr e))) + ((eq? (car e) 'goto) + `(goto ,(get label-table (cadr e)))) + ((eq? (car e) 'enter) + `(enter ,(get label-table (cadr e)) ,@(map renumber-stuff (cddr e)))) ((eq? (car e) 'gotoifnot) `(gotoifnot ,(renumber-stuff (cadr e)) ,(get label-table (caddr e)))) ((eq? (car e) 'lambda) diff --git a/src/julia.expmap.in b/src/julia.expmap.in index 213d087fdc2ad..e5f9ee890205f 100644 --- a/src/julia.expmap.in +++ b/src/julia.expmap.in @@ -1,7 +1,7 @@ @JULIA_SHLIB_SYMBOL_VERSION@ { global: pthread*; - __stack_chk_guard; + __stack_chk_*; asprintf; bitvector_*; ios_*; diff --git a/src/julia.h b/src/julia.h index bcef58d647c38..e90e9653d2c85 100644 --- a/src/julia.h +++ b/src/julia.h @@ -13,6 +13,7 @@ #undef jl_setjmp #undef jl_longjmp #undef jl_egal +#undef jl_genericmemory_owner #endif #include "julia_fasttls.h" @@ -45,11 +46,16 @@ #endif // Define the largest size (bytes) of a properly aligned object that the -// processor family and compiler typically supports without a lock -// (assumed to be at least a pointer size). Since C is bad at handling 16-byte -// types, we currently use 8 here as the default. +// processor family (MAX_ATOMIC_SIZE) and compiler (MAX_POINTERATOMIC_SIZE) +// typically supports without a lock (assumed to be at least a pointer size) +// with MAX_POINTERATOMIC_SIZE >= MAX_ATOMIC_SIZE. +#ifdef _P64 +#define MAX_ATOMIC_SIZE 16 +#define MAX_POINTERATOMIC_SIZE 16 +#else #define MAX_ATOMIC_SIZE 8 #define MAX_POINTERATOMIC_SIZE 8 +#endif #ifdef _P64 #define NWORDS(sz) (((sz)+7)>>3) @@ -120,7 +126,8 @@ JL_DLLEXPORT jl_taggedvalue_t *_jl_astaggedvalue(jl_value_t *v JL_PROPAGATES_ROO jl_value_t *_jl_valueof(jl_taggedvalue_t *tv JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; #define jl_valueof(v) _jl_valueof((jl_taggedvalue_t*)(v)) JL_DLLEXPORT jl_value_t *_jl_typeof(jl_value_t *v JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; -#define jl_typeof(v) _jl_typeof((jl_value_t*)(v)) +#define jl_typeof(v) (_jl_typeof((jl_value_t*)(v))) +#define jl_typetagof(v) ((uintptr_t)_jl_typeof((jl_value_t*)(v))) #else #define jl_astaggedvalue(v) \ ((jl_taggedvalue_t*)((char*)(v) - sizeof(jl_taggedvalue_t))) @@ -128,6 +135,8 @@ JL_DLLEXPORT jl_value_t *_jl_typeof(jl_value_t *v JL_PROPAGATES_ROOT) JL_NOTSAFE ((jl_value_t*)((char*)(v) + sizeof(jl_taggedvalue_t))) #define jl_typeof(v) \ jl_to_typeof(jl_typetagof(v)) +#define jl_typetagof(v) \ + ((jl_astaggedvalue(v)->header) & ~(uintptr_t)15) #endif static inline void jl_set_typeof(void *v, void *t) JL_NOTSAFEPOINT { @@ -135,8 +144,6 @@ static inline void jl_set_typeof(void *v, void *t) JL_NOTSAFEPOINT jl_taggedvalue_t *tag = jl_astaggedvalue(v); jl_atomic_store_relaxed((_Atomic(jl_value_t*)*)&tag->type, (jl_value_t*)t); } -#define jl_typetagof(v) \ - ((jl_astaggedvalue(v)->header) & ~(uintptr_t)15) #define jl_typeis(v,t) (jl_typeof(v)==(jl_value_t*)(t)) #define jl_typetagis(v,t) (jl_typetagof(v)==(uintptr_t)(t)) #define jl_set_typetagof(v,t,gc) (jl_set_typeof((v), (void*)(((uintptr_t)(t) << 4) | (gc)))) @@ -167,47 +174,36 @@ typedef struct { // jl_value_t *data[]; } jl_svec_t; -typedef struct { - /* - how - allocation style - 0 = data is inlined, or a foreign pointer we don't manage - 1 = julia-allocated buffer that needs to be marked - 2 = malloc-allocated pointer this array object manages - 3 = has a pointer to the object that owns the data - */ - uint16_t how:2; - uint16_t ndims:9; - uint16_t pooled:1; - uint16_t ptrarray:1; // representation is pointer array - uint16_t hasptr:1; // representation has embedded pointers - uint16_t isshared:1; // data is shared by multiple Arrays - uint16_t isaligned:1; // data allocated with memalign -} jl_array_flags_t; - JL_EXTENSION typedef struct { JL_DATA_TYPE - void *data; size_t length; - jl_array_flags_t flags; - uint16_t elsize; // element size including alignment (dim 1 memory stride) - uint32_t offset; // for 1-d only. does not need to get big. - size_t nrows; - union { - // 1d - size_t maxsize; - // Nd - size_t ncols; - }; - // other dim sizes go here for ndims > 2 + void *ptr; + // followed by padding and inline data, or owner pointer +#ifdef _P64 + // union { + // jl_value_t *owner; + // T inl[]; + // }; +#else + // + // jl_value_t *owner; + // size_t padding[1]; + // T inl[]; +#endif +} jl_genericmemory_t; - // followed by alignment padding and inline data, or owner pointer +JL_EXTENSION typedef struct { + JL_DATA_TYPE + void *ptr_or_offset; + jl_genericmemory_t *mem; +} jl_genericmemoryref_t; + +JL_EXTENSION typedef struct { + JL_DATA_TYPE + jl_genericmemoryref_t ref; + size_t dimsize[]; // length for 1-D, otherwise length is mem->length } jl_array_t; -// compute # of extra words needed to store dimensions -STATIC_INLINE int jl_array_ndimwords(uint32_t ndims) JL_NOTSAFEPOINT -{ - return (ndims < 3 ? 0 : ndims-2); -} typedef struct _jl_datatype_t jl_tupletype_t; struct _jl_code_instance_t; @@ -254,53 +250,78 @@ typedef struct _jl_line_info_node_t { int32_t inlined_at; } jl_line_info_node_t; +struct jl_codeloc_t { + int32_t line; + int32_t to; + int32_t pc; +}; + +typedef struct _jl_debuginfo_t { + jl_value_t *def; + struct _jl_debuginfo_t *linetable; // or nothing + jl_svec_t *edges; // Memory{DebugInfo} + jl_value_t *codelocs; // String // Memory{UInt8} // compressed info +} jl_debuginfo_t; + // the following mirrors `struct EffectsOverride` in `base/compiler/effects.jl` typedef union __jl_purity_overrides_t { struct { - uint8_t ipo_consistent : 1; - uint8_t ipo_effect_free : 1; - uint8_t ipo_nothrow : 1; - uint8_t ipo_terminates_globally : 1; + uint16_t ipo_consistent : 1; + uint16_t ipo_effect_free : 1; + uint16_t ipo_nothrow : 1; + uint16_t ipo_terminates_globally : 1; // Weaker form of `terminates` that asserts // that any control flow syntactically in the method // is guaranteed to terminate, but does not make // assertions about any called functions. - uint8_t ipo_terminates_locally : 1; - uint8_t ipo_notaskstate : 1; - uint8_t ipo_inaccessiblememonly : 1; - uint8_t ipo_noub : 1; + uint16_t ipo_terminates_locally : 1; + uint16_t ipo_notaskstate : 1; + uint16_t ipo_inaccessiblememonly : 1; + uint16_t ipo_noub : 1; + uint16_t ipo_noub_if_noinbounds : 1; } overrides; - uint8_t bits; + uint16_t bits; } _jl_purity_overrides_t; +#define NUM_EFFECTS_OVERRIDES 9 +#define NUM_IR_FLAGS 12 + // This type describes a single function body typedef struct _jl_code_info_t { // ssavalue-indexed arrays of properties: jl_array_t *code; // Any array of statements - jl_value_t *codelocs; // Int32 array of indices into the line table + jl_debuginfo_t *debuginfo; // Table of edge data for each statement jl_value_t *ssavaluetypes; // types of ssa values (or count of them) - jl_array_t *ssaflags; // flags associated with each statement: - // 0 = inbounds - // 1 = inline - // 2 = noinline - // 3 = strict-ieee (strictfp) - // 4 = effect-free (may be deleted if unused) - // 5-6 = - // 7 = has out-of-band info + jl_array_t *ssaflags; // 32 bits flags associated with each statement: + // 1 << 0 = inbounds region + // 1 << 1 = callsite inline region + // 1 << 2 = callsite noinline region + // 1 << 3 = throw block + // 1 << 4 = refined statement + // 1 << 5 = :consistent + // 1 << 6 = :effect_free + // 1 << 7 = :nothrow + // 1 << 8 = :terminates + // 1 << 9 = :noub + // 1 << 10 = :effect_free_if_inaccessiblememonly + // 1 << 11 = :inaccessiblemem_or_argmemonly + // 1 << 12-19 = callsite effects overrides // miscellaneous data: - jl_value_t *method_for_inference_limit_heuristics; // optional method used during inference - jl_value_t *linetable; // Table of locations [TODO: make this volatile like slotnames] jl_array_t *slotnames; // names of local variables jl_array_t *slotflags; // local var bit flags // the following are optional transient properties (not preserved by compression--as they typically get stored elsewhere): jl_value_t *slottypes; // inferred types of slots - jl_value_t *rettype; - jl_method_instance_t *parent; // context (optionally, if available, otherwise nothing) - jl_value_t *edges; // forward edges to method instances that must be invalidated + jl_method_instance_t *parent; // context (after inference, otherwise nothing) + + // These may be used by generated functions to further constrain the resulting inputs. + // They are not used by any other part of the system and may be moved elsewhere in the + // future. + jl_value_t *method_for_inference_limit_heuristics; // optional method used during inference + jl_value_t *edges; // forward edges to method instances that must be invalidated (for copying to debuginfo) size_t min_world; size_t max_world; + // various boolean properties: - uint8_t inferred; uint8_t propagate_inbounds; uint8_t has_fcall; uint8_t nospecializeinfer; @@ -320,19 +341,20 @@ typedef struct _jl_method_t { struct _jl_module_t *module; jl_sym_t *file; int32_t line; - size_t primary_world; - size_t deleted_world; + _Atomic(size_t) primary_world; + _Atomic(size_t) deleted_world; // method's type signature. redundant with TypeMapEntry->specTypes jl_value_t *sig; // table of all jl_method_instance_t specializations we have _Atomic(jl_value_t*) specializations; // allocated as [hashable, ..., NULL, linear, ....], or a single item - _Atomic(jl_array_t*) speckeyset; // index lookup by hash into specializations + _Atomic(jl_genericmemory_t*) speckeyset; // index lookup by hash into specializations jl_value_t *slot_syms; // compacted list of slot names (String) jl_value_t *external_mt; // reference to the method table this method is part of, null if part of the internal table jl_value_t *source; // original code template (jl_code_info_t, but may be compressed), null for builtins + jl_debuginfo_t *debuginfo; // fixed linetable from the source argument, null if not available _Atomic(jl_method_instance_t*) unspecialized; // unspecialized executable method instance, or null jl_value_t *generator; // executable code-generating function if available jl_array_t *roots; // pointers in generated code (shared to reduce memory), or null @@ -387,10 +409,9 @@ struct _jl_method_instance_t { jl_method_t *method; // method this is specialized from } def; // pointer back to the context for this code jl_value_t *specTypes; // argument types this was specialized for - jl_svec_t *sparam_vals; // static parameter values, indexed by def.method->sparam_syms + jl_svec_t *sparam_vals; // static parameter values, indexed by def.method->sig _Atomic(jl_value_t*) uninferred; // cached uncompressed code, for generated functions, top-level thunks, or the interpreter jl_array_t *backedges; // list of method-instances which call this method-instance; `invoke` records (invokesig, caller) pairs - jl_array_t *callbacks; // list of callback functions to inform external caches about invalidations _Atomic(struct _jl_code_instance_t*) cache; uint8_t inInference; // flags to tell if inference is running on this object uint8_t cache_with_orig; // !cache_with_specTypes @@ -411,17 +432,25 @@ typedef struct _jl_opaque_closure_t { typedef struct _jl_code_instance_t { JL_DATA_TYPE jl_method_instance_t *def; // method this is specialized from + jl_value_t *owner; // Compiler token this belongs to, `jl_nothing` is reserved for native _Atomic(struct _jl_code_instance_t*) next; // pointer to the next cache entry // world range for which this object is valid to use - size_t min_world; - size_t max_world; + _Atomic(size_t) min_world; + _Atomic(size_t) max_world; // inference state cache jl_value_t *rettype; // return type for fptr + jl_value_t *exctype; // thrown type for fptr jl_value_t *rettype_const; // inferred constant return value, or null - _Atomic(jl_value_t *) inferred; // inferred jl_code_info_t (may be compressed), or jl_nothing, or null - //TODO: jl_array_t *edges; // stored information about edges from this object + + // Inferred result. When part of the runtime cache, either + // - A jl_code_info_t (may be compressed) containing the inferred IR + // - jl_nothing, indicating that inference was completed, but the result was + // deleted to save space. + // - null, indicating that inference was not yet completed or did not succeed + _Atomic(jl_value_t *) inferred; + _Atomic(jl_debuginfo_t *) debuginfo; // stored information about edges from this object (set once, with a happens-before both source and invoke) //TODO: uint8_t absolute_max; // whether true max world is unknown // purity results @@ -444,7 +473,7 @@ typedef struct _jl_code_instance_t { // uint8_t nonoverlayed : 1; // uint8_t notaskstate : 2; // uint8_t inaccessiblememonly : 2; - jl_value_t *argescapes; // escape information of call arguments + jl_value_t *analysis_results; // Analysis results about this code (IPO-safe) // compilation state cache _Atomic(uint8_t) specsigflags; // & 0b001 == specptr is a specialized function signature for specTypes->rettype @@ -539,9 +568,14 @@ typedef struct { uint32_t npointers; // number of pointers embedded inside int32_t first_ptr; // index of the first pointer (or -1) uint16_t alignment; // strictest alignment over all fields - uint16_t haspadding : 1; // has internal undefined bytes - uint16_t fielddesc_type : 2; // 0 -> 8, 1 -> 16, 2 -> 32, 3 -> foreign type - uint16_t padding : 13; + struct { // combine these fields into a struct so that we can take addressof them + uint16_t haspadding : 1; // has internal undefined bytes + uint16_t fielddesc_type : 2; // 0 -> 8, 1 -> 16, 2 -> 32, 3 -> foreign type + // metadata bit only for GenericMemory eltype layout + uint16_t arrayelem_isboxed : 1; + uint16_t arrayelem_isunion : 1; + uint16_t padding : 11; + } flags; // union { // jl_fielddesc8_t field8[nfields]; // jl_fielddesc16_t field16[nfields]; @@ -613,12 +647,11 @@ typedef struct _jl_module_t { jl_sym_t *name; struct _jl_module_t *parent; _Atomic(jl_svec_t*) bindings; - _Atomic(jl_array_t*) bindingkeyset; // index lookup by name into bindings + _Atomic(jl_genericmemory_t*) bindingkeyset; // index lookup by name into bindings // hidden fields: arraylist_t usings; // modules with all bindings potentially imported jl_uuid_t build_id; jl_uuid_t uuid; - size_t primary_world; _Atomic(uint32_t) counter; int32_t nospecialize; // global bit flags: initialization for new methods int8_t optlevel; @@ -630,11 +663,11 @@ typedef struct _jl_module_t { intptr_t hash; } jl_module_t; -typedef struct _jl_globalref_t { +struct _jl_globalref_t { jl_module_t *mod; jl_sym_t *name; jl_binding_t *binding; -} jl_globalref_t; +}; // one Type-to-Value entry typedef struct _jl_typemap_entry_t { @@ -643,8 +676,8 @@ typedef struct _jl_typemap_entry_t { jl_tupletype_t *sig; // the type signature for this entry jl_tupletype_t *simplesig; // a simple signature for fast rejection jl_svec_t *guardsigs; - size_t min_world; - size_t max_world; + _Atomic(size_t) min_world; + _Atomic(size_t) max_world; union { jl_value_t *value; // generic accessor jl_method_instance_t *linfo; // [nullable] for guard entries @@ -664,10 +697,10 @@ typedef struct _jl_typemap_level_t { // next split may be on Type{T} as LeafTypes then TypeName's parents up to Any // next split may be on LeafType // next split may be on TypeName - _Atomic(jl_array_t*) arg1; // contains LeafType (in a map of non-abstract TypeName) - _Atomic(jl_array_t*) targ; // contains Type{LeafType} (in a map of non-abstract TypeName) - _Atomic(jl_array_t*) name1; // a map for a map for TypeName, for parents up to (excluding) Any - _Atomic(jl_array_t*) tname; // a map for Type{TypeName}, for parents up to (including) Any + _Atomic(jl_genericmemory_t*) arg1; // contains LeafType (in a map of non-abstract TypeName) + _Atomic(jl_genericmemory_t*) targ; // contains Type{LeafType} (in a map of non-abstract TypeName) + _Atomic(jl_genericmemory_t*) name1; // a map for a map for TypeName, for parents up to (excluding) Any + _Atomic(jl_genericmemory_t*) tname; // a map for Type{TypeName}, for parents up to (including) Any // next a linear list of things too complicated at this level for analysis (no more levels) _Atomic(jl_typemap_entry_t*) linear; // finally, start a new level if the type at offs is Any @@ -679,7 +712,7 @@ typedef struct _jl_methtable_t { JL_DATA_TYPE jl_sym_t *name; // sometimes used for debug printing _Atomic(jl_typemap_t*) defs; - _Atomic(jl_array_t*) leafcache; + _Atomic(jl_genericmemory_t*) leafcache; _Atomic(jl_typemap_t*) cache; _Atomic(intptr_t) max_args; // max # of non-vararg arguments in a signature jl_module_t *module; // sometimes used for debug printing @@ -807,12 +840,20 @@ extern JL_DLLIMPORT jl_value_t *jl_bottom_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_method_instance_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_code_instance_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_code_info_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_datatype_t *jl_debuginfo_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_method_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_module_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_unionall_t *jl_addrspace_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_typename_t *jl_addrspace_typename JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_datatype_t *jl_addrspacecore_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_unionall_t *jl_abstractarray_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_unionall_t *jl_densearray_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_unionall_t *jl_array_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_typename_t *jl_array_typename JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_unionall_t *jl_genericmemory_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_typename_t *jl_genericmemory_typename JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_unionall_t *jl_genericmemoryref_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_typename_t *jl_genericmemoryref_typename JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_weakref_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_abstractstring_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_string_type JL_GLOBALLY_ROOTED; @@ -824,6 +865,7 @@ extern JL_DLLIMPORT jl_datatype_t *jl_typeerror_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_methoderror_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_undefvarerror_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_atomicerror_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_datatype_t *jl_missingcodeerror_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_lineinfonode_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_stackovf_exception JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_memory_exception JL_GLOBALLY_ROOTED; @@ -831,8 +873,10 @@ extern JL_DLLIMPORT jl_value_t *jl_readonlymemory_exception JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_diverror_exception JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_undefref_exception JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_interrupt_exception JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_value_t *jl_precompilable_error JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_boundserror_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_an_empty_vec_any JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_value_t *jl_an_empty_memory_any JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_an_empty_string JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_bool_type JL_GLOBALLY_ROOTED; @@ -872,12 +916,20 @@ extern JL_DLLIMPORT jl_value_t *jl_array_symbol_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_array_int32_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_array_uint32_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_array_uint64_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_value_t *jl_memory_uint8_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_value_t *jl_memory_uint16_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_value_t *jl_memory_uint32_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_value_t *jl_memory_uint64_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_value_t *jl_memory_any_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_value_t *jl_memoryref_uint8_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_value_t *jl_memoryref_any_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_expr_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_binding_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_globalref_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_linenumbernode_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_gotonode_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_gotoifnot_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_datatype_t *jl_enternode_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_returnnode_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_phinode_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_pinode_type JL_GLOBALLY_ROOTED; @@ -930,8 +982,10 @@ extern void JL_GC_PUSH2(void *, void *) JL_NOTSAFEPOINT; extern void JL_GC_PUSH3(void *, void *, void *) JL_NOTSAFEPOINT; extern void JL_GC_PUSH4(void *, void *, void *, void *) JL_NOTSAFEPOINT; extern void JL_GC_PUSH5(void *, void *, void *, void *, void *) JL_NOTSAFEPOINT; +extern void JL_GC_PUSH6(void *, void *, void *, void *, void *, void *) JL_NOTSAFEPOINT; extern void JL_GC_PUSH7(void *, void *, void *, void *, void *, void *, void *) JL_NOTSAFEPOINT; extern void JL_GC_PUSH8(void *, void *, void *, void *, void *, void *, void *, void *) JL_NOTSAFEPOINT; +extern void JL_GC_PUSH9(void *, void *, void *, void *, void *, void *, void *, void *, void *) JL_NOTSAFEPOINT; extern void _JL_GC_PUSHARGS(jl_value_t **, size_t) JL_NOTSAFEPOINT; // This is necessary, because otherwise the analyzer considers this undefined // behavior and terminates the exploration @@ -971,10 +1025,15 @@ extern void JL_GC_POP() JL_NOTSAFEPOINT; #define JL_GC_PUSH7(arg1, arg2, arg3, arg4, arg5, arg6, arg7) \ void *__gc_stkf[] = {(void*)JL_GC_ENCODE_PUSH(7), jl_pgcstack, arg1, arg2, arg3, arg4, arg5, arg6, arg7}; \ jl_pgcstack = (jl_gcframe_t*)__gc_stkf; + #define JL_GC_PUSH8(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) \ void *__gc_stkf[] = {(void*)JL_GC_ENCODE_PUSH(8), jl_pgcstack, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8}; \ jl_pgcstack = (jl_gcframe_t*)__gc_stkf; +#define JL_GC_PUSH9(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) \ + void *__gc_stkf[] = {(void*)JL_GC_ENCODE_PUSH(9), jl_pgcstack, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9}; \ + jl_pgcstack = (jl_gcframe_t*)__gc_stkf; + #define JL_GC_PUSHARGS(rts_var,n) \ rts_var = ((jl_value_t**)alloca(((n)+2)*sizeof(jl_value_t*)))+2; \ @@ -1019,7 +1078,7 @@ JL_DLLEXPORT void jl_clear_malloc_data(void); // GC write barriers JL_DLLEXPORT void jl_gc_queue_root(const jl_value_t *root) JL_NOTSAFEPOINT; -JL_DLLEXPORT void jl_gc_queue_multiroot(const jl_value_t *root, const jl_value_t *stored) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_gc_queue_multiroot(const jl_value_t *root, const void *stored, jl_datatype_t *dt) JL_NOTSAFEPOINT; STATIC_INLINE void jl_gc_wb(const void *parent, const void *ptr) JL_NOTSAFEPOINT { @@ -1039,6 +1098,7 @@ STATIC_INLINE void jl_gc_wb_back(const void *ptr) JL_NOTSAFEPOINT // ptr isa jl_ STATIC_INLINE void jl_gc_multi_wb(const void *parent, const jl_value_t *ptr) JL_NOTSAFEPOINT { + // 3 == GC_OLD_MARKED // ptr is an immutable object if (__likely(jl_astaggedvalue(parent)->bits.gc != 3)) return; // parent is young or in remset @@ -1047,7 +1107,7 @@ STATIC_INLINE void jl_gc_multi_wb(const void *parent, const jl_value_t *ptr) JL_ jl_datatype_t *dt = (jl_datatype_t*)jl_typeof(ptr); const jl_datatype_layout_t *ly = dt->layout; if (ly->npointers) - jl_gc_queue_multiroot((jl_value_t*)parent, ptr); + jl_gc_queue_multiroot((jl_value_t*)parent, ptr, dt); } JL_DLLEXPORT void *jl_gc_managed_malloc(size_t sz); @@ -1097,16 +1157,101 @@ STATIC_INLINE jl_value_t *jl_svecset( } #endif -#define jl_array_len(a) (((jl_array_t*)(a))->length) -#define jl_array_data(a) ((void*)((jl_array_t*)(a))->data) -#define jl_array_dim(a,i) ((&((jl_array_t*)(a))->nrows)[i]) -#define jl_array_dim0(a) (((jl_array_t*)(a))->nrows) -#define jl_array_nrows(a) (((jl_array_t*)(a))->nrows) -#define jl_array_ndims(a) ((int32_t)(((jl_array_t*)a)->flags.ndims)) -#define jl_array_data_owner_offset(ndims) (offsetof(jl_array_t,ncols) + sizeof(size_t)*(1+jl_array_ndimwords(ndims))) // in bytes -#define jl_array_data_owner(a) (*((jl_value_t**)((char*)a + jl_array_data_owner_offset(jl_array_ndims(a))))) +#define jl_genericmemory_data_owner_field(a) (*(jl_value_t**)((jl_genericmemory_t*)(a) + 1)) -JL_DLLEXPORT char *jl_array_typetagdata(jl_array_t *a) JL_NOTSAFEPOINT; +#define jl_nparams(t) jl_svec_len(((jl_datatype_t*)(t))->parameters) +#define jl_tparam0(t) jl_svecref(((jl_datatype_t*)(t))->parameters, 0) +#define jl_tparam1(t) jl_svecref(((jl_datatype_t*)(t))->parameters, 1) +#define jl_tparam2(t) jl_svecref(((jl_datatype_t*)(t))->parameters, 2) +#define jl_tparam(t,i) jl_svecref(((jl_datatype_t*)(t))->parameters, i) +#define jl_array_data(a,t) ((t*)((jl_array_t*)(a))->ref.ptr_or_offset) +#define jl_array_data_(a) ((void*)((jl_array_t*)(a))->ref.ptr_or_offset) +#define jl_array_dim(a,i) (((jl_array_t*)(a))->dimsize[i]) +#define jl_array_dim0(a) (((jl_array_t*)(a))->dimsize[0]) +#define jl_array_nrows(a) (((jl_array_t*)(a))->dimsize[0]) +#define jl_array_ndims(a) (*(size_t*)jl_tparam1(jl_typetagof(a))) +#define jl_array_maxsize(a) (((jl_array_t*)(a))->ref.mem->length) +#define jl_array_len(a) (jl_array_ndims(a) == 1 ? jl_array_nrows(a) : jl_array_maxsize(a)) + +/* + how - allocation style + 0 = data is inlined + 1 = owns the gc-managed data, exclusively + 2 = malloc-allocated pointer (may or may not own it) + 3 = has a pointer to the object that owns the data pointer +*/ +STATIC_INLINE int jl_genericmemory_how(jl_genericmemory_t *m) JL_NOTSAFEPOINT +{ + if (m->ptr == (void*)((char*)m + 16)) // JL_SMALL_BYTE_ALIGNMENT (from julia_internal.h) + return 0; + jl_value_t *owner = jl_genericmemory_data_owner_field(m); + if (owner == (jl_value_t*)m) + return 1; + if (owner == NULL) + return 2; + return 3; +} + +STATIC_INLINE jl_value_t *jl_genericmemory_owner(jl_genericmemory_t *m JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT +{ + if (jl_genericmemory_how(m) == 3) + return jl_genericmemory_data_owner_field(m); + return (jl_value_t*)m; +} + +JL_DLLEXPORT char *jl_genericmemory_typetagdata(jl_genericmemory_t *m) JL_NOTSAFEPOINT; + +#ifdef __clang_gcanalyzer__ +jl_value_t **jl_genericmemory_ptr_data(jl_genericmemory_t *m JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; +STATIC_INLINE jl_value_t *jl_genericmemory_ptr_ref(void *m JL_PROPAGATES_ROOT, size_t i) JL_NOTSAFEPOINT; +STATIC_INLINE jl_value_t *jl_genericmemory_ptr_set( + void *m JL_ROOTING_ARGUMENT, size_t i, + void *x JL_ROOTED_ARGUMENT) JL_NOTSAFEPOINT; +#else +#define jl_genericmemory_ptr_data(a) ((jl_value_t**)((jl_genericmemory_t*)(a))->ptr) +STATIC_INLINE jl_value_t *jl_genericmemory_ptr_ref(void *m JL_PROPAGATES_ROOT, size_t i) JL_NOTSAFEPOINT +{ + jl_genericmemory_t *m_ = (jl_genericmemory_t*)m; + assert(((jl_datatype_t*)jl_typetagof(m_))->layout->flags.arrayelem_isboxed); + assert(i < m_->length); + return jl_atomic_load_relaxed(((_Atomic(jl_value_t*)*)(m_->ptr)) + i); +} +STATIC_INLINE jl_value_t *jl_genericmemory_ptr_set( + void *m JL_ROOTING_ARGUMENT, size_t i, + void *x JL_ROOTED_ARGUMENT) JL_NOTSAFEPOINT +{ + jl_genericmemory_t *m_ = (jl_genericmemory_t*)m; + assert(((jl_datatype_t*)jl_typetagof(m_))->layout->flags.arrayelem_isboxed); + assert(i < m_->length); + jl_atomic_store_release(((_Atomic(jl_value_t*)*)(m_->ptr)) + i, (jl_value_t*)x); + if (x) { + if (jl_genericmemory_how(m_) == 3) + m = (void*)jl_genericmemory_data_owner_field(m_); + jl_gc_wb(m, x); + } + return (jl_value_t*)x; +} +#endif + +STATIC_INLINE uint8_t jl_memory_uint8_ref(void *m, size_t i) JL_NOTSAFEPOINT +{ + jl_genericmemory_t *m_ = (jl_genericmemory_t*)m; + assert(jl_typetagis(m_, jl_memory_uint8_type)); + assert(i < m_->length); + return ((uint8_t*)m_->ptr)[i]; +} +STATIC_INLINE void jl_memory_uint8_set(void *m, size_t i, uint8_t x) JL_NOTSAFEPOINT +{ + jl_genericmemory_t *m_ = (jl_genericmemory_t*)m; + assert(jl_typetagis(m_, jl_memory_uint8_type)); + assert(i < m_->length); + ((uint8_t*)m_->ptr)[i] = x; +} + +STATIC_INLINE jl_value_t *jl_array_owner(jl_array_t *a JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT +{ + return jl_genericmemory_owner(a->ref.mem); +} #ifdef __clang_gcanalyzer__ jl_value_t **jl_array_ptr_data(jl_array_t *a JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; @@ -1115,25 +1260,22 @@ STATIC_INLINE jl_value_t *jl_array_ptr_set( void *a JL_ROOTING_ARGUMENT, size_t i, void *x JL_ROOTED_ARGUMENT) JL_NOTSAFEPOINT; #else -#define jl_array_ptr_data(a) ((jl_value_t**)((jl_array_t*)(a))->data) +#define jl_array_ptr_data(a) (jl_array_data(a, jl_value_t*)) STATIC_INLINE jl_value_t *jl_array_ptr_ref(void *a JL_PROPAGATES_ROOT, size_t i) JL_NOTSAFEPOINT { - assert(((jl_array_t*)a)->flags.ptrarray); + assert(((jl_datatype_t*)jl_typetagof(((jl_array_t*)a)->ref.mem))->layout->flags.arrayelem_isboxed); assert(i < jl_array_len(a)); - return jl_atomic_load_relaxed(((_Atomic(jl_value_t*)*)(jl_array_data(a))) + i); + return jl_atomic_load_relaxed(jl_array_data(a, _Atomic(jl_value_t*)) + i); } STATIC_INLINE jl_value_t *jl_array_ptr_set( void *a JL_ROOTING_ARGUMENT, size_t i, void *x JL_ROOTED_ARGUMENT) JL_NOTSAFEPOINT { - assert(((jl_array_t*)a)->flags.ptrarray); + assert(((jl_datatype_t*)jl_typetagof(((jl_array_t*)a)->ref.mem))->layout->flags.arrayelem_isboxed); assert(i < jl_array_len(a)); - jl_atomic_store_release(((_Atomic(jl_value_t*)*)(jl_array_data(a))) + i, (jl_value_t*)x); + jl_atomic_store_release(jl_array_data(a, _Atomic(jl_value_t*)) + i, (jl_value_t*)x); if (x) { - if (((jl_array_t*)a)->flags.how == 3) { - a = jl_array_data_owner(a); - } - jl_gc_wb(a, x); + jl_gc_wb(jl_array_owner((jl_array_t*)a), x); } return (jl_value_t*)x; } @@ -1141,32 +1283,26 @@ STATIC_INLINE jl_value_t *jl_array_ptr_set( STATIC_INLINE uint8_t jl_array_uint8_ref(void *a, size_t i) JL_NOTSAFEPOINT { - assert(i < jl_array_len(a)); assert(jl_typetagis(a, jl_array_uint8_type)); - return ((uint8_t*)(jl_array_data(a)))[i]; + assert(i < jl_array_len(a)); + return jl_array_data(a, uint8_t)[i]; } STATIC_INLINE void jl_array_uint8_set(void *a, size_t i, uint8_t x) JL_NOTSAFEPOINT { - assert(i < jl_array_len(a)); assert(jl_typetagis(a, jl_array_uint8_type)); - ((uint8_t*)(jl_array_data(a)))[i] = x; -} -STATIC_INLINE uint8_t jl_array_uint32_ref(void *a, size_t i) JL_NOTSAFEPOINT -{ assert(i < jl_array_len(a)); - assert(jl_typetagis(a, jl_array_uint32_type)); - return ((uint32_t*)(jl_array_data(a)))[i]; + jl_array_data(a, uint8_t)[i] = x; } -STATIC_INLINE void jl_array_uint32_set(void *a, size_t i, uint8_t x) JL_NOTSAFEPOINT +STATIC_INLINE void jl_array_uint32_set(void *a, size_t i, uint32_t x) JL_NOTSAFEPOINT { assert(i < jl_array_len(a)); - assert(jl_typetagis(a, jl_array_uint32_type)); - ((uint32_t*)(jl_array_data(a)))[i] = x; + assert(jl_typetagis(a, jl_array_uint32_type) || jl_typetagis(a, jl_array_int32_type)); + jl_array_data(a, uint32_t)[i] = x; } #define jl_exprarg(e,n) jl_array_ptr_ref(((jl_expr_t*)(e))->args, n) #define jl_exprargset(e, n, v) jl_array_ptr_set(((jl_expr_t*)(e))->args, n, v) -#define jl_expr_nargs(e) jl_array_len(((jl_expr_t*)(e))->args) +#define jl_expr_nargs(e) jl_array_nrows(((jl_expr_t*)(e))->args) #define jl_fieldref(s,i) jl_get_nth_field(((jl_value_t*)(s)),i) #define jl_fieldref_noalloc(s,i) jl_get_nth_field_noalloc(((jl_value_t*)(s)),i) @@ -1180,28 +1316,26 @@ STATIC_INLINE void jl_array_uint32_set(void *a, size_t i, uint8_t x) JL_NOTSAFEP #define jl_gotonode_label(x) (((intptr_t*)(x))[0]) #define jl_gotoifnot_cond(x) (((jl_value_t**)(x))[0]) #define jl_gotoifnot_label(x) (((intptr_t*)(x))[1]) +#define jl_enternode_catch_dest(x) (((intptr_t*)(x))[0]) +#define jl_enternode_scope(x) (((jl_value_t**)(x))[1]) #define jl_globalref_mod(s) (*(jl_module_t**)(s)) #define jl_globalref_name(s) (((jl_sym_t**)(s))[1]) #define jl_quotenode_value(x) (((jl_value_t**)x)[0]) #define jl_returnnode_value(x) (((jl_value_t**)x)[0]) -#define jl_nparams(t) jl_svec_len(((jl_datatype_t*)(t))->parameters) -#define jl_tparam0(t) jl_svecref(((jl_datatype_t*)(t))->parameters, 0) -#define jl_tparam1(t) jl_svecref(((jl_datatype_t*)(t))->parameters, 1) -#define jl_tparam(t,i) jl_svecref(((jl_datatype_t*)(t))->parameters, i) - // get a pointer to the data in a datatype #define jl_data_ptr(v) ((jl_value_t**)v) #define jl_string_data(s) ((char*)s + sizeof(void*)) #define jl_string_len(s) (*(size_t*)s) -#define jl_gf_mtable(f) (((jl_datatype_t*)jl_typeof(f))->name->mt) +#define jl_gf_ft_mtable(ft) (((jl_datatype_t*)ft)->name->mt) +#define jl_gf_mtable(f) (jl_gf_ft_mtable(jl_typeof(f))) #define jl_gf_name(f) (jl_gf_mtable(f)->name) // struct type info -JL_DLLEXPORT jl_svec_t *jl_compute_fieldtypes(jl_datatype_t *st JL_PROPAGATES_ROOT, void *stack); -#define jl_get_fieldtypes(st) ((st)->types ? (st)->types : jl_compute_fieldtypes((st), NULL)) +JL_DLLEXPORT jl_svec_t *jl_compute_fieldtypes(jl_datatype_t *st JL_PROPAGATES_ROOT, void *stack, int cacheable); +#define jl_get_fieldtypes(st) ((st)->types ? (st)->types : jl_compute_fieldtypes((st), NULL, 0)) STATIC_INLINE jl_svec_t *jl_field_names(jl_datatype_t *st) JL_NOTSAFEPOINT { return st->name->names; @@ -1216,10 +1350,24 @@ STATIC_INLINE jl_value_t *jl_field_type_concrete(jl_datatype_t *st JL_PROPAGATES return jl_svecref(st->types, i); } -#define jl_datatype_size(t) (((jl_datatype_t*)t)->layout->size) -#define jl_datatype_align(t) (((jl_datatype_t*)t)->layout->alignment) -#define jl_datatype_nbits(t) ((((jl_datatype_t*)t)->layout->size)*8) -#define jl_datatype_nfields(t) (((jl_datatype_t*)(t))->layout->nfields) +STATIC_INLINE int jl_is_layout_opaque(const jl_datatype_layout_t *l) JL_NOTSAFEPOINT +{ + return l->nfields == 0 && l->npointers > 0; +} + +JL_DLLEXPORT jl_value_t *jl_unwrap_unionall(jl_value_t *v JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; + +#define jl_inlinedatatype_layout(t) (((jl_datatype_t*)t)->layout) +STATIC_INLINE const jl_datatype_layout_t *jl_datatype_layout(jl_datatype_t *t) JL_NOTSAFEPOINT +{ + if (jl_is_layout_opaque(t->layout)) // e.g. GenericMemory + t = (jl_datatype_t*)jl_unwrap_unionall(t->name->wrapper); + return t->layout; +} +#define jl_datatype_size(t) (jl_datatype_layout((jl_datatype_t*)(t))->size) +#define jl_datatype_align(t) (jl_datatype_layout((jl_datatype_t*)(t))->alignment) +#define jl_datatype_nbits(t) ((jl_datatype_layout((jl_datatype_t*)(t))->size)*8) +#define jl_datatype_nfields(t) (jl_datatype_layout((jl_datatype_t*)(t))->nfields) JL_DLLEXPORT void *jl_symbol_name(jl_sym_t *s); // inline version with strong type check to detect typos in a `->name` chain @@ -1247,23 +1395,23 @@ static inline uint32_t jl_fielddesc_size(int8_t fielddesc_type) JL_NOTSAFEPOINT #define jl_dt_layout_fields(d) ((const char*)(d) + sizeof(jl_datatype_layout_t)) static inline const char *jl_dt_layout_ptrs(const jl_datatype_layout_t *l) JL_NOTSAFEPOINT { - return jl_dt_layout_fields(l) + jl_fielddesc_size(l->fielddesc_type) * l->nfields; + return jl_dt_layout_fields(l) + jl_fielddesc_size(l->flags.fielddesc_type) * l->nfields; } #define DEFINE_FIELD_ACCESSORS(f) \ static inline uint32_t jl_field_##f(jl_datatype_t *st, \ int i) JL_NOTSAFEPOINT \ { \ - const jl_datatype_layout_t *ly = st->layout; \ + const jl_datatype_layout_t *ly = jl_datatype_layout(st); \ assert(i >= 0 && (size_t)i < ly->nfields); \ - if (ly->fielddesc_type == 0) { \ + if (ly->flags.fielddesc_type == 0) { \ return ((const jl_fielddesc8_t*)jl_dt_layout_fields(ly))[i].f; \ } \ - else if (ly->fielddesc_type == 1) { \ + else if (ly->flags.fielddesc_type == 1) { \ return ((const jl_fielddesc16_t*)jl_dt_layout_fields(ly))[i].f; \ } \ else { \ - assert(ly->fielddesc_type == 2); \ + assert(ly->flags.fielddesc_type == 2); \ return ((const jl_fielddesc32_t*)jl_dt_layout_fields(ly))[i].f; \ } \ } \ @@ -1274,24 +1422,24 @@ DEFINE_FIELD_ACCESSORS(size) static inline int jl_field_isptr(jl_datatype_t *st, int i) JL_NOTSAFEPOINT { - const jl_datatype_layout_t *ly = st->layout; + const jl_datatype_layout_t *ly = jl_datatype_layout(st); assert(i >= 0 && (size_t)i < ly->nfields); - return ((const jl_fielddesc8_t*)(jl_dt_layout_fields(ly) + jl_fielddesc_size(ly->fielddesc_type) * i))->isptr; + return ((const jl_fielddesc8_t*)(jl_dt_layout_fields(ly) + jl_fielddesc_size(ly->flags.fielddesc_type) * i))->isptr; } static inline uint32_t jl_ptr_offset(jl_datatype_t *st, int i) JL_NOTSAFEPOINT { - const jl_datatype_layout_t *ly = st->layout; + const jl_datatype_layout_t *ly = st->layout; // NOT jl_datatype_layout(st) assert(i >= 0 && (size_t)i < ly->npointers); const void *ptrs = jl_dt_layout_ptrs(ly); - if (ly->fielddesc_type == 0) { + if (ly->flags.fielddesc_type == 0) { return ((const uint8_t*)ptrs)[i]; } - else if (ly->fielddesc_type == 1) { + else if (ly->flags.fielddesc_type == 1) { return ((const uint16_t*)ptrs)[i]; } else { - assert(ly->fielddesc_type == 2); + assert(ly->flags.fielddesc_type == 2); return ((const uint32_t*)ptrs)[i]; } } @@ -1320,11 +1468,6 @@ static inline int jl_field_isconst(jl_datatype_t *st, int i) JL_NOTSAFEPOINT } -static inline int jl_is_layout_opaque(const jl_datatype_layout_t *l) JL_NOTSAFEPOINT -{ - return l->nfields == 0 && l->npointers > 0; -} - // basic predicates ----------------------------------------------------------- #define jl_is_nothing(v) (((jl_value_t*)(v)) == ((jl_value_t*)jl_nothing)) #define jl_is_tuple(v) (((jl_datatype_t*)jl_typeof(v))->name == jl_tuple_typename) @@ -1359,6 +1502,7 @@ static inline int jl_is_layout_opaque(const jl_datatype_layout_t *l) JL_NOTSAFEP #define jl_is_gotonode(v) jl_typetagis(v,jl_gotonode_type) #define jl_is_gotoifnot(v) jl_typetagis(v,jl_gotoifnot_type) #define jl_is_returnnode(v) jl_typetagis(v,jl_returnnode_type) +#define jl_is_enternode(v) jl_typetagis(v,jl_enternode_type) #define jl_is_argument(v) jl_typetagis(v,jl_argument_type) #define jl_is_pinode(v) jl_typetagis(v,jl_pinode_type) #define jl_is_phinode(v) jl_typetagis(v,jl_phinode_type) @@ -1380,10 +1524,13 @@ static inline int jl_is_layout_opaque(const jl_datatype_layout_t *l) JL_NOTSAFEP #define jl_is_uint8pointer(v)jl_typetagis(v,jl_uint8pointer_type) #define jl_is_llvmpointer(v) (((jl_datatype_t*)jl_typeof(v))->name == jl_llvmpointer_typename) #define jl_is_intrinsic(v) jl_typetagis(v,jl_intrinsic_type) -#define jl_array_isbitsunion(a) (!(((jl_array_t*)(a))->flags.ptrarray) && jl_is_uniontype(jl_tparam0(jl_typeof(a)))) +#define jl_is_addrspacecore(v) jl_typetagis(v,jl_addrspacecore_type) +#define jl_genericmemory_isbitsunion(a) (((jl_datatype_t*)jl_typetagof(a))->layout->flags.arrayelem_isunion) JL_DLLEXPORT int jl_subtype(jl_value_t *a, jl_value_t *b); +int is_leaf_bound(jl_value_t *v) JL_NOTSAFEPOINT; + STATIC_INLINE int jl_is_kind(jl_value_t *v) JL_NOTSAFEPOINT { return (v==(jl_value_t*)jl_uniontype_type || v==(jl_value_t*)jl_datatype_type || @@ -1416,23 +1563,23 @@ STATIC_INLINE int jl_is_structtype(void *v) JL_NOTSAFEPOINT STATIC_INLINE int jl_isbits(void *t) JL_NOTSAFEPOINT // corresponding to isbitstype() in julia { - return (jl_is_datatype(t) && ((jl_datatype_t*)t)->isbitstype); + return jl_is_datatype(t) && ((jl_datatype_t*)t)->isbitstype; } STATIC_INLINE int jl_is_datatype_singleton(jl_datatype_t *d) JL_NOTSAFEPOINT { - return (d->instance != NULL); + return d->instance != NULL && d->layout->size == 0 && d->layout->npointers == 0; } STATIC_INLINE int jl_is_abstracttype(void *v) JL_NOTSAFEPOINT { - return (jl_is_datatype(v) && ((jl_datatype_t*)(v))->name->abstract); + return jl_is_datatype(v) && ((jl_datatype_t*)(v))->name->abstract; } STATIC_INLINE int jl_is_array_type(void *t) JL_NOTSAFEPOINT { - return (jl_is_datatype(t) && - ((jl_datatype_t*)(t))->name == jl_array_typename); + return jl_is_datatype(t) && + ((jl_datatype_t*)(t))->name == jl_array_typename; } STATIC_INLINE int jl_is_array(void *v) JL_NOTSAFEPOINT @@ -1441,6 +1588,42 @@ STATIC_INLINE int jl_is_array(void *v) JL_NOTSAFEPOINT return jl_is_array_type(t); } +STATIC_INLINE int jl_is_genericmemory_type(void *t) JL_NOTSAFEPOINT +{ + return (jl_is_datatype(t) && + ((jl_datatype_t*)(t))->name == jl_genericmemory_typename); +} + +STATIC_INLINE int jl_is_genericmemory(void *v) JL_NOTSAFEPOINT +{ + jl_value_t *t = jl_typeof(v); + return jl_is_genericmemory_type(t); +} + +STATIC_INLINE int jl_is_genericmemoryref_type(void *t) JL_NOTSAFEPOINT +{ + return (jl_is_datatype(t) && + ((jl_datatype_t*)(t))->name == jl_genericmemoryref_typename); +} + +STATIC_INLINE int jl_is_genericmemoryref(void *v) JL_NOTSAFEPOINT +{ + jl_value_t *t = jl_typeof(v); + return jl_is_genericmemoryref_type(t); +} + +STATIC_INLINE int jl_is_addrspace_type(void *t) JL_NOTSAFEPOINT +{ + return (jl_is_datatype(t) && + ((jl_datatype_t*)(t))->name == jl_addrspace_typename); +} + +STATIC_INLINE int jl_is_addrspace(void *v) JL_NOTSAFEPOINT +{ + jl_value_t *t = jl_typeof(v); + return jl_is_addrspace_type(t); +} + STATIC_INLINE int jl_is_opaque_closure_type(void *t) JL_NOTSAFEPOINT { @@ -1496,12 +1679,9 @@ STATIC_INLINE int jl_is_type_type(jl_value_t *v) JL_NOTSAFEPOINT ((jl_datatype_t*)(v))->name == ((jl_datatype_t*)jl_type_type->body)->name); } -STATIC_INLINE int jl_is_array_zeroinit(jl_array_t *a) JL_NOTSAFEPOINT +STATIC_INLINE int jl_is_genericmemory_zeroinit(jl_genericmemory_t *m) JL_NOTSAFEPOINT { - if (a->flags.ptrarray || a->flags.hasptr) - return 1; - jl_value_t *elty = jl_tparam0(jl_typeof(a)); - return jl_is_datatype(elty) && ((jl_datatype_t*)elty)->zeroinit; + return ((jl_datatype_t*)jl_typeof(m))->zeroinit; } // object identity @@ -1550,6 +1730,7 @@ JL_DLLEXPORT jl_value_t *jl_type_unionall(jl_tvar_t *v, jl_value_t *body); JL_DLLEXPORT const char *jl_typename_str(jl_value_t *v) JL_NOTSAFEPOINT; JL_DLLEXPORT const char *jl_typeof_str(jl_value_t *v) JL_NOTSAFEPOINT; JL_DLLEXPORT int jl_type_morespecific(jl_value_t *a, jl_value_t *b); +JL_DLLEXPORT int jl_method_morespecific(jl_method_t *ma, jl_method_t *mb); STATIC_INLINE int jl_is_dispatch_tupletype(jl_value_t *v) JL_NOTSAFEPOINT { @@ -1570,6 +1751,7 @@ 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_value_t *jl_apply_type3(jl_value_t *tc, jl_value_t *p1, jl_value_t *p2, jl_value_t *p3); 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_value_t *jl_apply_tuple_type(jl_svec_t *params, int check); // if uncertain, set check=1 @@ -1591,10 +1773,11 @@ JL_DLLEXPORT jl_datatype_t *jl_new_primitivetype(jl_value_t *name, // constructors JL_DLLEXPORT jl_value_t *jl_new_bits(jl_value_t *bt, const void *src); 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 void jl_atomic_store_bits(char *dst, const jl_value_t *src, int nb) JL_NOTSAFEPOINT; 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, jl_datatype_t *rettype, char *dst, const jl_value_t *expected, 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_NOTSAFEPOINT; +JL_DLLEXPORT int jl_atomic_cmpswap_bits(jl_datatype_t *dt, jl_value_t *y, char *dst, const jl_value_t *expected, const jl_value_t *src, int nb) JL_NOTSAFEPOINT; +JL_DLLEXPORT int jl_atomic_storeonce_bits(jl_datatype_t *dt, char *dst, const jl_value_t *src, int nb) JL_NOTSAFEPOINT; 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); @@ -1688,44 +1871,51 @@ int jl_uniontype_size(jl_value_t *ty, size_t *sz); JL_DLLEXPORT int jl_islayout_inline(jl_value_t *eltype, size_t *fsz, size_t *al); // arrays -JL_DLLEXPORT jl_array_t *jl_new_array(jl_value_t *atype, jl_value_t *dims); -JL_DLLEXPORT jl_array_t *jl_reshape_array(jl_value_t *atype, jl_array_t *data, - jl_value_t *dims); JL_DLLEXPORT jl_array_t *jl_ptr_to_array_1d(jl_value_t *atype, void *data, size_t nel, int own_buffer); JL_DLLEXPORT jl_array_t *jl_ptr_to_array(jl_value_t *atype, void *data, jl_value_t *dims, int own_buffer); JL_DLLEXPORT jl_array_t *jl_alloc_array_1d(jl_value_t *atype, size_t nr); -JL_DLLEXPORT jl_array_t *jl_alloc_array_2d(jl_value_t *atype, size_t nr, - size_t nc); -JL_DLLEXPORT jl_array_t *jl_alloc_array_3d(jl_value_t *atype, size_t nr, - size_t nc, size_t z); +JL_DLLEXPORT jl_array_t *jl_alloc_array_2d(jl_value_t *atype, size_t nr, size_t nc); +JL_DLLEXPORT jl_array_t *jl_alloc_array_3d(jl_value_t *atype, size_t nr, size_t nc, size_t z); +JL_DLLEXPORT jl_array_t *jl_alloc_array_nd(jl_value_t *atype, size_t *dims, size_t ndims); JL_DLLEXPORT jl_array_t *jl_pchar_to_array(const char *str, size_t len); JL_DLLEXPORT jl_value_t *jl_pchar_to_string(const char *str, size_t len); JL_DLLEXPORT jl_value_t *jl_cstr_to_string(const char *str); JL_DLLEXPORT jl_value_t *jl_alloc_string(size_t len); JL_DLLEXPORT jl_value_t *jl_array_to_string(jl_array_t *a); JL_DLLEXPORT jl_array_t *jl_alloc_vec_any(size_t n); -JL_DLLEXPORT jl_value_t *jl_arrayref(jl_array_t *a, size_t i); // 0-indexed -JL_DLLEXPORT jl_value_t *jl_ptrarrayref(jl_array_t *a JL_PROPAGATES_ROOT, size_t i) JL_NOTSAFEPOINT; // 0-indexed -JL_DLLEXPORT void jl_arrayset(jl_array_t *a JL_ROOTING_ARGUMENT, jl_value_t *v JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED, size_t i); // 0-indexed -JL_DLLEXPORT void jl_arrayunset(jl_array_t *a, size_t i); // 0-indexed -JL_DLLEXPORT int jl_array_isassigned(jl_array_t *a, size_t i); // 0-indexed JL_DLLEXPORT void jl_array_grow_end(jl_array_t *a, size_t inc); JL_DLLEXPORT void jl_array_del_end(jl_array_t *a, size_t dec); -JL_DLLEXPORT void jl_array_grow_beg(jl_array_t *a, size_t inc); -JL_DLLEXPORT void jl_array_del_beg(jl_array_t *a, size_t dec); -JL_DLLEXPORT void jl_array_sizehint(jl_array_t *a, size_t sz); JL_DLLEXPORT void jl_array_ptr_1d_push(jl_array_t *a, jl_value_t *item); JL_DLLEXPORT void jl_array_ptr_1d_append(jl_array_t *a, jl_array_t *a2); JL_DLLEXPORT jl_value_t *jl_apply_array_type(jl_value_t *type, size_t dim); -JL_DLLEXPORT int jl_array_validate_dims(size_t *nel, size_t *tot, uint32_t ndims, size_t *dims, size_t elsz); // property access JL_DLLEXPORT void *jl_array_ptr(jl_array_t *a); JL_DLLEXPORT void *jl_array_eltype(jl_value_t *a); JL_DLLEXPORT int jl_array_rank(jl_value_t *a); -JL_DLLEXPORT size_t jl_array_size(jl_value_t *a, int d); + +// genericmemory +JL_DLLEXPORT jl_genericmemory_t *jl_new_genericmemory(jl_value_t *mtype, jl_value_t *dim); +JL_DLLEXPORT jl_genericmemory_t *jl_ptr_to_genericmemory(jl_value_t *mtype, void *data, + size_t nel, int own_buffer); +JL_DLLEXPORT jl_genericmemory_t *jl_alloc_genericmemory(jl_value_t *mtype, size_t nel); +JL_DLLEXPORT jl_genericmemory_t *jl_pchar_to_memory(const char *str, size_t len); +JL_DLLEXPORT jl_value_t *jl_genericmemory_to_string(jl_genericmemory_t *m, size_t len); +JL_DLLEXPORT jl_genericmemory_t *jl_alloc_memory_any(size_t n); +JL_DLLEXPORT jl_value_t *jl_genericmemoryref(jl_genericmemory_t *m, size_t i); // 0-indexed + +JL_DLLEXPORT jl_genericmemoryref_t *jl_new_memoryref(jl_value_t *typ, jl_genericmemory_t *mem, void *data); +JL_DLLEXPORT jl_value_t *jl_memoryrefget(jl_genericmemoryref_t m JL_PROPAGATES_ROOT, int isatomic); +JL_DLLEXPORT jl_value_t *jl_ptrmemoryrefget(jl_genericmemoryref_t m JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; +JL_DLLEXPORT jl_value_t *jl_memoryref_isassigned(jl_genericmemoryref_t m, int isatomic) JL_GLOBALLY_ROOTED; +JL_DLLEXPORT jl_genericmemoryref_t jl_memoryrefindex(jl_genericmemoryref_t m JL_PROPAGATES_ROOT, size_t idx) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_memoryrefset(jl_genericmemoryref_t m JL_ROOTING_ARGUMENT, jl_value_t *v JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED, int isatomic); +JL_DLLEXPORT jl_value_t *jl_memoryrefswap(jl_genericmemoryref_t m, jl_value_t *v, int isatomic); +JL_DLLEXPORT jl_value_t *jl_memoryrefmodify(jl_genericmemoryref_t m, jl_value_t *op, jl_value_t *v, int isatomic); +JL_DLLEXPORT jl_value_t *jl_memoryrefreplace(jl_genericmemoryref_t m, jl_value_t *expected, jl_value_t *v, int isatomic); +JL_DLLEXPORT jl_value_t *jl_memoryrefsetonce(jl_genericmemoryref_t m, jl_value_t *v, int isatomic); // strings JL_DLLEXPORT const char *jl_string_ptr(jl_value_t *s); @@ -1749,7 +1939,6 @@ JL_DLLEXPORT int jl_get_module_max_methods(jl_module_t *m); // get binding for reading JL_DLLEXPORT jl_binding_t *jl_get_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); JL_DLLEXPORT jl_binding_t *jl_get_binding_or_error(jl_module_t *m, jl_sym_t *var); -JL_DLLEXPORT jl_binding_t *jl_get_binding_if_bound(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT jl_value_t *jl_module_globalref(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var); // get binding for assignment @@ -1766,6 +1955,10 @@ JL_DLLEXPORT jl_value_t *jl_get_global(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym JL_DLLEXPORT void jl_set_global(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT); JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT); JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs JL_MAYBE_UNROOTED); +JL_DLLEXPORT jl_value_t *jl_checked_swap(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs JL_MAYBE_UNROOTED); +JL_DLLEXPORT jl_value_t *jl_checked_replace(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *expected, jl_value_t *rhs); +JL_DLLEXPORT jl_value_t *jl_checked_modify(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *op, jl_value_t *rhs); +JL_DLLEXPORT jl_value_t *jl_checked_assignonce(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs JL_MAYBE_UNROOTED); JL_DLLEXPORT void jl_declare_constant(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var); JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from); JL_DLLEXPORT void jl_module_use(jl_module_t *to, jl_module_t *from, jl_sym_t *s); @@ -1782,10 +1975,10 @@ STATIC_INLINE jl_function_t *jl_get_function(jl_module_t *m, const char *name) } // eq hash tables -JL_DLLEXPORT jl_array_t *jl_eqtable_put(jl_array_t *h JL_ROOTING_ARGUMENT, jl_value_t *key, jl_value_t *val JL_ROOTED_ARGUMENT, int *inserted); -JL_DLLEXPORT jl_value_t *jl_eqtable_get(jl_array_t *h JL_PROPAGATES_ROOT, jl_value_t *key, jl_value_t *deflt) JL_NOTSAFEPOINT; -JL_DLLEXPORT jl_value_t *jl_eqtable_pop(jl_array_t *h, jl_value_t *key, jl_value_t *deflt, int *found); -jl_value_t *jl_eqtable_getkey(jl_array_t *h JL_PROPAGATES_ROOT, jl_value_t *key, jl_value_t *deflt) JL_NOTSAFEPOINT; +JL_DLLEXPORT jl_genericmemory_t *jl_eqtable_put(jl_genericmemory_t *h JL_ROOTING_ARGUMENT, jl_value_t *key, jl_value_t *val JL_ROOTED_ARGUMENT, int *inserted); +JL_DLLEXPORT jl_value_t *jl_eqtable_get(jl_genericmemory_t *h JL_PROPAGATES_ROOT, jl_value_t *key, jl_value_t *deflt) JL_NOTSAFEPOINT; +JL_DLLEXPORT jl_value_t *jl_eqtable_pop(jl_genericmemory_t *h, jl_value_t *key, jl_value_t *deflt, int *found); +jl_value_t *jl_eqtable_getkey(jl_genericmemory_t *h JL_PROPAGATES_ROOT, jl_value_t *key, jl_value_t *deflt) JL_NOTSAFEPOINT; // system information JL_DLLEXPORT int jl_errno(void) JL_NOTSAFEPOINT; @@ -1825,7 +2018,7 @@ JL_DLLEXPORT void JL_NORETURN jl_type_error_rt(const char *fname, const char *context, jl_value_t *ty JL_MAYBE_UNROOTED, jl_value_t *got JL_MAYBE_UNROOTED); -JL_DLLEXPORT void JL_NORETURN jl_undefined_var_error(jl_sym_t *var); +JL_DLLEXPORT void JL_NORETURN jl_undefined_var_error(jl_sym_t *var, jl_value_t *scope JL_MAYBE_UNROOTED); JL_DLLEXPORT void JL_NORETURN jl_has_no_field_error(jl_sym_t *type_name, jl_sym_t *var); JL_DLLEXPORT void JL_NORETURN jl_atomic_error(char *str); JL_DLLEXPORT void JL_NORETURN jl_bounds_error(jl_value_t *v JL_MAYBE_UNROOTED, @@ -1839,18 +2032,6 @@ JL_DLLEXPORT void JL_NORETURN jl_bounds_error_tuple_int(jl_value_t **v, JL_DLLEXPORT void JL_NORETURN jl_bounds_error_unboxed_int(void *v, jl_value_t *vt, size_t i); JL_DLLEXPORT void JL_NORETURN jl_bounds_error_ints(jl_value_t *v JL_MAYBE_UNROOTED, size_t *idxs, size_t nidxs); -JL_DLLEXPORT void JL_NORETURN jl_eof_error(void); - -// Return the exception currently being handled, or `jl_nothing`. -// -// The catch scope is determined dynamically so this works in functions called -// from a catch block. The returned value is gc rooted until we exit the -// enclosing JL_CATCH. -// FIXME: Teach the static analyzer about this rather than using -// JL_GLOBALLY_ROOTED which is far too optimistic. -JL_DLLEXPORT jl_value_t *jl_current_exception(void) JL_GLOBALLY_ROOTED JL_NOTSAFEPOINT; -JL_DLLEXPORT jl_value_t *jl_exception_occurred(void); -JL_DLLEXPORT void jl_exception_clear(void) JL_NOTSAFEPOINT; #define JL_NARGS(fname, min, max) \ if (nargs < min) jl_too_few_args(#fname, min); \ @@ -1897,6 +2078,7 @@ JL_DLLEXPORT void jl_create_system_image(void **, jl_array_t *worklist, bool_t e JL_DLLEXPORT void jl_restore_system_image(const char *fname); JL_DLLEXPORT void jl_restore_system_image_data(const char *buf, size_t len); JL_DLLEXPORT jl_value_t *jl_restore_incremental(const char *fname, jl_array_t *depmods, int complete, const char *pkgimage); +JL_DLLEXPORT jl_value_t *jl_object_top_module(jl_value_t* v) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_set_newly_inferred(jl_value_t *newly_inferred); JL_DLLEXPORT void jl_push_newly_inferred(jl_value_t *ci); @@ -1964,7 +2146,6 @@ JL_DLLEXPORT jl_value_t *jl_copy_ast(jl_value_t *expr JL_MAYBE_UNROOTED); // IR representation JL_DLLEXPORT jl_value_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code); JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t *metadata, jl_value_t *data); -JL_DLLEXPORT uint8_t jl_ir_flag_inferred(jl_value_t *data) JL_NOTSAFEPOINT; JL_DLLEXPORT uint8_t jl_ir_flag_inlining(jl_value_t *data) JL_NOTSAFEPOINT; JL_DLLEXPORT uint8_t jl_ir_flag_has_fcall(jl_value_t *data) JL_NOTSAFEPOINT; JL_DLLEXPORT uint16_t jl_ir_inlining_cost(jl_value_t *data) JL_NOTSAFEPOINT; @@ -1973,13 +2154,15 @@ JL_DLLEXPORT uint8_t jl_ir_slotflag(jl_value_t *data, size_t i) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_compress_argnames(jl_array_t *syms); JL_DLLEXPORT jl_array_t *jl_uncompress_argnames(jl_value_t *syms); JL_DLLEXPORT jl_value_t *jl_uncompress_argname_n(jl_value_t *syms, size_t i); +JL_DLLEXPORT struct jl_codeloc_t jl_uncompress1_codeloc(jl_value_t *cl, size_t pc) JL_NOTSAFEPOINT; +JL_DLLEXPORT jl_value_t *jl_compress_codelocs(int32_t firstline, jl_value_t *codelocs, size_t nstmts); +JL_DLLEXPORT jl_value_t *jl_uncompress_codelocs(jl_value_t *cl, size_t nstmts); - -JL_DLLEXPORT int jl_is_operator(char *sym); -JL_DLLEXPORT int jl_is_unary_operator(char *sym); -JL_DLLEXPORT int jl_is_unary_and_binary_operator(char *sym); -JL_DLLEXPORT int jl_is_syntactic_operator(char *sym); -JL_DLLEXPORT int jl_operator_precedence(char *sym); +JL_DLLEXPORT int jl_is_operator(const char *sym); +JL_DLLEXPORT int jl_is_unary_operator(const char *sym); +JL_DLLEXPORT int jl_is_unary_and_binary_operator(const char *sym); +JL_DLLEXPORT int jl_is_syntactic_operator(const char *sym); +JL_DLLEXPORT int jl_operator_precedence(const char *sym); STATIC_INLINE int jl_vinfo_sa(uint8_t vi) { @@ -2009,9 +2192,6 @@ JL_DLLEXPORT jl_value_t *jl_call2(jl_function_t *f JL_MAYBE_UNROOTED, jl_value_t JL_DLLEXPORT jl_value_t *jl_call3(jl_function_t *f JL_MAYBE_UNROOTED, jl_value_t *a JL_MAYBE_UNROOTED, jl_value_t *b JL_MAYBE_UNROOTED, jl_value_t *c JL_MAYBE_UNROOTED); -// interfacing with Task runtime -JL_DLLEXPORT void jl_yield(void); - // async signal handling ------------------------------------------------------ JL_DLLEXPORT void jl_install_sigint_handler(void); @@ -2113,11 +2293,24 @@ extern JL_DLLIMPORT int jl_task_ptls_offset; #include "julia_locks.h" // requires jl_task_t definition -JL_DLLEXPORT void jl_enter_handler(jl_handler_t *eh); -JL_DLLEXPORT void jl_eh_restore_state(jl_handler_t *eh); -JL_DLLEXPORT void jl_pop_handler(int n); -JL_DLLEXPORT size_t jl_excstack_state(void) JL_NOTSAFEPOINT; -JL_DLLEXPORT void jl_restore_excstack(size_t state) JL_NOTSAFEPOINT; +// Return the exception currently being handled, or `jl_nothing`. +// +// The catch scope is determined dynamically so this works in functions called +// from a catch block. The returned value is gc rooted until we exit the +// enclosing JL_CATCH. +// FIXME: Teach the static analyzer about this rather than using +// JL_GLOBALLY_ROOTED which is far too optimistic. +JL_DLLEXPORT jl_value_t *jl_current_exception(jl_task_t *ct) JL_GLOBALLY_ROOTED JL_NOTSAFEPOINT; +JL_DLLEXPORT jl_value_t *jl_exception_occurred(void); +JL_DLLEXPORT void jl_exception_clear(void) JL_NOTSAFEPOINT; + +JL_DLLEXPORT void jl_enter_handler(jl_task_t *ct, jl_handler_t *eh) JL_NOTSAFEPOINT ; +JL_DLLEXPORT void jl_eh_restore_state(jl_task_t *ct, jl_handler_t *eh); +JL_DLLEXPORT void jl_eh_restore_state_noexcept(jl_task_t *ct, jl_handler_t *eh) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_pop_handler(jl_task_t *ct, int n) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_pop_handler_noexcept(jl_task_t *ct, int n) JL_NOTSAFEPOINT; +JL_DLLEXPORT size_t jl_excstack_state(jl_task_t *ct) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_restore_excstack(jl_task_t *ct, size_t state) JL_NOTSAFEPOINT; #if defined(_OS_WINDOWS_) #if defined(_COMPILER_GCC_) @@ -2157,10 +2350,9 @@ void (ijl_longjmp)(jmp_buf _Buf, int _Value); #define jl_setjmp_name "sigsetjmp" #endif #define jl_setjmp(a,b) sigsetjmp(a,b) -#if defined(_COMPILER_ASAN_ENABLED_) && __GLIBC__ -// Bypass the ASAN longjmp wrapper - we're unpoisoning the stack ourselves. -JL_DLLIMPORT int __attribute__ ((nothrow)) (__libc_siglongjmp)(jl_jmp_buf buf, int val); -#define jl_longjmp(a,b) __libc_siglongjmp(a,b) +#if defined(_COMPILER_ASAN_ENABLED_) && defined(__GLIBC__) +extern void (*real_siglongjmp)(jmp_buf _Buf, int _Value); +#define jl_longjmp(a,b) real_siglongjmp(a,b) #else #define jl_longjmp(a,b) siglongjmp(a,b) #endif @@ -2169,24 +2361,36 @@ JL_DLLIMPORT int __attribute__ ((nothrow)) (__libc_siglongjmp)(jl_jmp_buf buf, i #ifdef __clang_gcanalyzer__ -// This is hard. Ideally we'd teach the static analyzer about the extra control -// flow edges. But for now, just hide this as best we can extern int had_exception; -#define JL_TRY if (1) -#define JL_CATCH if (had_exception) + +// The analyzer assumes that the TRY block always executes to completion. +// This can lead to both false positives and false negatives, since it doesn't model the fact that throwing always leaves the try block early. +#define JL_TRY \ + int i__try, i__catch; jl_handler_t __eh; jl_task_t *__eh_ct; \ + __eh_ct = jl_current_task; \ + size_t __excstack_state = jl_excstack_state(__eh_ct); \ + jl_enter_handler(__eh_ct, &__eh); \ + if (1) + /* TRY BLOCK; */ +#define JL_CATCH \ + if (!had_exception) \ + jl_eh_restore_state_noexcept(__eh_ct, &__eh); \ + else \ + for (i__catch=1, jl_eh_restore_state(__eh_ct, &__eh); i__catch; i__catch=0, /* CATCH BLOCK; */ jl_restore_excstack(__eh_ct, __excstack_state)) #else -#define JL_TRY \ - int i__tr, i__ca; jl_handler_t __eh; \ - size_t __excstack_state = jl_excstack_state(); \ - jl_enter_handler(&__eh); \ - if (!jl_setjmp(__eh.eh_ctx,0)) \ - for (i__tr=1; i__tr; i__tr=0, jl_eh_restore_state(&__eh)) +#define JL_TRY \ + int i__try, i__catch; jl_handler_t __eh; jl_task_t *__eh_ct; \ + __eh_ct = jl_current_task; \ + size_t __excstack_state = jl_excstack_state(__eh_ct); \ + jl_enter_handler(__eh_ct, &__eh); \ + if (!jl_setjmp(__eh.eh_ctx, 0)) \ + for (i__try=1; i__try; i__try=0, /* TRY BLOCK; */ jl_eh_restore_state_noexcept(__eh_ct, &__eh)) -#define JL_CATCH \ - else \ - for (i__ca=1, jl_eh_restore_state(&__eh); i__ca; i__ca=0, jl_restore_excstack(__excstack_state)) +#define JL_CATCH \ + else \ + for (i__catch=1, jl_eh_restore_state(__eh_ct, &__eh); i__catch; i__catch=0, /* CATCH BLOCK; */ jl_restore_excstack(__eh_ct, __excstack_state)) #endif @@ -2246,7 +2450,6 @@ JL_DLLEXPORT int jl_termios_size(void); // showing and std streams JL_DLLEXPORT void jl_flush_cstdio(void) JL_NOTSAFEPOINT; -JL_DLLEXPORT jl_value_t *jl_stdout_obj(void) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_stderr_obj(void) JL_NOTSAFEPOINT; JL_DLLEXPORT size_t jl_static_show(JL_STREAM *out, jl_value_t *v) JL_NOTSAFEPOINT; JL_DLLEXPORT size_t jl_static_show_func_sig(JL_STREAM *s, jl_value_t *type) JL_NOTSAFEPOINT; @@ -2331,10 +2534,12 @@ JL_DLLEXPORT int jl_generating_output(void) JL_NOTSAFEPOINT; #define JL_OPTIONS_USE_SYSIMAGE_NATIVE_CODE_YES 1 #define JL_OPTIONS_USE_SYSIMAGE_NATIVE_CODE_NO 0 +#define JL_OPTIONS_USE_COMPILED_MODULES_STRICT 3 #define JL_OPTIONS_USE_COMPILED_MODULES_EXISTING 2 #define JL_OPTIONS_USE_COMPILED_MODULES_YES 1 #define JL_OPTIONS_USE_COMPILED_MODULES_NO 0 +#define JL_OPTIONS_USE_PKGIMAGES_EXISTING 2 #define JL_OPTIONS_USE_PKGIMAGES_YES 1 #define JL_OPTIONS_USE_PKGIMAGES_NO 0 @@ -2346,8 +2551,6 @@ JL_DLLEXPORT extern int jl_ver_minor(void); JL_DLLEXPORT extern int jl_ver_patch(void); JL_DLLEXPORT extern int jl_ver_is_release(void); JL_DLLEXPORT extern const char *jl_ver_string(void); -JL_DLLEXPORT const char *jl_git_branch(void); -JL_DLLEXPORT const char *jl_git_commit(void); // nullable struct representations typedef struct { @@ -2387,7 +2590,7 @@ typedef struct { int gcstack_arg; // Pass the ptls value as an argument with swiftself int use_jlplt; // Whether to use the Julia PLT mechanism or emit symbols directly - // Cache access. Default: jl_rettype_inferred. + // Cache access. Default: jl_rettype_inferred_native. jl_codeinstance_lookup_t lookup; } jl_cgparams_t; extern JL_DLLEXPORT int jl_default_debug_info_kind; diff --git a/src/julia_atomics.h b/src/julia_atomics.h index c4488f774c987..c094afcc54cd5 100644 --- a/src/julia_atomics.h +++ b/src/julia_atomics.h @@ -56,6 +56,15 @@ enum jl_memory_order { jl_memory_order_seq_cst }; +/** + * Cache line size +*/ +#if (defined(_CPU_AARCH64_) && defined(_OS_DARWIN_)) || defined(_CPU_PPC64_) // Apple silicon and PPC7+ have 128 byte cache lines +#define JL_CACHE_BYTE_ALIGNMENT 128 +#else +#define JL_CACHE_BYTE_ALIGNMENT 64 +#endif + /** * Thread synchronization primitives: * @@ -165,6 +174,11 @@ bool jl_atomic_cmpswap_acqrel(std::atomic *ptr, T *expected, S val) { return std::atomic_compare_exchange_strong_explicit(ptr, expected, val, memory_order_acq_rel, memory_order_acquire); } +template +bool jl_atomic_cmpswap_release(std::atomic *ptr, T *expected, S val) +{ + return std::atomic_compare_exchange_strong_explicit(ptr, expected, val, memory_order_release, memory_order_relaxed); +} #define jl_atomic_cmpswap_relaxed(ptr, expected, val) jl_atomic_cmpswap_explicit(ptr, expected, val, memory_order_relaxed) template T jl_atomic_exchange(std::atomic *ptr, S desired) @@ -176,6 +190,7 @@ T jl_atomic_exchange_explicit(std::atomic *ptr, S desired, std::memory_order { return std::atomic_exchange_explicit(ptr, desired, order); } +#define jl_atomic_exchange_release(ptr, val) jl_atomic_exchange_explicit(ptr, val, memory_order_reease) #define jl_atomic_exchange_relaxed(ptr, val) jl_atomic_exchange_explicit(ptr, val, memory_order_relaxed) extern "C" { #else @@ -196,11 +211,15 @@ extern "C" { atomic_compare_exchange_strong(obj, expected, desired) # define jl_atomic_cmpswap_relaxed(obj, expected, desired) \ atomic_compare_exchange_strong_explicit(obj, expected, desired, memory_order_relaxed, memory_order_relaxed) -#define jl_atomic_cmpswap_acqrel(obj, expected, desired) \ +# define jl_atomic_cmpswap_release(obj, expected, desired) \ + atomic_compare_exchange_strong_explicit(obj, expected, desired, memory_order_release, memory_order_relaxed) +# define jl_atomic_cmpswap_acqrel(obj, expected, desired) \ atomic_compare_exchange_strong_explicit(obj, expected, desired, memory_order_acq_rel, memory_order_acquire) // TODO: Maybe add jl_atomic_cmpswap_weak for spin lock # define jl_atomic_exchange(obj, desired) \ atomic_exchange(obj, desired) +# define jl_atomic_exchange_release(obj, desired) \ + atomic_exchange_explicit(obj, desired, memory_order_release) # define jl_atomic_exchange_relaxed(obj, desired) \ atomic_exchange_explicit(obj, desired, memory_order_relaxed) # define jl_atomic_store(obj, val) \ @@ -247,6 +266,7 @@ extern "C" { #define _Atomic(T) T #undef jl_atomic_exchange +#undef jl_atomic_exchange_release #undef jl_atomic_exchange_relaxed #define jl_atomic_exchange(obj, desired) \ (__extension__({ \ @@ -255,10 +275,12 @@ extern "C" { *p__analyzer__ = (desired); \ temp__analyzer__; \ })) +#define jl_atomic_exchange_release jl_atomic_exchange #define jl_atomic_exchange_relaxed jl_atomic_exchange #undef jl_atomic_cmpswap #undef jl_atomic_cmpswap_acqrel +#undef jl_atomic_cmpswap_release #undef jl_atomic_cmpswap_relaxed #define jl_atomic_cmpswap(obj, expected, desired) \ (__extension__({ \ @@ -273,6 +295,7 @@ extern "C" { eq__analyzer__; \ })) #define jl_atomic_cmpswap_acqrel jl_atomic_cmpswap +#define jl_atomic_cmpswap_release jl_atomic_cmpswap #define jl_atomic_cmpswap_relaxed jl_atomic_cmpswap #undef jl_atomic_store diff --git a/src/julia_gcext.h b/src/julia_gcext.h index 27f0a6b5ec11c..c65586b85547a 100644 --- a/src/julia_gcext.h +++ b/src/julia_gcext.h @@ -34,6 +34,10 @@ JL_DLLEXPORT void jl_gc_set_cb_notify_external_alloc(jl_gc_cb_notify_external_al JL_DLLEXPORT void jl_gc_set_cb_notify_external_free(jl_gc_cb_notify_external_free_t cb, int enable); +// Memory pressure callback +typedef void (*jl_gc_cb_notify_gc_pressure_t)(void) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_gc_set_cb_notify_gc_pressure(jl_gc_cb_notify_gc_pressure_t cb, int enable); + // Types for custom mark and sweep functions. typedef uintptr_t (*jl_markfunc_t)(jl_ptls_t, jl_value_t *obj); typedef void (*jl_sweepfunc_t)(jl_value_t *obj); diff --git a/src/julia_internal.h b/src/julia_internal.h index 7883844d908f8..9840da6b17448 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -97,23 +97,6 @@ JL_DLLIMPORT void *__tsan_get_current_fiber(void); JL_DLLIMPORT void __tsan_destroy_fiber(void *fiber); JL_DLLIMPORT void __tsan_switch_to_fiber(void *fiber, unsigned flags); #endif -#ifdef __cplusplus -} -#endif - -// Remove when C11 is required for C code. -#ifndef static_assert -# ifndef __cplusplus -// C11 should already have `static_assert` from `` so there's no need -// to check C version. -# ifdef __GNUC__ -# define static_assert _Static_assert -# else -# define static_assert(...) -# endif -# endif -// For C++, C++11 or MSVC is required. Both provide `static_assert`. -#endif #ifndef alignof # ifndef __cplusplus @@ -182,10 +165,8 @@ extern jl_mutex_t jl_uv_mutex; extern _Atomic(int) jl_uv_n_waiters; void JL_UV_LOCK(void); #define JL_UV_UNLOCK() JL_UNLOCK(&jl_uv_mutex) - -#ifdef __cplusplus -extern "C" { -#endif +extern _Atomic(unsigned) _threadedregion; +extern _Atomic(uint16_t) io_loop_tid; int jl_running_under_rr(int recheck) JL_NOTSAFEPOINT; @@ -194,7 +175,6 @@ int jl_running_under_rr(int recheck) JL_NOTSAFEPOINT; // Returns time in nanosec JL_DLLEXPORT uint64_t jl_hrtime(void) JL_NOTSAFEPOINT; -JL_DLLEXPORT void jl_set_peek_cond(uintptr_t); JL_DLLEXPORT double jl_get_profile_peek_duration(void); JL_DLLEXPORT void jl_set_profile_peek_duration(double); @@ -211,13 +191,13 @@ void jl_unlock_stackwalk(int lockret) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_LEAVE; static inline uint64_t cycleclock(void) JL_NOTSAFEPOINT { #if defined(_CPU_X86_64_) - // This is nopl 0(%rax, %rax, 1), but assembler are incosistent about whether + // This is nopl 0(%rax, %rax, 1), but assembler are inconsistent about whether // they emit that as a 4 or 5 byte sequence and we need to be guaranteed to use // the 5 byte one. #define NOP5_OVERRIDE_NOP ".byte 0x0f, 0x1f, 0x44, 0x00, 0x00\n\t" uint64_t low, high; // This instruction sequence is promised by rr to be patchable. rr can usually - // also patch `rdtsc` in regular code, but without the preceeding nop, there could + // also patch `rdtsc` in regular code, but without the preceding nop, there could // be an interfering branch into the middle of rr's patch region. Using this // sequence prevents a massive rr-induced slowdown if the compiler happens to emit // an unlucky pattern. See https://github.com/rr-debugger/rr/pull/3580. @@ -270,6 +250,9 @@ static inline uint64_t cycleclock(void) JL_NOTSAFEPOINT #include "timing.h" +extern JL_DLLEXPORT uint64_t jl_typeinf_timing_begin(void) JL_NOTSAFEPOINT; +extern JL_DLLEXPORT void jl_typeinf_timing_end(uint64_t start, int is_recompile) JL_NOTSAFEPOINT; + // Global *atomic* integers controlling *process-wide* measurement of compilation time. extern JL_DLLEXPORT _Atomic(uint8_t) jl_measure_compile_time_enabled; extern JL_DLLEXPORT _Atomic(uint64_t) jl_cumulative_compile_time; @@ -294,23 +277,40 @@ STATIC_INLINE uint32_t jl_int32hash_fast(uint32_t a) // without risk of creating pointers out of thin air // TODO: replace with LLVM's llvm.memmove.element.unordered.atomic.p0i8.p0i8.i32 // aka `__llvm_memmove_element_unordered_atomic_8` (for 64 bit) -static inline void memmove_refs(void **dstp, void *const *srcp, size_t n) JL_NOTSAFEPOINT +static inline void memmove_refs(_Atomic(void*) *dstp, _Atomic(void*) *srcp, size_t n) JL_NOTSAFEPOINT { size_t i; - _Atomic(void*) *srcpa = (_Atomic(void*)*)srcp; - _Atomic(void*) *dstpa = (_Atomic(void*)*)dstp; if (dstp < srcp || dstp > srcp + n) { for (i = 0; i < n; i++) { - jl_atomic_store_release(dstpa + i, jl_atomic_load_relaxed(srcpa + i)); + jl_atomic_store_release(dstp + i, jl_atomic_load_relaxed(srcp + i)); } } else { for (i = 0; i < n; i++) { - jl_atomic_store_release(dstpa + n - i - 1, jl_atomic_load_relaxed(srcpa + n - i - 1)); + jl_atomic_store_release(dstp + n - i - 1, jl_atomic_load_relaxed(srcp + n - i - 1)); } } } +static inline void memassign_safe(int hasptr, char *dst, const jl_value_t *src, size_t nb) JL_NOTSAFEPOINT +{ + assert(nb == jl_datatype_size(jl_typeof(src))); + if (hasptr) { + size_t nptr = nb / sizeof(void*); + memmove_refs((_Atomic(void*)*)dst, (_Atomic(void*)*)src, nptr); + nb -= nptr * sizeof(void*); + if (__likely(nb == 0)) + return; + src = (jl_value_t*)((char*)src + nptr * sizeof(void*)); + dst = dst + nptr * sizeof(void*); + } + else if (nb >= 16) { + memcpy(dst, jl_assume_aligned(src, 16), nb); + return; + } + memcpy(dst, jl_assume_aligned(src, sizeof(void*)), nb); +} + // -- gc.c -- // #define GC_CLEAN 0 // freshly allocated @@ -333,6 +333,7 @@ void print_func_loc(JL_STREAM *s, jl_method_t *m); extern jl_array_t *_jl_debug_method_invalidation JL_GLOBALLY_ROOTED; JL_DLLEXPORT extern arraylist_t jl_linkage_blobs; // external linkage: sysimg/pkgimages JL_DLLEXPORT extern arraylist_t jl_image_relocs; // external linkage: sysimg/pkgimages +JL_DLLEXPORT extern arraylist_t jl_top_mods; // external linkage: sysimg/pkgimages extern arraylist_t eytzinger_image_tree; extern arraylist_t eytzinger_idxs; @@ -340,7 +341,6 @@ extern JL_DLLEXPORT size_t jl_page_size; extern jl_function_t *jl_typeinf_func JL_GLOBALLY_ROOTED; extern JL_DLLEXPORT size_t jl_typeinf_world; extern _Atomic(jl_typemap_entry_t*) call_cache[N_CALL_CACHE] JL_GLOBALLY_ROOTED; -extern jl_array_t *jl_all_methods JL_GLOBALLY_ROOTED; JL_DLLEXPORT extern int jl_lineno; JL_DLLEXPORT extern const char *jl_filename; @@ -352,7 +352,7 @@ JL_DLLEXPORT int jl_gc_classify_pools(size_t sz, int *osize) JL_NOTSAFEPOINT; extern uv_mutex_t gc_perm_lock; void *jl_gc_perm_alloc_nolock(size_t sz, int zero, unsigned align, unsigned offset) JL_NOTSAFEPOINT; -void *jl_gc_perm_alloc(size_t sz, int zero, +JL_DLLEXPORT void *jl_gc_perm_alloc(size_t sz, int zero, unsigned align, unsigned offset) JL_NOTSAFEPOINT; void gc_sweep_sysimg(void); @@ -376,24 +376,48 @@ static const int jl_gc_sizeclasses[] = { 144, 160, 176, 192, 208, 224, 240, 256, // the following tables are computed for maximum packing efficiency via the formula: - // pg = 2^14 + // pg = GC_SMALL_PAGE ? 2^12 : 2^14 // sz = (div.(pg-8, rng).÷16)*16; hcat(sz, (pg-8).÷sz, pg .- (pg-8).÷sz.*sz)' +#ifdef GC_SMALL_PAGE + // rng = 15:-1:2 (14 pools) + 272, 288, 304, 336, 368, 400, 448, 496, 576, 672, 816, 1008, 1360, 2032 +// 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, /pool +// 16, 64, 144, 64, 48, 96, 64, 128, 64, 64, 16, 64, 16, 32, bytes lost +#else // rng = 60:-4:32 (8 pools) 272, 288, 304, 336, 368, 400, 448, 496, -// 60, 56, 53, 48, 44, 40, 36, 33, /pool -// 64, 256, 272, 256, 192, 384, 256, 16, bytes lost +// 60, 56, 53, 48, 44, 40, 36, 33, /pool +// 64, 256, 272, 256, 192, 384, 256, 16, bytes lost // rng = 30:-2:16 (8 pools) 544, 576, 624, 672, 736, 816, 896, 1008, -// 30, 28, 26, 24, 22, 20, 18, 16, /pool -// 64, 256, 160, 256, 192, 64, 256, 256, bytes lost +// 30, 28, 26, 24, 22, 20, 18, 16, /pool +// 64, 256, 160, 256, 192, 64, 256, 256, bytes lost // rng = 15:-1:8 (8 pools) 1088, 1168, 1248, 1360, 1488, 1632, 1808, 2032 -// 15, 14, 13, 12, 11, 10, 9, 8, /pool -// 64, 32, 160, 64, 16, 64, 112, 128, bytes lost +// 15, 14, 13, 12, 11, 10, 9, 8, /pool +// 64, 32, 160, 64, 16, 64, 112, 128, bytes lost +#endif }; +#ifdef GC_SMALL_PAGE +#ifdef _P64 +# define JL_GC_N_POOLS 39 +#elif MAX_ALIGN == 8 +# define JL_GC_N_POOLS 40 +#else +# define JL_GC_N_POOLS 41 +#endif +#else +#ifdef _P64 +# define JL_GC_N_POOLS 49 +#elif MAX_ALIGN == 8 +# define JL_GC_N_POOLS 50 +#else +# define JL_GC_N_POOLS 51 +#endif +#endif static_assert(sizeof(jl_gc_sizeclasses) / sizeof(jl_gc_sizeclasses[0]) == JL_GC_N_POOLS, ""); STATIC_INLINE int jl_gc_alignment(size_t sz) JL_NOTSAFEPOINT @@ -420,7 +444,12 @@ JL_DLLEXPORT int jl_alignment(size_t sz) JL_NOTSAFEPOINT; // the following table is computed as: // [searchsortedfirst(jl_gc_sizeclasses, i) - 1 for i = 0:16:jl_gc_sizeclasses[end]] -static const uint8_t szclass_table[] = {0, 1, 3, 5, 7, 9, 11, 13, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 28, 29, 29, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33, 33, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 38, 39, 39, 39, 39, 39, 40, 40, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 42, 42, 42, 42, 42, 43, 43, 43, 43, 43, 44, 44, 44, 44, 44, 44, 44, 45, 45, 45, 45, 45, 45, 45, 45, 46, 46, 46, 46, 46, 46, 46, 46, 46, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48}; +static const uint8_t szclass_table[] = +#ifdef GC_SMALL_PAGE + {0,1,3,5,7,9,11,13,15,17,18,19,20,21,22,23,24,25,26,27,28,28,29,29,30,30,31,31,31,32,32,32,33,33,33,33,33,34,34,34,34,34,34,35,35,35,35,35,35,35,35,35,36,36,36,36,36,36,36,36,36,36,36,36,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38}; +#else + {0,1,3,5,7,9,11,13,15,17,18,19,20,21,22,23,24,25,26,27,28,28,29,29,30,30,31,31,31,32,32,32,33,33,33,34,34,35,35,35,36,36,36,37,37,37,37,38,38,38,38,38,39,39,39,39,39,40,40,40,40,40,40,40,41,41,41,41,41,42,42,42,42,42,43,43,43,43,43,44,44,44,44,44,44,44,45,45,45,45,45,45,45,45,46,46,46,46,46,46,46,46,46,47,47,47,47,47,47,47,47,47,47,47,48,48,48,48,48,48,48,48,48,48,48,48,48,48}; +#endif static_assert(sizeof(szclass_table) == 128, ""); STATIC_INLINE uint8_t JL_CONST_FUNC jl_gc_szclass(unsigned sz) JL_NOTSAFEPOINT @@ -459,7 +488,6 @@ STATIC_INLINE uint8_t JL_CONST_FUNC jl_gc_szclass_align8(unsigned sz) JL_NOTSAFE } #define JL_SMALL_BYTE_ALIGNMENT 16 -#define JL_CACHE_BYTE_ALIGNMENT 64 // JL_HEAP_ALIGNMENT is the maximum alignment that the GC can provide #define JL_HEAP_ALIGNMENT JL_SMALL_BYTE_ALIGNMENT #define GC_MAX_SZCLASS (2032-sizeof(void*)) @@ -571,27 +599,12 @@ JL_DLLEXPORT void JL_NORETURN jl_throw_out_of_memory_error(void); JL_DLLEXPORT int64_t jl_gc_diff_total_bytes(void) JL_NOTSAFEPOINT; JL_DLLEXPORT int64_t jl_gc_sync_total_bytes(int64_t offset) JL_NOTSAFEPOINT; void jl_gc_track_malloced_array(jl_ptls_t ptls, jl_array_t *a) JL_NOTSAFEPOINT; +void jl_gc_track_malloced_genericmemory(jl_ptls_t ptls, jl_genericmemory_t *m, int isaligned) JL_NOTSAFEPOINT; void jl_gc_count_allocd(size_t sz) JL_NOTSAFEPOINT; void jl_gc_run_all_finalizers(jl_task_t *ct); void jl_release_task_stack(jl_ptls_t ptls, jl_task_t *task); void jl_gc_add_finalizer_(jl_ptls_t ptls, void *v, void *f) JL_NOTSAFEPOINT; -void gc_setmark_buf(jl_ptls_t ptls, void *buf, uint8_t, size_t) JL_NOTSAFEPOINT; - -STATIC_INLINE void jl_gc_wb_binding(jl_binding_t *bnd, void *val) JL_NOTSAFEPOINT // val isa jl_value_t* -{ - jl_gc_wb(bnd, val); -} - -STATIC_INLINE void jl_gc_wb_buf(void *parent, void *bufptr, size_t minsz) JL_NOTSAFEPOINT // parent isa jl_value_t* -{ - // if parent is marked and buf is not - if (__unlikely(jl_astaggedvalue(parent)->bits.gc & 1)) { - jl_task_t *ct = jl_current_task; - gc_setmark_buf(ct->ptls, bufptr, 3, minsz); - } -} - void jl_gc_debug_print_status(void) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_gc_debug_critical_error(void) JL_NOTSAFEPOINT; void jl_print_gc_stats(JL_STREAM *s); @@ -615,7 +628,6 @@ STATIC_INLINE jl_value_t *undefref_check(jl_datatype_t *dt, jl_value_t *v) JL_NO // -- helper types -- // typedef struct { - uint8_t inferred:1; uint8_t propagate_inbounds:1; uint8_t has_fcall:1; uint8_t nospecializeinfer:1; @@ -630,14 +642,32 @@ typedef union { // -- functions -- // -JL_DLLEXPORT jl_code_info_t *jl_type_infer(jl_method_instance_t *li, size_t world, int force); +// Also defined in typeinfer.jl - See documentation there. +#define SOURCE_MODE_NOT_REQUIRED 0x0 +#define SOURCE_MODE_ABI 0x1 +#define SOURCE_MODE_FORCE_SOURCE 0x2 +#define SOURCE_MODE_FORCE_SOURCE_UNCACHED 0x3 + +JL_DLLEXPORT jl_code_instance_t *jl_type_infer(jl_method_instance_t *li, size_t world, int force, uint8_t source_mode); JL_DLLEXPORT jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *meth JL_PROPAGATES_ROOT, size_t world); JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred( jl_method_instance_t *mi JL_PROPAGATES_ROOT, jl_value_t *rettype, - size_t min_world, size_t max_world); -jl_method_instance_t *jl_get_unspecialized_from_mi(jl_method_instance_t *method JL_PROPAGATES_ROOT); + size_t min_world, size_t max_world, jl_debuginfo_t *edges); jl_method_instance_t *jl_get_unspecialized(jl_method_t *def JL_PROPAGATES_ROOT); +JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( + jl_method_instance_t *mi, jl_value_t *owner, + jl_value_t *rettype, jl_value_t *exctype, + jl_value_t *inferred_const, jl_value_t *inferred, + int32_t const_flags, size_t min_world, size_t max_world, + uint32_t ipo_effects, uint32_t effects, jl_value_t *analysis_results, + uint8_t relocatability, jl_debuginfo_t *edges /* , int absolute_max*/); + +JL_DLLEXPORT const char *jl_debuginfo_file(jl_debuginfo_t *debuginfo) JL_NOTSAFEPOINT; +JL_DLLEXPORT const char *jl_debuginfo_file1(jl_debuginfo_t *debuginfo) JL_NOTSAFEPOINT; +JL_DLLEXPORT jl_module_t *jl_debuginfo_module1(jl_value_t *debuginfo_def) JL_NOTSAFEPOINT; +JL_DLLEXPORT const char *jl_debuginfo_name(jl_value_t *func) JL_NOTSAFEPOINT; + JL_DLLEXPORT void jl_compile_method_instance(jl_method_instance_t *mi, jl_tupletype_t *types, size_t world); JL_DLLEXPORT int jl_compile_hint(jl_tupletype_t *types); jl_code_info_t *jl_code_for_interpreter(jl_method_instance_t *lam JL_PROPAGATES_ROOT, size_t world); @@ -676,7 +706,7 @@ void jl_install_default_signal_handlers(void); void restore_signals(void); void jl_install_thread_signal_handler(jl_ptls_t ptls); -JL_DLLEXPORT jl_fptr_args_t jl_get_builtin_fptr(jl_value_t *b); +JL_DLLEXPORT jl_fptr_args_t jl_get_builtin_fptr(jl_datatype_t *dt); extern uv_loop_t *jl_io_loop; JL_DLLEXPORT void jl_uv_flush(uv_stream_t *stream); @@ -690,14 +720,23 @@ typedef struct jl_typeenv_t { int jl_tuple_isa(jl_value_t **child, size_t cl, jl_datatype_t *pdt); int jl_tuple1_isa(jl_value_t *child1, jl_value_t **child, size_t cl, jl_datatype_t *pdt); +enum atomic_kind { + isatomic_none = 0, + isatomic_object = 1, + isatomic_field = 2 +}; + JL_DLLEXPORT int jl_has_intersect_type_not_kind(jl_value_t *t); int jl_subtype_invariant(jl_value_t *a, jl_value_t *b, int ta); int jl_has_concrete_subtype(jl_value_t *typ); jl_tupletype_t *jl_inst_arg_tuple_type(jl_value_t *arg1, jl_value_t **args, size_t nargs, int leaf); jl_tupletype_t *jl_lookup_arg_tuple_type(jl_value_t *arg1 JL_PROPAGATES_ROOT, jl_value_t **args, size_t nargs, int leaf); JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method, jl_tupletype_t *simpletype); +void jl_method_table_activate(jl_methtable_t *mt, jl_typemap_entry_t *newentry); +jl_typemap_entry_t *jl_method_table_add(jl_methtable_t *mt, jl_method_t *method, jl_tupletype_t *simpletype); jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_args_t fptr) JL_GC_DISABLED; int jl_obviously_unequal(jl_value_t *a, jl_value_t *b); +int jl_has_bound_typevars(jl_value_t *v, jl_typeenv_t *env) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_array_t *jl_find_free_typevars(jl_value_t *v); int jl_has_fixed_layout(jl_datatype_t *t); JL_DLLEXPORT int jl_struct_try_layout(jl_datatype_t *dt); @@ -706,7 +745,7 @@ jl_svec_t *jl_outer_unionall_vars(jl_value_t *u); jl_value_t *jl_type_intersection_env_s(jl_value_t *a, jl_value_t *b, jl_svec_t **penv, int *issubty); jl_value_t *jl_type_intersection_env(jl_value_t *a, jl_value_t *b, jl_svec_t **penv); int jl_subtype_matching(jl_value_t *a, jl_value_t *b, jl_svec_t **penv); -JL_DLLEXPORT int jl_types_egal(jl_value_t *a, jl_value_t *b); +JL_DLLEXPORT int jl_types_egal(jl_value_t *a, jl_value_t *b) JL_NOTSAFEPOINT; // specificity comparison assuming !(a <: b) and !(b <: a) JL_DLLEXPORT int jl_type_morespecific_no_subtype(jl_value_t *a, jl_value_t *b); jl_value_t *jl_instantiate_type_with(jl_value_t *t, jl_value_t **env, size_t n); @@ -733,6 +772,13 @@ void set_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *rhs, jl_value_t *swap_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *rhs, int isatomic); jl_value_t *modify_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *op, jl_value_t *rhs, int isatomic); jl_value_t *replace_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *expected, jl_value_t *rhs, int isatomic); +int set_nth_fieldonce(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *rhs, int isatomic); +jl_value_t *swap_bits(jl_value_t *ty, char *v, uint8_t *psel, jl_value_t *parent, jl_value_t *rhs, enum atomic_kind isatomic); +jl_value_t *replace_value(jl_value_t *ty, _Atomic(jl_value_t*) *p, jl_value_t *parent, jl_value_t *expected, jl_value_t *rhs, int isatomic, jl_module_t *mod, jl_sym_t *name); +jl_value_t *replace_bits(jl_value_t *ty, char *p, uint8_t *psel, jl_value_t *parent, jl_value_t *expected, jl_value_t *rhs, enum atomic_kind isatomic); +jl_value_t *modify_value(jl_value_t *ty, _Atomic(jl_value_t*) *p, jl_value_t *parent, jl_value_t *op, jl_value_t *rhs, int isatomic, jl_module_t *mod, jl_sym_t *name); +jl_value_t *modify_bits(jl_value_t *ty, char *p, uint8_t *psel, jl_value_t *parent, jl_value_t *op, jl_value_t *rhs, enum atomic_kind isatomic); +int setonce_bits(jl_datatype_t *rty, char *p, jl_value_t *owner, jl_value_t *rhs, enum atomic_kind isatomic); jl_expr_t *jl_exprn(jl_sym_t *head, size_t n); jl_function_t *jl_new_generic_function(jl_sym_t *name, jl_module_t *module); jl_function_t *jl_new_generic_function_with_supertype(jl_sym_t *name, jl_module_t *module, jl_datatype_t *st); @@ -745,7 +791,7 @@ JL_DLLEXPORT int jl_datatype_isinlinealloc(jl_datatype_t *ty, int pointerfree); int jl_type_equality_is_identity(jl_value_t *t1, jl_value_t *t2) JL_NOTSAFEPOINT; void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type); -jl_value_t *jl_toplevel_eval_flex(jl_module_t *m, jl_value_t *e, int fast, int expanded); +JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *m, jl_value_t *e, int fast, int expanded, const char **toplevel_filename, int *toplevel_lineno); jl_value_t *jl_eval_global_var(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *e); jl_value_t *jl_interpret_opaque_closure(jl_opaque_closure_t *clos, jl_value_t **args, size_t nargs); @@ -757,7 +803,8 @@ JL_DLLEXPORT int jl_is_toplevel_only_expr(jl_value_t *e) JL_NOTSAFEPOINT; jl_value_t *jl_call_scm_on_ast_and_loc(const char *funcname, jl_value_t *expr, jl_module_t *inmodule, const char *file, int line); -jl_method_instance_t *jl_method_lookup(jl_value_t **args, size_t nargs, size_t world); +JL_DLLEXPORT jl_value_t *jl_method_lookup_by_tt(jl_tupletype_t *tt, size_t world, jl_value_t *_mt); +JL_DLLEXPORT jl_method_instance_t *jl_method_lookup(jl_value_t **args, size_t nargs, size_t world); jl_value_t *jl_gf_invoke_by_method(jl_method_t *method, jl_value_t *gf, jl_value_t **args, size_t nargs); jl_value_t *jl_gf_invoke(jl_value_t *types, jl_value_t *f, jl_value_t **args, size_t nargs); @@ -779,22 +826,31 @@ JL_DLLEXPORT jl_methtable_t *jl_method_get_table( JL_DLLEXPORT int jl_pointer_egal(jl_value_t *t); JL_DLLEXPORT jl_value_t *jl_nth_slot_type(jl_value_t *sig JL_PROPAGATES_ROOT, size_t i) JL_NOTSAFEPOINT; void jl_compute_field_offsets(jl_datatype_t *st); -jl_array_t *jl_new_array_for_deserialization(jl_value_t *atype, uint32_t ndims, size_t *dims, - int isunboxed, int hasptr, int isunion, int elsz); void jl_module_run_initializer(jl_module_t *m); JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc); JL_DLLEXPORT void jl_binding_deprecation_warning(jl_module_t *m, jl_sym_t *sym, jl_binding_t *b); extern jl_array_t *jl_module_init_order JL_GLOBALLY_ROOTED; extern htable_t jl_current_modules JL_GLOBALLY_ROOTED; extern JL_DLLEXPORT jl_module_t *jl_precompile_toplevel_module JL_GLOBALLY_ROOTED; -extern jl_array_t *jl_global_roots_table JL_GLOBALLY_ROOTED; +extern jl_genericmemory_t *jl_global_roots_list JL_GLOBALLY_ROOTED; +extern jl_genericmemory_t *jl_global_roots_keyset JL_GLOBALLY_ROOTED; JL_DLLEXPORT int jl_is_globally_rooted(jl_value_t *val JL_MAYBE_UNROOTED) JL_NOTSAFEPOINT; -JL_DLLEXPORT jl_value_t *jl_as_global_root(jl_value_t *val JL_MAYBE_UNROOTED); +JL_DLLEXPORT jl_value_t *jl_as_global_root(jl_value_t *val, int insert) JL_GLOBALLY_ROOTED; jl_opaque_closure_t *jl_new_opaque_closure(jl_tupletype_t *argt, jl_value_t *rt_lb, jl_value_t *rt_ub, jl_value_t *source, jl_value_t **env, size_t nenv, int do_compile); +jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name, + int nargs, jl_value_t *functionloc, jl_code_info_t *ci, int isva, int isinferred); JL_DLLEXPORT int jl_is_valid_oc_argtype(jl_tupletype_t *argt, jl_method_t *source); +STATIC_INLINE int is_anonfn_typename(char *name) +{ + if (name[0] != '#' || name[1] == '#') + return 0; + char *other = strrchr(name, '#'); + return other > &name[1] && other[1] > '0' && other[1] <= '9'; +} + // Each tuple can exist in one of 4 Vararg states: // NONE: no vararg Tuple{Int,Float32} // INT: vararg with integer length Tuple{Int,Vararg{Float32,2}} @@ -947,17 +1003,7 @@ JL_DLLEXPORT void jl_pgcstack_getkey(jl_get_pgcstack_func **f, jl_pgcstack_key_t extern pthread_mutex_t in_signal_lock; #endif -static inline void jl_set_gc_and_wait(void) // n.b. not used on _OS_DARWIN_ -{ - jl_task_t *ct = jl_current_task; - // reading own gc state doesn't need atomic ops since no one else - // should store to it. - int8_t state = jl_atomic_load_relaxed(&ct->ptls->gc_state); - jl_atomic_store_release(&ct->ptls->gc_state, JL_GC_STATE_WAITING); - jl_safepoint_wait_gc(); - jl_atomic_store_release(&ct->ptls->gc_state, state); - jl_safepoint_wait_thread_resume(); // block in thread-suspend now if requested, after clearing the gc_state -} +void jl_set_gc_and_wait(void); // n.b. not used on _OS_DARWIN_ // Query if a Julia object is if a permalloc region (due to part of a sys- pkg-image) STATIC_INLINE size_t n_linkage_blobs(void) JL_NOTSAFEPOINT @@ -967,20 +1013,23 @@ STATIC_INLINE size_t n_linkage_blobs(void) JL_NOTSAFEPOINT size_t external_blob_index(jl_value_t *v) JL_NOTSAFEPOINT; -uint8_t jl_object_in_image(jl_value_t* v) JL_NOTSAFEPOINT; +// Query if this object is perm-allocated in an image. +JL_DLLEXPORT uint8_t jl_object_in_image(jl_value_t* v) JL_NOTSAFEPOINT; // the first argument to jl_idtable_rehash is used to return a value // make sure it is rooted if it is used after the function returns -JL_DLLEXPORT jl_array_t *jl_idtable_rehash(jl_array_t *a, size_t newsz); -_Atomic(jl_value_t*) *jl_table_peek_bp(jl_array_t *a, jl_value_t *key) JL_NOTSAFEPOINT; +JL_DLLEXPORT jl_genericmemory_t *jl_idtable_rehash(jl_genericmemory_t *a, size_t newsz); +_Atomic(jl_value_t*) *jl_table_peek_bp(jl_genericmemory_t *a, jl_value_t *key) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_method_t *jl_new_method_uninit(jl_module_t*); JL_DLLEXPORT jl_methtable_t *jl_new_method_table(jl_sym_t *name, jl_module_t *module); JL_DLLEXPORT jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types, size_t world, size_t *min_valid, size_t *max_valid, int mt_cache); jl_method_instance_t *jl_get_specialized(jl_method_t *m, jl_value_t *types, jl_svec_t *sp); -JL_DLLEXPORT jl_value_t *jl_rettype_inferred(jl_method_instance_t *li JL_PROPAGATES_ROOT, size_t min_world, size_t max_world); +JL_DLLEXPORT jl_value_t *jl_rettype_inferred(jl_value_t *owner, jl_method_instance_t *li JL_PROPAGATES_ROOT, size_t min_world, size_t max_world); +JL_DLLEXPORT jl_value_t *jl_rettype_inferred_native(jl_method_instance_t *mi, size_t min_world, size_t max_world) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_code_instance_t *jl_method_compiled(jl_method_instance_t *mi JL_PROPAGATES_ROOT, size_t world) JL_NOTSAFEPOINT; +JL_DLLEXPORT jl_code_instance_t *jl_method_inferred_with_abi(jl_method_instance_t *mi JL_PROPAGATES_ROOT, size_t world) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_methtable_lookup(jl_methtable_t *mt JL_PROPAGATES_ROOT, jl_value_t *type, size_t world); JL_DLLEXPORT jl_method_instance_t *jl_specializations_get_linfo( jl_method_t *m JL_PROPAGATES_ROOT, jl_value_t *type, jl_svec_t *sparams); @@ -989,6 +1038,7 @@ JL_DLLEXPORT void jl_method_instance_add_backedge(jl_method_instance_t *callee, JL_DLLEXPORT void jl_method_table_add_backedge(jl_methtable_t *mt, jl_value_t *typ, jl_value_t *caller); JL_DLLEXPORT void jl_mi_cache_insert(jl_method_instance_t *mi JL_ROOTING_ARGUMENT, jl_code_instance_t *ci JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED); +JL_DLLEXPORT int jl_mi_cache_has_ci(jl_method_instance_t *mi, jl_code_instance_t *ci) JL_NOTSAFEPOINT; JL_DLLEXPORT extern jl_value_t *(*const jl_rettype_inferred_addr)(jl_method_instance_t *mi JL_PROPAGATES_ROOT, size_t min_world, size_t max_world) JL_NOTSAFEPOINT; uint32_t jl_module_next_counter(jl_module_t *m) JL_NOTSAFEPOINT; @@ -1224,7 +1274,7 @@ STATIC_INLINE size_t jl_excstack_next(jl_excstack_t *stack, size_t itr) JL_NOTSA return itr-2 - jl_excstack_bt_size(stack, itr); } // Exception stack manipulation -void jl_push_excstack(jl_task_t* task, jl_excstack_t **stack JL_REQUIRE_ROOTED_SLOT JL_ROOTING_ARGUMENT, +void jl_push_excstack(jl_task_t *ct, jl_excstack_t **stack JL_REQUIRE_ROOTED_SLOT JL_ROOTING_ARGUMENT, jl_value_t *exception JL_ROOTED_ARGUMENT, jl_bt_element_t *bt_data, size_t bt_size); @@ -1290,7 +1340,7 @@ JL_DLLEXPORT unsigned jl_intrinsic_nargs(int f) JL_NOTSAFEPOINT; STATIC_INLINE int is_valid_intrinsic_elptr(jl_value_t *ety) { - return ety == (jl_value_t*)jl_any_type || (jl_is_concrete_type(ety) && !jl_is_layout_opaque(((jl_datatype_t*)ety)->layout)); + return ety == (jl_value_t*)jl_any_type || (jl_is_concrete_type(ety) && !jl_is_layout_opaque(((jl_datatype_t*)ety)->layout) && !jl_is_array_type(ety)); } JL_DLLEXPORT jl_value_t *jl_bitcast(jl_value_t *ty, jl_value_t *v); JL_DLLEXPORT jl_value_t *jl_pointerref(jl_value_t *p, jl_value_t *i, jl_value_t *align); @@ -1381,14 +1431,12 @@ JL_DLLEXPORT jl_value_t *jl_abs_float(jl_value_t *a); JL_DLLEXPORT jl_value_t *jl_copysign_float(jl_value_t *a, jl_value_t *b); JL_DLLEXPORT jl_value_t *jl_flipsign_int(jl_value_t *a, jl_value_t *b); -JL_DLLEXPORT jl_value_t *jl_arraylen(jl_value_t *a); JL_DLLEXPORT jl_value_t *jl_have_fma(jl_value_t *a); JL_DLLEXPORT int jl_stored_inline(jl_value_t *el_type); JL_DLLEXPORT jl_value_t *(jl_array_data_owner)(jl_array_t *a); -JL_DLLEXPORT int jl_array_isassigned(jl_array_t *a, size_t i); JL_DLLEXPORT jl_array_t *jl_array_copy(jl_array_t *ary); -JL_DLLEXPORT uintptr_t jl_object_id_(jl_value_t *tv, jl_value_t *v) JL_NOTSAFEPOINT; +JL_DLLEXPORT uintptr_t jl_object_id_(uintptr_t tv, jl_value_t *v) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_set_next_task(jl_task_t *task) JL_NOTSAFEPOINT; // -- synchronization utilities -- // @@ -1403,10 +1451,19 @@ void jl_safepoint_resume_thread_mach(jl_ptls_t ptls2, int16_t tid2) JL_NOTSAFEPO // -- smallintset.c -- // -typedef uint_t (*smallintset_hash)(size_t val, jl_svec_t *data); -typedef int (*smallintset_eq)(size_t val, const void *key, jl_svec_t *data, uint_t hv); -ssize_t jl_smallintset_lookup(jl_array_t *cache, smallintset_eq eq, const void *key, jl_svec_t *data, uint_t hv); -void jl_smallintset_insert(_Atomic(jl_array_t*) *pcache, jl_value_t *parent, smallintset_hash hash, size_t val, jl_svec_t *data); +typedef uint_t (*smallintset_hash)(size_t val, jl_value_t *data); +typedef int (*smallintset_eq)(size_t val, const void *key, jl_value_t *data, uint_t hv); +ssize_t jl_smallintset_lookup(jl_genericmemory_t *cache, smallintset_eq eq, const void *key, jl_value_t *data, uint_t hv, int pop); +void jl_smallintset_insert(_Atomic(jl_genericmemory_t*) *pcache, jl_value_t *parent, smallintset_hash hash, size_t val, jl_value_t *data); +jl_genericmemory_t* smallintset_rehash(jl_genericmemory_t* a, smallintset_hash hash, jl_value_t *data, size_t newsz, size_t np); +void smallintset_empty(const jl_genericmemory_t *a) JL_NOTSAFEPOINT; + +JL_DLLEXPORT jl_genericmemory_t *jl_idset_rehash(jl_genericmemory_t *keys, jl_genericmemory_t *idxs, size_t newsz); +JL_DLLEXPORT ssize_t jl_idset_peek_bp(jl_genericmemory_t *keys, jl_genericmemory_t *idxs, jl_value_t *key) JL_NOTSAFEPOINT; +jl_value_t *jl_idset_get(jl_genericmemory_t *keys JL_PROPAGATES_ROOT, jl_genericmemory_t *idxs, jl_value_t *key) JL_NOTSAFEPOINT; +JL_DLLEXPORT jl_genericmemory_t *jl_idset_put_key(jl_genericmemory_t *keys, jl_value_t *key, ssize_t *newidx); +JL_DLLEXPORT jl_genericmemory_t *jl_idset_put_idx(jl_genericmemory_t *keys, jl_genericmemory_t *idxs, ssize_t idx); +JL_DLLEXPORT ssize_t jl_idset_pop(jl_genericmemory_t *keys, jl_genericmemory_t *idxs, jl_value_t *key) JL_NOTSAFEPOINT; // -- typemap.c -- // @@ -1422,8 +1479,6 @@ struct jl_typemap_assoc { size_t const world; // outputs jl_svec_t *env; // subtype env (initialize to null to perform intersection without an environment) - size_t min_valid; - size_t max_valid; }; jl_typemap_entry_t *jl_typemap_assoc_by_type( @@ -1471,10 +1526,6 @@ void typemap_slurp_search(jl_typemap_entry_t *ml, struct typemap_intersection_en // -- simplevector.c -- // -// For codegen only. -JL_DLLEXPORT size_t (jl_svec_len)(jl_svec_t *t) JL_NOTSAFEPOINT; -JL_DLLEXPORT jl_value_t *jl_svec_ref(jl_svec_t *t JL_PROPAGATES_ROOT, ssize_t i); - // check whether the specified number of arguments is compatible with the // specified number of parameters of the tuple type JL_DLLEXPORT int jl_tupletype_length_compat(jl_value_t *v, size_t nargs) JL_NOTSAFEPOINT; @@ -1550,6 +1601,7 @@ extern JL_DLLEXPORT jl_sym_t *jl_thunk_sym; extern JL_DLLEXPORT jl_sym_t *jl_foreigncall_sym; extern JL_DLLEXPORT jl_sym_t *jl_as_sym; extern JL_DLLEXPORT jl_sym_t *jl_global_sym; +extern JL_DLLEXPORT jl_sym_t *jl_local_sym; extern JL_DLLEXPORT jl_sym_t *jl_list_sym; extern JL_DLLEXPORT jl_sym_t *jl_dot_sym; extern JL_DLLEXPORT jl_sym_t *jl_newvar_sym; @@ -1588,6 +1640,8 @@ extern JL_DLLEXPORT jl_sym_t *jl_aliasscope_sym; extern JL_DLLEXPORT jl_sym_t *jl_popaliasscope_sym; extern JL_DLLEXPORT jl_sym_t *jl_optlevel_sym; extern JL_DLLEXPORT jl_sym_t *jl_thismodule_sym; +extern JL_DLLEXPORT jl_sym_t *jl_eval_sym; +extern JL_DLLEXPORT jl_sym_t *jl_include_sym; extern JL_DLLEXPORT jl_sym_t *jl_atom_sym; extern JL_DLLEXPORT jl_sym_t *jl_statement_sym; extern JL_DLLEXPORT jl_sym_t *jl_all_sym; @@ -1609,7 +1663,7 @@ JL_DLLEXPORT enum jl_memory_order jl_get_atomic_order_checked(jl_sym_t *order, c struct _jl_image_fptrs_t; -void jl_write_coverage_data(const char*); +JL_DLLEXPORT void jl_write_coverage_data(const char*); void jl_write_malloc_log(void); #if jl_has_builtin(__builtin_unreachable) || defined(_COMPILER_GCC_) || defined(_COMPILER_INTEL_) @@ -1661,20 +1715,12 @@ jl_sym_t *_jl_symbol(const char *str, size_t len) JL_NOTSAFEPOINT; #define JL_WEAK_SYMBOL_DEFAULT(sym) NULL #endif -JL_DLLEXPORT float julia__gnu_h2f_ieee(uint16_t param) JL_NOTSAFEPOINT; -JL_DLLEXPORT uint16_t julia__gnu_f2h_ieee(float param) JL_NOTSAFEPOINT; -JL_DLLEXPORT uint16_t julia__truncdfhf2(double param) JL_NOTSAFEPOINT; -JL_DLLEXPORT float julia__truncsfbf2(float param) JL_NOTSAFEPOINT; -JL_DLLEXPORT float julia__truncdfbf2(double param) JL_NOTSAFEPOINT; -//JL_DLLEXPORT double julia__extendhfdf2(uint16_t n) JL_NOTSAFEPOINT; -//JL_DLLEXPORT int32_t julia__fixhfsi(uint16_t n) JL_NOTSAFEPOINT; -//JL_DLLEXPORT int64_t julia__fixhfdi(uint16_t n) JL_NOTSAFEPOINT; -//JL_DLLEXPORT uint32_t julia__fixunshfsi(uint16_t n) JL_NOTSAFEPOINT; -//JL_DLLEXPORT uint64_t julia__fixunshfdi(uint16_t n) JL_NOTSAFEPOINT; -//JL_DLLEXPORT uint16_t julia__floatsihf(int32_t n) JL_NOTSAFEPOINT; -//JL_DLLEXPORT uint16_t julia__floatdihf(int64_t n) JL_NOTSAFEPOINT; -//JL_DLLEXPORT uint16_t julia__floatunsihf(uint32_t n) JL_NOTSAFEPOINT; -//JL_DLLEXPORT uint16_t julia__floatundihf(uint64_t n) JL_NOTSAFEPOINT; +//JL_DLLEXPORT float julia__gnu_h2f_ieee(half param) JL_NOTSAFEPOINT; +//JL_DLLEXPORT half julia__gnu_f2h_ieee(float param) JL_NOTSAFEPOINT; +//JL_DLLEXPORT half julia__truncdfhf2(double param) JL_NOTSAFEPOINT; +//JL_DLLEXPORT float julia__truncsfbf2(float param) JL_NOTSAFEPOINT; +//JL_DLLEXPORT float julia__truncdfbf2(double param) JL_NOTSAFEPOINT; +//JL_DLLEXPORT double julia__extendhfdf2(half n) JL_NOTSAFEPOINT; JL_DLLEXPORT uint32_t jl_crc32c(uint32_t crc, const char *buf, size_t len); @@ -1682,9 +1728,8 @@ JL_DLLEXPORT uint32_t jl_crc32c(uint32_t crc, const char *buf, size_t len); #define IR_FLAG_INBOUNDS 0x01 -JL_DLLIMPORT jl_code_instance_t *jl_generate_fptr(jl_method_instance_t *mi JL_PROPAGATES_ROOT, size_t world); JL_DLLIMPORT void jl_generate_fptr_for_unspecialized(jl_code_instance_t *unspec); -JL_DLLIMPORT void jl_generate_fptr_for_oc_wrapper(jl_code_instance_t *unspec); +JL_DLLIMPORT int jl_compile_codeinst(jl_code_instance_t *unspec); JL_DLLIMPORT int jl_compile_extern_c(LLVMOrcThreadSafeModuleRef llvmmod, void *params, void *sysimg, jl_value_t *declrt, jl_value_t *sigt); typedef struct { diff --git a/src/julia_locks.h b/src/julia_locks.h index 47e258f69aab2..5774ddada60c6 100644 --- a/src/julia_locks.h +++ b/src/julia_locks.h @@ -96,6 +96,11 @@ static inline void jl_mutex_init(jl_mutex_t *lock, const char *name) JL_NOTSAFEP #define JL_LOCK_NOGC(m) jl_mutex_lock_nogc(m) #define JL_UNLOCK_NOGC(m) jl_mutex_unlock_nogc(m) +JL_DLLEXPORT void jl_lock_value(jl_mutex_t *v) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_unlock_value(jl_mutex_t *v) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_lock_field(jl_mutex_t *v) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_unlock_field(jl_mutex_t *v) JL_NOTSAFEPOINT; + #ifdef __cplusplus } #endif diff --git a/src/julia_threads.h b/src/julia_threads.h index b8276682ee359..3a4e9a66cf5a7 100644 --- a/src/julia_threads.h +++ b/src/julia_threads.h @@ -4,8 +4,8 @@ #ifndef JL_THREADS_H #define JL_THREADS_H -#include "work-stealing-queue.h" #include "julia_atomics.h" +#include "work-stealing-queue.h" #ifndef _OS_WINDOWS_ #include "pthread.h" #endif @@ -30,7 +30,6 @@ JL_DLLEXPORT int8_t jl_threadpoolid(int16_t tid) JL_NOTSAFEPOINT; // JL_HAVE_ASM -- mostly setjmp // JL_HAVE_ASM && JL_HAVE_UNW_CONTEXT -- libunwind-based // JL_HAVE_UNW_CONTEXT -- libunwind-based -// JL_HAVE_ASYNCIFY -- task switching based on the binary asyncify transform // JL_HAVE_UCONTEXT -- posix standard API, requires syscall for resume // JL_HAVE_SIGALTSTACK -- requires several syscall for start, setjmp for resume @@ -38,6 +37,16 @@ JL_DLLEXPORT int8_t jl_threadpoolid(int16_t tid) JL_NOTSAFEPOINT; #define JL_HAVE_UCONTEXT typedef win32_ucontext_t jl_stack_context_t; typedef jl_stack_context_t _jl_ucontext_t; + +#elif defined(_OS_OPENBSD_) +#define JL_HAVE_UNW_CONTEXT +#define UNW_LOCAL_ONLY +#include +typedef unw_context_t _jl_ucontext_t; +typedef struct { + jl_jmp_buf uc_mcontext; +} jl_stack_context_t; + #else typedef struct { jl_jmp_buf uc_mcontext; @@ -45,8 +54,7 @@ typedef struct { #if !defined(JL_HAVE_UCONTEXT) && \ !defined(JL_HAVE_ASM) && \ !defined(JL_HAVE_UNW_CONTEXT) && \ - !defined(JL_HAVE_SIGALTSTACK) && \ - !defined(JL_HAVE_ASYNCIFY) + !defined(JL_HAVE_SIGALTSTACK) #if (defined(_CPU_X86_64_) || defined(_CPU_X86_) || defined(_CPU_AARCH64_) || \ defined(_CPU_ARM_) || defined(_CPU_PPC64_)) #define JL_HAVE_ASM @@ -57,8 +65,6 @@ typedef struct { //#define JL_HAVE_UNW_CONTEXT //#elif defined(_OS_LINUX_) //#define JL_HAVE_UNW_CONTEXT -#elif defined(_OS_EMSCRIPTEN_) -#define JL_HAVE_ASYNCIFY #elif !defined(JL_HAVE_ASM) #define JL_HAVE_UNW_CONTEXT // optimistically? #endif @@ -67,19 +73,6 @@ typedef struct { #if (!defined(JL_HAVE_UNW_CONTEXT) && defined(JL_HAVE_ASM)) || defined(JL_HAVE_SIGALTSTACK) typedef jl_stack_context_t _jl_ucontext_t; #endif -#if defined(JL_HAVE_ASYNCIFY) -#if defined(_COMPILER_TSAN_ENABLED_) -#error TSAN not currently supported with asyncify -#endif -typedef struct { - // This is the extent of the asyncify stack, but because the top of the - // asyncify stack (stacktop) is also the bottom of the C stack, we can - // reuse stacktop for both. N.B.: This matches the layout of the - // __asyncify_data struct. - void *stackbottom; - void *stacktop; -} _jl_ucontext_t; -#endif #pragma GCC visibility push(default) #if defined(JL_HAVE_UNW_CONTEXT) #define UNW_LOCAL_ONLY @@ -130,6 +123,7 @@ typedef struct { typedef struct { _Atomic(int64_t) allocd; + _Atomic(int64_t) pool_live_bytes; _Atomic(uint64_t) malloc; _Atomic(uint64_t) realloc; _Atomic(uint64_t) poolalloc; @@ -160,14 +154,8 @@ typedef struct { arraylist_t *last_remset; // variables for allocating objects from pools -#ifdef _P64 -# define JL_GC_N_POOLS 49 -#elif MAX_ALIGN == 8 -# define JL_GC_N_POOLS 50 -#else -# define JL_GC_N_POOLS 51 -#endif - jl_gc_pool_t norm_pools[JL_GC_N_POOLS]; +#define JL_GC_N_MAX_POOLS 51 // conservative. must be kept in sync with `src/julia_internal.h` + jl_gc_pool_t norm_pools[JL_GC_N_MAX_POOLS]; #define JL_N_STACK_POOLS 16 small_arraylist_t free_stacks[JL_N_STACK_POOLS]; @@ -214,6 +202,9 @@ typedef struct _jl_tls_states_t { _Atomic(volatile size_t *) safepoint; // may be changed to the suspend page by any thread _Atomic(int8_t) sleep_check_state; // read/write from foreign threads // Whether it is safe to execute GC at the same time. +#define JL_GC_STATE_UNSAFE 0 + // gc_state = 0 means the thread is running Julia code and is not + // safe to run concurrently to the GC #define JL_GC_STATE_WAITING 1 // gc_state = 1 means the thread is doing GC or is waiting for the GC to // finish. @@ -262,6 +253,7 @@ typedef struct _jl_tls_states_t { int needs_resetstkoflw; #else void *signal_stack; + size_t signal_stack_size; #endif jl_thread_t system_id; _Atomic(int16_t) suspend_count; @@ -351,9 +343,7 @@ STATIC_INLINE int8_t jl_gc_state_set(jl_ptls_t ptls, int8_t state, int8_t old_state) { jl_atomic_store_release(&ptls->gc_state, state); - // A safe point is required if we transition from GC-safe region to - // non GC-safe region. - if (old_state && !state) + if (state == JL_GC_STATE_UNSAFE || old_state == JL_GC_STATE_UNSAFE) jl_gc_safepoint_(ptls); return old_state; } @@ -368,18 +358,18 @@ void jl_gc_unsafe_leave(jl_ptls_t ptls, int8_t state) JL_NOTSAFEPOINT JL_NOTSAFE int8_t jl_gc_safe_enter(jl_ptls_t ptls) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_ENTER; void jl_gc_safe_leave(jl_ptls_t ptls, int8_t state) JL_NOTSAFEPOINT_LEAVE; // this might not be a safepoint, but we have to assume it could be (statically) #else -#define jl_gc_unsafe_enter(ptls) jl_gc_state_save_and_set(ptls, 0) -#define jl_gc_unsafe_leave(ptls, state) ((void)jl_gc_state_set(ptls, (state), 0)) +#define jl_gc_unsafe_enter(ptls) jl_gc_state_save_and_set(ptls, JL_GC_STATE_UNSAFE) +#define jl_gc_unsafe_leave(ptls, state) ((void)jl_gc_state_set(ptls, (state), JL_GC_STATE_UNSAFE)) #define jl_gc_safe_enter(ptls) jl_gc_state_save_and_set(ptls, JL_GC_STATE_SAFE) #define jl_gc_safe_leave(ptls, state) ((void)jl_gc_state_set(ptls, (state), JL_GC_STATE_SAFE)) #endif JL_DLLEXPORT void jl_gc_enable_finalizers(struct _jl_task_t *ct, int on); -JL_DLLEXPORT void jl_gc_disable_finalizers_internal(void); +JL_DLLEXPORT void jl_gc_disable_finalizers_internal(void) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_gc_enable_finalizers_internal(void); JL_DLLEXPORT void jl_gc_run_pending_finalizers(struct _jl_task_t *ct); extern JL_DLLEXPORT _Atomic(int) jl_gc_have_pending_finalizers; -JL_DLLEXPORT int8_t jl_gc_is_in_finalizer(void); +JL_DLLEXPORT int8_t jl_gc_is_in_finalizer(void) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_wakeup_thread(int16_t tid); diff --git a/src/llvm-alloc-helpers.cpp b/src/llvm-alloc-helpers.cpp index d24c08b4b4930..953ecc1830142 100644 --- a/src/llvm-alloc-helpers.cpp +++ b/src/llvm-alloc-helpers.cpp @@ -125,6 +125,12 @@ JL_USED_FUNC void AllocUseInfo::dump(llvm::raw_ostream &OS) OS << "hastypeof: " << hastypeof << '\n'; OS << "refload: " << refload << '\n'; OS << "refstore: " << refstore << '\n'; + OS << "allockind:"; + if ((allockind & AllocFnKind::Uninitialized) != AllocFnKind::Unknown) + OS << " uninitialized"; + if ((allockind & AllocFnKind::Zeroed) != AllocFnKind::Unknown) + OS << " zeroed"; + OS << '\n'; OS << "Uses: " << uses.size() << '\n'; for (auto inst: uses) inst->print(OS); @@ -164,8 +170,11 @@ JL_USED_FUNC void AllocUseInfo::dump() #define REMARK(remark) #endif -void jl_alloc::runEscapeAnalysis(llvm::Instruction *I, EscapeAnalysisRequiredArgs required, EscapeAnalysisOptionalArgs options) { +void jl_alloc::runEscapeAnalysis(llvm::CallInst *I, EscapeAnalysisRequiredArgs required, EscapeAnalysisOptionalArgs options) { required.use_info.reset(); + Attribute allockind = I->getFnAttr(Attribute::AllocKind); + if (allockind.isValid()) + required.use_info.allockind = allockind.getAllocKind(); if (I->use_empty()) return; CheckInst::Frame cur{I, 0, I->use_begin(), I->use_end()}; @@ -325,7 +334,7 @@ void jl_alloc::runEscapeAnalysis(llvm::Instruction *I, EscapeAnalysisRequiredArg else { next_offset = apoffset.getLimitedValue(); if (next_offset > UINT32_MAX) { - LLVM_DEBUG(dbgs() << "GEP inst exceeeds 32-bit offset\n"); + LLVM_DEBUG(dbgs() << "GEP inst exceeds 32-bit offset\n"); next_offset = UINT32_MAX; } } diff --git a/src/llvm-alloc-helpers.h b/src/llvm-alloc-helpers.h index 3bd80704a0888..49c3b15332a56 100644 --- a/src/llvm-alloc-helpers.h +++ b/src/llvm-alloc-helpers.h @@ -87,6 +87,8 @@ namespace jl_alloc { bool returned:1; // The object is used in an error function bool haserror:1; + // For checking attributes of "uninitialized" or "zeroed" or unknown + llvm::AllocFnKind allockind; // The alloc has a Julia object reference not in an explicit field. bool has_unknown_objref:1; @@ -105,6 +107,7 @@ namespace jl_alloc { hasunknownmem = false; returned = false; haserror = false; + allockind = llvm::AllocFnKind::Unknown; has_unknown_objref = false; has_unknown_objrefaggr = false; uses.clear(); @@ -153,7 +156,7 @@ namespace jl_alloc { } }; - void runEscapeAnalysis(llvm::Instruction *I, EscapeAnalysisRequiredArgs required, EscapeAnalysisOptionalArgs options=EscapeAnalysisOptionalArgs()); + void runEscapeAnalysis(llvm::CallInst *I, EscapeAnalysisRequiredArgs required, EscapeAnalysisOptionalArgs options=EscapeAnalysisOptionalArgs()); } diff --git a/src/llvm-alloc-opt.cpp b/src/llvm-alloc-opt.cpp index 7ba78fd8b1e69..08a884304747e 100644 --- a/src/llvm-alloc-opt.cpp +++ b/src/llvm-alloc-opt.cpp @@ -79,6 +79,7 @@ static void removeGCPreserve(CallInst *call, Instruction *val) * * * load * * `pointer_from_objref` + * * `gc_loaded` * * Any real llvm intrinsics * * gc preserve intrinsics * * `ccall` gcroot array (`jl_roots` operand bundle) @@ -94,7 +95,6 @@ static void removeGCPreserve(CallInst *call, Instruction *val) * TODO: * * Return twice * * Handle phi node. - * * Look through `pointer_from_objref`. * * Handle jl_box* */ @@ -135,12 +135,13 @@ struct Optimizer { // insert llvm.lifetime.* calls for `ptr` with size `sz` based on the use of `orig`. void insertLifetime(Value *ptr, Constant *sz, Instruction *orig); - void checkInst(Instruction *I); + void checkInst(CallInst *I); void replaceIntrinsicUseWith(IntrinsicInst *call, Intrinsic::ID ID, Instruction *orig_i, Instruction *new_i); void removeAlloc(CallInst *orig_inst); - void moveToStack(CallInst *orig_inst, size_t sz, bool has_ref); + void moveToStack(CallInst *orig_inst, size_t sz, bool has_ref, AllocFnKind allockind); + void initializeAlloca(IRBuilder<> &prolog_builder, AllocaInst *buff, AllocFnKind allockind); void splitOnStack(CallInst *orig_inst); void optimizeTag(CallInst *orig_inst); @@ -288,7 +289,7 @@ void Optimizer::optimizeAll() << "GC allocation moved to stack " << ore::NV("GC Allocation", orig); }); // The object has no fields with mix reference access - moveToStack(orig, sz, has_ref); + moveToStack(orig, sz, has_ref, use_info.allockind); } } @@ -310,7 +311,9 @@ bool Optimizer::isSafepoint(Instruction *inst) return false; if (auto callee = call->getCalledFunction()) { // Known functions emitted in codegen that are not safepoints - if (callee == pass.pointer_from_objref_func || callee->getName() == "memcmp") { + if (callee == pass.pointer_from_objref_func + || callee == pass.gc_loaded_func + || callee->getName() == "memcmp") { return false; } } @@ -353,7 +356,7 @@ ssize_t Optimizer::getGCAllocSize(Instruction *I) return -1; } -void Optimizer::checkInst(Instruction *I) +void Optimizer::checkInst(CallInst *I) { LLVM_DEBUG(dbgs() << "Running escape analysis on " << *I << "\n"); jl_alloc::EscapeAnalysisRequiredArgs required{use_info, check_stack, pass, *pass.DL}; @@ -566,7 +569,7 @@ void Optimizer::replaceIntrinsicUseWith(IntrinsicInst *call, Intrinsic::ID ID, auto oldfType = call->getFunctionType(); auto newfType = FunctionType::get( oldfType->getReturnType(), - makeArrayRef(argTys).slice(0, oldfType->getNumParams()), + ArrayRef(argTys).slice(0, oldfType->getNumParams()), oldfType->isVarArg()); // Accumulate an array of overloaded types for the given intrinsic @@ -596,9 +599,25 @@ void Optimizer::replaceIntrinsicUseWith(IntrinsicInst *call, Intrinsic::ID ID, call->eraseFromParent(); } +void Optimizer::initializeAlloca(IRBuilder<> &prolog_builder, AllocaInst *buff, AllocFnKind allockind) +{ + if ((allockind & AllocFnKind::Uninitialized) != AllocFnKind::Unknown) + return; + assert(!buff->isArrayAllocation()); + Type *T = buff->getAllocatedType(); + Value *Init = UndefValue::get(T); + if ((allockind & AllocFnKind::Zeroed) != AllocFnKind::Unknown) + Init = Constant::getNullValue(T); // zero, as described + else if (allockind == AllocFnKind::Unknown) + Init = Constant::getNullValue(T); // assume zeroed since we didn't find the attribute + else + Init = prolog_builder.CreateFreeze(UndefValue::get(T)); // assume freeze, since LLVM does not natively support this case + prolog_builder.CreateStore(Init, buff); +} + // This function should not erase any safepoint so that the lifetime marker can find and cache // all the original safepoints. -void Optimizer::moveToStack(CallInst *orig_inst, size_t sz, bool has_ref) +void Optimizer::moveToStack(CallInst *orig_inst, size_t sz, bool has_ref, AllocFnKind allockind) { ++RemovedAllocs; ++StackAllocs; @@ -641,6 +660,10 @@ void Optimizer::moveToStack(CallInst *orig_inst, size_t sz, bool has_ref) ptr = cast(prolog_builder.CreateBitCast(buff, Type::getInt8PtrTy(prolog_builder.getContext(), buff->getType()->getPointerAddressSpace()))); } insertLifetime(ptr, ConstantInt::get(Type::getInt64Ty(prolog_builder.getContext()), sz), orig_inst); + if (sz != 0 && !has_ref) { // TODO: fix has_ref case too + IRBuilder<> builder(orig_inst); + initializeAlloca(builder, buff, allockind); + } Instruction *new_inst = cast(prolog_builder.CreateBitCast(ptr, JuliaType::get_pjlvalue_ty(prolog_builder.getContext(), buff->getType()->getPointerAddressSpace()))); new_inst->takeName(orig_inst); @@ -693,6 +716,11 @@ void Optimizer::moveToStack(CallInst *orig_inst, size_t sz, bool has_ref) call->eraseFromParent(); return; } + //if (pass.gc_loaded_func == callee) { + // call->replaceAllUsesWith(new_i); + // call->eraseFromParent(); + // return; + //} if (pass.typeof_func == callee) { ++RemovedTypeofs; call->replaceAllUsesWith(tag); @@ -920,8 +948,10 @@ void Optimizer::splitOnStack(CallInst *orig_inst) allocty = ArrayType::get(Type::getInt8Ty(pass.getLLVMContext()), field.size); } slot.slot = prolog_builder.CreateAlloca(allocty); + IRBuilder<> builder(orig_inst); insertLifetime(prolog_builder.CreateBitCast(slot.slot, Type::getInt8PtrTy(prolog_builder.getContext())), ConstantInt::get(Type::getInt64Ty(prolog_builder.getContext()), field.size), orig_inst); + initializeAlloca(builder, slot.slot, use_info.allockind); slots.push_back(std::move(slot)); } const auto nslots = slots.size(); @@ -1037,10 +1067,11 @@ void Optimizer::splitOnStack(CallInst *orig_inst) store_ty = T_pjlvalue; } else { - store_ty = PointerType::getWithSamePointeeType(T_pjlvalue, cast(store_ty)->getAddressSpace()); + store_ty = PointerType::getWithSamePointeeType( + T_pjlvalue, store_ty->getPointerAddressSpace()); store_val = builder.CreateBitCast(store_val, store_ty); } - if (cast(store_ty)->getAddressSpace() != AddressSpace::Tracked) + if (store_ty->getPointerAddressSpace() != AddressSpace::Tracked) store_val = builder.CreateAddrSpaceCast(store_val, pass.T_prjlvalue); newstore = builder.CreateStore(store_val, slot.slot); } diff --git a/src/llvm-codegen-shared.h b/src/llvm-codegen-shared.h index 4dc68483fd1b7..b355dd05436c1 100644 --- a/src/llvm-codegen-shared.h +++ b/src/llvm-codegen-shared.h @@ -8,11 +8,30 @@ #include #include #include + +#if JL_LLVM_VERSION >= 160000 +#include +#endif + #include "julia.h" #define STR(csym) #csym #define XSTR(csym) STR(csym) +#if JL_LLVM_VERSION >= 160000 + +#include + +template +using Optional = std::optional; +static constexpr std::nullopt_t None = std::nullopt; + +#else + +#include + +#endif + enum AddressSpace { Generic = 0, Tracked = 10, @@ -60,9 +79,19 @@ namespace JuliaType { return llvm::FunctionType::get(T_prjlvalue, { T_prjlvalue, // function T_pprjlvalue, // args[] - llvm::Type::getInt32Ty(C), - T_prjlvalue, // linfo - }, // nargs + llvm::Type::getInt32Ty(C), // nargs + T_prjlvalue}, // linfo + false); + } + + static inline auto get_jlfunc3_ty(llvm::LLVMContext &C) { + auto T_prjlvalue = get_prjlvalue_ty(C); + auto T_pprjlvalue = llvm::PointerType::get(T_prjlvalue, 0); + auto T = get_pjlvalue_ty(C, Derived); + return llvm::FunctionType::get(T_prjlvalue, { + T, // function + T_pprjlvalue, // args[] + llvm::Type::getInt32Ty(C)}, // nargs false); } @@ -93,7 +122,7 @@ struct CountTrackedPointers { unsigned count = 0; bool all = true; bool derived = false; - CountTrackedPointers(llvm::Type *T); + CountTrackedPointers(llvm::Type *T, bool ignore_loaded=false); }; unsigned TrackWithShadow(llvm::Value *Src, llvm::Type *T, bool isptr, llvm::Value *Dst, llvm::Type *DTy, llvm::IRBuilder<> &irbuilder); @@ -149,9 +178,11 @@ static inline llvm::MDNode *get_tbaa_const(llvm::LLVMContext &ctxt) { static inline llvm::Instruction *tbaa_decorate(llvm::MDNode *md, llvm::Instruction *inst) { + using namespace llvm; inst->setMetadata(llvm::LLVMContext::MD_tbaa, md); - if (llvm::isa(inst) && md && md == get_tbaa_const(md->getContext())) - inst->setMetadata(llvm::LLVMContext::MD_invariant_load, llvm::MDNode::get(md->getContext(), llvm::None)); + if (llvm::isa(inst) && md && md == get_tbaa_const(md->getContext())) { + inst->setMetadata(llvm::LLVMContext::MD_invariant_load, llvm::MDNode::get(md->getContext(), None)); + } return inst; } @@ -191,7 +222,7 @@ static inline llvm::Value *get_current_ptls_from_task(llvm::IRBuilder<> &builder auto T_pjlvalue = JuliaType::get_pjlvalue_ty(builder.getContext()); const int ptls_offset = offsetof(jl_task_t, ptls); llvm::Value *pptls = builder.CreateInBoundsGEP( - T_pjlvalue, current_task, + T_pjlvalue, emit_bitcast_with_builder(builder, current_task, T_ppjlvalue), ConstantInt::get(T_size, ptls_offset / sizeof(void *)), "ptls_field"); LoadInst *ptls_load = builder.CreateAlignedLoad(T_pjlvalue, @@ -241,7 +272,11 @@ static inline void emit_gc_safepoint(llvm::IRBuilder<> &builder, llvm::Type *T_s auto T_psize = T_size->getPointerTo(); FunctionType *FT = FunctionType::get(Type::getVoidTy(C), {T_psize}, false); F = Function::Create(FT, Function::ExternalLinkage, "julia.safepoint", M); +#if JL_LLVM_VERSION >= 160000 + F->setMemoryEffects(MemoryEffects::inaccessibleOrArgMemOnly()); +#else F->addFnAttr(Attribute::InaccessibleMemOrArgMemOnly); +#endif } builder.CreateCall(F, {signal_page}); } @@ -269,8 +304,8 @@ static inline llvm::Value *emit_gc_state_set(llvm::IRBuilder<> &builder, llvm::T BasicBlock *passBB = BasicBlock::Create(builder.getContext(), "safepoint", builder.GetInsertBlock()->getParent()); BasicBlock *exitBB = BasicBlock::Create(builder.getContext(), "after_safepoint", builder.GetInsertBlock()->getParent()); Constant *zero8 = ConstantInt::get(T_int8, 0); - builder.CreateCondBr(builder.CreateAnd(builder.CreateICmpNE(old_state, zero8), // if (old_state && !state) - builder.CreateICmpEQ(state, zero8)), + builder.CreateCondBr(builder.CreateOr(builder.CreateICmpEQ(old_state, zero8), // if (!old_state || !state) + builder.CreateICmpEQ(state, zero8)), passBB, exitBB); builder.SetInsertPoint(passBB); MDNode *tbaa = get_tbaa_const(builder.getContext()); @@ -290,7 +325,7 @@ static inline llvm::Value *emit_gc_unsafe_enter(llvm::IRBuilder<> &builder, llvm static inline llvm::Value *emit_gc_unsafe_leave(llvm::IRBuilder<> &builder, llvm::Type *T_size, llvm::Value *ptls, llvm::Value *state, bool final) { using namespace llvm; - Value *old_state = builder.getInt8(0); + Value *old_state = builder.getInt8(JL_GC_STATE_UNSAFE); return emit_gc_state_set(builder, T_size, ptls, state, old_state, final); } diff --git a/src/llvm-cpufeatures.cpp b/src/llvm-cpufeatures.cpp index 02a449cf3b3d5..2539c5cd2e37c 100644 --- a/src/llvm-cpufeatures.cpp +++ b/src/llvm-cpufeatures.cpp @@ -43,14 +43,14 @@ Optional always_have_fma(Function &intr, const Triple &TT) JL_NOTSAFEPOINT auto typ = intr_name.substr(strlen("julia.cpu.have_fma.")); return typ == "f32" || typ == "f64"; } else { - return {}; + return None; } } static bool have_fma(Function &intr, Function &caller, const Triple &TT) JL_NOTSAFEPOINT { auto unconditional = always_have_fma(intr, TT); - if (unconditional.hasValue()) - return unconditional.getValue(); + if (unconditional) + return *unconditional; auto intr_name = intr.getName(); auto typ = intr_name.substr(strlen("julia.cpu.have_fma.")); @@ -59,7 +59,7 @@ static bool have_fma(Function &intr, Function &caller, const Triple &TT) JL_NOTS StringRef FS = FSAttr.isValid() ? FSAttr.getValueAsString() : jl_ExecutionEngine->getTargetFeatureString(); - SmallVector Features; + SmallVector Features; FS.split(Features, ','); for (StringRef Feature : Features) if (TT.isARM()) { @@ -67,7 +67,7 @@ static bool have_fma(Function &intr, Function &caller, const Triple &TT) JL_NOTS return typ == "f32" || typ == "f64"; else if (Feature == "+vfp4sp") return typ == "f32"; - } else { + } else if (TT.isX86()) { if (Feature == "+fma" || Feature == "+fma4") return typ == "f32" || typ == "f64"; } diff --git a/src/llvm-final-gc-lowering.cpp b/src/llvm-final-gc-lowering.cpp index 1edf37b960c03..5a53ce4d8e510 100644 --- a/src/llvm-final-gc-lowering.cpp +++ b/src/llvm-final-gc-lowering.cpp @@ -116,7 +116,7 @@ void FinalLowerGC::lowerPushGCFrame(CallInst *target, Function &F) PointerType::get(T_ppjlvalue, 0)), Align(sizeof(void*))); inst->setMetadata(LLVMContext::MD_tbaa, tbaa_gcframe); - inst = builder.CreateAlignedStore( + builder.CreateAlignedStore( gcframe, builder.CreateBitCast(pgcstack, PointerType::get(PointerType::get(T_prjlvalue, 0), 0)), Align(sizeof(void*))); @@ -189,8 +189,7 @@ void FinalLowerGC::lowerGCAllocBytes(CallInst *target, Function &F) IRBuilder<> builder(target); auto ptls = target->getArgOperand(0); auto type = target->getArgOperand(2); - Attribute derefAttr; - + uint64_t derefBytes = 0; if (auto CI = dyn_cast(target->getArgOperand(1))) { size_t sz = (size_t)CI->getZExtValue(); // This is strongly architecture and OS dependent @@ -200,22 +199,27 @@ void FinalLowerGC::lowerGCAllocBytes(CallInst *target, Function &F) newI = builder.CreateCall( bigAllocFunc, { ptls, ConstantInt::get(T_size, sz + sizeof(void*)), type }); - derefAttr = Attribute::getWithDereferenceableBytes(F.getContext(), sz + sizeof(void*)); + if (sz > 0) + derefBytes = sz; } else { auto pool_offs = ConstantInt::get(Type::getInt32Ty(F.getContext()), offset); auto pool_osize = ConstantInt::get(Type::getInt32Ty(F.getContext()), osize); newI = builder.CreateCall(poolAllocFunc, { ptls, pool_offs, pool_osize, type }); - derefAttr = Attribute::getWithDereferenceableBytes(F.getContext(), osize); + if (sz > 0) + derefBytes = sz; } } else { auto size = builder.CreateZExtOrTrunc(target->getArgOperand(1), T_size); size = builder.CreateAdd(size, ConstantInt::get(T_size, sizeof(void*))); newI = builder.CreateCall(allocTypedFunc, { ptls, size, type }); - derefAttr = Attribute::getWithDereferenceableBytes(F.getContext(), sizeof(void*)); + derefBytes = sizeof(void*); } newI->setAttributes(newI->getCalledFunction()->getAttributes()); - newI->addRetAttr(derefAttr); + unsigned align = std::max((unsigned)target->getRetAlign().valueOrOne().value(), (unsigned)sizeof(void*)); + newI->addRetAttr(Attribute::getWithAlignment(F.getContext(), Align(align))); + if (derefBytes > 0) + newI->addDereferenceableRetAttr(derefBytes); newI->takeName(target); target->replaceAllUsesWith(newI); target->eraseFromParent(); diff --git a/src/llvm-gc-invariant-verifier.cpp b/src/llvm-gc-invariant-verifier.cpp index f1d9df0997e39..5badbca807569 100644 --- a/src/llvm-gc-invariant-verifier.cpp +++ b/src/llvm-gc-invariant-verifier.cpp @@ -62,8 +62,8 @@ struct GCInvariantVerifier : public InstVisitor { }; void GCInvariantVerifier::visitAddrSpaceCastInst(AddrSpaceCastInst &I) { - unsigned FromAS = cast(I.getSrcTy())->getAddressSpace(); - unsigned ToAS = cast(I.getDestTy())->getAddressSpace(); + unsigned FromAS = I.getSrcTy()->getPointerAddressSpace(); + unsigned ToAS = I.getDestTy()->getPointerAddressSpace(); if (FromAS == 0) return; Check(ToAS != AddressSpace::Loaded && FromAS != AddressSpace::Loaded, @@ -78,10 +78,10 @@ void GCInvariantVerifier::visitAddrSpaceCastInst(AddrSpaceCastInst &I) { } void GCInvariantVerifier::checkStoreInst(Type *VTy, unsigned AS, Value &SI) { - if (VTy->isPointerTy()) { + if (VTy->isPtrOrPtrVectorTy()) { /* We currently don't obey this for arguments. That's ok - they're externally rooted. */ - unsigned AS = cast(VTy)->getAddressSpace(); + unsigned AS = VTy->getPointerAddressSpace(); Check(AS != AddressSpace::CalleeRooted && AS != AddressSpace::Derived, "Illegal store of decayed value", &SI); @@ -107,15 +107,15 @@ void GCInvariantVerifier::visitAtomicCmpXchgInst(AtomicCmpXchgInst &SI) { void GCInvariantVerifier::visitLoadInst(LoadInst &LI) { Type *Ty = LI.getType(); - if (Ty->isPointerTy()) { - unsigned AS = cast(Ty)->getAddressSpace(); + if (Ty->isPtrOrPtrVectorTy()) { + unsigned AS = Ty->getPointerAddressSpace(); Check(AS != AddressSpace::CalleeRooted && AS != AddressSpace::Derived, "Illegal load of gc relevant value", &LI); } Ty = LI.getPointerOperand()->getType(); - if (Ty->isPointerTy()) { - unsigned AS = cast(Ty)->getAddressSpace(); + if (Ty->isPtrOrPtrVectorTy()) { + unsigned AS = Ty->getPointerAddressSpace(); Check(AS != AddressSpace::CalleeRooted, "Illegal load of callee rooted value", &LI); } @@ -129,18 +129,18 @@ void GCInvariantVerifier::visitReturnInst(ReturnInst &RI) { if (!RI.getReturnValue()) return; Type *RTy = RI.getReturnValue()->getType(); - if (!RTy->isPointerTy()) + if (!RTy->isPtrOrPtrVectorTy()) return; - unsigned AS = cast(RTy)->getAddressSpace(); + unsigned AS = RTy->getPointerAddressSpace(); Check(!isSpecialAS(AS) || AS == AddressSpace::Tracked, "Only gc tracked values may be directly returned", &RI); } void GCInvariantVerifier::visitGetElementPtrInst(GetElementPtrInst &GEP) { Type *Ty = GEP.getType(); - if (!Ty->isPointerTy()) + if (!Ty->isPtrOrPtrVectorTy()) return; - unsigned AS = cast(Ty)->getAddressSpace(); + unsigned AS = Ty->getPointerAddressSpace(); if (!isSpecialAS(AS)) return; /* We're actually ok with GEPs here, as long as they don't feed into any @@ -161,13 +161,18 @@ void GCInvariantVerifier::visitGetElementPtrInst(GetElementPtrInst &GEP) { void GCInvariantVerifier::visitCallInst(CallInst &CI) { Function *Callee = CI.getCalledFunction(); if (Callee && (Callee->getName() == "julia.call" || - Callee->getName() == "julia.call2")) { - bool First = true; + Callee->getName() == "julia.call2" || + Callee->getName() == "julia.call3")) { + unsigned Fixed = CI.getFunctionType()->getNumParams(); for (Value *Arg : CI.args()) { + if (Fixed) { + Fixed--; + continue; + } Type *Ty = Arg->getType(); - Check(Ty->isPointerTy() && cast(Ty)->getAddressSpace() == (First ? 0 : AddressSpace::Tracked), - "Invalid derived pointer in jlcall", &CI); - First = false; + Check(Ty->isPtrOrPtrVectorTy() && + Ty->getPointerAddressSpace() == AddressSpace::Tracked, + "Invalid derived pointer in jlcall", &CI); } } } diff --git a/src/llvm-julia-licm.cpp b/src/llvm-julia-licm.cpp index 082398ef7a5d5..e76beaa3df44f 100644 --- a/src/llvm-julia-licm.cpp +++ b/src/llvm-julia-licm.cpp @@ -343,7 +343,11 @@ struct JuliaLICM : public JuliaPassContext { } } if (changed && SE) { +#if JL_LLVM_VERSION >= 160000 + SE->forgetLoopDispositions(); +#else SE->forgetLoopDispositions(L); +#endif } #ifdef JL_VERIFY_PASSES assert(!verifyLLVMIR(*L)); diff --git a/src/llvm-late-gc-lowering.cpp b/src/llvm-late-gc-lowering.cpp index fa666ad464cda..a6178d95d5476 100644 --- a/src/llvm-late-gc-lowering.cpp +++ b/src/llvm-late-gc-lowering.cpp @@ -12,7 +12,8 @@ #include #include #include -#include "llvm/Analysis/CFG.h" +#include +#include #include #include #include @@ -140,7 +141,7 @@ using namespace llvm; not sunk into the gc frame. Nevertheless performing such sinking can still be profitable. Since all arguments to a jlcall are guaranteed to be live at that call in some gc slot, we can attempt to rearrange the slots within - the gc-frame, or re-use slots not assigned at that particular location + the gc-frame, or reuse slots not assigned at that particular location for the gcframe. However, even without this optimization, stack frames are at most two times larger than optimal (because regular stack coloring can merge the jlcall allocas). @@ -378,16 +379,18 @@ static bool isSpecialPtr(Type *Ty) { // return how many Special pointers are in T (count > 0), // and if there is anything else in T (all == false) -CountTrackedPointers::CountTrackedPointers(Type *T) { +CountTrackedPointers::CountTrackedPointers(Type *T, bool ignore_loaded) { if (isa(T)) { if (isSpecialPtr(T)) { + if (ignore_loaded && T->getPointerAddressSpace() == AddressSpace::Loaded) + return; count++; if (T->getPointerAddressSpace() != AddressSpace::Tracked) derived = true; } } else if (isa(T) || isa(T) || isa(T)) { for (Type *ElT : T->subtypes()) { - auto sub = CountTrackedPointers(ElT); + auto sub = CountTrackedPointers(ElT, ignore_loaded); count += sub.count; all &= sub.all; derived |= sub.derived; @@ -403,6 +406,20 @@ CountTrackedPointers::CountTrackedPointers(Type *T) { all = false; } +bool hasLoadedTy(Type *T) { + if (isa(T)) { + if (T->getPointerAddressSpace() == AddressSpace::Loaded) + return true; + } else if (isa(T) || isa(T) || isa(T)) { + for (Type *ElT : T->subtypes()) { + if (hasLoadedTy(ElT)) + return true; + } + } + return false; +} + + unsigned getCompositeNumElements(Type *T) { if (auto *ST = dyn_cast(T)) return ST->getNumElements(); @@ -439,7 +456,6 @@ SmallVector, 0> TrackCompositeType(Type *T) { } - // Walk through simple expressions to until we hit something that requires root numbering // If the input value is a scalar (pointer), we may return a composite value as base // in which case the second member of the pair is the index of the value in the vector. @@ -484,18 +500,19 @@ static std::pair FindBaseValue(const State &S, Value *V, bool UseCac CurrentV = EEI->getVectorOperand(); } else if (auto LI = dyn_cast(CurrentV)) { - if (auto PtrT = dyn_cast(LI->getType()->getScalarType())) { - if (PtrT->getAddressSpace() == AddressSpace::Loaded) { - CurrentV = LI->getPointerOperand(); - fld_idx = -1; - if (!isSpecialPtr(CurrentV->getType())) { - // This could really be anything, but it's not loaded - // from a tracked pointer, so it doesn't matter what - // it is--just pick something simple. - CurrentV = ConstantPointerNull::get(Type::getInt8PtrTy(V->getContext())); - } - continue; + if (hasLoadedTy(LI->getType())) { + // This is the old (now deprecated) implementation for loaded. + // New code should use the gc_loaded intrinsic to ensure that + // the load is paired with the correct Tracked value. + CurrentV = LI->getPointerOperand(); + fld_idx = -1; + if (!isSpecialPtr(CurrentV->getType())) { + // This could really be anything, but it's not loaded + // from a tracked pointer, so it doesn't matter what + // it is--just pick something simple. + CurrentV = ConstantPointerNull::get(Type::getInt8PtrTy(V->getContext())); } + continue; } // In general a load terminates a walk break; @@ -517,36 +534,42 @@ static std::pair FindBaseValue(const State &S, Value *V, bool UseCac if (II->getIntrinsicID() == Intrinsic::masked_load || II->getIntrinsicID() == Intrinsic::masked_gather) { if (auto VTy = dyn_cast(II->getType())) { - if (auto PtrT = dyn_cast(VTy->getElementType())) { - if (PtrT->getAddressSpace() == AddressSpace::Loaded) { - Value *Mask = II->getOperand(2); - Value *Passthrough = II->getOperand(3); - if (!isa(Mask) || !cast(Mask)->isAllOnesValue()) { - assert(isa(Passthrough) && "unimplemented"); - (void)Passthrough; + if (hasLoadedTy(VTy->getElementType())) { + Value *Mask = II->getOperand(2); + Value *Passthrough = II->getOperand(3); + if (!isa(Mask) || !cast(Mask)->isAllOnesValue()) { + assert(isa(Passthrough) && "unimplemented"); + (void)Passthrough; + } + CurrentV = II->getOperand(0); + if (II->getIntrinsicID() == Intrinsic::masked_load) { + fld_idx = -1; + if (!isSpecialPtr(CurrentV->getType())) { + CurrentV = ConstantPointerNull::get(Type::getInt8PtrTy(V->getContext())); } - CurrentV = II->getOperand(0); - if (II->getIntrinsicID() == Intrinsic::masked_load) { - fld_idx = -1; - if (!isSpecialPtr(CurrentV->getType())) { + } else { + if (auto VTy2 = dyn_cast(CurrentV->getType())) { + if (!isSpecialPtr(VTy2->getElementType())) { CurrentV = ConstantPointerNull::get(Type::getInt8PtrTy(V->getContext())); - } - } else { - if (auto VTy2 = dyn_cast(CurrentV->getType())) { - if (!isSpecialPtr(VTy2->getElementType())) { - CurrentV = ConstantPointerNull::get(Type::getInt8PtrTy(V->getContext())); - fld_idx = -1; - } + fld_idx = -1; } } - continue; } + continue; } } // In general a load terminates a walk break; } } + else if (auto CI = dyn_cast(CurrentV)) { + auto callee = CI->getCalledFunction(); + if (callee && callee->getName() == "julia.gc_loaded") { + CurrentV = CI->getArgOperand(0); + continue; + } + break; + } else { break; } @@ -577,7 +600,7 @@ Value *LateLowerGCFrame::MaybeExtractScalar(State &S, std::pair ValE } else if (ValExpr.second != -1) { auto Tracked = TrackCompositeType(V->getType()); - auto Idxs = makeArrayRef(Tracked[ValExpr.second]); + auto Idxs = ArrayRef(Tracked[ValExpr.second]); auto IdxsNotVec = Idxs.slice(0, Idxs.size() - 1); Type *FinalT = ExtractValueInst::getIndexedType(V->getType(), IdxsNotVec); bool IsVector = isa(FinalT); @@ -593,12 +616,12 @@ Value *LateLowerGCFrame::MaybeExtractScalar(State &S, std::pair ValE V = ConstantPointerNull::get(cast(T_prjlvalue)); return V; } + IRBuilder foldbuilder(InsertBefore->getContext(), InstSimplifyFolder(InsertBefore->getModule()->getDataLayout())); + foldbuilder.SetInsertPoint(InsertBefore); if (Idxs.size() > IsVector) - V = ExtractValueInst::Create(V, IsVector ? IdxsNotVec : Idxs, "", InsertBefore); + V = foldbuilder.CreateExtractValue(V, IsVector ? IdxsNotVec : Idxs); if (IsVector) - V = ExtractElementInst::Create(V, - ConstantInt::get(Type::getInt32Ty(V->getContext()), Idxs.back()), - "", InsertBefore); + V = foldbuilder.CreateExtractElement(V, ConstantInt::get(Type::getInt32Ty(V->getContext()), Idxs.back())); } return V; } @@ -638,15 +661,12 @@ void LateLowerGCFrame::LiftSelect(State &S, SelectInst *SI) { // already visited here--nothing to do return; } + assert(!isTrackedValue(SI)); SmallVector Numbers; unsigned NumRoots = 1; - if (auto VTy = dyn_cast(SI->getType())) { - ElementCount EC = VTy->getElementCount(); - Numbers.resize(EC.getKnownMinValue(), -1); - } - else - assert(isa(SI->getType()) && "unimplemented"); - assert(!isTrackedValue(SI)); + Type *STy = SI->getType(); + if (!isa(STy)) + Numbers.resize(CountTrackedPointers(STy).count, -1); // find the base root for the arguments Value *TrueBase = MaybeExtractScalar(S, FindBaseValue(S, SI->getTrueValue(), false), SI); Value *FalseBase = MaybeExtractScalar(S, FindBaseValue(S, SI->getFalseValue(), false), SI); @@ -723,20 +743,17 @@ void LateLowerGCFrame::LiftPhi(State &S, PHINode *Phi) { SmallVector lifted; SmallVector Numbers; unsigned NumRoots = 1; - if (auto VTy = dyn_cast(Phi->getType())) { - NumRoots = VTy->getNumElements(); + Type *PTy = Phi->getType(); + if (!isa(PTy)) { + NumRoots = CountTrackedPointers(PTy).count; Numbers.resize(NumRoots); } - else { - // TODO: SVE - assert(isa(Phi->getType()) && "unimplemented"); - } for (unsigned i = 0; i < NumRoots; ++i) { PHINode *lift = PHINode::Create(T_prjlvalue, Phi->getNumIncomingValues(), "gclift", Phi); int Number = ++S.MaxPtrNumber; S.AllPtrNumbering[lift] = Number; S.ReversePtrNumbering[Number] = lift; - if (!isa(Phi->getType())) + if (isa(PTy)) S.AllPtrNumbering[Phi] = Number; else Numbers[i] = Number; @@ -891,7 +908,7 @@ SmallVector LateLowerGCFrame::NumberAllBase(State &S, Value *CurrentV) { auto Idxs = IVI->getIndices(); unsigned j = 0; for (unsigned i = 0; i < Tracked.size(); ++i) { - auto Elem = makeArrayRef(Tracked[i]); + auto Elem = ArrayRef(Tracked[i]); if (Elem.size() < Idxs.size()) continue; if (Idxs.equals(Elem.slice(0, Idxs.size()))) // Tracked.startswith(Idxs) @@ -904,7 +921,7 @@ SmallVector LateLowerGCFrame::NumberAllBase(State &S, Value *CurrentV) { assert(Tracked.size() == BaseNumbers.size()); auto Idxs = EVI->getIndices(); for (unsigned i = 0; i < Tracked.size(); ++i) { - auto Elem = makeArrayRef(Tracked[i]); + auto Elem = ArrayRef(Tracked[i]); if (Elem.size() < Idxs.size()) continue; if (Idxs.equals(Elem.slice(0, Idxs.size()))) // Tracked.startswith(Idxs) @@ -1176,7 +1193,7 @@ static bool isLoadFromImmut(LoadInst *LI) if (LI->getMetadata(LLVMContext::MD_invariant_load)) return true; MDNode *TBAA = LI->getMetadata(LLVMContext::MD_tbaa); - if (isTBAA(TBAA, {"jtbaa_immut", "jtbaa_const", "jtbaa_datatype"})) + if (isTBAA(TBAA, {"jtbaa_immut", "jtbaa_const", "jtbaa_datatype", "jtbaa_memoryptr", "jtbaa_memorylen", "jtbaa_memoryown"})) return true; return false; } @@ -1231,6 +1248,10 @@ static bool isLoadFromConstGV(Value *v, bool &task_local, PhiSet *seen = nullptr task_local = true; return true; } + if (callee && callee->getName() == "julia.gc_loaded") { + return isLoadFromConstGV(call->getArgOperand(0), task_local, seen) && + isLoadFromConstGV(call->getArgOperand(1), task_local, seen); + } } if (isa(v)) { task_local = true; @@ -1255,8 +1276,7 @@ static bool isLoadFromConstGV(LoadInst *LI, bool &task_local, PhiSet *seen) auto load_base = LI->getPointerOperand()->stripInBoundsOffsets(); assert(load_base); // Static analyzer auto gv = dyn_cast(load_base); - if (isTBAA(LI->getMetadata(LLVMContext::MD_tbaa), - {"jtbaa_immut", "jtbaa_const", "jtbaa_datatype"})) { + if (isLoadFromImmut(LI)) { if (gv) return true; return isLoadFromConstGV(load_base, task_local, seen); @@ -1491,20 +1511,18 @@ State LateLowerGCFrame::LocalScan(Function &F) { if (II->getIntrinsicID() == Intrinsic::masked_load || II->getIntrinsicID() == Intrinsic::masked_gather) { if (auto VTy = dyn_cast(II->getType())) { - if (auto PtrT = dyn_cast(VTy->getElementType())) { - if (isSpecialPtr(PtrT)) { - // LLVM sometimes tries to materialize these operations with undefined pointers in our non-integral address space. - // Hopefully LLVM didn't already propagate that information and poison our users. Set those to NULL now. - Value *passthru = II->getArgOperand(3); - if (isa(passthru)) { - II->setArgOperand(3, Constant::getNullValue(passthru->getType())); - } - if (PtrT->getAddressSpace() == AddressSpace::Loaded) { - // These are not real defs - continue; - } + if (CountTrackedPointers(VTy->getElementType()).count) { + // LLVM sometimes tries to materialize these operations with undefined pointers in our non-integral address space. + // Hopefully LLVM didn't already propagate that information and poison our users. Set those to NULL now. + Value *passthru = II->getArgOperand(3); + if (isa(passthru)) { + II->setArgOperand(3, Constant::getNullValue(passthru->getType())); } } + if (hasLoadedTy(VTy->getElementType())) { + // These are not real defs + continue; + } } } } @@ -1512,13 +1530,16 @@ State LateLowerGCFrame::LocalScan(Function &F) { if (callee && callee == typeof_func) { MaybeNoteDef(S, BBS, CI, BBS.Safepoints, SmallVector{-2}); } + else if (callee && callee->getName() == "julia.gc_loaded") { + continue; + } else { MaybeNoteDef(S, BBS, CI, BBS.Safepoints); } if (CI->hasStructRetAttr()) { Type *ElT = getAttributeAtIndex(CI->getAttributes(), 1, Attribute::StructRet).getValueAsType(); assert(cast(CI->getArgOperand(0)->getType())->isOpaqueOrPointeeTypeMatches(getAttributeAtIndex(CI->getAttributes(), 1, Attribute::StructRet).getValueAsType())); - auto tracked = CountTrackedPointers(ElT); + auto tracked = CountTrackedPointers(ElT, true); if (tracked.count) { AllocaInst *SRet = dyn_cast((CI->arg_begin()[0])->stripInBoundsOffsets()); assert(SRet); @@ -1599,24 +1620,43 @@ State LateLowerGCFrame::LocalScan(Function &F) { callee == gc_preserve_end_func || callee == typeof_func || callee == pgcstack_getter || callee->getName() == XSTR(jl_egal__unboxed) || callee->getName() == XSTR(jl_lock_value) || callee->getName() == XSTR(jl_unlock_value) || - callee == write_barrier_func || + callee->getName() == XSTR(jl_lock_field) || callee->getName() == XSTR(jl_unlock_field) || + callee == write_barrier_func || callee == gc_loaded_func || callee == pop_handler_noexcept_func || callee->getName() == "memcmp") { continue; } +#if JL_LLVM_VERSION >= 160000 + if (callee->getMemoryEffects().onlyReadsMemory() || + callee->getMemoryEffects().onlyAccessesArgPointees()) { + continue; + } +#else if (callee->hasFnAttribute(Attribute::ReadNone) || callee->hasFnAttribute(Attribute::ReadOnly) || callee->hasFnAttribute(Attribute::ArgMemOnly)) { continue; } +#endif if (MemTransferInst *MI = dyn_cast(CI)) { MaybeTrackDst(S, MI); } } - if (isa(CI) || CI->hasFnAttr(Attribute::ArgMemOnly) || - CI->hasFnAttr(Attribute::ReadNone) || CI->hasFnAttr(Attribute::ReadOnly)) { +#if JL_LLVM_VERSION >= 160000 + if (isa(CI) || + CI->getMemoryEffects().onlyAccessesArgPointees() || + CI->getMemoryEffects().onlyReadsMemory()) { + // Intrinsics are never safepoints. + continue; + } +#else + if (isa(CI) || + CI->hasFnAttr(Attribute::ArgMemOnly) || + CI->hasFnAttr(Attribute::ReadNone) || + CI->hasFnAttr(Attribute::ReadOnly)) { // Intrinsics are never safepoints. continue; } +#endif SmallVector CalleeRoots; for (Use &U : CI->args()) { // Find all callee rooted arguments. @@ -1663,9 +1703,8 @@ State LateLowerGCFrame::LocalScan(Function &F) { // task but we do need to issue write barriers for when the current task dies. RefinedPtr.push_back(task_local ? -1 : -2); } - if (!Ty->isPointerTy() || Ty->getPointerAddressSpace() != AddressSpace::Loaded) { + if (!hasLoadedTy(Ty)) MaybeNoteDef(S, BBS, LI, BBS.Safepoints, std::move(RefinedPtr)); - } NoteOperandUses(S, BBS, I); } else if (auto *LI = dyn_cast(&I)) { Type *Ty = LI->getNewValOperand()->getType()->getScalarType(); @@ -1778,11 +1817,13 @@ static Value *ExtractScalar(Value *V, Type *VTy, bool isptr, ArrayRef auto IdxsNotVec = Idxs.slice(0, Idxs.size() - 1); Type *FinalT = ExtractValueInst::getIndexedType(V->getType(), IdxsNotVec); bool IsVector = isa(FinalT); + IRBuilder foldbuilder(irbuilder.getContext(), InstSimplifyFolder(irbuilder.GetInsertBlock()->getModule()->getDataLayout())); + foldbuilder.restoreIP(irbuilder.saveIP()); + foldbuilder.SetCurrentDebugLocation(irbuilder.getCurrentDebugLocation()); if (Idxs.size() > IsVector) - V = irbuilder.Insert(ExtractValueInst::Create(V, IsVector ? IdxsNotVec : Idxs)); + V = foldbuilder.CreateExtractValue(V, IsVector ? IdxsNotVec : Idxs); if (IsVector) - V = irbuilder.Insert(ExtractElementInst::Create(V, - ConstantInt::get(Type::getInt32Ty(V->getContext()), Idxs.back()))); + V = foldbuilder.CreateExtractElement(V, ConstantInt::get(Type::getInt32Ty(V->getContext()), Idxs.back())); } return V; } @@ -1823,11 +1864,12 @@ SmallVector ExtractTrackedValues(Value *Src, Type *STy, bool isptr, I return false; }; for (unsigned i = 0; i < Tracked.size(); ++i) { - auto Idxs = makeArrayRef(Tracked[i]); + auto Idxs = ArrayRef(Tracked[i]); if (ignore_field(Idxs)) continue; Value *Elem = ExtractScalar(Src, STy, isptr, Idxs, irbuilder); - Ptrs.push_back(Elem); + if (isTrackedValue(Elem)) // ignore addrspace Loaded when it appears + Ptrs.push_back(Elem); } return Ptrs; } @@ -2117,7 +2159,7 @@ struct PEOIterator { } if (NextElement == -1) return NextElement; - // Make sure not to try to re-use this later. + // Make sure not to try to reuse this later. Elements[NextElement].weight = (unsigned)-1; // Raise neighbors for (int Neighbor : Neighbors[NextElement]) { @@ -2294,7 +2336,7 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S, bool *CFGModified) { if (I->getMetadata(LLVMContext::MD_invariant_load)) I->setMetadata(LLVMContext::MD_invariant_load, NULL); if (MDNode *TBAA = I->getMetadata(LLVMContext::MD_tbaa)) { - if (TBAA->getNumOperands() == 4 && isTBAA(TBAA, {"jtbaa_const"})) { + if (TBAA->getNumOperands() == 4 && isTBAA(TBAA, {"jtbaa_const", "jtbaa_memoryptr", "jtbaa_memorylen", "tbaa_memoryown"})) { MDNode *MutableTBAA = createMutableTBAAAccessTag(TBAA); if (MutableTBAA != TBAA) I->setMetadata(LLVMContext::MD_tbaa, MutableTBAA); @@ -2323,7 +2365,13 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S, bool *CFGModified) { /* No replacement */ } else if (pointer_from_objref_func != nullptr && callee == pointer_from_objref_func) { auto *obj = CI->getOperand(0); - auto *ASCI = new AddrSpaceCastInst(obj, JuliaType::get_pjlvalue_ty(obj->getContext()), "", CI); + auto *ASCI = new AddrSpaceCastInst(obj, CI->getType(), "", CI); + ASCI->takeName(CI); + CI->replaceAllUsesWith(ASCI); + UpdatePtrNumbering(CI, ASCI, S); + } else if (gc_loaded_func != nullptr && callee == gc_loaded_func) { + auto *obj = CI->getOperand(1); + auto *ASCI = new AddrSpaceCastInst(obj, CI->getType(), "", CI); ASCI->takeName(CI); CI->replaceAllUsesWith(ASCI); UpdatePtrNumbering(CI, ASCI, S); @@ -2390,12 +2438,13 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S, bool *CFGModified) { false), builder.CreatePtrToInt(tag, T_size), }); + newI->setAttributes(allocBytesIntrinsic->getAttributes()); + newI->addDereferenceableRetAttr(CI->getRetDereferenceableBytes()); newI->takeName(CI); - // Now, finally, set the tag. We do this in IR instead of in the C alloc // function, to provide possible optimization opportunities. (I think? TBH // the most recent editor of this code is not entirely clear on why we - // prefer to set the tag in the generated code. Providing optimziation + // prefer to set the tag in the generated code. Providing optimization // opportunities is the most likely reason; the tradeoff is slightly // larger code size and increased compilation time, compiling this // instruction at every allocation site, rather than once in the C alloc @@ -2432,14 +2481,15 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S, bool *CFGModified) { ++it; continue; } else if ((call_func && callee == call_func) || - (call2_func && callee == call2_func)) { + (call2_func && callee == call2_func) || + (call3_func && callee == call3_func)) { assert(T_prjlvalue); size_t nargs = CI->arg_size(); size_t nframeargs = nargs-1; - if (callee == call_func) - nframeargs -= 1; - else if (callee == call2_func) + if (callee == call2_func) nframeargs -= 2; + else + nframeargs -= 1; SmallVector ReplacementArgs; auto arg_it = CI->arg_begin(); assert(arg_it != CI->arg_end()); @@ -2472,7 +2522,9 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S, bool *CFGModified) { ReplacementArgs.erase(ReplacementArgs.begin()); ReplacementArgs.push_back(front); } - FunctionType *FTy = callee == call2_func ? JuliaType::get_jlfunc2_ty(CI->getContext()) : JuliaType::get_jlfunc_ty(CI->getContext()); + FunctionType *FTy = callee == call3_func ? JuliaType::get_jlfunc3_ty(CI->getContext()) : + callee == call2_func ? JuliaType::get_jlfunc2_ty(CI->getContext()) : + JuliaType::get_jlfunc_ty(CI->getContext()); CallInst *NewCall = CallInst::Create(FTy, new_callee, ReplacementArgs, "", CI); NewCall->setTailCallKind(CI->getTailCallKind()); auto callattrs = CI->getAttributes(); @@ -2730,7 +2782,7 @@ void LateLowerGCFrame::PlaceRootsAndUpdateCalls(SmallVectorImpl &Colors, St assert(Elem->getContext().supportsTypedPointers()); Elem = new BitCastInst(Elem, T_prjlvalue, "", SI); } - //auto Idxs = makeArrayRef(Tracked[i]); + //auto Idxs = ArrayRef(Tracked[i]); //Value *Elem = ExtractScalar(Base, true, Idxs, SI); Value *shadowStore = new StoreInst(Elem, slotAddress, SI); (void)shadowStore; diff --git a/src/llvm-lower-handlers.cpp b/src/llvm-lower-handlers.cpp index 2e2b5955fcf67..d6d4793f3c1c0 100644 --- a/src/llvm-lower-handlers.cpp +++ b/src/llvm-lower-handlers.cpp @@ -37,13 +37,13 @@ using namespace llvm; /* Lowers Julia Exception Handlers and colors EH frames. * * Our task is to lower: - * call void @julia.except_enter() + * call void @julia.except_enter(ct) * <...> * call void jl_pop_handler(1) * * to * - * call void @jl_enter_handler(jl_handler *%buff) + * call void @jl_enter_handler(ct, jl_handler *%buff) * <...> * call void jl_pop_handler(1) * @@ -60,7 +60,7 @@ using namespace llvm; * \ / * br i1 %cond, %left2, %right2 * / \ - * jl_pop_hander ret + * jl_pop_handler ret * ret * * The frontend doesn't emit structures like this. However, the optimizer @@ -81,25 +81,19 @@ namespace { * If the module doesn't have declarations for the jl_enter_handler and setjmp * functions, insert them. */ -static void ensure_enter_function(Module &M, const Triple &TT) +static void ensure_enter_function(Module &M, Type *T_pjlvalue, const Triple &TT) { auto T_int8 = Type::getInt8Ty(M.getContext()); auto T_pint8 = PointerType::get(T_int8, 0); auto T_void = Type::getVoidTy(M.getContext()); auto T_int32 = Type::getInt32Ty(M.getContext()); if (!M.getNamedValue(XSTR(jl_enter_handler))) { - SmallVector ehargs(0); - ehargs.push_back(T_pint8); - Function::Create(FunctionType::get(T_void, ehargs, false), + Function::Create(FunctionType::get(T_void, {T_pjlvalue, T_pint8}, false), Function::ExternalLinkage, XSTR(jl_enter_handler), &M); } if (!M.getNamedValue(jl_setjmp_name)) { - SmallVector args2(0); - args2.push_back(T_pint8); - if (!TT.isOSWindows()) { - args2.push_back(T_int32); - } - Function::Create(FunctionType::get(T_int32, args2, false), + Type *args2[] = {T_pint8, T_int32}; + Function::Create(FunctionType::get(T_int32, ArrayRef(args2, TT.isOSWindows() ? 1 : 2), false), Function::ExternalLinkage, jl_setjmp_name, &M) ->addFnAttr(Attribute::ReturnsTwice); } @@ -111,8 +105,9 @@ static bool lowerExcHandlers(Function &F) { Function *except_enter_func = M.getFunction("julia.except_enter"); if (!except_enter_func) return false; // No EH frames in this module - ensure_enter_function(M, TT); + ensure_enter_function(M, except_enter_func->getFunctionType()->getParamType(0), TT); Function *leave_func = M.getFunction(XSTR(jl_pop_handler)); + Function *leave_noexcept_func = M.getFunction(XSTR(jl_pop_handler_noexcept)); Function *jlenter_func = M.getFunction(XSTR(jl_enter_handler)); Function *setjmp_func = M.getFunction(jl_setjmp_name); @@ -150,9 +145,9 @@ static bool lowerExcHandlers(Function &F) { continue; if (Callee == except_enter_func) EnterDepth[CI] = Depth++; - else if (Callee == leave_func) { + else if (Callee == leave_func || Callee == leave_noexcept_func) { LeaveDepth[CI] = Depth; - Depth -= cast(CI->getArgOperand(0))->getLimitedValue(); + Depth -= cast(CI->getArgOperand(1))->getLimitedValue(); } assert(Depth >= 0); if (Depth > MaxDepth) @@ -192,7 +187,7 @@ static bool lowerExcHandlers(Function &F) { assert(it.second >= 0); Instruction *buff = buffs[it.second]; CallInst *enter = it.first; - auto new_enter = CallInst::Create(jlenter_func, buff, "", enter); + auto new_enter = CallInst::Create(jlenter_func, {enter->getArgOperand(0), buff}, "", enter); Value *lifetime_args[] = { handler_sz64, buff @@ -200,10 +195,7 @@ static bool lowerExcHandlers(Function &F) { CallInst::Create(lifetime_start, lifetime_args, "", new_enter); CallInst *sj; if (!TT.isOSWindows()) { - // For LLVM 3.3 compatibility - Value *args[] = {buff, - ConstantInt::get(Type::getInt32Ty(F.getContext()), 0)}; - sj = CallInst::Create(setjmp_func, args, "", enter); + sj = CallInst::Create(setjmp_func, {buff, ConstantInt::get(Type::getInt32Ty(F.getContext()), 0)}, "", enter); } else { sj = CallInst::Create(setjmp_func, buff, "", enter); } @@ -219,7 +211,7 @@ static bool lowerExcHandlers(Function &F) { // Insert lifetime end intrinsics after every leave. for (auto it : LeaveDepth) { int StartDepth = it.second - 1; - int npops = cast(it.first->getArgOperand(0))->getLimitedValue(); + int npops = cast(it.first->getArgOperand(1))->getLimitedValue(); for (int i = 0; i < npops; ++i) { assert(StartDepth-i >= 0); Value *lifetime_args[] = { diff --git a/src/llvm-multiversioning.cpp b/src/llvm-multiversioning.cpp index 4ad278a3be75c..0110fa8efc8b6 100644 --- a/src/llvm-multiversioning.cpp +++ b/src/llvm-multiversioning.cpp @@ -103,7 +103,8 @@ static uint32_t collect_func_info(Function &F, const Triple &TT, bool &has_vecca if (name.startswith("julia.cpu.have_fma.")) { // for some platforms we know they always do (or don't) support // FMA. in those cases we don't need to clone the function. - if (!always_have_fma(*callee, TT).hasValue()) + // always_have_fma returns an optional + if (!always_have_fma(*callee, TT)) flag |= JL_TARGET_CLONE_CPU; } else { flag |= JL_TARGET_CLONE_CPU; @@ -383,7 +384,6 @@ struct CloneCtx { SmallVector groups{}; SmallVector linearized; SmallVector fvars; - SmallVector gvars; Module &M; Type *T_size; Triple TT; @@ -440,7 +440,6 @@ CloneCtx::CloneCtx(Module &M, bool allow_bad_fvars) : tbaa_const(tbaa_make_child_with_context(M.getContext(), "jtbaa_const", nullptr, true).first), specs(*get_target_specs(M)), fvars(consume_gv(M, "jl_fvars", allow_bad_fvars)), - gvars(consume_gv(M, "jl_gvars", false)), M(M), T_size(M.getDataLayout().getIntPtrType(M.getContext())), TT(M.getTargetTriple()), @@ -531,7 +530,7 @@ void CloneCtx::clone_decls() new_F->setVisibility(F->getVisibility()); new_F->setDSOLocal(true); auto base_func = F; - if (specs[i].flags & JL_TARGET_CLONE_ALL) + if (!(specs[i].flags & JL_TARGET_CLONE_ALL)) base_func = static_cast(linearized[specs[i].base])->base_func(F); (*linearized[i]->vmap)[base_func] = new_F; } @@ -586,7 +585,7 @@ void CloneCtx::clone_bodies() } for (auto &target : groups[i].clones) { prepare_vmap(*target.vmap); - auto target_F = cast_or_null(map_get(*target.vmap, F)); + auto target_F = cast_or_null(map_get(*target.vmap, group_F)); if (target_F) { if (!F->isDeclaration()) { clone_function(group_F, target_F, *target.vmap); @@ -690,7 +689,7 @@ void CloneCtx::rewrite_alias(GlobalAlias *alias, Function *F) SmallVector Args; for (auto &arg : trampoline->args()) Args.push_back(&arg); - auto call = irbuilder.CreateCall(F->getFunctionType(), ptr, makeArrayRef(Args)); + auto call = irbuilder.CreateCall(F->getFunctionType(), ptr, ArrayRef(Args)); if (F->isVarArg()) { assert(!TT.isARM() && !TT.isPPC() && "musttail not supported on ARM/PPC!"); call->setTailCallKind(CallInst::TCK_MustTail); @@ -755,7 +754,7 @@ std::pair CloneCtx::get_reloc_slot(Function *F) const if (F->isDeclaration()) { auto extern_decl = extern_relocs.find(F); assert(extern_decl != extern_relocs.end() && "Missing extern relocation slot!"); - return {(uint32_t)-1, extern_decl->second}; + return {UINT32_MAX, extern_decl->second}; } else { auto id = get_func_id(F); @@ -875,47 +874,28 @@ static Constant *get_ptrdiff32(Type *T_size, Constant *ptr, Constant *base) if (ptr->getType()->isPointerTy()) ptr = ConstantExpr::getPtrToInt(ptr, T_size); auto ptrdiff = ConstantExpr::getSub(ptr, base); - return sizeof(void*) == 8 ? ConstantExpr::getTrunc(ptrdiff, Type::getInt32Ty(ptr->getContext())) : ptrdiff; + return T_size->getPrimitiveSizeInBits() > 32 ? ConstantExpr::getTrunc(ptrdiff, Type::getInt32Ty(ptr->getContext())) : ptrdiff; } -template -static Constant *emit_offset_table(Module &M, Type *T_size, const SmallVectorImpl &vars, - StringRef name, StringRef suffix) +static void emit_table(Module &M, Type *T_size, ArrayRef vars, StringRef name, StringRef suffix) { - auto T_int32 = Type::getInt32Ty(M.getContext()); uint32_t nvars = vars.size(); - Constant *base = nullptr; - if (nvars > 0) { - base = ConstantExpr::getBitCast(vars[0], T_size->getPointerTo()); - auto ga = GlobalAlias::create(T_size, 0, GlobalVariable::ExternalLinkage, - name + "_base" + suffix, - base, &M); - ga->setVisibility(GlobalValue::HiddenVisibility); - ga->setDSOLocal(true); - } else { - auto gv = new GlobalVariable(M, T_size, true, GlobalValue::ExternalLinkage, Constant::getNullValue(T_size), name + "_base" + suffix); - gv->setVisibility(GlobalValue::HiddenVisibility); - gv->setDSOLocal(true); - base = gv; - } - auto vbase = ConstantExpr::getPtrToInt(base, T_size); - SmallVector offsets(nvars + 1); - offsets[0] = ConstantInt::get(T_int32, nvars); - if (nvars > 0) { - offsets[1] = ConstantInt::get(T_int32, 0); - for (uint32_t i = 1; i < nvars; i++) - offsets[i + 1] = get_ptrdiff32(T_size, vars[i], vbase); - } - ArrayType *vars_type = ArrayType::get(T_int32, nvars + 1); - auto gv = new GlobalVariable(M, vars_type, true, - GlobalVariable::ExternalLinkage, - ConstantArray::get(vars_type, offsets), - name + "_offsets" + suffix); + SmallVector castvars(nvars); + for (size_t i = 0; i < nvars; i++) + castvars[i] = ConstantExpr::getBitCast(vars[i], T_size->getPointerTo()); + auto gv = new GlobalVariable(M, T_size, true, GlobalValue::ExternalLinkage, ConstantInt::get(T_size, nvars), name + "_count" + suffix); + gv->setVisibility(GlobalValue::HiddenVisibility); + gv->setDSOLocal(true); + ArrayType *vars_type = ArrayType::get(T_size->getPointerTo(), nvars); + gv = new GlobalVariable(M, vars_type, false, + GlobalVariable::ExternalLinkage, + ConstantArray::get(vars_type, castvars), + name + "_ptrs" + suffix); gv->setVisibility(GlobalValue::HiddenVisibility); gv->setDSOLocal(true); - return vbase; } + void CloneCtx::emit_metadata() { uint32_t nfvars = fvars.size(); @@ -930,11 +910,8 @@ void CloneCtx::emit_metadata() } // Store back the information about exported functions. - auto fbase = emit_offset_table(M, T_size, fvars, "jl_fvar", suffix); - auto gbase = emit_offset_table(M, T_size, gvars, "jl_gvar", suffix); - + emit_table(M, T_size, ArrayRef((Constant* const*)fvars.data(), fvars.size()), "jl_fvar", suffix); M.getGlobalVariable("jl_fvar_idxs")->setName("jl_fvar_idxs" + suffix); - M.getGlobalVariable("jl_gvar_idxs")->setName("jl_gvar_idxs" + suffix); uint32_t ntargets = specs.size(); @@ -943,8 +920,8 @@ void CloneCtx::emit_metadata() { auto T_int32 = Type::getInt32Ty(M.getContext()); std::sort(gv_relocs.begin(), gv_relocs.end(), - [] (const std::pair &lhs, - const std::pair &rhs) { + [] (const std::pair &lhs, + const std::pair &rhs) { return lhs.second < rhs.second; }); SmallVector values{nullptr}; @@ -959,28 +936,31 @@ void CloneCtx::emit_metadata() gv_reloc_idx++) { shared_relocs.insert(id); values.push_back(id_v); - values.push_back(get_ptrdiff32(T_size, gv_relocs[gv_reloc_idx].first, gbase)); + values.push_back(gv_relocs[gv_reloc_idx].first); } auto it = const_relocs.find(id); if (it != const_relocs.end()) { shared_relocs.insert(id); values.push_back(id_v); - values.push_back(get_ptrdiff32(T_size, it->second, gbase)); + values.push_back(it->second); } } values[0] = ConstantInt::get(T_int32, values.size() / 2); ArrayType *vars_type = ArrayType::get(T_int32, values.size()); - auto gv = new GlobalVariable(M, vars_type, true, GlobalVariable::ExternalLinkage, - ConstantArray::get(vars_type, values), - "jl_clone_slots" + suffix); + auto gv = new GlobalVariable(M, vars_type, true, GlobalVariable::ExternalLinkage, nullptr, "jl_clone_slots" + suffix); + auto gbase = ConstantExpr::getPtrToInt(gv, T_size); + for (size_t i = 2; i < values.size(); i += 2) + values[i] = get_ptrdiff32(T_size, values[i], gbase); + gv->setInitializer(ConstantArray::get(vars_type, values)); gv->setVisibility(GlobalValue::HiddenVisibility); gv->setDSOLocal(true); } - // Generate `jl_dispatch_fvars_idxs` and `jl_dispatch_fvars_offsets` + // Generate `jl_dispatch_fvars_idxs` and `jl_dispatch_fvars` { SmallVector idxs; - SmallVector offsets; + SmallVector fptrs; + Type *Tfptr = T_size->getPointerTo(); for (uint32_t i = 0; i < ntargets; i++) { auto tgt = linearized[i]; auto &spec = specs[i]; @@ -996,7 +976,7 @@ void CloneCtx::emit_metadata() idxs.push_back(j); } if (i != 0) { - offsets.push_back(get_ptrdiff32(T_size, grp->base_func(fvars[j]), fbase)); + fptrs.push_back(grp->base_func(fvars[j])); } } } @@ -1010,12 +990,12 @@ void CloneCtx::emit_metadata() count++; idxs.push_back(jl_sysimg_tag_mask | j); auto f = map_get(*tgt->vmap, base_f, base_f); - offsets.push_back(get_ptrdiff32(T_size, cast(f), fbase)); + fptrs.push_back(cast(f)); } else if (auto f = map_get(*tgt->vmap, base_f)) { count++; idxs.push_back(j); - offsets.push_back(get_ptrdiff32(T_size, cast(f), fbase)); + fptrs.push_back(cast(f)); } } } @@ -1027,11 +1007,13 @@ void CloneCtx::emit_metadata() idxval, "jl_clone_idxs" + suffix); gv1->setVisibility(GlobalValue::HiddenVisibility); gv1->setDSOLocal(true); - ArrayType *offsets_type = ArrayType::get(Type::getInt32Ty(M.getContext()), offsets.size()); + for (size_t i = 0; i < fptrs.size(); i++) + fptrs[i] = ConstantExpr::getBitCast(fptrs[i], Tfptr); + ArrayType *offsets_type = ArrayType::get(Tfptr, fptrs.size()); auto gv2 = new GlobalVariable(M, offsets_type, true, GlobalVariable::ExternalLinkage, - ConstantArray::get(offsets_type, offsets), - "jl_clone_offsets" + suffix); + ConstantArray::get(offsets_type, fptrs), + "jl_clone_ptrs" + suffix); gv2->setVisibility(GlobalValue::HiddenVisibility); gv2->setDSOLocal(true); } @@ -1064,9 +1046,7 @@ static bool runMultiVersioning(Module &M, bool allow_bad_fvars) } GlobalVariable *fvars = M.getGlobalVariable("jl_fvars"); - GlobalVariable *gvars = M.getGlobalVariable("jl_gvars"); - if (allow_bad_fvars && (!fvars || !fvars->hasInitializer() || !isa(fvars->getInitializer()) || - !gvars || !gvars->hasInitializer() || !isa(gvars->getInitializer()))) + if (allow_bad_fvars && (!fvars || !fvars->hasInitializer() || !isa(fvars->getInitializer()))) return false; CloneCtx clone(M, allow_bad_fvars); diff --git a/src/llvm-pass-helpers.cpp b/src/llvm-pass-helpers.cpp index 543cdb137e570..f217d27035200 100644 --- a/src/llvm-pass-helpers.cpp +++ b/src/llvm-pass-helpers.cpp @@ -7,6 +7,7 @@ #include "llvm-version.h" +#include "llvm/IR/Attributes.h" #include #include #include @@ -25,9 +26,9 @@ JuliaPassContext::JuliaPassContext() pgcstack_getter(nullptr), adoptthread_func(nullptr), gc_flush_func(nullptr), gc_preserve_begin_func(nullptr), gc_preserve_end_func(nullptr), - pointer_from_objref_func(nullptr), alloc_obj_func(nullptr), - typeof_func(nullptr), write_barrier_func(nullptr), - call_func(nullptr), call2_func(nullptr), module(nullptr) + pointer_from_objref_func(nullptr), gc_loaded_func(nullptr), alloc_obj_func(nullptr), + typeof_func(nullptr), write_barrier_func(nullptr), pop_handler_noexcept_func(nullptr), + call_func(nullptr), call2_func(nullptr), call3_func(nullptr), module(nullptr) { } @@ -48,11 +49,14 @@ void JuliaPassContext::initFunctions(Module &M) gc_preserve_begin_func = M.getFunction("llvm.julia.gc_preserve_begin"); gc_preserve_end_func = M.getFunction("llvm.julia.gc_preserve_end"); pointer_from_objref_func = M.getFunction("julia.pointer_from_objref"); + gc_loaded_func = M.getFunction("julia.gc_loaded"); typeof_func = M.getFunction("julia.typeof"); write_barrier_func = M.getFunction("julia.write_barrier"); alloc_obj_func = M.getFunction("julia.gc_alloc_obj"); + pop_handler_noexcept_func = M.getFunction(XSTR(jl_pop_handler_noexcept)); call_func = M.getFunction("julia.call"); call2_func = M.getFunction("julia.call2"); + call3_func = M.getFunction("julia.call3"); } void JuliaPassContext::initAll(Module &M) @@ -121,9 +125,16 @@ namespace jl_intrinsics { // Annotates a function with attributes suitable for GC allocation // functions. Specifically, the return value is marked noalias and nonnull. - // The allocation size is set to the first argument. static Function *addGCAllocAttributes(Function *target) { + auto FnAttrs = AttrBuilder(target->getContext()); +#if JL_LLVM_VERSION >= 160000 + FnAttrs.addMemoryAttr(MemoryEffects::argMemOnly(ModRefInfo::Ref) | MemoryEffects::inaccessibleMemOnly(ModRefInfo::ModRef)); +#endif + FnAttrs.addAllocKindAttr(AllocFnKind::Alloc); + FnAttrs.addAttribute(Attribute::WillReturn); + FnAttrs.addAttribute(Attribute::NoUnwind); + target->addFnAttrs(FnAttrs); addRetAttr(target, Attribute::NoAlias); addRetAttr(target, Attribute::NonNull); return target; @@ -216,7 +227,11 @@ namespace jl_intrinsics { false), Function::ExternalLinkage, QUEUE_GC_ROOT_NAME); +#if JL_LLVM_VERSION >= 160000 + intrinsic->setMemoryEffects(MemoryEffects::inaccessibleOrArgMemOnly()); +#else intrinsic->addFnAttr(Attribute::InaccessibleMemOrArgMemOnly); +#endif return intrinsic; }); @@ -232,7 +247,11 @@ namespace jl_intrinsics { false), Function::ExternalLinkage, SAFEPOINT_NAME); +#if JL_LLVM_VERSION >= 160000 + intrinsic->setMemoryEffects(MemoryEffects::inaccessibleOrArgMemOnly()); +#else intrinsic->addFnAttr(Attribute::InaccessibleMemOrArgMemOnly); +#endif return intrinsic; }); } @@ -289,7 +308,11 @@ namespace jl_well_known { false), Function::ExternalLinkage, GC_QUEUE_ROOT_NAME); +#if JL_LLVM_VERSION >= 160000 + func->setMemoryEffects(MemoryEffects::inaccessibleOrArgMemOnly()); +#else func->addFnAttr(Attribute::InaccessibleMemOrArgMemOnly); +#endif return func; }); diff --git a/src/llvm-pass-helpers.h b/src/llvm-pass-helpers.h index 97cc2a03415b2..d0ac7faa01d2c 100644 --- a/src/llvm-pass-helpers.h +++ b/src/llvm-pass-helpers.h @@ -56,11 +56,14 @@ struct JuliaPassContext { llvm::Function *gc_preserve_begin_func; llvm::Function *gc_preserve_end_func; llvm::Function *pointer_from_objref_func; + llvm::Function *gc_loaded_func; llvm::Function *alloc_obj_func; llvm::Function *typeof_func; llvm::Function *write_barrier_func; + llvm::Function *pop_handler_noexcept_func; llvm::Function *call_func; llvm::Function *call2_func; + llvm::Function *call3_func; // Creates a pass context. Type and function pointers // are set to `nullptr`. Metadata nodes are initialized. diff --git a/src/llvm-propagate-addrspaces.cpp b/src/llvm-propagate-addrspaces.cpp index 1df6daca818a3..485eabefb5f8b 100644 --- a/src/llvm-propagate-addrspaces.cpp +++ b/src/llvm-propagate-addrspaces.cpp @@ -60,7 +60,7 @@ struct PropagateJuliaAddrspacesVisitor : public InstVisitor(V->getType())->getAddressSpace(); + return V->getType()->getPointerAddressSpace(); } static bool isSpecialAS(unsigned AS) { @@ -105,7 +105,6 @@ Value *PropagateJuliaAddrspacesVisitor::LiftPointer(Module *M, Value *V, Instruc } else if (auto *GEP = dyn_cast(CurrentV)) { if (LiftingMap.count(GEP)) { - CurrentV = LiftingMap[GEP]; break; } else if (Visited.count(GEP)) { return nullptr; @@ -140,7 +139,7 @@ Value *PropagateJuliaAddrspacesVisitor::LiftPointer(Module *M, Value *V, Instruc break; } else { // Ok, we've reached a leaf - check if it is eligible for lifting - if (!CurrentV->getType()->isPointerTy() || + if (!CurrentV->getType()->isPtrOrPtrVectorTy() || isSpecialAS(getValueAddrSpace(CurrentV))) { // If not, poison all (recursive) users of this value, to prevent // looking at them again in future iterations. diff --git a/src/llvm-ptls.cpp b/src/llvm-ptls.cpp index 050750fbf10f5..d2650d6875cd4 100644 --- a/src/llvm-ptls.cpp +++ b/src/llvm-ptls.cpp @@ -63,7 +63,11 @@ struct LowerPTLS { void LowerPTLS::set_pgcstack_attrs(CallInst *pgcstack) const { +#if JL_LLVM_VERSION >= 160000 + pgcstack->addFnAttr(Attribute::getWithMemoryEffects(pgcstack->getContext(), MemoryEffects::none())); +#else addFnAttr(pgcstack, Attribute::ReadNone); +#endif addFnAttr(pgcstack, Attribute::NoUnwind); } diff --git a/src/llvm-remove-addrspaces.cpp b/src/llvm-remove-addrspaces.cpp index b0a68ade4c42b..3e3315cd43672 100644 --- a/src/llvm-remove-addrspaces.cpp +++ b/src/llvm-remove-addrspaces.cpp @@ -208,7 +208,14 @@ bool RemoveNoopAddrSpaceCasts(Function *F) LLVM_DEBUG( dbgs() << "Removing noop address space cast:\n" << I << "\n"); - ASC->replaceAllUsesWith(ASC->getOperand(0)); + if (ASC->getType() == ASC->getOperand(0)->getType()) { + ASC->replaceAllUsesWith(ASC->getOperand(0)); + } else { + // uncanonicalized addrspacecast; demote to bitcast + llvm::IRBuilder<> builder(ASC); + auto BC = builder.CreateBitCast(ASC->getOperand(0), ASC->getType()); + ASC->replaceAllUsesWith(BC); + } NoopCasts.push_back(ASC); } } @@ -335,7 +342,7 @@ bool removeAddrspaces(Module &M, AddrspaceRemapFunction ASRemapper) GlobalVariable *NGV = cast(VMap[GV]); if (GV->hasInitializer()) - NGV->setInitializer(MapValue(GV->getInitializer(), VMap)); + NGV->setInitializer(MapValue(GV->getInitializer(), VMap, RF_None, &TypeRemapper, &Materializer)); SmallVector, 1> MDs; GV->getAllMetadata(MDs); @@ -400,7 +407,7 @@ bool removeAddrspaces(Module &M, AddrspaceRemapFunction ASRemapper) for (GlobalAlias *GA : Aliases) { GlobalAlias *NGA = cast(VMap[GA]); if (const Constant *C = GA->getAliasee()) - NGA->setAliasee(MapValue(C, VMap)); + NGA->setAliasee(MapValue(C, VMap, RF_None, &TypeRemapper, &Materializer)); GA->setAliasee(nullptr); } @@ -423,7 +430,7 @@ bool removeAddrspaces(Module &M, AddrspaceRemapFunction ASRemapper) for (Module::iterator FI = M.begin(), FE = M.end(); FI != FE;) { Function *F = &*FI++; if (auto Remangled = Intrinsic::remangleIntrinsicFunction(F)) { - F->replaceAllUsesWith(Remangled.getValue()); + F->replaceAllUsesWith(*Remangled); F->eraseFromParent(); } } diff --git a/src/llvm-simdloop.cpp b/src/llvm-simdloop.cpp index 62fb26ac163a4..f29802b438e1e 100644 --- a/src/llvm-simdloop.cpp +++ b/src/llvm-simdloop.cpp @@ -191,7 +191,7 @@ static bool processLoop(Loop &L, OptimizationRemarkEmitter &ORE, ScalarEvolution LLVM_DEBUG(dbgs() << "LSL: simd: " << simd << " ivdep: " << ivdep << "\n"); if (!simd && !ivdep) return false; - + ++TotalMarkedLoops; LLVMContext &Context = L.getHeader()->getContext(); LoopID = MDNode::get(Context, MDs); // Set operand 0 to refer to the loop id itself @@ -231,7 +231,11 @@ static bool processLoop(Loop &L, OptimizationRemarkEmitter &ORE, ScalarEvolution } if (SE) +#if JL_LLVM_VERSION >= 160000 + SE->forgetLoopDispositions(); +#else SE->forgetLoopDispositions(&L); +#endif } #ifdef JL_VERIFY_PASSES diff --git a/src/llvm-version.h b/src/llvm-version.h index 01638b8d44a6e..f7da953a99562 100644 --- a/src/llvm-version.h +++ b/src/llvm-version.h @@ -10,23 +10,14 @@ #define JL_LLVM_VERSION (LLVM_VERSION_MAJOR * 10000 + LLVM_VERSION_MINOR * 100 \ + LLVM_VERSION_PATCH) -#if JL_LLVM_VERSION < 140000 - #error Only LLVM versions >= 14.0.0 are supported by Julia +#if JL_LLVM_VERSION < 150000 + #error Only LLVM versions >= 15.0.0 are supported by Julia #endif #if JL_LLVM_VERSION >= 160000 #define JL_LLVM_OPAQUE_POINTERS 1 #endif -// Pre GCC 12 libgcc defined the ABI for Float16->Float32 -// to take an i16. GCC 12 silently changed the ABI to now pass -// Float16 in Float32 registers. -#if JL_LLVM_VERSION < 150000 || defined(_CPU_PPC64_) || defined(_CPU_PPC_) -#define JULIA_FLOAT16_ABI 1 -#else -#define JULIA_FLOAT16_ABI 2 -#endif - #ifdef __cplusplus #if defined(__GNUC__) && (__GNUC__ >= 9) // Added in GCC 9, this warning is annoying diff --git a/src/mach_excServer.c b/src/mach_excServer.c index 7e99331fa8554..669fd0e2313aa 100644 --- a/src/mach_excServer.c +++ b/src/mach_excServer.c @@ -20,7 +20,7 @@ * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this file. + * https://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER diff --git a/src/macroexpand.scm b/src/macroexpand.scm index e0e809eee08f1..74d47aec45479 100644 --- a/src/macroexpand.scm +++ b/src/macroexpand.scm @@ -126,6 +126,16 @@ (else '()))) (else '())))))) + ;; for/generator + (pattern-lambda (for assgn body) + (if (eq? (car assgn) 'block) + `(varlist ,@(map cadr (cdr assgn))) + (cons 'varlist (cadr assgn)))) + (pattern-lambda (generator body (filter filt . assgn)) + (cons 'varlist (map (lambda (x) (cadr x)) assgn))) + (pattern-lambda (generator body . assgn) + (cons 'varlist (map (lambda (x) (cadr x)) assgn))) + ;; macro definition (pattern-lambda (macro (call name . argl) body) `(-> (tuple ,@argl) ,body)) @@ -184,18 +194,18 @@ (unescape (cadr e)) e)) -(define (unescape-global-lhs e env m parent-scope inarg) +(define (unescape-global-lhs e env m lno parent-scope inarg) (cond ((not (pair? e)) e) - ((eq? (car e) 'escape) (unescape-global-lhs (cadr e) env m parent-scope inarg)) + ((eq? (car e) 'escape) (unescape-global-lhs (cadr e) env m lno parent-scope inarg)) ((memq (car e) '(parameters tuple)) (list* (car e) (map (lambda (e) - (unescape-global-lhs e env m parent-scope inarg)) + (unescape-global-lhs e env m lno parent-scope inarg)) (cdr e)))) ((and (memq (car e) '(|::| kw)) (length= e 3)) - (list (car e) (unescape-global-lhs (cadr e) env m parent-scope inarg) - (resolve-expansion-vars-with-new-env (caddr e) env m parent-scope inarg))) + (list (car e) (unescape-global-lhs (cadr e) env m lno parent-scope inarg) + (resolve-expansion-vars-with-new-env (caddr e) env m lno parent-scope inarg))) (else - (resolve-expansion-vars-with-new-env e env m parent-scope inarg)))) + (resolve-expansion-vars-with-new-env e env m lno parent-scope inarg)))) (define (typedef-expr-name e) (cond ((atom? e) e) @@ -280,18 +290,18 @@ ;; resolve-expansion-vars-with-new-env, but turn on `inarg` if we get inside ;; a formal argument list. `e` in general might be e.g. `(f{T}(x)::T) where T`, ;; and we want `inarg` to be true for the `(x)` part. -(define (resolve-in-lhs e env m parent-scope inarg) - (define (recur x) (resolve-in-lhs x env m parent-scope inarg)) - (define (other x) (resolve-expansion-vars-with-new-env x env m parent-scope inarg)) +(define (resolve-in-lhs e env m lno parent-scope inarg) + (define (recur x) (resolve-in-lhs x env m lno parent-scope inarg)) + (define (other x) (resolve-expansion-vars-with-new-env x env m lno parent-scope inarg)) (case (and (pair? e) (car e)) ((where) `(where ,(recur (cadr e)) ,@(map other (cddr e)))) ((|::|) `(|::| ,(recur (cadr e)) ,(other (caddr e)))) ((call) `(call ,(other (cadr e)) ,@(map (lambda (x) - (resolve-expansion-vars-with-new-env x env m parent-scope #t)) + (resolve-expansion-vars-with-new-env x env m lno parent-scope #t)) (cddr e)))) ((tuple) `(tuple ,@(map (lambda (x) - (resolve-expansion-vars-with-new-env x env m parent-scope #t)) + (resolve-expansion-vars-with-new-env x env m lno parent-scope #t)) (cdr e)))) (else (other e)))) @@ -328,7 +338,7 @@ (keywords-introduced-by x)) env))))))) -(define (resolve-expansion-vars-with-new-env x env m parent-scope inarg (outermost #f)) +(define (resolve-expansion-vars-with-new-env x env m lno parent-scope inarg (outermost #f)) (resolve-expansion-vars- x (if (and (pair? x) (eq? (car x) 'let)) @@ -336,14 +346,50 @@ ;; the same expression env (new-expansion-env-for x env outermost)) - m parent-scope inarg)) + m lno parent-scope inarg)) (define (reescape ux x) (if (and (pair? x) (eq? (car x) 'escape)) - (reescape '(escape ,ux) (cadr x))) - ux) - -(define (resolve-expansion-vars- e env m parent-scope inarg) + (reescape `(escape ,ux) (cadr x)) + ux)) + +;; type has special behavior: identifiers inside are +;; field names, not expressions. +(define (resolve-struct-field-expansion x env m lno parent-scope inarg) + (let ((ux (unescape x))) + (cond + ((atom? ux) ux) + ((and (pair? ux) (eq? (car ux) '|::|)) + `(|::| ,(unescape (cadr ux)) + ,(resolve-expansion-vars- (reescape (caddr ux) x) env m lno parent-scope inarg))) + ((and (pair? ux) (memq (car ux) '(const atomic))) + `(,(car ux) ,(resolve-struct-field-expansion (reescape (cadr ux) x) env m lno parent-scope inarg))) + (else + (resolve-expansion-vars-with-new-env x env m lno parent-scope inarg))))) + +(define (resolve-letlike-assign bind env newenv m lno parent-scope inarg) + (if (assignment? bind) + (make-assignment + ;; expand binds in newenv with dummy RHS + (cadr (resolve-expansion-vars- (make-assignment (cadr bind) 0) + newenv m lno parent-scope inarg)) + ;; expand initial values in old env + (resolve-expansion-vars- (caddr bind) env m lno parent-scope inarg)) + ;; Just expand everything else that's not an assignment. N.B.: This includes + ;; assignments inside escapes, which probably need special handling (TODO). + (resolve-expansion-vars- bind newenv m lno parent-scope inarg))) + +(define (for-ranges-list ranges) + (if (eq? (car ranges) 'escape) + (map (lambda (range) `(escape ,range)) (for-ranges-list (cadr ranges))) + (if (eq? (car ranges) 'block) + (cdr ranges) + (list ranges)))) + +(define (just-line? ex) + (and (pair? ex) (eq? (car ex) 'line) (atom? (cadr ex)) (or (atom? (caddr ex)) (nothing? (caddr ex))))) + +(define (resolve-expansion-vars- e env m lno parent-scope inarg) (cond ((or (eq? e 'begin) (eq? e 'end) (eq? e 'ccall) (eq? e 'cglobal) (underscore-symbol? e)) e) ((symbol? e) @@ -362,31 +408,35 @@ (env (car scope)) (m (cadr scope)) (parent-scope (cdr parent-scope))) - (resolve-expansion-vars-with-new-env (cadr e) env m parent-scope inarg)))) + (resolve-expansion-vars-with-new-env (cadr e) env m lno parent-scope inarg)))) ((global) `(global ,@(map (lambda (arg) (if (assignment? arg) - `(= ,(unescape-global-lhs (cadr arg) env m parent-scope inarg) - ,(resolve-expansion-vars-with-new-env (caddr arg) env m parent-scope inarg)) - (unescape-global-lhs arg env m parent-scope inarg))) + `(= ,(unescape-global-lhs (cadr arg) env m lno parent-scope inarg) + ,(resolve-expansion-vars-with-new-env (caddr arg) env m lno parent-scope inarg)) + (unescape-global-lhs arg env m lno parent-scope inarg))) (cdr e)))) - ((using import export meta line inbounds boundscheck loopinfo inline noinline) (map unescape e)) + ((toplevel) ; re-wrap Expr(:toplevel) in the current hygienic-scope(s) + `(toplevel + ,@(map (lambda (arg) + ;; Minor optimization: A lot of toplevel exprs have just bare line numbers in them. + ;; don't bother with the full rewrapping in that case (even though + ;; this would be semantically legal) - lowering won't touch them anyways. + (if (just-line? arg) arg + (let loop ((parent-scope parent-scope) (m m) (lno lno) (arg arg)) + (let ((wrapped `(hygienic-scope ,arg ,m ,@lno))) + (if (null? parent-scope) wrapped + (loop (cdr parent-scope) (cadar parent-scope) (caddar parent-scope) wrapped)))))) + (cdr e)))) + ((using import export meta line inbounds boundscheck loopinfo inline noinline purity) (map unescape e)) ((macrocall) e) ; invalid syntax anyways, so just act like it's quoted. ((symboliclabel) e) ((symbolicgoto) e) ((struct) - `(struct ,(cadr e) ,(resolve-expansion-vars- (caddr e) env m parent-scope inarg) - ;; type has special behavior: identifiers inside are - ;; field names, not expressions. + `(struct ,(cadr e) ,(resolve-expansion-vars- (caddr e) env m lno parent-scope inarg) ,(map (lambda (x) - (let ((ux (unescape x))) - (cond ((atom? ux) ux) - ((and (pair? ux) (eq? (car ux) '|::|)) - `(|::| ,(unescape (cadr ux)) - ,(resolve-expansion-vars- (reescape (caddr ux) x) env m parent-scope inarg))) - (else - (resolve-expansion-vars-with-new-env x env m parent-scope inarg))))) + (resolve-struct-field-expansion x env m lno parent-scope inarg)) (cadddr e)))) ((parameters) @@ -397,17 +447,17 @@ (x (if (and (not inarg) (symbol? ux)) `(kw ,ux ,x) x))) - (resolve-expansion-vars- x env m parent-scope #f))) + (resolve-expansion-vars- x env m lno parent-scope #f))) (cdr e)))) ((->) - `(-> ,(resolve-in-lhs (tuple-wrap-arrow-sig (cadr e)) env m parent-scope inarg) - ,(resolve-expansion-vars-with-new-env (caddr e) env m parent-scope inarg))) + `(-> ,(resolve-in-lhs (tuple-wrap-arrow-sig (cadr e)) env m lno parent-scope inarg) + ,(resolve-expansion-vars-with-new-env (caddr e) env m lno parent-scope inarg))) ((= function) - `(,(car e) ,(resolve-in-lhs (cadr e) env m parent-scope inarg) + `(,(car e) ,(resolve-in-lhs (cadr e) env m lno parent-scope inarg) ,@(map (lambda (x) - (resolve-expansion-vars-with-new-env x env m parent-scope inarg)) + (resolve-expansion-vars-with-new-env x env m lno parent-scope inarg)) (cddr e)))) ((kw) @@ -421,55 +471,67 @@ `(kw (|::| ,@(if argname (list (if inarg - (resolve-expansion-vars- argname env m parent-scope inarg) + (resolve-expansion-vars- argname env m lno parent-scope inarg) ;; in keyword arg A=B, don't transform "A" (unescape argname))) '()) - ,(resolve-expansion-vars- type env m parent-scope inarg)) - ,(resolve-expansion-vars-with-new-env (caddr e) env m parent-scope inarg)))) + ,(resolve-expansion-vars- type env m lno parent-scope inarg)) + ,(resolve-expansion-vars-with-new-env (caddr e) env m lno parent-scope inarg)))) (else `(kw ,(if inarg - (resolve-expansion-vars- (cadr e) env m parent-scope inarg) + (resolve-expansion-vars- (cadr e) env m lno parent-scope inarg) (unescape (cadr e))) - ,(resolve-expansion-vars-with-new-env (caddr e) env m parent-scope inarg))))) + ,(resolve-expansion-vars-with-new-env (caddr e) env m lno parent-scope inarg))))) ((let) (let* ((newenv (new-expansion-env-for e env)) - (body (resolve-expansion-vars- (caddr e) newenv m parent-scope inarg)) + (body (resolve-expansion-vars- (caddr e) newenv m lno parent-scope inarg)) (binds (let-binds e))) `(let (block ,@(map (lambda (bind) - (if (assignment? bind) - (make-assignment - ;; expand binds in old env with dummy RHS - (cadr (resolve-expansion-vars- (make-assignment (cadr bind) 0) - newenv m parent-scope inarg)) - ;; expand initial values in old env - (resolve-expansion-vars- (caddr bind) env m parent-scope inarg)) - (resolve-expansion-vars- bind newenv m parent-scope inarg))) + (resolve-letlike-assign bind env newenv m lno parent-scope inarg)) binds)) ,body))) + ((for) + (let* ((newenv (new-expansion-env-for e env)) + (body (resolve-expansion-vars- (caddr e) newenv m lno parent-scope inarg)) + (expanded-ranges (map (lambda (range) + (resolve-letlike-assign range env newenv m lno parent-scope inarg)) (for-ranges-list (cadr e))))) + (if (length= expanded-ranges 1) + `(for ,@expanded-ranges ,body)) + `(for (block ,@expanded-ranges) ,body))) + ((generator) + (let* ((newenv (new-expansion-env-for e env)) + (body (resolve-expansion-vars- (cadr e) newenv m lno parent-scope inarg)) + (filt? (eq? (car (caddr e)) 'filter)) + (range-exprs (if filt? (cddr (caddr e)) (cddr e))) + (filt (if filt? (resolve-expansion-vars- (cadr (caddr e)) newenv m lno parent-scope inarg))) + (expanded-ranges (map (lambda (range) + (resolve-letlike-assign range env newenv m lno parent-scope inarg)) range-exprs))) + (if filt? + `(generator ,body (filter ,filt ,@expanded-ranges)) + `(generator ,body ,@expanded-ranges)))) ((hygienic-scope) ; TODO: move this lowering to resolve-scopes, instead of reimplementing it here badly - (let ((parent-scope (cons (list env m) parent-scope)) + (let ((parent-scope (cons (list env m lno) parent-scope)) (body (cadr e)) (m (caddr e)) (lno (cdddr e))) - (resolve-expansion-vars-with-new-env body env m parent-scope inarg #t))) + (resolve-expansion-vars-with-new-env body env m lno parent-scope inarg #t))) ((tuple) (cons (car e) (map (lambda (x) (if (assignment? x) `(= ,(unescape (cadr x)) - ,(resolve-expansion-vars-with-new-env (caddr x) env m parent-scope inarg)) - (resolve-expansion-vars-with-new-env x env m parent-scope inarg))) + ,(resolve-expansion-vars-with-new-env (caddr x) env m lno parent-scope inarg)) + (resolve-expansion-vars-with-new-env x env m lno parent-scope inarg))) (cdr e)))) ;; todo: trycatch (else (cons (car e) (map (lambda (x) - (resolve-expansion-vars-with-new-env x env m parent-scope inarg)) + (resolve-expansion-vars-with-new-env x env m lno parent-scope inarg)) (cdr e)))))))) ;; decl-var that also identifies f in f()=... @@ -570,11 +632,11 @@ (cdr v) '()))) -(define (resolve-expansion-vars e m) +(define (resolve-expansion-vars e m lno) ;; expand binding form patterns ;; keep track of environment, rename locals to gensyms ;; and wrap globals in (globalref module var) for macro's home module - (resolve-expansion-vars-with-new-env e '() m '() #f #t)) + (resolve-expansion-vars-with-new-env e '() m lno '() #f #t)) (define (julia-expand-quotes e) (cond ((not (pair? e)) e) @@ -590,11 +652,12 @@ (cond ((not (pair? e)) e) ((eq? (car e) 'inert) e) ((eq? (car e) 'module) e) + ((eq? (car e) 'toplevel) e) ((eq? (car e) 'hygienic-scope) (let ((form (cadr e)) ;; form is the expression returned from expand-macros (modu (caddr e)) ;; m is the macro's def module (lno (cdddr e))) ;; lno is (optionally) the line number node - (resolve-expansion-vars form modu))) + (resolve-expansion-vars form modu lno))) (else (map julia-expand-macroscopes- e)))) diff --git a/src/method.c b/src/method.c index 7d8d0e9ec4a78..e3b19a5007b58 100644 --- a/src/method.c +++ b/src/method.c @@ -20,9 +20,6 @@ extern jl_value_t *jl_builtin_tuple; jl_methtable_t *jl_kwcall_mt; jl_method_t *jl_opaque_closure_method; -jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name, - int nargs, jl_value_t *functionloc, jl_code_info_t *ci, int isva); - static void check_c_types(const char *where, jl_value_t *rt, jl_value_t *at) { if (jl_is_svec(rt)) @@ -64,6 +61,21 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve } return expr; } + else if (jl_is_enternode(expr)) { + jl_value_t *scope = jl_enternode_scope(expr); + if (scope) { + jl_value_t *val = resolve_globals(scope, module, sparam_vals, binding_effects, eager_resolve); + if (val != scope) { + intptr_t catch_dest = jl_enternode_catch_dest(expr); + JL_GC_PUSH1(&val); + expr = jl_new_struct_uninit(jl_enternode_type); + jl_enternode_catch_dest(expr) = catch_dest; + jl_enternode_scope(expr) = val; + JL_GC_POP(); + } + } + return expr; + } else if (jl_is_gotoifnot(expr)) { jl_value_t *cond = resolve_globals(jl_gotoifnot_cond(expr), module, sparam_vals, binding_effects, eager_resolve); if (cond != jl_gotoifnot_cond(expr)) { @@ -94,7 +106,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve // ignore these } else { - size_t i = 0, nargs = jl_array_len(e->args); + size_t i = 0, nargs = jl_array_nrows(e->args); if (e->head == jl_opaque_closure_method_sym) { if (nargs != 5) { jl_error("opaque_closure_method: invalid syntax"); @@ -109,11 +121,13 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve } else if (!jl_is_long(oc_nargs)) { jl_type_error("opaque_closure_method", (jl_value_t*)jl_long_type, oc_nargs); } - jl_method_t *m = jl_make_opaque_closure_method(module, name, jl_unbox_long(oc_nargs), functionloc, (jl_code_info_t*)ci, isva); + jl_method_t *m = jl_make_opaque_closure_method(module, name, + jl_unbox_long(oc_nargs), functionloc, (jl_code_info_t*)ci, isva, /*isinferred*/0); return (jl_value_t*)m; } if (e->head == jl_cfunction_sym) { JL_NARGS(cfunction method definition, 5, 5); // (type, func, rt, at, cc) + jl_task_t *ct = jl_current_task; jl_value_t *typ = jl_exprarg(e, 0); if (!jl_is_type(typ)) jl_error("first parameter to :cfunction must be a type"); @@ -130,7 +144,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve rt = jl_interpret_toplevel_expr_in(module, rt, NULL, sparam_vals); } JL_CATCH { - if (jl_typetagis(jl_current_exception(), jl_errorexception_type)) + if (jl_typetagis(jl_current_exception(ct), jl_errorexception_type)) jl_error("could not evaluate cfunction return type (it might depend on a local variable)"); else jl_rethrow(); @@ -142,7 +156,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve at = jl_interpret_toplevel_expr_in(module, at, NULL, sparam_vals); } JL_CATCH { - if (jl_typetagis(jl_current_exception(), jl_errorexception_type)) + if (jl_typetagis(jl_current_exception(ct), jl_errorexception_type)) jl_error("could not evaluate cfunction argument type (it might depend on a local variable)"); else jl_rethrow(); @@ -156,6 +170,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve } if (e->head == jl_foreigncall_sym) { JL_NARGSV(ccall method definition, 5); // (fptr, rt, at, nreq, (cc, effects)) + jl_task_t *ct = jl_current_task; jl_value_t *rt = jl_exprarg(e, 1); jl_value_t *at = jl_exprarg(e, 2); if (!jl_is_type(rt)) { @@ -163,7 +178,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve rt = jl_interpret_toplevel_expr_in(module, rt, NULL, sparam_vals); } JL_CATCH { - if (jl_typetagis(jl_current_exception(), jl_errorexception_type)) + if (jl_typetagis(jl_current_exception(ct), jl_errorexception_type)) jl_error("could not evaluate ccall return type (it might depend on a local variable)"); else jl_rethrow(); @@ -175,7 +190,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve at = jl_interpret_toplevel_expr_in(module, at, NULL, sparam_vals); } JL_CATCH { - if (jl_typetagis(jl_current_exception(), jl_errorexception_type)) + if (jl_typetagis(jl_current_exception(ct), jl_errorexception_type)) jl_error("could not evaluate ccall argument type (it might depend on a local variable)"); else jl_rethrow(); @@ -192,12 +207,12 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve jl_error("In ccall calling convention, expected two argument tuple or symbol."); } JL_TYPECHK(ccall method definition, symbol, jl_get_nth_field(cc, 0)); - JL_TYPECHK(ccall method definition, uint8, jl_get_nth_field(cc, 1)); + JL_TYPECHK(ccall method definition, uint16, jl_get_nth_field(cc, 1)); } jl_exprargset(e, 0, resolve_globals(jl_exprarg(e, 0), module, sparam_vals, binding_effects, 1)); i++; } - if (e->head == jl_method_sym || e->head == jl_module_sym) { + if (e->head == jl_method_sym || e->head == jl_module_sym || e->head == jl_throw_undef_if_not_sym) { i++; } for (; i < nargs; i++) { @@ -251,6 +266,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve val = jl_interpret_toplevel_expr_in(module, (jl_value_t*)e, NULL, sparam_vals); } JL_CATCH { + val = NULL; // To make the analyzer happy see #define JL_TRY } if (val) return val; @@ -266,7 +282,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve JL_DLLEXPORT void jl_resolve_globals_in_ir(jl_array_t *stmts, jl_module_t *m, jl_svec_t *sparam_vals, int binding_effects) { - size_t i, l = jl_array_len(stmts); + size_t i, l = jl_array_nrows(stmts); for (i = 0; i < l; i++) { jl_value_t *stmt = jl_array_ptr_ref(stmts, i); jl_array_ptr_set(stmts, i, resolve_globals(stmt, m, sparam_vals, binding_effects, 0)); @@ -278,34 +294,155 @@ jl_value_t *expr_arg1(jl_value_t *expr) { return jl_array_ptr_ref(args, 0); } +static jl_value_t *alloc_edges(arraylist_t *edges_list) +{ + jl_value_t *jledges = (jl_value_t*)jl_alloc_svec(edges_list->len); + jl_value_t *jledges2 = NULL; + jl_value_t *codelocs = NULL; + JL_GC_PUSH3(&jledges, &jledges2, &codelocs); + size_t i; + for (i = 0; i < edges_list->len; i++) { + arraylist_t *edge = (arraylist_t*)edges_list->items[i]; + jl_value_t *file = (jl_value_t*)edge->items[0]; + int32_t line = 0; // not preserved by lowering (and probably lost even before that) + arraylist_t *edges_list2 = (arraylist_t*)edge->items[1]; + size_t j, nlocs = (edge->len - 2) / 3; + codelocs = (jl_value_t*)jl_alloc_array_1d(jl_array_int32_type, nlocs * 3); + for (j = 0; j < nlocs; j++) { + jl_array_data(codelocs,int32_t)[3 * j + 0] = (intptr_t)edge->items[3 * j + 0 + 2]; + jl_array_data(codelocs,int32_t)[3 * j + 1] = (intptr_t)edge->items[3 * j + 1 + 2]; + jl_array_data(codelocs,int32_t)[3 * j + 2] = (intptr_t)edge->items[3 * j + 2 + 2]; + } + codelocs = (jl_value_t*)jl_compress_codelocs(line, codelocs, nlocs); + jledges2 = alloc_edges(edges_list2); + jl_value_t *debuginfo = jl_new_struct(jl_debuginfo_type, file, jl_nothing, jledges2, codelocs); + jledges2 = NULL; + jl_svecset(jledges, i, debuginfo); + free(edges_list2); + free(edge); + } + JL_GC_POP(); + return jledges; +} + +static void add_edge(arraylist_t *edges_list, arraylist_t *inlinestack, int32_t *p_to, int32_t *p_pc) +{ + jl_value_t *locinfo = (jl_value_t*)arraylist_pop(inlinestack); + jl_sym_t *filesym = (jl_sym_t*)jl_fieldref_noalloc(locinfo, 0); + int32_t line = jl_unbox_int32(jl_fieldref(locinfo, 1)); + size_t i; + arraylist_t *edge = NULL; + for (i = 0; i < edges_list->len; i++) { + edge = (arraylist_t*)edges_list->items[i]; + if (edge->items[0] == filesym) + break; + } + if (i == edges_list->len) { + edge = (arraylist_t*)malloc(sizeof(arraylist_t)); + arraylist_t *edge_list2 = (arraylist_t*)malloc(sizeof(arraylist_t)); + arraylist_new(edge, 0); + arraylist_new(edge_list2, 0); + arraylist_push(edge, (void*)filesym); + arraylist_push(edge, (void*)edge_list2); + arraylist_push(edges_list, (void*)edge); + } + *p_to = i + 1; + int32_t to = 0, pc = 0; + if (inlinestack->len) { + arraylist_t *edge_list2 = (arraylist_t*)edge->items[1]; + add_edge(edge_list2, inlinestack, &to, &pc); + } + for (i = 2; i < edge->len; i += 3) { + if ((intptr_t)edge->items[i + 0] == line && + (intptr_t)edge->items[i + 1] == to && + (intptr_t)edge->items[i + 2] == pc) { + break; + } + } + if (i == edge->len) { + arraylist_push(edge, (void*)(intptr_t)line); + arraylist_push(edge, (void*)(intptr_t)to); + arraylist_push(edge, (void*)(intptr_t)pc); + } + *p_pc = (i - 2) / 3 + 1; +} + +jl_debuginfo_t *jl_linetable_to_debuginfo(jl_array_t *codelocs_any, jl_array_t *linetable) +{ + size_t nlocs = jl_array_nrows(codelocs_any); + jl_value_t *toplocinfo = jl_array_ptr_ref(linetable, 0); + jl_sym_t *topfile = (jl_sym_t*)jl_fieldref_noalloc(toplocinfo, 0); + int32_t topline = jl_unbox_int32(jl_fieldref(toplocinfo, 1)); + arraylist_t inlinestack; + arraylist_new(&inlinestack, 0); + arraylist_t edges_list; + arraylist_new(&edges_list, 0); + jl_value_t *jledges = NULL; + jl_value_t *codelocs = (jl_value_t*)jl_alloc_array_1d(jl_array_int32_type, nlocs * 3); + jl_debuginfo_t *debuginfo = NULL; + JL_GC_PUSH3(&jledges, &codelocs, &debuginfo); + int32_t *codelocs32 = jl_array_data(codelocs,int32_t); + size_t j; + for (j = 0; j < nlocs; j++) { + size_t lineidx = jl_unbox_long(jl_array_ptr_ref((jl_array_t*)codelocs_any, j)); // 1 indexed! + while (lineidx != 0) { + jl_value_t *locinfo = jl_array_ptr_ref(linetable, lineidx - 1); + lineidx = jl_unbox_int32(jl_fieldref(locinfo, 2)); + arraylist_push(&inlinestack, locinfo); + } + int32_t line = 0, to = 0, pc = 0; + if (inlinestack.len) { + jl_value_t *locinfo = (jl_value_t*)arraylist_pop(&inlinestack); + jl_sym_t *filesym = (jl_sym_t*)jl_fieldref_noalloc(locinfo, 0); + if (filesym == topfile) + line = jl_unbox_int32(jl_fieldref(locinfo, 1)); + else + arraylist_push(&inlinestack, locinfo); + if (inlinestack.len) { + add_edge(&edges_list, &inlinestack, &to, &pc); + } + } + codelocs32[j * 3 + 0] = line; + codelocs32[j * 3 + 1] = to; + codelocs32[j * 3 + 2] = pc; + } + codelocs = (jl_value_t*)jl_compress_codelocs(topline, codelocs, nlocs); + jledges = alloc_edges(&edges_list); + debuginfo = (jl_debuginfo_t*)jl_new_struct(jl_debuginfo_type, topfile, jl_nothing, jledges, codelocs); + JL_GC_POP(); + return debuginfo; +} + // copy a :lambda Expr into its CodeInfo representation, // including popping of known meta nodes -static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir) +jl_code_info_t *jl_new_code_info_from_ir(jl_expr_t *ir) { + jl_code_info_t *li = NULL; + JL_GC_PUSH1(&li); + li = jl_new_code_info_uninit(); assert(jl_is_expr(ir)); jl_expr_t *bodyex = (jl_expr_t*)jl_exprarg(ir, 2); - jl_value_t *codelocs = jl_exprarg(ir, 3); - li->linetable = jl_exprarg(ir, 4); - size_t nlocs = jl_array_len(codelocs); - li->codelocs = (jl_value_t*)jl_alloc_array_1d(jl_array_int32_type, nlocs); - size_t j; - for (j = 0; j < nlocs; j++) { - jl_arrayset((jl_array_t*)li->codelocs, jl_box_int32(jl_unbox_long(jl_arrayref((jl_array_t*)codelocs, j))), - j); - } + + jl_array_t *codelocs_any = (jl_array_t*)jl_exprarg(ir, 3); + jl_array_t *linetable = (jl_array_t*)jl_exprarg(ir, 4); + li->debuginfo = jl_linetable_to_debuginfo(codelocs_any, linetable); + jl_gc_wb(li, li->debuginfo); + assert(jl_is_expr(bodyex)); jl_array_t *body = bodyex->args; li->code = body; jl_gc_wb(li, li->code); - size_t n = jl_array_len(body); + size_t n = jl_array_nrows(body); jl_value_t **bd = (jl_value_t**)jl_array_ptr_data((jl_array_t*)li->code); li->ssaflags = jl_alloc_array_1d(jl_array_uint32_type, n); jl_gc_wb(li, li->ssaflags); int inbounds_depth = 0; // number of stacked inbounds - // isempty(inline_flags): no user annotation - // last(inline_flags) == 1: inline region - // last(inline_flags) == 0: noinline region + // isempty(inline_flags): no user callsite inline annotation + // last(inline_flags) == 1: callsite inline region + // last(inline_flags) == 0: callsite noinline region arraylist_t *inline_flags = arraylist_new((arraylist_t*)malloc_s(sizeof(arraylist_t)), 0); + arraylist_t *purity_exprs = arraylist_new((arraylist_t*)malloc_s(sizeof(arraylist_t)), 0); + size_t j; for (j = 0; j < n; j++) { jl_value_t *st = bd[j]; int is_flag_stmt = 0; @@ -328,7 +465,7 @@ static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir) else if (ma == (jl_value_t*)jl_no_constprop_sym) li->constprop = 2; else if (jl_is_expr(ma) && ((jl_expr_t*)ma)->head == jl_purity_sym) { - if (jl_expr_nargs(ma) == 8) { + if (jl_expr_nargs(ma) == NUM_EFFECTS_OVERRIDES) { li->purity.overrides.ipo_consistent = jl_unbox_bool(jl_exprarg(ma, 0)); li->purity.overrides.ipo_effect_free = jl_unbox_bool(jl_exprarg(ma, 1)); li->purity.overrides.ipo_nothrow = jl_unbox_bool(jl_exprarg(ma, 2)); @@ -337,6 +474,7 @@ static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir) li->purity.overrides.ipo_notaskstate = jl_unbox_bool(jl_exprarg(ma, 5)); li->purity.overrides.ipo_inaccessiblememonly = jl_unbox_bool(jl_exprarg(ma, 6)); li->purity.overrides.ipo_noub = jl_unbox_bool(jl_exprarg(ma, 7)); + li->purity.overrides.ipo_noub_if_noinbounds = jl_unbox_bool(jl_exprarg(ma, 8)); } } else @@ -381,35 +519,53 @@ static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir) } bd[j] = jl_nothing; } - else if (jl_is_expr(st) && ((jl_expr_t*)st)->head == jl_boundscheck_sym) { - // Don't set IR_FLAG_INBOUNDS on boundscheck at the same level + else if (jl_is_expr(st) && ((jl_expr_t*)st)->head == jl_purity_sym) { is_flag_stmt = 1; + size_t na = jl_expr_nargs(st); + if (na == NUM_EFFECTS_OVERRIDES) + arraylist_push(purity_exprs, (void*)st); + else { + assert(na == 0); + arraylist_pop(purity_exprs); + } + bd[j] = jl_nothing; } - else if (jl_is_expr(st) && ((jl_expr_t*)st)->head == jl_return_sym) { + else if (jl_is_expr(st) && ((jl_expr_t*)st)->head == jl_boundscheck_sym) + // Don't set IR_FLAG_INBOUNDS on boundscheck at the same level + is_flag_stmt = 1; + else if (jl_is_expr(st) && ((jl_expr_t*)st)->head == jl_return_sym) jl_array_ptr_set(body, j, jl_new_struct(jl_returnnode_type, jl_exprarg(st, 0))); - } - else if (jl_is_expr(st) && (((jl_expr_t*)st)->head == jl_foreigncall_sym || ((jl_expr_t*)st)->head == jl_cfunction_sym)) { + else if (jl_is_expr(st) && (((jl_expr_t*)st)->head == jl_foreigncall_sym || ((jl_expr_t*)st)->head == jl_cfunction_sym)) li->has_fcall = 1; - } if (is_flag_stmt) jl_array_uint32_set(li->ssaflags, j, 0); else { - uint8_t flag = 0; + uint32_t flag = 0; if (inbounds_depth > 0) flag |= IR_FLAG_INBOUNDS; if (inline_flags->len > 0) { - void* inline_flag = inline_flags->items[inline_flags->len - 1]; + void* inline_flag = inline_flags->items[inline_flags->len-1]; flag |= 1 << (inline_flag ? 1 : 2); } + int n_purity_exprs = purity_exprs->len; + if (n_purity_exprs > 0) { + // apply all purity overrides + for (int i = 0; i < n_purity_exprs; i++) { + void* purity_expr = purity_exprs->items[i]; + for (int j = 0; j < NUM_EFFECTS_OVERRIDES; j++) { + flag |= jl_unbox_bool(jl_exprarg((jl_value_t*)purity_expr, j)) ? (1 << (NUM_IR_FLAGS+j)) : 0; + } + } + } jl_array_uint32_set(li->ssaflags, j, flag); } } - assert(inline_flags->len == 0); // malformed otherwise - arraylist_free(inline_flags); - free(inline_flags); + assert(inline_flags->len == 0 && purity_exprs->len == 0); // malformed otherwise + arraylist_free(inline_flags); arraylist_free(purity_exprs); + free(inline_flags); free(purity_exprs); jl_array_t *vinfo = (jl_array_t*)jl_exprarg(ir, 1); jl_array_t *vis = (jl_array_t*)jl_array_ptr_ref(vinfo, 0); - size_t nslots = jl_array_len(vis); + size_t nslots = jl_array_nrows(vis); jl_value_t *ssavalue_types = jl_array_ptr_ref(vinfo, 2); assert(jl_is_long(ssavalue_types)); size_t nssavalue = jl_unbox_long(ssavalue_types); @@ -441,6 +597,8 @@ static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir) jl_array_ptr_set(li->slotnames, i, name); jl_array_uint8_set(li->slotflags, i, vinfo_mask & jl_unbox_long(jl_array_ptr_ref(vi, 2))); } + JL_GC_POP(); + return li; } JL_DLLEXPORT jl_method_instance_t *jl_new_method_instance_uninit(void) @@ -454,7 +612,6 @@ JL_DLLEXPORT jl_method_instance_t *jl_new_method_instance_uninit(void) mi->sparam_vals = jl_emptysvec; jl_atomic_store_relaxed(&mi->uninferred, NULL); mi->backedges = NULL; - mi->callbacks = NULL; jl_atomic_store_relaxed(&mi->cache, NULL); mi->inInference = 0; mi->cache_with_orig = 0; @@ -469,23 +626,20 @@ JL_DLLEXPORT jl_code_info_t *jl_new_code_info_uninit(void) (jl_code_info_t*)jl_gc_alloc(ct->ptls, sizeof(jl_code_info_t), jl_code_info_type); src->code = NULL; - src->codelocs = NULL; + src->debuginfo = NULL; src->ssavaluetypes = NULL; src->ssaflags = NULL; src->method_for_inference_limit_heuristics = jl_nothing; - src->linetable = jl_nothing; src->slotflags = NULL; src->slotnames = NULL; src->slottypes = jl_nothing; src->parent = (jl_method_instance_t*)jl_nothing; - src->rettype = (jl_value_t*)jl_any_type; src->min_world = 1; src->max_world = ~(size_t)0; - src->inferred = 0; + src->edges = jl_nothing; src->propagate_inbounds = 0; src->has_fcall = 0; src->nospecializeinfer = 0; - src->edges = jl_nothing; src->constprop = 0; src->inlining = 0; src->purity.bits = 0; @@ -493,40 +647,6 @@ JL_DLLEXPORT jl_code_info_t *jl_new_code_info_uninit(void) return src; } -jl_code_info_t *jl_new_code_info_from_ir(jl_expr_t *ir) -{ - jl_code_info_t *src = NULL; - JL_GC_PUSH1(&src); - src = jl_new_code_info_uninit(); - jl_code_info_set_ir(src, ir); - JL_GC_POP(); - return src; -} - -void jl_add_function_to_lineinfo(jl_code_info_t *ci, jl_value_t *func) -{ - // func may contain jl_symbol (function name), jl_method_t, or jl_method_instance_t - jl_array_t *li = (jl_array_t*)ci->linetable; - size_t i, n = jl_array_len(li); - jl_value_t *rt = NULL, *lno = NULL, *inl = NULL; - JL_GC_PUSH3(&rt, &lno, &inl); - for (i = 0; i < n; i++) { - jl_value_t *ln = jl_array_ptr_ref(li, i); - assert(jl_typetagis(ln, jl_lineinfonode_type)); - jl_value_t *mod = jl_fieldref_noalloc(ln, 0); - jl_value_t *file = jl_fieldref_noalloc(ln, 2); - lno = jl_fieldref(ln, 3); - inl = jl_fieldref(ln, 4); - // respect a given linetable if available - jl_value_t *ln_func = jl_fieldref_noalloc(ln, 1); - if (jl_is_symbol(ln_func) && (jl_sym_t*)ln_func == jl_symbol("none") && jl_is_int32(inl) && jl_unbox_int32(inl) == 0) - ln_func = func; - rt = jl_new_struct(jl_lineinfonode_type, mod, ln_func, file, lno, inl); - jl_array_ptr_set(li, i, rt); - } - JL_GC_POP(); -} - // invoke (compiling if necessary) the jlcall function pointer for a method template static jl_value_t *jl_call_staged(jl_method_t *def, jl_value_t *generator, size_t world, jl_svec_t *sparam_vals, jl_value_t **args, uint32_t nargs) @@ -591,7 +711,9 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo, siz JL_TRY { ct->ptls->in_pure_callback = 1; - ct->world_age = def->primary_world; + ct->world_age = jl_atomic_load_relaxed(&def->primary_world); + if (ct->world_age > jl_atomic_load_acquire(&jl_world_counter) || jl_atomic_load_relaxed(&def->deleted_world) < ct->world_age) + jl_error("The generator method cannot run until it is added to a method table."); // invoke code generator jl_tupletype_t *ttdt = (jl_tupletype_t*)jl_unwrap_unionall(tt); @@ -614,11 +736,10 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo, siz jl_error("The function body AST defined by this @generated function is not pure. This likely means it contains a closure, a comprehension or a generator."); } } - jl_add_function_to_lineinfo(func, (jl_value_t*)def->name); // If this generated function has an opaque closure, cache it for // correctness of method identity - for (int i = 0; i < jl_array_len(func->code); ++i) { + for (int i = 0; i < jl_array_nrows(func->code); ++i) { jl_value_t *stmt = jl_array_ptr_ref(func->code, i); if (jl_is_expr(stmt) && ((jl_expr_t*)stmt)->head == jl_new_opaque_closure_sym) { if (jl_options.incremental && jl_generating_output()) @@ -693,14 +814,13 @@ JL_DLLEXPORT void jl_method_set_source(jl_method_t *m, jl_code_info_t *src) m->nospecializeinfer = src->nospecializeinfer; m->constprop = src->constprop; m->purity.bits = src->purity.bits; - jl_add_function_to_lineinfo(src, (jl_value_t*)m->name); jl_array_t *copy = NULL; jl_svec_t *sparam_vars = jl_outer_unionall_vars(m->sig); JL_GC_PUSH3(©, &sparam_vars, &src); assert(jl_typetagis(src->code, jl_array_any_type)); jl_array_t *stmts = (jl_array_t*)src->code; - size_t i, n = jl_array_len(stmts); + size_t i, n = jl_array_nrows(stmts); copy = jl_alloc_vec_any(n); for (i = 0; i < n; i++) { jl_value_t *st = jl_array_ptr_ref(stmts, i); @@ -781,11 +901,17 @@ JL_DLLEXPORT void jl_method_set_source(jl_method_t *m, jl_code_info_t *src) jl_gc_wb(src, copy); m->slot_syms = jl_compress_argnames(src->slotnames); jl_gc_wb(m, m->slot_syms); - if (gen_only) + if (gen_only) { m->source = NULL; - else - m->source = (jl_value_t*)jl_compress_ir(m, src); - jl_gc_wb(m, m->source); + } + else { + m->debuginfo = src->debuginfo; + jl_gc_wb(m, m->debuginfo); + m->source = (jl_value_t*)src; + jl_gc_wb(m, m->source); + m->source = (jl_value_t*)jl_compress_ir(m, NULL); + jl_gc_wb(m, m->source); + } JL_GC_POP(); } @@ -795,7 +921,7 @@ JL_DLLEXPORT jl_method_t *jl_new_method_uninit(jl_module_t *module) jl_method_t *m = (jl_method_t*)jl_gc_alloc(ct->ptls, sizeof(jl_method_t), jl_method_type); jl_atomic_store_relaxed(&m->specializations, (jl_value_t*)jl_emptysvec); - jl_atomic_store_relaxed(&m->speckeyset, (jl_array_t*)jl_an_empty_vec_any); + jl_atomic_store_relaxed(&m->speckeyset, (jl_genericmemory_t*)jl_an_empty_memory_any); m->sig = NULL; m->slot_syms = NULL; m->roots = NULL; @@ -805,6 +931,7 @@ JL_DLLEXPORT jl_method_t *jl_new_method_uninit(jl_module_t *module) m->module = module; m->external_mt = NULL; m->source = NULL; + m->debuginfo = NULL; jl_atomic_store_relaxed(&m->unspecialized, NULL); m->generator = NULL; m->name = NULL; @@ -817,8 +944,8 @@ JL_DLLEXPORT jl_method_t *jl_new_method_uninit(jl_module_t *module) m->recursion_relation = NULL; m->isva = 0; m->nargs = 0; - m->primary_world = 1; - m->deleted_world = ~(size_t)0; + jl_atomic_store_relaxed(&m->primary_world, ~(size_t)0); + jl_atomic_store_relaxed(&m->deleted_world, 1); m->is_for_opaque_closure = 0; m->nospecializeinfer = 0; m->constprop = 0; @@ -874,7 +1001,7 @@ void push_edge(jl_array_t *list, jl_value_t *invokesig, jl_method_instance_t *ca // method definition ---------------------------------------------------------- jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name, - int nargs, jl_value_t *functionloc, jl_code_info_t *ci, int isva) + int nargs, jl_value_t *functionloc, jl_code_info_t *ci, int isva, int isinferred) { jl_method_t *m = jl_new_method_uninit(module); JL_GC_PUSH1(&m); @@ -893,7 +1020,12 @@ jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name jl_value_t *file = jl_linenode_file(functionloc); m->file = jl_is_symbol(file) ? (jl_sym_t*)file : jl_empty_sym; m->line = jl_linenode_line(functionloc); - jl_method_set_source(m, ci); + if (isinferred) { + m->slot_syms = jl_compress_argnames(ci->slotnames); + jl_gc_wb(m, m->slot_syms); + } else { + jl_method_set_source(m, ci); + } JL_GC_POP(); return m; } @@ -974,8 +1106,6 @@ JL_DLLEXPORT jl_methtable_t *jl_method_get_table(jl_method_t *method JL_PROPAGAT return method->external_mt ? (jl_methtable_t*)method->external_mt : jl_method_table_for(method->sig); } -jl_array_t *jl_all_methods JL_GLOBALLY_ROOTED; - JL_DLLEXPORT jl_method_t* jl_method_def(jl_svec_t *argdata, jl_methtable_t *mt, jl_code_info_t *f, @@ -1105,16 +1235,6 @@ JL_DLLEXPORT jl_method_t* jl_method_def(jl_svec_t *argdata, m->line = line; jl_method_set_source(m, f); -#ifdef RECORD_METHOD_ORDER - if (jl_all_methods == NULL) - jl_all_methods = jl_alloc_vec_any(0); -#endif - if (jl_all_methods != NULL) { - while (jl_array_len(jl_all_methods) < m->primary_world) - jl_array_ptr_1d_push(jl_all_methods, NULL); - jl_array_ptr_1d_push(jl_all_methods, (jl_value_t*)m); - } - jl_method_table_insert(mt, m, NULL); if (jl_newmeth_tracer) jl_call_tracer(jl_newmeth_tracer, (jl_value_t*)m); @@ -1170,10 +1290,10 @@ static uint64_t current_root_id(jl_array_t *root_blocks) if (!root_blocks) return 0; assert(jl_is_array(root_blocks)); - size_t nx2 = jl_array_len(root_blocks); + size_t nx2 = jl_array_nrows(root_blocks); if (nx2 == 0) return 0; - uint64_t *blocks = (uint64_t*)jl_array_data(root_blocks); + uint64_t *blocks = jl_array_data(root_blocks, uint64_t); return blocks[nx2-2]; } @@ -1182,8 +1302,8 @@ static void add_root_block(jl_array_t *root_blocks, uint64_t modid, size_t len) { assert(jl_is_array(root_blocks)); jl_array_grow_end(root_blocks, 2); - uint64_t *blocks = (uint64_t*)jl_array_data(root_blocks); - int nx2 = jl_array_len(root_blocks); + uint64_t *blocks = jl_array_data(root_blocks, uint64_t); + int nx2 = jl_array_nrows(root_blocks); blocks[nx2-2] = modid; blocks[nx2-1] = len; } @@ -1213,7 +1333,7 @@ JL_DLLEXPORT void jl_add_method_root(jl_method_t *m, jl_module_t *mod, jl_value_ assert(jl_is_method(m)); prepare_method_for_roots(m, modid); if (current_root_id(m->root_blocks) != modid) - add_root_block(m->root_blocks, modid, jl_array_len(m->roots)); + add_root_block(m->root_blocks, modid, jl_array_nrows(m->roots)); jl_array_ptr_1d_push(m->roots, root); JL_GC_POP(); } @@ -1225,7 +1345,7 @@ void jl_append_method_roots(jl_method_t *m, uint64_t modid, jl_array_t* roots) assert(jl_is_method(m)); assert(jl_is_array(roots)); prepare_method_for_roots(m, modid); - add_root_block(m->root_blocks, modid, jl_array_len(m->roots)); + add_root_block(m->root_blocks, modid, jl_array_nrows(m->roots)); jl_array_ptr_1d_append(m->roots, roots); JL_GC_POP(); } @@ -1239,7 +1359,7 @@ int get_root_reference(rle_reference *rr, jl_method_t *m, size_t i) rr->index = i; return i < m->nroots_sysimg; } - rle_index_to_reference(rr, i, (uint64_t*)jl_array_data(m->root_blocks), jl_array_len(m->root_blocks), 0); + rle_index_to_reference(rr, i, jl_array_data(m->root_blocks, uint64_t), jl_array_nrows(m->root_blocks), 0); if (rr->key) return 1; return i < m->nroots_sysimg; @@ -1254,7 +1374,7 @@ jl_value_t *lookup_root(jl_method_t *m, uint64_t key, int index) return jl_array_ptr_ref(m->roots, index); } rle_reference rr = {key, index}; - size_t i = rle_reference_to_index(&rr, (uint64_t*)jl_array_data(m->root_blocks), jl_array_len(m->root_blocks), 0); + size_t i = rle_reference_to_index(&rr, jl_array_data(m->root_blocks, uint64_t), jl_array_nrows(m->root_blocks), 0); return jl_array_ptr_ref(m->roots, i); } @@ -1263,11 +1383,11 @@ int nroots_with_key(jl_method_t *m, uint64_t key) { size_t nroots = 0; if (m->roots) - nroots = jl_array_len(m->roots); + nroots = jl_array_nrows(m->roots); if (!m->root_blocks) return key == 0 ? nroots : 0; - uint64_t *rletable = (uint64_t*)jl_array_data(m->root_blocks); - size_t j, nblocks2 = jl_array_len(m->root_blocks); + uint64_t *rletable = jl_array_data(m->root_blocks, uint64_t); + size_t j, nblocks2 = jl_array_nrows(m->root_blocks); int nwithkey = 0; for (j = 0; j < nblocks2; j+=2) { if (rletable[j] == key) diff --git a/src/module.c b/src/module.c index 4cda3479adba1..702c98f165782 100644 --- a/src/module.c +++ b/src/module.c @@ -1,4 +1,5 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license +// /* modules and top-level bindings @@ -28,7 +29,6 @@ JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, jl_module_t *parent, ui if (!m->build_id.lo) m->build_id.lo++; // build id 0 is invalid m->build_id.hi = ~(uint64_t)0; - m->primary_world = 0; jl_atomic_store_relaxed(&m->counter, 1); m->nospecialize = 0; m->optlevel = -1; @@ -39,18 +39,16 @@ JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, jl_module_t *parent, ui bitmix(name->hash, parent->hash); JL_MUTEX_INIT(&m->lock, "module->lock"); jl_atomic_store_relaxed(&m->bindings, jl_emptysvec); - jl_atomic_store_relaxed(&m->bindingkeyset, (jl_array_t*)jl_an_empty_vec_any); + jl_atomic_store_relaxed(&m->bindingkeyset, (jl_genericmemory_t*)jl_an_empty_memory_any); arraylist_new(&m->usings, 0); - JL_GC_PUSH1(&m); if (jl_core_module && default_names) { + JL_GC_PUSH1(&m); jl_module_using(m, jl_core_module); - } - // export own name, so "using Foo" makes "Foo" itself visible - if (default_names) { + // export own name, so "using Foo" makes "Foo" itself visible jl_set_const(m, name, (jl_value_t*)m); + jl_module_public(m, name, 1); + JL_GC_POP(); } - jl_module_public(m, name, 1); - JL_GC_POP(); return m; } @@ -61,7 +59,7 @@ JL_DLLEXPORT jl_module_t *jl_new_module(jl_sym_t *name, jl_module_t *parent) uint32_t jl_module_next_counter(jl_module_t *m) { - return jl_atomic_fetch_add(&m->counter, 1); + return jl_atomic_fetch_add_relaxed(&m->counter, 1); } JL_DLLEXPORT jl_value_t *jl_f_new_module(jl_sym_t *name, uint8_t std_imports, uint8_t default_names) @@ -428,13 +426,6 @@ static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t * return b2; } -JL_DLLEXPORT jl_binding_t *jl_get_binding_if_bound(jl_module_t *m, jl_sym_t *var) -{ - jl_binding_t *b = jl_get_module_binding(m, var, 0); - return b == NULL ? NULL : jl_atomic_load_relaxed(&b->owner); -} - - // get the current likely owner of binding when accessing m.var, without resolving the binding (it may change later) JL_DLLEXPORT jl_binding_t *jl_binding_owner(jl_module_t *m, jl_sym_t *var) { @@ -469,7 +460,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_or_error(jl_module_t *m, jl_sym_t *var { jl_binding_t *b = jl_get_binding(m, var); if (b == NULL) - jl_undefined_var_error(var); + jl_undefined_var_error(var, (jl_value_t*)m); // XXX: this only considers if the original is deprecated, not the binding in m if (b->deprecated) jl_binding_deprecation_warning(m, var, b); @@ -650,7 +641,7 @@ JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from) // silently override a "using" name. see issue #2054. jl_svec_t *table = jl_atomic_load_relaxed(&from->bindings); for (size_t i = 0; i < jl_svec_len(table); i++) { - jl_binding_t *b = (jl_binding_t*)jl_svec_ref(table, i); + jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i); if ((void*)b == jl_nothing) break; if (b->exportp && (jl_atomic_load_relaxed(&b->owner) == b || b->imported)) { @@ -674,14 +665,23 @@ JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from) JL_DLLEXPORT void jl_module_public(jl_module_t *from, jl_sym_t *s, int exported) { jl_binding_t *b = jl_get_module_binding(from, s, 1); + if (b->publicp) { + // check for conflicting declarations + if (b->exportp && !exported) + jl_errorf("cannot declare %s.%s public; it is already declared exported", + jl_symbol_name(from->name), jl_symbol_name(s)); + if (!b->exportp && exported) + jl_errorf("cannot declare %s.%s exported; it is already declared public", + jl_symbol_name(from->name), jl_symbol_name(s)); + } b->publicp = 1; - b->exportp = exported; + b->exportp |= exported; } -JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var) +JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var) // unlike most queries here, this is currently seq_cst { jl_binding_t *b = jl_get_binding(m, var); - return b && (jl_atomic_load_relaxed(&b->value) != NULL); + return b && (jl_atomic_load(&b->value) != NULL); } JL_DLLEXPORT int jl_defines_or_exports_p(jl_module_t *m, jl_sym_t *var) @@ -708,14 +708,14 @@ JL_DLLEXPORT int jl_binding_resolved_p(jl_module_t *m, jl_sym_t *var) return b && jl_atomic_load_relaxed(&b->owner) != NULL; } -static uint_t bindingkey_hash(size_t idx, jl_svec_t *data) +static uint_t bindingkey_hash(size_t idx, jl_value_t *data) { jl_binding_t *b = (jl_binding_t*)jl_svecref(data, idx); jl_sym_t *var = b->globalref->name; return var->hash; } -static int bindingkey_eq(size_t idx, const void *var, jl_svec_t *data, uint_t hv) +static int bindingkey_eq(size_t idx, const void *var, jl_value_t *data, uint_t hv) { jl_binding_t *b = (jl_binding_t*)jl_svecref(data, idx); jl_sym_t *name = b->globalref->name; @@ -726,9 +726,9 @@ JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m, jl_sym_t *var, { uint_t hv = var->hash; for (int locked = 0; ; locked++) { - jl_array_t *bindingkeyset = jl_atomic_load_acquire(&m->bindingkeyset); + jl_genericmemory_t *bindingkeyset = jl_atomic_load_acquire(&m->bindingkeyset); jl_svec_t *bindings = jl_atomic_load_relaxed(&m->bindings); - ssize_t idx = jl_smallintset_lookup(bindingkeyset, bindingkey_eq, var, bindings, hv); // acquire + ssize_t idx = jl_smallintset_lookup(bindingkeyset, bindingkey_eq, var, (jl_value_t*)bindings, hv, 0); // acquire if (idx != -1) { jl_binding_t *b = (jl_binding_t*)jl_svecref(bindings, idx); // relaxed if (locked) @@ -762,7 +762,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m, jl_sym_t *var, jl_binding_t *b = new_binding(m, var); assert(jl_svecref(bindings, i) == jl_nothing); jl_svecset(bindings, i, b); // relaxed - jl_smallintset_insert(&m->bindingkeyset, (jl_value_t*)m, bindingkey_hash, i, bindings); // release + jl_smallintset_insert(&m->bindingkeyset, (jl_value_t*)m, bindingkey_hash, i, (jl_value_t*)bindings); // release JL_UNLOCK(&m->lock); return b; } @@ -810,7 +810,7 @@ JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var if (constp = bp->constp, bp->constp = 1, constp == 0) { jl_value_t *old = NULL; if (jl_atomic_cmpswap(&bp->value, &old, val)) { - jl_gc_wb_binding(bp, val); + jl_gc_wb(bp, val); return; } } @@ -884,7 +884,7 @@ void jl_binding_deprecation_warning(jl_module_t *m, jl_sym_t *s, jl_binding_t *b } } -JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs) +jl_value_t *jl_check_binding_wr(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs JL_MAYBE_UNROOTED, int reassign) { jl_value_t *old_ty = NULL; if (!jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type)) { @@ -896,24 +896,73 @@ JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_module_t *mod, jl_sy JL_GC_POP(); } } + else { + old_ty = (jl_value_t*)jl_any_type; + } if (b->constp) { - jl_value_t *old = NULL; - if (jl_atomic_cmpswap(&b->value, &old, rhs)) { - jl_gc_wb_binding(b, rhs); - return; + if (reassign) { + jl_value_t *old = NULL; + if (jl_atomic_cmpswap(&b->value, &old, rhs)) { + jl_gc_wb(b, rhs); + return NULL; + } + if (jl_egal(rhs, old)) + return NULL; + if (jl_typeof(rhs) != jl_typeof(old) || jl_is_type(rhs) || jl_is_module(rhs)) + reassign = 0; + else + jl_safe_printf("WARNING: redefinition of constant %s.%s. This may fail, cause incorrect answers, or produce other errors.\n", + jl_symbol_name(mod->name), jl_symbol_name(var)); } - if (jl_egal(rhs, old)) - return; - if (jl_typeof(rhs) != jl_typeof(old) || jl_is_type(rhs) || jl_is_module(rhs)) { + if (!reassign) jl_errorf("invalid redefinition of constant %s.%s", jl_symbol_name(mod->name), jl_symbol_name(var)); + } + return old_ty; +} - } - jl_safe_printf("WARNING: redefinition of constant %s.%s. This may fail, cause incorrect answers, or produce other errors.\n", - jl_symbol_name(mod->name), jl_symbol_name(var)); +JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs) +{ + if (jl_check_binding_wr(b, mod, var, rhs, 1) != NULL) { + jl_atomic_store_release(&b->value, rhs); + jl_gc_wb(b, rhs); } - jl_atomic_store_release(&b->value, rhs); - jl_gc_wb_binding(b, rhs); +} + +JL_DLLEXPORT jl_value_t *jl_checked_swap(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs) +{ + jl_check_binding_wr(b, mod, var, rhs, 0); + jl_value_t *old = jl_atomic_exchange(&b->value, rhs); + jl_gc_wb(b, rhs); + if (__unlikely(old == NULL)) + jl_undefined_var_error(var, (jl_value_t*)mod); + return old; +} + +JL_DLLEXPORT jl_value_t *jl_checked_replace(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *expected, jl_value_t *rhs) +{ + jl_value_t *ty = jl_check_binding_wr(b, mod, var, rhs, 0); + return replace_value(ty, &b->value, (jl_value_t*)b, expected, rhs, 1, mod, var); +} + +JL_DLLEXPORT jl_value_t *jl_checked_modify(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *op, jl_value_t *rhs) +{ + jl_value_t *ty = NULL; + if (jl_atomic_cmpswap_relaxed(&b->ty, &ty, (jl_value_t*)jl_any_type)) + ty = (jl_value_t*)jl_any_type; + if (b->constp) + jl_errorf("invalid redefinition of constant %s.%s", + jl_symbol_name(mod->name), jl_symbol_name(var)); + return modify_value(ty, &b->value, (jl_value_t*)b, op, rhs, 1, mod, var); +} + +JL_DLLEXPORT jl_value_t *jl_checked_assignonce(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs ) +{ + jl_check_binding_wr(b, mod, var, rhs, 0); + jl_value_t *old = NULL; + if (jl_atomic_cmpswap(&b->value, &old, rhs)) + jl_gc_wb(b, rhs); + return old; } JL_DLLEXPORT void jl_declare_constant(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var) @@ -948,14 +997,15 @@ JL_DLLEXPORT jl_value_t *jl_module_names(jl_module_t *m, int all, int imported) JL_GC_PUSH1(&a); jl_svec_t *table = jl_atomic_load_relaxed(&m->bindings); for (size_t i = 0; i < jl_svec_len(table); i++) { - jl_binding_t *b = (jl_binding_t*)jl_svec_ref(table, i); + jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i); if ((void*)b == jl_nothing) break; jl_sym_t *asname = b->globalref->name; int hidden = jl_symbol_name(asname)[0]=='#'; + int main_public = (m == jl_main_module && !(asname == jl_eval_sym || asname == jl_include_sym)); if ((b->publicp || (imported && b->imported) || - (jl_atomic_load_relaxed(&b->owner) == b && !b->imported && (all || m == jl_main_module))) && + (jl_atomic_load_relaxed(&b->owner) == b && !b->imported && (all || main_public))) && (all || (!b->deprecated && !hidden))) { jl_array_grow_end(a, 1); // n.b. change to jl_arrayset if array storage allocation for Array{Symbols,1} changes: @@ -1004,7 +1054,7 @@ JL_DLLEXPORT void jl_clear_implicit_imports(jl_module_t *m) JL_LOCK(&m->lock); jl_svec_t *table = jl_atomic_load_relaxed(&m->bindings); for (size_t i = 0; i < jl_svec_len(table); i++) { - jl_binding_t *b = (jl_binding_t*)jl_svec_ref(table, i); + jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i); if ((void*)b == jl_nothing) break; if (jl_atomic_load_relaxed(&b->owner) && jl_atomic_load_relaxed(&b->owner) != b && !b->imported) diff --git a/src/opaque_closure.c b/src/opaque_closure.c index d73beff0f8587..c9f94f6b15746 100644 --- a/src/opaque_closure.c +++ b/src/opaque_closure.c @@ -105,9 +105,9 @@ static jl_opaque_closure_t *new_opaque_closure(jl_tupletype_t *argt, jl_value_t jl_method_instance_t *mi_generic = jl_specializations_get_linfo(jl_opaque_closure_method, sigtype, jl_emptysvec); // OC wrapper methods are not world dependent - ci = jl_get_method_inferred(mi_generic, selected_rt, 1, ~(size_t)0); + ci = jl_get_method_inferred(mi_generic, selected_rt, 1, ~(size_t)0, NULL); if (!jl_atomic_load_acquire(&ci->invoke)) - jl_generate_fptr_for_oc_wrapper(ci); + jl_compile_codeinst(ci); specptr = jl_atomic_load_relaxed(&ci->specptr.fptr); } jl_opaque_closure_t *oc = (jl_opaque_closure_t*)jl_gc_alloc(ct->ptls, sizeof(jl_opaque_closure_t), oc_type); @@ -131,35 +131,28 @@ jl_opaque_closure_t *jl_new_opaque_closure(jl_tupletype_t *argt, jl_value_t *rt_ return oc; } -jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name, - int nargs, jl_value_t *functionloc, jl_code_info_t *ci, int isva); - -JL_DLLEXPORT jl_code_instance_t* jl_new_codeinst( - jl_method_instance_t *mi, jl_value_t *rettype, - jl_value_t *inferred_const, jl_value_t *inferred, - int32_t const_flags, size_t min_world, size_t max_world, - uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, - uint8_t relocatability); - JL_DLLEXPORT jl_opaque_closure_t *jl_new_opaque_closure_from_code_info(jl_tupletype_t *argt, jl_value_t *rt_lb, jl_value_t *rt_ub, - jl_module_t *mod, jl_code_info_t *ci, int lineno, jl_value_t *file, int nargs, int isva, jl_value_t *env, int do_compile) + jl_module_t *mod, jl_code_info_t *ci, int lineno, jl_value_t *file, int nargs, int isva, jl_value_t *env, int do_compile, int isinferred) { - if (!ci->inferred) - jl_error("CodeInfo must already be inferred"); jl_value_t *root = NULL, *sigtype = NULL; jl_code_instance_t *inst = NULL; JL_GC_PUSH3(&root, &sigtype, &inst); root = jl_box_long(lineno); root = jl_new_struct(jl_linenumbernode_type, root, file); - jl_method_t *meth = jl_make_opaque_closure_method(mod, jl_nothing, nargs, root, ci, isva); + jl_method_t *meth = jl_make_opaque_closure_method(mod, jl_nothing, nargs, root, ci, isva, isinferred); root = (jl_value_t*)meth; - meth->primary_world = jl_current_task->world_age; - - sigtype = jl_argtype_with_function(env, (jl_value_t*)argt); - jl_method_instance_t *mi = jl_specializations_get_linfo((jl_method_t*)root, sigtype, jl_emptysvec); - inst = jl_new_codeinst(mi, rt_ub, NULL, (jl_value_t*)ci, - 0, meth->primary_world, -1, 0, 0, jl_nothing, 0); - jl_mi_cache_insert(mi, inst); + size_t world = jl_current_task->world_age; + // these are only legal in the current world since they are not in any tables + jl_atomic_store_release(&meth->primary_world, world); + jl_atomic_store_release(&meth->deleted_world, world); + + if (isinferred) { + sigtype = jl_argtype_with_function(env, (jl_value_t*)argt); + jl_method_instance_t *mi = jl_specializations_get_linfo((jl_method_t*)root, sigtype, jl_emptysvec); + inst = jl_new_codeinst(mi, jl_nothing, rt_ub, (jl_value_t*)jl_any_type, NULL, (jl_value_t*)ci, + 0, world, world, 0, 0, jl_nothing, 0, ci->debuginfo); + jl_mi_cache_insert(mi, inst); + } jl_opaque_closure_t *oc = new_opaque_closure(argt, rt_lb, rt_ub, root, env, do_compile); JL_GC_POP(); diff --git a/src/options.h b/src/options.h index b535d5ad4566f..69b31ea918008 100644 --- a/src/options.h +++ b/src/options.h @@ -33,11 +33,6 @@ // delete julia IR for non-inlineable functions after they're codegen'd #define JL_DELETE_NON_INLINEABLE 1 -// fill in the jl_all_methods in world-counter order -// so that it is possible to map (in a debugger) from -// an inferred world validity range back to the offending definition -// #define RECORD_METHOD_ORDER - // GC options ----------------------------------------------------------------- // debugging options @@ -64,6 +59,10 @@ #endif #endif +// GC_ASSERT_PARENT_VALIDITY will check whether an object is valid when **pushing** +// it to the mark queue +// #define GC_ASSERT_PARENT_VALIDITY + // profiling options // GC_FINAL_STATS prints total GC stats at exit @@ -78,6 +77,11 @@ // OBJPROFILE counts objects by type // #define OBJPROFILE +// pool allocator configuration options + +// GC_SMALL_PAGE allocates objects in 4k pages +// #define GC_SMALL_PAGE + // method dispatch profiling -------------------------------------------------- @@ -105,6 +109,7 @@ // When not using COPY_STACKS the task-system is less memory efficient so // you probably want to choose a smaller default stack size (factor of 8-10) +#if !defined(JL_STACK_SIZE) #if defined(_COMPILER_ASAN_ENABLED_) || defined(_COMPILER_MSAN_ENABLED_) #define JL_STACK_SIZE (64*1024*1024) #elif defined(_P64) @@ -112,6 +117,7 @@ #else #define JL_STACK_SIZE (2*1024*1024) #endif +#endif // allow a suspended Task to restart on a different thread #define MIGRATE_TASKS @@ -138,26 +144,6 @@ #define MACHINE_EXCLUSIVE_NAME "JULIA_EXCLUSIVE" #define DEFAULT_MACHINE_EXCLUSIVE 0 -// partr -- parallel tasks runtime options ------------------------------------ - -// multiq - // number of heaps = MULTIQ_HEAP_C * nthreads -#define MULTIQ_HEAP_C 4 - // how many in each heap -#define MULTIQ_TASKS_PER_HEAP 129 - -// parfor - // tasks = niters / (GRAIN_K * nthreads) -#define GRAIN_K 4 - -// synchronization - // narrivers = ((GRAIN_K * nthreads) ^ ARRIVERS_P) + 1 - // limit for number of recursive parfors -#define ARRIVERS_P 2 - // nreducers = narrivers * REDUCERS_FRAC -#define REDUCERS_FRAC 1 - - // sanitizer defaults --------------------------------------------------------- // Automatically enable MEMDEBUG and KEEP_BODIES for the sanitizers diff --git a/src/pipeline.cpp b/src/pipeline.cpp index a68ed979492e7..aafce01856634 100644 --- a/src/pipeline.cpp +++ b/src/pipeline.cpp @@ -79,7 +79,6 @@ #include #include #include - #ifdef _COMPILER_GCC_ #pragma GCC diagnostic pop #endif @@ -92,7 +91,6 @@ #include "julia_assert.h" #include "passes.h" - using namespace llvm; namespace { @@ -159,10 +157,16 @@ namespace { // Opts.UseAfterScope = CodeGenOpts.SanitizeAddressUseAfterScope; // Opts.UseAfterReturn = CodeGenOpts.getSanitizeAddressUseAfterReturn(); // MPM.addPass(RequireAnalysisPass()); + //Let's assume the defaults are actually fine for our purposes + #if JL_LLVM_VERSION < 160000 // MPM.addPass(ModuleAddressSanitizerPass( // Opts, UseGlobalGC, UseOdrIndicator, DestructorKind)); - //Let's assume the defaults are actually fine for our purposes MPM.addPass(ModuleAddressSanitizerPass(AddressSanitizerOptions())); + #else // LLVM 16+ + // MPM.addPass(AddressSanitizerPass( + // Opts, UseGlobalGC, UseOdrIndicator, DestructorKind)); + MPM.addPass(AddressSanitizerPass(AddressSanitizerOptions(), true, false)); + #endif // } }; ASanPass(/*SanitizerKind::Address, */false); @@ -211,9 +215,6 @@ namespace { // .sinkCommonInsts(true) ; } -#if JL_LLVM_VERSION < 150000 -#define LICMOptions() -#endif // At any given time exactly one of each pair of overloads is strictly unused #ifdef _COMPILER_GCC_ @@ -334,175 +335,200 @@ static void buildEarlySimplificationPipeline(ModulePassManager &MPM, PassBuilder #ifdef JL_DEBUG_BUILD addVerificationPasses(MPM, options.llvm_only); #endif - // Place after verification in case we want to force it anyways - MPM.addPass(ForceFunctionAttrsPass()); - invokePipelineStartCallbacks(MPM, PB, O); - MPM.addPass(Annotation2MetadataPass()); - MPM.addPass(ConstantMergePass()); - { - FunctionPassManager FPM; - FPM.addPass(LowerExpectIntrinsicPass()); - if (O.getSpeedupLevel() >= 2) { - JULIA_PASS(FPM.addPass(PropagateJuliaAddrspacesPass())); - } - // DCE must come before simplifycfg - // codegen can generate unused statements when generating builtin calls, - // and those dead statements can alter how simplifycfg optimizes the CFG - FPM.addPass(DCEPass()); - FPM.addPass(SimplifyCFGPass(basicSimplifyCFGOptions())); - if (O.getSpeedupLevel() >= 1) { - FPM.addPass(SROAPass()); - } - MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); + if (options.enable_early_simplifications) { + // Place after verification in case we want to force it anyways + MPM.addPass(ForceFunctionAttrsPass()); + invokePipelineStartCallbacks(MPM, PB, O); + MPM.addPass(Annotation2MetadataPass()); + MPM.addPass(ConstantMergePass()); + { + FunctionPassManager FPM; + FPM.addPass(LowerExpectIntrinsicPass()); + if (O.getSpeedupLevel() >= 2) { + JULIA_PASS(FPM.addPass(PropagateJuliaAddrspacesPass())); + } + // DCE must come before simplifycfg + // codegen can generate unused statements when generating builtin calls, + // and those dead statements can alter how simplifycfg optimizes the CFG + FPM.addPass(DCEPass()); + FPM.addPass(SimplifyCFGPass(basicSimplifyCFGOptions())); + if (O.getSpeedupLevel() >= 1) { +#if JL_LLVM_VERSION >= 160000 + // TODO check the LLVM 15 default. + FPM.addPass(SROAPass(SROAOptions::PreserveCFG)); +#else + FPM.addPass(SROAPass()); +#endif + } + MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); + } + invokeEarlySimplificationCallbacks(MPM, PB, O); } - invokeEarlySimplificationCallbacks(MPM, PB, O); MPM.addPass(AfterEarlySimplificationMarkerPass()); } static void buildEarlyOptimizerPipeline(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O, const OptimizationOptions &options) JL_NOTSAFEPOINT { MPM.addPass(BeforeEarlyOptimizationMarkerPass()); - invokeOptimizerEarlyCallbacks(MPM, PB, O); - { - CGSCCPassManager CGPM; - invokeCGSCCCallbacks(CGPM, PB, O); - if (O.getSpeedupLevel() >= 2) { - FunctionPassManager FPM; - JULIA_PASS(FPM.addPass(AllocOptPass())); - FPM.addPass(Float2IntPass()); - FPM.addPass(LowerConstantIntrinsicsPass()); - CGPM.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM))); - } - MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); - } - if (O.getSpeedupLevel() >= 2) { - MPM.addPass(RequireAnalysisPass()); - } - // MPM.addPass(createModuleToFunctionPassAdaptor(InvalidateAnalysisPass())); - if (options.dump_native) { - MPM.addPass(StripDeadPrototypesPass()); - JULIA_PASS(MPM.addPass(MultiVersioningPass(options.external_use))); - } - JULIA_PASS(MPM.addPass(CPUFeaturesPass())); - if (O.getSpeedupLevel() >= 1) { - FunctionPassManager FPM; - if (O.getSpeedupLevel() >= 2) { - FPM.addPass(SROAPass()); - // SROA can duplicate PHI nodes which can block LowerSIMD - FPM.addPass(InstCombinePass()); - FPM.addPass(JumpThreadingPass()); - FPM.addPass(CorrelatedValuePropagationPass()); - FPM.addPass(ReassociatePass()); - FPM.addPass(EarlyCSEPass()); - JULIA_PASS(FPM.addPass(AllocOptPass())); - } else { // if (O.getSpeedupLevel() >= 1) (exactly) - FPM.addPass(InstCombinePass()); - FPM.addPass(EarlyCSEPass()); - } - invokePeepholeEPCallbacks(FPM, PB, O); - MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); + if (options.enable_early_optimizations) { + invokeOptimizerEarlyCallbacks(MPM, PB, O); + { + CGSCCPassManager CGPM; + invokeCGSCCCallbacks(CGPM, PB, O); + if (O.getSpeedupLevel() >= 2) { + FunctionPassManager FPM; + JULIA_PASS(FPM.addPass(AllocOptPass())); + FPM.addPass(Float2IntPass()); + FPM.addPass(LowerConstantIntrinsicsPass()); + CGPM.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM))); + } + MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); + } + if (O.getSpeedupLevel() >= 2) { + MPM.addPass(RequireAnalysisPass()); + } + // MPM.addPass(createModuleToFunctionPassAdaptor(InvalidateAnalysisPass())); + if (options.dump_native) { + MPM.addPass(StripDeadPrototypesPass()); + JULIA_PASS(MPM.addPass(MultiVersioningPass(options.external_use))); + } + JULIA_PASS(MPM.addPass(CPUFeaturesPass())); + if (O.getSpeedupLevel() >= 1) { + FunctionPassManager FPM; + if (O.getSpeedupLevel() >= 2) { +#if JL_LLVM_VERSION >= 160000 + // TODO check the LLVM 15 default. + FPM.addPass(SROAPass(SROAOptions::PreserveCFG)); +#else + FPM.addPass(SROAPass()); +#endif + // SROA can duplicate PHI nodes which can block LowerSIMD + FPM.addPass(InstCombinePass()); + FPM.addPass(JumpThreadingPass()); + FPM.addPass(CorrelatedValuePropagationPass()); + FPM.addPass(ReassociatePass()); + FPM.addPass(EarlyCSEPass()); + JULIA_PASS(FPM.addPass(AllocOptPass())); + } else { // if (O.getSpeedupLevel() >= 1) (exactly) + FPM.addPass(InstCombinePass()); + FPM.addPass(EarlyCSEPass()); + } + invokePeepholeEPCallbacks(FPM, PB, O); + MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); + } + MPM.addPass(GlobalDCEPass()); } - MPM.addPass(GlobalDCEPass()); MPM.addPass(AfterEarlyOptimizationMarkerPass()); } static void buildLoopOptimizerPipeline(FunctionPassManager &FPM, PassBuilder *PB, OptimizationLevel O, const OptimizationOptions &options) JL_NOTSAFEPOINT { FPM.addPass(BeforeLoopOptimizationMarkerPass()); - { - LoopPassManager LPM; - LPM.addPass(LowerSIMDLoopPass()); + if (options.enable_loop_optimizations) { + { + LoopPassManager LPM; + LPM.addPass(LowerSIMDLoopPass()); + if (O.getSpeedupLevel() >= 2) { + LPM.addPass(LoopRotatePass()); + } + invokeLateLoopOptimizationCallbacks(LPM, PB, O); + //We don't know if the loop callbacks support MSSA + FPM.addPass(createFunctionToLoopPassAdaptor(std::move(LPM), /*UseMemorySSA = */false)); + } if (O.getSpeedupLevel() >= 2) { - LPM.addPass(LoopRotatePass()); + LoopPassManager LPM; + LPM.addPass(BeforeLICMMarkerPass()); + LPM.addPass(LICMPass(LICMOptions())); + LPM.addPass(JuliaLICMPass()); + LPM.addPass(SimpleLoopUnswitchPass(/*NonTrivial*/true, true)); + LPM.addPass(LICMPass(LICMOptions())); + LPM.addPass(JuliaLICMPass()); + LPM.addPass(AfterLICMMarkerPass()); + //LICM needs MemorySSA now, so we must use it + FPM.addPass(createFunctionToLoopPassAdaptor(std::move(LPM), /*UseMemorySSA = */true)); } - invokeLateLoopOptimizationCallbacks(LPM, PB, O); - //We don't know if the loop callbacks support MSSA - FPM.addPass(createFunctionToLoopPassAdaptor(std::move(LPM), /*UseMemorySSA = */false)); - } - if (O.getSpeedupLevel() >= 2) { - LoopPassManager LPM; - LPM.addPass(BeforeLICMMarkerPass()); - LPM.addPass(LICMPass(LICMOptions())); - LPM.addPass(JuliaLICMPass()); - LPM.addPass(SimpleLoopUnswitchPass(/*NonTrivial*/true, true)); - LPM.addPass(LICMPass(LICMOptions())); - LPM.addPass(JuliaLICMPass()); - LPM.addPass(AfterLICMMarkerPass()); - //LICM needs MemorySSA now, so we must use it - FPM.addPass(createFunctionToLoopPassAdaptor(std::move(LPM), /*UseMemorySSA = */true)); - } - if (O.getSpeedupLevel() >= 2) { - FPM.addPass(IRCEPass()); - } - { - LoopPassManager LPM; - LPM.addPass(BeforeLoopSimplificationMarkerPass()); if (O.getSpeedupLevel() >= 2) { - LPM.addPass(LoopInstSimplifyPass()); - LPM.addPass(LoopIdiomRecognizePass()); - LPM.addPass(IndVarSimplifyPass()); - LPM.addPass(LoopDeletionPass()); - // This unroll will only unroll loops when the trip count is known and small, - // so that no loop remains - LPM.addPass(LoopFullUnrollPass()); + FPM.addPass(IRCEPass()); + } + { + LoopPassManager LPM; + LPM.addPass(BeforeLoopSimplificationMarkerPass()); + if (O.getSpeedupLevel() >= 2) { + LPM.addPass(LoopInstSimplifyPass()); + LPM.addPass(LoopIdiomRecognizePass()); + LPM.addPass(IndVarSimplifyPass()); + LPM.addPass(LoopDeletionPass()); + // This unroll will only unroll loops when the trip count is known and small, + // so that no loop remains + LPM.addPass(LoopFullUnrollPass()); + } + invokeLoopOptimizerEndCallbacks(LPM, PB, O); + LPM.addPass(AfterLoopSimplificationMarkerPass()); + //We don't know if the loop end callbacks support MSSA + FPM.addPass(createFunctionToLoopPassAdaptor(std::move(LPM), /*UseMemorySSA = */false)); } - invokeLoopOptimizerEndCallbacks(LPM, PB, O); - LPM.addPass(AfterLoopSimplificationMarkerPass()); - //We don't know if the loop end callbacks support MSSA - FPM.addPass(createFunctionToLoopPassAdaptor(std::move(LPM), /*UseMemorySSA = */false)); } FPM.addPass(AfterLoopOptimizationMarkerPass()); } static void buildScalarOptimizerPipeline(FunctionPassManager &FPM, PassBuilder *PB, OptimizationLevel O, const OptimizationOptions &options) JL_NOTSAFEPOINT { FPM.addPass(BeforeScalarOptimizationMarkerPass()); - if (O.getSpeedupLevel() >= 2) { - JULIA_PASS(FPM.addPass(AllocOptPass())); - FPM.addPass(SROAPass()); - FPM.addPass(InstSimplifyPass()); - FPM.addPass(GVNPass()); - FPM.addPass(MemCpyOptPass()); - FPM.addPass(SCCPPass()); - FPM.addPass(CorrelatedValuePropagationPass()); - FPM.addPass(DCEPass()); - FPM.addPass(IRCEPass()); - FPM.addPass(InstCombinePass()); - FPM.addPass(JumpThreadingPass()); - } - if (O.getSpeedupLevel() >= 3) { - FPM.addPass(GVNPass()); - } - if (O.getSpeedupLevel() >= 2) { - FPM.addPass(DSEPass()); - invokePeepholeEPCallbacks(FPM, PB, O); - FPM.addPass(SimplifyCFGPass(aggressiveSimplifyCFGOptions())); - JULIA_PASS(FPM.addPass(AllocOptPass())); - { - LoopPassManager LPM; - LPM.addPass(LoopDeletionPass()); - LPM.addPass(LoopInstSimplifyPass()); - FPM.addPass(createFunctionToLoopPassAdaptor(std::move(LPM))); + if (options.enable_scalar_optimizations) { + if (O.getSpeedupLevel() >= 2) { + JULIA_PASS(FPM.addPass(AllocOptPass())); + #if JL_LLVM_VERSION >= 160000 + // TODO check the LLVM 15 default. + FPM.addPass(SROAPass(SROAOptions::PreserveCFG)); + #else + FPM.addPass(SROAPass()); + #endif + FPM.addPass(InstSimplifyPass()); + FPM.addPass(GVNPass()); + FPM.addPass(MemCpyOptPass()); + FPM.addPass(SCCPPass()); + FPM.addPass(CorrelatedValuePropagationPass()); + FPM.addPass(DCEPass()); + FPM.addPass(IRCEPass()); + FPM.addPass(InstCombinePass()); + FPM.addPass(JumpThreadingPass()); + } + if (O.getSpeedupLevel() >= 3) { + FPM.addPass(GVNPass()); + } + if (O.getSpeedupLevel() >= 2) { + FPM.addPass(DSEPass()); + invokePeepholeEPCallbacks(FPM, PB, O); + FPM.addPass(SimplifyCFGPass(aggressiveSimplifyCFGOptions())); + JULIA_PASS(FPM.addPass(AllocOptPass())); + { + LoopPassManager LPM; + LPM.addPass(LoopDeletionPass()); + LPM.addPass(LoopInstSimplifyPass()); + FPM.addPass(createFunctionToLoopPassAdaptor(std::move(LPM))); + } + FPM.addPass(LoopDistributePass()); } - FPM.addPass(LoopDistributePass()); + invokeScalarOptimizerCallbacks(FPM, PB, O); } - invokeScalarOptimizerCallbacks(FPM, PB, O); FPM.addPass(AfterScalarOptimizationMarkerPass()); } static void buildVectorPipeline(FunctionPassManager &FPM, PassBuilder *PB, OptimizationLevel O, const OptimizationOptions &options) JL_NOTSAFEPOINT { FPM.addPass(BeforeVectorizationMarkerPass()); - //TODO look into loop vectorize options - FPM.addPass(InjectTLIMappings()); - FPM.addPass(LoopVectorizePass()); - FPM.addPass(LoopLoadEliminationPass()); - FPM.addPass(InstCombinePass()); - FPM.addPass(SimplifyCFGPass(aggressiveSimplifyCFGOptions())); - FPM.addPass(SLPVectorizerPass()); - invokeVectorizerCallbacks(FPM, PB, O); - FPM.addPass(VectorCombinePass()); - FPM.addPass(ADCEPass()); - //TODO add BDCEPass here? - // This unroll will unroll vectorized loops - // as well as loops that we tried but failed to vectorize - FPM.addPass(LoopUnrollPass(LoopUnrollOptions(O.getSpeedupLevel(), /*OnlyWhenForced = */ false, /*ForgetSCEV = */false))); + if (options.enable_vector_pipeline) { + //TODO look into loop vectorize options + FPM.addPass(InjectTLIMappings()); + FPM.addPass(LoopVectorizePass()); + FPM.addPass(LoopLoadEliminationPass()); + FPM.addPass(InstCombinePass()); + FPM.addPass(SimplifyCFGPass(aggressiveSimplifyCFGOptions())); + FPM.addPass(SLPVectorizerPass()); + invokeVectorizerCallbacks(FPM, PB, O); + FPM.addPass(VectorCombinePass()); + FPM.addPass(ADCEPass()); + //TODO add BDCEPass here? + // This unroll will unroll vectorized loops + // as well as loops that we tried but failed to vectorize + FPM.addPass(LoopUnrollPass(LoopUnrollOptions(O.getSpeedupLevel(), /*OnlyWhenForced = */ false, /*ForgetSCEV = */false))); + } FPM.addPass(AfterVectorizationMarkerPass()); } @@ -518,6 +544,7 @@ static void buildIntrinsicLoweringPipeline(ModulePassManager &MPM, PassBuilder * } // Needed **before** LateLowerGCFrame on LLVM < 12 // due to bug in `CreateAlignmentAssumption`. + assert(options.remove_ni); JULIA_PASS(MPM.addPass(RemoveNIPass())); { FunctionPassManager FPM; @@ -531,13 +558,14 @@ static void buildIntrinsicLoweringPipeline(ModulePassManager &MPM, PassBuilder * MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); } JULIA_PASS(MPM.addPass(LowerPTLSPass(options.dump_native))); + MPM.addPass(RemoveJuliaAddrspacesPass()); //TODO: Make this conditional on arches (GlobalISel doesn't like our addrsspaces) if (O.getSpeedupLevel() >= 1) { FunctionPassManager FPM; FPM.addPass(InstCombinePass()); FPM.addPass(SimplifyCFGPass(aggressiveSimplifyCFGOptions())); MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); } - } else { + } else if (!options.remove_ni) { JULIA_PASS(MPM.addPass(RemoveNIPass())); } MPM.addPass(AfterIntrinsicLoweringMarkerPass()); @@ -545,22 +573,24 @@ static void buildIntrinsicLoweringPipeline(ModulePassManager &MPM, PassBuilder * static void buildCleanupPipeline(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O, const OptimizationOptions &options) JL_NOTSAFEPOINT { MPM.addPass(BeforeCleanupMarkerPass()); - if (O.getSpeedupLevel() >= 2) { - FunctionPassManager FPM; - JULIA_PASS(FPM.addPass(CombineMulAddPass())); - FPM.addPass(DivRemPairsPass()); - MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); - } - invokeOptimizerLastCallbacks(MPM, PB, O); - MPM.addPass(createModuleToFunctionPassAdaptor(AnnotationRemarksPass())); - addSanitizerPasses(MPM, O); - { - FunctionPassManager FPM; - JULIA_PASS(FPM.addPass(DemoteFloat16Pass())); + if (options.cleanup) { if (O.getSpeedupLevel() >= 2) { - FPM.addPass(GVNPass()); + FunctionPassManager FPM; + JULIA_PASS(FPM.addPass(CombineMulAddPass())); + FPM.addPass(DivRemPairsPass()); + MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); + } + invokeOptimizerLastCallbacks(MPM, PB, O); + MPM.addPass(createModuleToFunctionPassAdaptor(AnnotationRemarksPass())); + addSanitizerPasses(MPM, O); + { + FunctionPassManager FPM; + JULIA_PASS(FPM.addPass(DemoteFloat16Pass())); + if (O.getSpeedupLevel() >= 2) { + FPM.addPass(GVNPass()); + } + MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); } - MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); } MPM.addPass(AfterCleanupMarkerPass()); } @@ -568,7 +598,8 @@ static void buildCleanupPipeline(ModulePassManager &MPM, PassBuilder *PB, Optimi static void buildPipeline(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O, const OptimizationOptions &options) JL_NOTSAFEPOINT { MPM.addPass(BeforeOptimizationMarkerPass()); buildEarlySimplificationPipeline(MPM, PB, O, options); - MPM.addPass(AlwaysInlinerPass()); + if (options.always_inline) + MPM.addPass(AlwaysInlinerPass()); buildEarlyOptimizerPipeline(MPM, PB, O, options); { FunctionPassManager FPM; @@ -585,11 +616,27 @@ static void buildPipeline(ModulePassManager &MPM, PassBuilder *PB, OptimizationL MPM.addPass(AfterOptimizationMarkerPass()); } -extern "C" JL_DLLEXPORT_CODEGEN void jl_build_newpm_pipeline_impl(void *MPM, void *PB, int Speedup, int Size, - int lower_intrinsics, int dump_native, int external_use, int llvm_only) JL_NOTSAFEPOINT +struct PipelineConfig { + int Speedup; + int Size; + int lower_intrinsics; + int dump_native; + int external_use; + int llvm_only; + int always_inline; + int enable_early_simplifications; + int enable_early_optimizations; + int enable_scalar_optimizations; + int enable_loop_optimizations; + int enable_vector_pipeline; + int remove_ni; + int cleanup; +}; + +extern "C" JL_DLLEXPORT_CODEGEN void jl_build_newpm_pipeline_impl(void *MPM, void *PB, PipelineConfig* config) JL_NOTSAFEPOINT { OptimizationLevel O; - switch (Size) { + switch (config->Size) { case 1: O = OptimizationLevel::Os; break; @@ -597,7 +644,7 @@ extern "C" JL_DLLEXPORT_CODEGEN void jl_build_newpm_pipeline_impl(void *MPM, voi O = OptimizationLevel::Oz; break; case 0: - switch (Speedup) { + switch (config->Speedup) { case 0: O = OptimizationLevel::O0; break; @@ -613,7 +660,18 @@ extern "C" JL_DLLEXPORT_CODEGEN void jl_build_newpm_pipeline_impl(void *MPM, voi } } buildPipeline(*reinterpret_cast(MPM), reinterpret_cast(PB), O, - OptimizationOptions{!!lower_intrinsics, !!dump_native, !!external_use, !!llvm_only}); + OptimizationOptions{!!config->lower_intrinsics, + !!config->dump_native, + !!config->external_use, + !!config->llvm_only, + !!config->always_inline, + !!config->enable_early_simplifications, + !!config->enable_early_optimizations, + !!config->enable_scalar_optimizations, + !!config->enable_loop_optimizations, + !!config->enable_vector_pipeline, + !!config->remove_ni, + !!config->cleanup}); } #undef JULIA_PASS @@ -687,13 +745,6 @@ PIC.addClassToPassName(decltype(CREATE_PASS)::name(), NAME); PIC.addClassToPassName("AfterOptimizationMarkerPass", "AfterOptimization"); } - auto createPIC(StandardInstrumentations &SI) JL_NOTSAFEPOINT { - auto PIC = std::make_unique(); - adjustPIC(*PIC); - SI.registerCallbacks(*PIC); - return PIC; - } - FunctionAnalysisManager createFAM(OptimizationLevel O, TargetMachine &TM) JL_NOTSAFEPOINT { FunctionAnalysisManager FAM; @@ -722,9 +773,8 @@ PIC.addClassToPassName(decltype(CREATE_PASS)::name(), NAME); } NewPM::NewPM(std::unique_ptr TM, OptimizationLevel O, OptimizationOptions options) : - TM(std::move(TM)), SI(false), PIC(createPIC(SI)), - PB(this->TM.get(), PipelineTuningOptions(), None, PIC.get()), - MPM(createMPM(PB, O, options)), O(O) {} + TM(std::move(TM)), O(O), options(options), TimePasses() {} + NewPM::~NewPM() = default; @@ -750,14 +800,34 @@ void NewPM::run(Module &M) { //We must recreate the analysis managers every time //so that analyses from previous runs of the pass manager //do not hang around for the next run - AnalysisManagers AM{*TM, PB, O}; +#if JL_LLVM_VERSION >= 160000 + StandardInstrumentations SI(M.getContext(),false); +#else + StandardInstrumentations SI(false); +#endif + FunctionAnalysisManager FAM(createFAM(O, *TM.get())); + PassInstrumentationCallbacks PIC; + adjustPIC(PIC); + TimePasses.registerCallbacks(PIC); + SI.registerCallbacks(PIC, &FAM); + SI.getTimePasses().setOutStream(nulls()); //TODO: figure out a better way of doing this + LoopAnalysisManager LAM; + CGSCCAnalysisManager CGAM; + ModuleAnalysisManager MAM; + PassBuilder PB(TM.get(), PipelineTuningOptions(), None, &PIC); + PB.registerLoopAnalyses(LAM); + PB.registerFunctionAnalyses(FAM); + PB.registerCGSCCAnalyses(CGAM); + PB.registerModuleAnalyses(MAM); + PB.crossRegisterProxies(LAM, FAM, CGAM, MAM); + ModulePassManager MPM = createMPM(PB, O, options); #ifndef __clang_gcanalyzer__ /* the analyzer cannot prove we have not added instrumentation callbacks with safepoints */ - MPM.run(M, AM.MAM); + MPM.run(M, MAM); #endif } void NewPM::printTimers() { - SI.getTimePasses().print(); + TimePasses.print(); } OptimizationLevel getOptLevel(int optlevel) { @@ -775,7 +845,7 @@ OptimizationLevel getOptLevel(int optlevel) { } //This part is also basically stolen from LLVM's PassBuilder.cpp file -static llvm::Optional> parseJuliaPipelineOptions(StringRef name) { +static Optional> parseJuliaPipelineOptions(StringRef name) { if (name.consume_front("julia")) { auto O = OptimizationLevel::O2; auto options = OptimizationOptions::defaults(); @@ -825,7 +895,7 @@ static llvm::Optional> parseJu } return {{O, options}}; } - return {}; + return None; } bool verifyLLVMIR(const Module &M) JL_NOTSAFEPOINT { diff --git a/src/precompile.c b/src/precompile.c index f6266e252f609..c40e867ea699e 100644 --- a/src/precompile.c +++ b/src/precompile.c @@ -35,27 +35,44 @@ void write_srctext(ios_t *f, jl_array_t *udeps, int64_t srctextpos) { // uint64: length of src text // char*: src text // At the end we write int32(0) as a terminal sentinel. - size_t len = jl_array_len(udeps); + size_t len = jl_array_nrows(udeps); + static jl_value_t *replace_depot_func = NULL; + if (!replace_depot_func) + replace_depot_func = jl_get_global(jl_base_module, jl_symbol("replace_depot_path")); ios_t srctext; + jl_value_t *deptuple = NULL; + JL_GC_PUSH2(&deptuple, &udeps); for (size_t i = 0; i < len; i++) { - jl_value_t *deptuple = jl_array_ptr_ref(udeps, i); + deptuple = jl_array_ptr_ref(udeps, i); jl_value_t *depmod = jl_fieldref(deptuple, 0); // module // Dependencies declared with `include_dependency` are excluded // because these may not be Julia code (and could be huge) if (depmod != (jl_value_t*)jl_main_module) { - jl_value_t *dep = jl_fieldref(deptuple, 1); // file abspath - const char *depstr = jl_string_data(dep); - if (!depstr[0]) + jl_value_t *abspath = jl_fieldref(deptuple, 1); // file abspath + const char *abspathstr = jl_string_data(abspath); + if (!abspathstr[0]) continue; - ios_t *srctp = ios_file(&srctext, depstr, 1, 0, 0, 0); + ios_t *srctp = ios_file(&srctext, abspathstr, 1, 0, 0, 0); if (!srctp) { jl_printf(JL_STDERR, "WARNING: could not cache source text for \"%s\".\n", - jl_string_data(dep)); + abspathstr); continue; } - size_t slen = jl_string_len(dep); + + jl_value_t **replace_depot_args; + JL_GC_PUSHARGS(replace_depot_args, 2); + replace_depot_args[0] = replace_depot_func; + replace_depot_args[1] = abspath; + jl_task_t *ct = jl_current_task; + size_t last_age = ct->world_age; + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); + jl_value_t *depalias = (jl_value_t*)jl_apply(replace_depot_args, 2); + ct->world_age = last_age; + JL_GC_POP(); + + size_t slen = jl_string_len(depalias); write_int32(f, slen); - ios_write(f, depstr, slen); + ios_write(f, jl_string_data(depalias), slen); posfile = ios_pos(f); write_uint64(f, 0); // placeholder for length of this file in bytes uint64_t filelen = (uint64_t) ios_copyall(f, &srctext); @@ -65,6 +82,7 @@ void write_srctext(ios_t *f, jl_array_t *udeps, int64_t srctextpos) { ios_seek_end(f); } } + JL_GC_POP(); } write_int32(f, 0); // mark the end of the source text } @@ -75,7 +93,12 @@ JL_DLLEXPORT void jl_write_compiler_output(void) return; } - jl_task_wait_empty(); + jl_task_wait_empty(); // wait for most work to finish (except possibly finalizers) + jl_gc_collect(JL_GC_FULL); + jl_gc_collect(JL_GC_INCREMENTAL); // sweep finalizers + jl_task_t *ct = jl_current_task; + jl_gc_enable_finalizers(ct, 0); // now disable finalizers, as they could schedule more work or make other unexpected changes to reachability + jl_task_wait_empty(); // then make sure we are the only thread alive that could be running user code past here if (!jl_module_init_order) { jl_printf(JL_STDERR, "WARNING: --output requested, but no modules defined during run\n"); @@ -86,9 +109,9 @@ JL_DLLEXPORT void jl_write_compiler_output(void) jl_array_t *udeps = NULL; JL_GC_PUSH2(&worklist, &udeps); jl_module_init_order = jl_alloc_vec_any(0); - int i, l = jl_array_len(worklist); + int i, l = jl_array_nrows(worklist); for (i = 0; i < l; i++) { - jl_value_t *m = jl_ptrarrayref(worklist, i); + jl_value_t *m = jl_array_ptr_ref(worklist, i); jl_value_t *f = jl_get_global((jl_module_t*)m, jl_symbol("__init__")); if (f) { jl_array_ptr_1d_push(jl_module_init_order, m); @@ -166,6 +189,7 @@ JL_DLLEXPORT void jl_write_compiler_output(void) } } JL_GC_POP(); + jl_gc_enable_finalizers(ct, 1); } #ifdef __cplusplus diff --git a/src/precompile_utils.c b/src/precompile_utils.c index 9a577b900a1b7..5a4f599d1f0eb 100644 --- a/src/precompile_utils.c +++ b/src/precompile_utils.c @@ -154,7 +154,7 @@ static void jl_compile_all_defs(jl_array_t *mis) jl_foreach_reachable_mtable(compile_all_collect_, allmeths); - size_t i, l = jl_array_len(allmeths); + size_t i, l = jl_array_nrows(allmeths); for (i = 0; i < l; i++) { jl_method_t *m = (jl_method_t*)jl_array_ptr_ref(allmeths, i); if (jl_is_datatype(m->sig) && jl_isa_compileable_sig((jl_tupletype_t*)m->sig, jl_emptysvec, m)) { @@ -182,12 +182,14 @@ static int precompile_enq_specialization_(jl_method_instance_t *mi, void *closur jl_code_instance_t *codeinst = jl_atomic_load_relaxed(&mi->cache); while (codeinst) { int do_compile = 0; - if (jl_atomic_load_relaxed(&codeinst->invoke) != jl_fptr_const_return) { + if (codeinst->owner != jl_nothing) { + // TODO(vchuravy) native code caching for foreign interpreters + } + else if (jl_atomic_load_relaxed(&codeinst->invoke) != jl_fptr_const_return) { jl_value_t *inferred = jl_atomic_load_relaxed(&codeinst->inferred); if (inferred && inferred != jl_nothing && - jl_ir_flag_inferred(inferred) && - (jl_ir_inlining_cost(inferred) == UINT16_MAX)) { + (jl_options.compile_enabled != JL_OPTIONS_COMPILE_ALL && jl_ir_inlining_cost(inferred) == UINT16_MAX)) { do_compile = 1; } else if (jl_atomic_load_relaxed(&codeinst->invoke) != NULL || jl_atomic_load_relaxed(&codeinst->precompile)) { @@ -243,7 +245,7 @@ static void *jl_precompile_(jl_array_t *m, int external_linkage) jl_method_instance_t *mi = NULL; JL_GC_PUSH2(&m2, &mi); m2 = jl_alloc_vec_any(0); - for (size_t i = 0; i < jl_array_len(m); i++) { + for (size_t i = 0; i < jl_array_nrows(m); i++) { jl_value_t *item = jl_array_ptr_ref(m, i); if (jl_is_method_instance(item)) { mi = (jl_method_instance_t*)item; @@ -279,7 +281,7 @@ static void *jl_precompile(int all) return native_code; } -static void *jl_precompile_worklist(jl_array_t *worklist, jl_array_t *extext_methods, jl_array_t *new_specializations) +static void *jl_precompile_worklist(jl_array_t *worklist, jl_array_t *extext_methods, jl_array_t *new_ext_cis) { if (!worklist) return NULL; @@ -287,13 +289,13 @@ static void *jl_precompile_worklist(jl_array_t *worklist, jl_array_t *extext_met // type signatures that were inferred but haven't been compiled jl_array_t *m = jl_alloc_vec_any(0); JL_GC_PUSH1(&m); - size_t i, n = jl_array_len(worklist); + size_t i, n = jl_array_nrows(worklist); for (i = 0; i < n; i++) { jl_module_t *mod = (jl_module_t*)jl_array_ptr_ref(worklist, i); assert(jl_is_module(mod)); foreach_mtable_in_module(mod, precompile_enq_all_specializations_, m); } - n = jl_array_len(extext_methods); + n = jl_array_nrows(extext_methods); for (i = 0; i < n; i++) { jl_method_t *method = (jl_method_t*)jl_array_ptr_ref(extext_methods, i); assert(jl_is_method(method)); @@ -310,9 +312,9 @@ static void *jl_precompile_worklist(jl_array_t *worklist, jl_array_t *extext_met } } } - n = jl_array_len(new_specializations); + n = jl_array_nrows(new_ext_cis); for (i = 0; i < n; i++) { - jl_code_instance_t *ci = (jl_code_instance_t*)jl_array_ptr_ref(new_specializations, i); + jl_code_instance_t *ci = (jl_code_instance_t*)jl_array_ptr_ref(new_ext_cis, i); precompile_enq_specialization_(ci->def, m); } void *native_code = jl_precompile_(m, 1); diff --git a/src/processor.cpp b/src/processor.cpp index 1e1c8eba796c2..730e470f4153d 100644 --- a/src/processor.cpp +++ b/src/processor.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include #include @@ -387,7 +389,7 @@ JL_UNUSED static uint32_t find_feature_bit(const FeatureName *features, size_t n return feature.bit; } } - return (uint32_t)-1; + return UINT32_MAX; } // This is how we save the target identification. @@ -640,7 +642,7 @@ static inline jl_image_t parse_sysimg(void *hdl, F &&callback) jl_value_t* rejection_reason = nullptr; JL_GC_PUSH1(&rejection_reason); uint32_t target_idx = callback(ids, &rejection_reason); - if (target_idx == (uint32_t)-1) { + if (target_idx == UINT32_MAX) { jl_error(jl_string_ptr(rejection_reason)); } JL_GC_POP(); @@ -649,48 +651,42 @@ static inline jl_image_t parse_sysimg(void *hdl, F &&callback) jl_error("Image file is not compatible with this version of Julia"); } - llvm::SmallVector fvars(pointers->header->nfvars); - llvm::SmallVector gvars(pointers->header->ngvars); + llvm::SmallVector fvars(pointers->header->nfvars); + llvm::SmallVector gvars(pointers->header->ngvars); - llvm::SmallVector, 0> clones; + llvm::SmallVector, 0> clones; for (unsigned i = 0; i < pointers->header->nshards; i++) { auto shard = pointers->shards[i]; - // .data base - char *data_base = (char *)shard.gvar_base; - - // .text base - const char *text_base = shard.fvar_base; - - const int32_t *offsets = shard.fvar_offsets; - uint32_t nfunc = offsets[0]; + void **fvar_shard = shard.fvar_ptrs; + uintptr_t nfunc = *shard.fvar_count; assert(nfunc <= pointers->header->nfvars); - offsets++; const int32_t *reloc_slots = shard.clone_slots; const uint32_t nreloc = reloc_slots[0]; - reloc_slots += 1; + reloc_slots++; const uint32_t *clone_idxs = shard.clone_idxs; - const int32_t *clone_offsets = shard.clone_offsets; + void **clone_ptrs = shard.clone_ptrs; uint32_t tag_len = clone_idxs[0]; - clone_idxs += 1; + clone_idxs++; assert(tag_len & jl_sysimg_tag_mask); - llvm::SmallVector base_offsets = {offsets}; + llvm::SmallVector base_ptrs(0); + base_ptrs.push_back(fvar_shard); // Find target - for (uint32_t i = 0;i < target_idx;i++) { + for (uint32_t i = 0; i < target_idx; i++) { uint32_t len = jl_sysimg_val_mask & tag_len; if (jl_sysimg_tag_mask & tag_len) { - if (i != 0) - clone_offsets += nfunc; clone_idxs += len + 1; + if (i != 0) + clone_ptrs += nfunc; } else { - clone_offsets += len; + clone_ptrs += len; clone_idxs += len + 2; } tag_len = clone_idxs[-1]; - base_offsets.push_back(tag_len & jl_sysimg_tag_mask ? clone_offsets : nullptr); + base_ptrs.push_back(tag_len & jl_sysimg_tag_mask ? clone_ptrs : nullptr); } bool clone_all = (tag_len & jl_sysimg_tag_mask) != 0; @@ -698,22 +694,22 @@ static inline jl_image_t parse_sysimg(void *hdl, F &&callback) if (clone_all) { // clone_all if (target_idx != 0) { - offsets = clone_offsets; + fvar_shard = clone_ptrs; } } else { uint32_t base_idx = clone_idxs[0]; assert(base_idx < target_idx); if (target_idx != 0) { - offsets = base_offsets[base_idx]; - assert(offsets); + fvar_shard = base_ptrs[base_idx]; + assert(fvar_shard); } clone_idxs++; unsigned start = clones.size(); clones.resize(start + tag_len); auto idxs = shard.fvar_idxs; for (unsigned i = 0; i < tag_len; i++) { - clones[start + i] = {(clone_idxs[i] & ~jl_sysimg_val_mask) | idxs[clone_idxs[i] & jl_sysimg_val_mask], clone_offsets[i] + text_base}; + clones[start + i] = {(clone_idxs[i] & ~jl_sysimg_val_mask) | idxs[clone_idxs[i] & jl_sysimg_val_mask], clone_ptrs[i]}; } } // Do relocation @@ -721,13 +717,13 @@ static inline jl_image_t parse_sysimg(void *hdl, F &&callback) uint32_t len = jl_sysimg_val_mask & tag_len; for (uint32_t i = 0; i < len; i++) { uint32_t idx = clone_idxs[i]; - int32_t offset; + void *fptr; if (clone_all) { - offset = offsets[idx]; + fptr = fvar_shard[idx]; } else if (idx & jl_sysimg_tag_mask) { idx = idx & jl_sysimg_val_mask; - offset = clone_offsets[i]; + fptr = clone_ptrs[i]; } else { continue; @@ -737,9 +733,10 @@ static inline jl_image_t parse_sysimg(void *hdl, F &&callback) auto reloc_idx = ((const uint32_t*)reloc_slots)[reloc_i * 2]; if (reloc_idx == idx) { found = true; + const char *data_base = (const char*)shard.clone_slots; auto slot = (const void**)(data_base + reloc_slots[reloc_i * 2 + 1]); assert(slot); - *slot = offset + text_base; + *slot = fptr; } else if (reloc_idx > idx) { break; @@ -751,34 +748,35 @@ static inline jl_image_t parse_sysimg(void *hdl, F &&callback) auto fidxs = shard.fvar_idxs; for (uint32_t i = 0; i < nfunc; i++) { - fvars[fidxs[i]] = text_base + offsets[i]; + fvars[fidxs[i]] = fvar_shard[i]; } + // .data base auto gidxs = shard.gvar_idxs; unsigned ngvars = shard.gvar_offsets[0]; assert(ngvars <= pointers->header->ngvars); + char *data_base = (char*)shard.gvar_offsets; for (uint32_t i = 0; i < ngvars; i++) { gvars[gidxs[i]] = data_base + shard.gvar_offsets[i+1]; } } if (!fvars.empty()) { - auto offsets = (int32_t *) malloc(sizeof(int32_t) * fvars.size()); - res.fptrs.base = fvars[0]; + auto ptrs = (void**) malloc(sizeof(void*) * fvars.size()); for (size_t i = 0; i < fvars.size(); i++) { assert(fvars[i] && "Missing function pointer!"); - offsets[i] = fvars[i] - res.fptrs.base; + ptrs[i] = fvars[i]; } - res.fptrs.offsets = offsets; - res.fptrs.noffsets = fvars.size(); + res.fptrs.ptrs = ptrs; + res.fptrs.nptrs = fvars.size(); } if (!gvars.empty()) { - auto offsets = (int32_t *) malloc(sizeof(int32_t) * gvars.size()); - res.gvars_base = (uintptr_t *)gvars[0]; + auto offsets = (int32_t*)malloc(sizeof(int32_t) * gvars.size()); + res.gvars_base = (const char*)pointers->header; for (size_t i = 0; i < gvars.size(); i++) { assert(gvars[i] && "Missing global variable pointer!"); - offsets[i] = gvars[i] - (const char *)res.gvars_base; + offsets[i] = gvars[i] - res.gvars_base; } res.gvars_offsets = offsets; res.ngvars = gvars.size(); @@ -786,15 +784,18 @@ static inline jl_image_t parse_sysimg(void *hdl, F &&callback) if (!clones.empty()) { assert(!fvars.empty()); - std::sort(clones.begin(), clones.end()); - auto clone_offsets = (int32_t *) malloc(sizeof(int32_t) * clones.size()); + std::sort(clones.begin(), clones.end(), + [](const std::pair &a, const std::pair &b) { + return (a.first & jl_sysimg_val_mask) < (b.first & jl_sysimg_val_mask); + }); + auto clone_ptrs = (void**) malloc(sizeof(void*) * clones.size()); auto clone_idxs = (uint32_t *) malloc(sizeof(uint32_t) * clones.size()); for (size_t i = 0; i < clones.size(); i++) { clone_idxs[i] = clones[i].first; - clone_offsets[i] = clones[i].second - res.fptrs.base; + clone_ptrs[i] = clones[i].second; } res.fptrs.clone_idxs = clone_idxs; - res.fptrs.clone_offsets = clone_offsets; + res.fptrs.clone_ptrs = clone_ptrs; res.fptrs.nclones = clones.size(); } @@ -855,7 +856,7 @@ static inline void check_cmdline(T &&cmdline, bool imaging) } struct SysimgMatch { - uint32_t best_idx{(uint32_t)-1}; + uint32_t best_idx{UINT32_MAX}; int vreg_size{0}; }; @@ -910,7 +911,7 @@ static inline SysimgMatch match_sysimg_targets(S &&sysimg, T &&target, F &&max_v feature_size = new_feature_size; rejection_reasons.push_back("Updating best match to this target\n"); } - if (match.best_idx == (uint32_t)-1) { + if (match.best_idx == UINT32_MAX) { // Construct a nice error message for debugging purposes std::string error_msg = "Unable to find compatible target in cached code image.\n"; for (size_t i = 0; i < rejection_reasons.size(); i++) { @@ -962,6 +963,43 @@ static inline void dump_cpu_spec(uint32_t cpu, const FeatureList &features, } +static std::string jl_get_cpu_name_llvm(void) +{ + return llvm::sys::getHostCPUName().str(); +} + +static std::string jl_get_cpu_features_llvm(void) +{ + llvm::StringMap HostFeatures; + llvm::sys::getHostCPUFeatures(HostFeatures); + std::string attr; + for (auto &ele: HostFeatures) { + if (ele.getValue()) { + if (!attr.empty()) { + attr.append(",+"); + } + else { + attr.append("+"); + } + attr.append(ele.getKey().str()); + } + } + // Explicitly disabled features need to be added at the end so that + // they are not re-enabled by other features that implies them by default. + for (auto &ele: HostFeatures) { + if (!ele.getValue()) { + if (!attr.empty()) { + attr.append(",-"); + } + else { + attr.append("-"); + } + attr.append(ele.getKey().str()); + } + } + return attr; +} + #if defined(_CPU_X86_) || defined(_CPU_X86_64_) #include "processor_x86.cpp" @@ -976,6 +1014,16 @@ static inline void dump_cpu_spec(uint32_t cpu, const FeatureList &features, #endif +JL_DLLEXPORT jl_value_t *jl_get_cpu_name(void) +{ + return jl_cstr_to_string(host_cpu_name().c_str()); +} + +JL_DLLEXPORT jl_value_t *jl_get_cpu_features(void) +{ + return jl_cstr_to_string(jl_get_cpu_features_llvm().c_str()); +} + extern "C" JL_DLLEXPORT jl_value_t* jl_reflect_clone_targets() { auto specs = jl_get_llvm_clone_targets(); const uint32_t base_flags = 0; @@ -993,7 +1041,7 @@ extern "C" JL_DLLEXPORT jl_value_t* jl_reflect_clone_targets() { } jl_value_t *arr = (jl_value_t*)jl_alloc_array_1d(jl_array_uint8_type, data.size()); - uint8_t *out = (uint8_t*)jl_array_data(arr); + uint8_t *out = jl_array_data(arr, uint8_t); memcpy(out, data.data(), data.size()); return arr; } diff --git a/src/processor.h b/src/processor.h index c22f8cff34a63..82a1121aaf7c4 100644 --- a/src/processor.h +++ b/src/processor.h @@ -65,28 +65,26 @@ static const uint32_t jl_sysimg_tag_mask = 0x80000000u; static const uint32_t jl_sysimg_val_mask = ~((uint32_t)0x80000000u); typedef struct _jl_image_fptrs_t { - // base function pointer - const char *base; // number of functions - uint32_t noffsets; - // function pointer offsets - const int32_t *offsets; + uint32_t nptrs; + // function pointers + void **ptrs; // Following fields contains the information about the selected target. // All of these fields are 0 if the selected targets have all the functions cloned. - // Instead the offsets are stored in `noffsets` and `offsets`. + // Instead the offsets are stored in `nptrs` and `ptrs`. // number of cloned functions uint32_t nclones; - // function pointer offsets of cloned functions - const int32_t *clone_offsets; + // function pointer of cloned functions + void **clone_ptrs; // sorted indices of the cloned functions (including the tag bit) const uint32_t *clone_idxs; } jl_image_fptrs_t; typedef struct { uint64_t base; - uintptr_t *gvars_base; + const char *gvars_base; const int32_t *gvars_offsets; uint32_t ngvars; jl_image_fptrs_t fptrs; @@ -109,31 +107,25 @@ typedef struct { // Per-shard data for image shards. Each image contains header->nshards of these. typedef struct { - - // This is the base function pointer - // (all other function pointers are stored as offsets to this address) - const char *fvar_base; - // The array of function pointer offsets (`int32_t`) from the base pointer. + // The array of function pointers (`void*`). // This includes all julia functions in sysimg as well as all other functions that are cloned. // The default function pointer is used if the function is cloned. - // The first element is the size of the array, which should **NOT** be used as the number + // The first element is the size of the array, which should **NOT** be used is the number // of julia functions in the sysimg. // Each entry in this array uniquely identifies a function we are interested in // (the function may have multiple function pointers corresponding to different versions). - // In other sysimg info, all references to functions are stored as their `uint32_t` index - // in this array. - const int32_t *fvar_offsets; + const uintptr_t *fvar_count; + void **fvar_ptrs; // This is the mapping of shard function index -> global function index // staticdata.c relies on the same order of functions in the global function array being // the same as what it saw when serializing the global function array. However, partitioning // into multiple shards will cause functions to be reordered. This array is used to map // back to the original function array for loading. const uint32_t *fvar_idxs; - // This is the base data pointer - // (all other data pointers in this shard are stored as offsets to this address) - uintptr_t *gvar_base; // This array of global variable offsets (`int32_t`) from the base pointer. // Similar to fvar_offsets, but for gvars + // This is also the base data pointer + // (all data pointers in this shard are stored as offsets to this address) const int32_t *gvar_offsets; // This is the mapping of shard global variable index -> global global variable index // Similar to fvar_idxs, but for gvars @@ -161,14 +153,12 @@ typedef struct { // this array as the original/base function offsets. // For other targets, this variable contains an offset array with the length defined in // `jl_dispatch_fvars_idxs`. Tagged indices need relocations. - const int32_t *clone_offsets; + void **clone_ptrs; // Target-specific function indices. // For each target, this includes a tagged `uint32_t` length, an optional `uint32_t` index // of the base target followed by an array of tagged function indices. // The base target index is required to be smaller than the index of the current target // and must be the default (`0`) or a `clone_all` target. - // If it's not `0`, the function pointer array for the `clone_all` target will be used as - // the base function pointer offsets instead. // The tag bits for both the length and the indices are the top bit. // A tagged length indicates that all of the functions are cloned and the indices follows // are the ones that requires relocation. The base target index is omitted in this case. @@ -177,10 +167,8 @@ typedef struct { // all other cloned functions that requires relocation. // A tagged index means that the function pointer should be filled into the GOT slots // identified by `jl_dispatch_reloc_slots`. There could be more than one slot per function. - // (Note that a tagged index could corresponds to a functions pointer that's the same as + // (Note that a tagged index could corresponds to a function's pointer that's the same as // the base one since this is the only way we currently represent relocations.) - // A tagged length implicitly tags all the indices and the indices will not have the tag bit - // set. The lengths in this variable is needed to decode `jl_dispatch_fvars_offsets`. const uint32_t *clone_idxs; } jl_image_shard_t; @@ -226,6 +214,8 @@ JL_DLLEXPORT jl_value_t *jl_get_cpu_name(void); // Return the features of the host CPU as a julia string. JL_DLLEXPORT jl_value_t *jl_get_cpu_features(void); // Dump the name and feature set of the host CPU +JL_DLLEXPORT jl_value_t *jl_cpu_has_fma(int bits); +// Check if the CPU has native FMA instructions; // For debugging only JL_DLLEXPORT void jl_dump_host_cpu(void); JL_DLLEXPORT jl_value_t* jl_check_pkgimage_clones(char* data); @@ -242,7 +232,7 @@ JL_DLLEXPORT int32_t jl_get_default_nans(void); #include extern JL_DLLEXPORT bool jl_processor_print_help; - +// NOLINTBEGIN(clang-diagnostic-return-type-c-linkage) /** * Returns the CPU name and feature string to be used by LLVM JIT. * @@ -274,9 +264,7 @@ struct jl_target_spec_t { * Return the list of targets to clone */ extern "C" JL_DLLEXPORT llvm::SmallVector jl_get_llvm_clone_targets(void) JL_NOTSAFEPOINT; -std::string jl_get_cpu_name_llvm(void) JL_NOTSAFEPOINT; -std::string jl_get_cpu_features_llvm(void) JL_NOTSAFEPOINT; - +// NOLINTEND(clang-diagnostic-return-type-c-linkage) struct FeatureName { const char *name; uint32_t bit; // bit index into a `uint32_t` array; diff --git a/src/processor_arm.cpp b/src/processor_arm.cpp index 1cb209dbbcd62..1852188c718a9 100644 --- a/src/processor_arm.cpp +++ b/src/processor_arm.cpp @@ -164,7 +164,11 @@ enum class CPU : uint32_t { apple_a12, apple_a13, apple_a14, + apple_a15, + apple_a16, apple_m1, + apple_m2, + apple_m3, apple_s4, apple_s5, @@ -203,7 +207,7 @@ static constexpr auto feature_masks = get_feature_masks( #undef JL_FEATURE_DEF -1); static const auto real_feature_masks = - feature_masks & FeatureList{{(uint32_t)-1, (uint32_t)-1, 0}}; + feature_masks & FeatureList{{UINT32_MAX, UINT32_MAX, 0}}; namespace Feature { enum : uint32_t { @@ -349,7 +353,11 @@ constexpr auto apple_a11 = armv8_2a_crypto | get_feature_masks(fullfp16); constexpr auto apple_a12 = armv8_3a_crypto | get_feature_masks(fullfp16); constexpr auto apple_a13 = armv8_4a_crypto | get_feature_masks(fp16fml, fullfp16, sha3); constexpr auto apple_a14 = armv8_5a_crypto | get_feature_masks(dotprod,fp16fml, fullfp16, sha3); +constexpr auto apple_a15 = armv8_5a_crypto | get_feature_masks(dotprod,fp16fml, fullfp16, sha3, i8mm, bf16); +constexpr auto apple_a16 = armv8_5a_crypto | get_feature_masks(dotprod,fp16fml, fullfp16, sha3, i8mm, bf16); constexpr auto apple_m1 = armv8_5a_crypto | get_feature_masks(dotprod,fp16fml, fullfp16, sha3); +constexpr auto apple_m2 = armv8_5a_crypto | get_feature_masks(dotprod,fp16fml, fullfp16, sha3, i8mm, bf16); +constexpr auto apple_m3 = armv8_5a_crypto | get_feature_masks(dotprod,fp16fml, fullfp16, sha3, i8mm, bf16); // Features based on https://github.com/llvm/llvm-project/blob/82507f1798768280cf5d5aab95caaafbc7fe6f47/llvm/include/llvm/Support/AArch64TargetParser.def // and sysctl -a hw.optional constexpr auto apple_s4 = apple_a12; @@ -431,7 +439,11 @@ static constexpr CPUSpec cpus[] = { {"apple-a12", CPU::apple_a12, CPU::generic, 100000, Feature::apple_a12}, {"apple-a13", CPU::apple_a13, CPU::generic, 100000, Feature::apple_a13}, {"apple-a14", CPU::apple_a14, CPU::apple_a13, 120000, Feature::apple_a14}, + {"apple-a15", CPU::apple_a15, CPU::apple_a14, 160000, Feature::apple_a15}, + {"apple-a16", CPU::apple_a16, CPU::apple_a14, 160000, Feature::apple_a16}, {"apple-m1", CPU::apple_m1, CPU::apple_a14, 130000, Feature::apple_m1}, + {"apple-m2", CPU::apple_m2, CPU::apple_m1, 160000, Feature::apple_m2}, + {"apple-m3", CPU::apple_m3, CPU::apple_m2, 180000, Feature::apple_m3}, {"apple-s4", CPU::apple_s4, CPU::generic, 100000, Feature::apple_s4}, {"apple-s5", CPU::apple_s5, CPU::generic, 100000, Feature::apple_s5}, {"thunderx3t110", CPU::marvell_thunderx3t110, CPU::cavium_thunderx2t99, 110000, @@ -461,7 +473,7 @@ static constexpr auto feature_masks = get_feature_masks( #undef JL_FEATURE_DEF -1); static const auto real_feature_masks = - feature_masks & FeatureList{{(uint32_t)-1, (uint32_t)-1, 0}}; + feature_masks & FeatureList{{UINT32_MAX, UINT32_MAX, 0}}; namespace Feature { enum : uint32_t { @@ -699,16 +711,17 @@ static inline const char *find_cpu_name(uint32_t cpu) static NOINLINE std::pair> _get_host_cpu() { + using namespace llvm; char buffer[128]; size_t bufferlen = 128; sysctlbyname("machdep.cpu.brand_string",&buffer,&bufferlen,NULL,0); - - if(strcmp(buffer,"Apple M1") == 0) - return std::make_pair((uint32_t)CPU::apple_m1, Feature::apple_m1); - else if(strcmp(buffer,"Apple M1 Max") == 0) - return std::make_pair((uint32_t)CPU::apple_m1, Feature::apple_m1); - else if(strcmp(buffer,"Apple M1 Pro") == 0) + StringRef cpu_name(buffer); + if (cpu_name.find("M1") != StringRef ::npos) return std::make_pair((uint32_t)CPU::apple_m1, Feature::apple_m1); + else if (cpu_name.find("M2") != StringRef ::npos) + return std::make_pair((uint32_t)CPU::apple_m2, Feature::apple_m2); + else if (cpu_name.find("M3") != StringRef ::npos) + return std::make_pair((uint32_t)CPU::apple_m3, Feature::apple_m3); else return std::make_pair((uint32_t)CPU::apple_m1, Feature::apple_m1); } @@ -1053,7 +1066,23 @@ static CPU get_cpu_name(CPUID cpuid) return CPU::apple_a14; case 0x22: // Icestorm m1 case 0x23: // Firestorm m1 + case 0x24: + case 0x25: // From https://github.com/AsahiLinux/m1n1/blob/3b9a71422e45209ef57c563e418f877bf54358be/src/chickens.c#L9 + case 0x28: + case 0x29: return CPU::apple_m1; + case 0x30: // Blizzard m2 + case 0x31: // Avalanche m2 + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x38: + case 0x39: + return CPU::apple_m2; + case 0x49: // Everest m3 + case 0x48: // Sawtooth m3 + return CPU::apple_m3; default: return CPU::generic; } case 0x68: // 'h': Huaxintong Semiconductor @@ -1493,7 +1522,7 @@ static const llvm::SmallVector, 0> &get_cmdline_targets(v } #endif auto fbit = find_feature_bit(feature_names, nfeature_names, str, len); - if (fbit == (uint32_t)-1) + if (fbit == UINT32_MAX) return false; set_bit(list, fbit, true); return true; @@ -1574,7 +1603,7 @@ static uint32_t sysimg_init_cb(const void *id, jl_value_t **rejection_reason) } } auto match = match_sysimg_targets(sysimg, target, max_vector_size, rejection_reason); - if (match.best_idx == -1) + if (match.best_idx == UINT32_MAX) return match.best_idx; // Now we've decided on which sysimg version to use. // Make sure the JIT target is compatible with it and save the JIT target. @@ -1798,14 +1827,20 @@ JL_DLLEXPORT void jl_dump_host_cpu(void) cpus, ncpu_names); } -JL_DLLEXPORT jl_value_t *jl_get_cpu_name(void) -{ - return jl_cstr_to_string(host_cpu_name().c_str()); -} - -JL_DLLEXPORT jl_value_t *jl_get_cpu_features(void) +JL_DLLEXPORT jl_value_t *jl_cpu_has_fma(int bits) { - return jl_cstr_to_string(jl_get_cpu_features_llvm().c_str()); +#ifdef _CPU_AARCH64_ + return jl_true; +#else + TargetData target = jit_targets.front(); + FeatureList features = target.en.features; + if (bits == 32 && test_nbit(features, Feature::vfp4sp)) + return jl_true; + else if ((bits == 64 || bits == 32) && test_nbit(features, Feature::vfp4)) + return jl_true; + else + return jl_false; +#endif } jl_image_t jl_init_processor_sysimg(void *hdl) @@ -1830,7 +1865,7 @@ JL_DLLEXPORT jl_value_t* jl_check_pkgimage_clones(char *data) JL_GC_PUSH1(&rejection_reason); uint32_t match_idx = pkgimg_init_cb(data, &rejection_reason); JL_GC_POP(); - if (match_idx == (uint32_t)-1) + if (match_idx == UINT32_MAX) return rejection_reason; return jl_nothing; } diff --git a/src/processor_fallback.cpp b/src/processor_fallback.cpp index 8c343aa982470..87d72d5ba7958 100644 --- a/src/processor_fallback.cpp +++ b/src/processor_fallback.cpp @@ -145,7 +145,7 @@ const std::pair &jl_get_llvm_disasm_target(void) return res; } -extern "C" llvm::SmallVector jl_get_llvm_clone_targets(void) +llvm::SmallVector jl_get_llvm_clone_targets(void) { if (jit_targets.empty()) jl_error("JIT targets not initialized"); @@ -162,14 +162,9 @@ extern "C" llvm::SmallVector jl_get_llvm_clone_targets(void return res; } -JL_DLLEXPORT jl_value_t *jl_get_cpu_name(void) +JL_DLLEXPORT jl_value_t *jl_cpu_has_fma(int bits) { - return jl_cstr_to_string(host_cpu_name().c_str()); -} - -JL_DLLEXPORT jl_value_t *jl_get_cpu_features(void) -{ - return jl_cstr_to_string(jl_get_cpu_features_llvm().c_str()); + return jl_false; // Match behaviour of have_fma in src/llvm-cpufeatures.cpp (assume false) } JL_DLLEXPORT void jl_dump_host_cpu(void) @@ -184,7 +179,7 @@ JL_DLLEXPORT jl_value_t* jl_check_pkgimage_clones(char *data) JL_GC_PUSH1(&rejection_reason); uint32_t match_idx = pkgimg_init_cb(data, &rejection_reason); JL_GC_POP(); - if (match_idx == (uint32_t)-1) + if (match_idx == UINT32_MAX) return rejection_reason; return jl_nothing; } diff --git a/src/processor_x86.cpp b/src/processor_x86.cpp index 361a8c6167541..7f173440c8b9b 100644 --- a/src/processor_x86.cpp +++ b/src/processor_x86.cpp @@ -4,6 +4,7 @@ // CPUID +#include "julia.h" extern "C" JL_DLLEXPORT void jl_cpuid(int32_t CPUInfo[4], int32_t InfoType) { asm volatile ( @@ -94,6 +95,7 @@ enum class CPU : uint32_t { amd_znver1, amd_znver2, amd_znver3, + amd_znver4, }; static constexpr size_t feature_sz = 11; @@ -234,6 +236,8 @@ constexpr auto znver1 = haswell | get_feature_masks(adx, aes, clflushopt, clzero rdseed, sha, sse4a, xsavec); constexpr auto znver2 = znver1 | get_feature_masks(clwb, rdpid, wbnoinvd); constexpr auto znver3 = znver2 | get_feature_masks(shstk, pku, vaes, vpclmulqdq); +constexpr auto znver4 = znver3 | get_feature_masks(avx512f, avx512cd, avx512dq, avx512bw, avx512vl, avx512ifma, avx512vbmi, + avx512vbmi2, avx512vnni, avx512bitalg, avx512vpopcntdq, avx512bf16, gfni, shstk, xsaves); } @@ -295,6 +299,7 @@ static constexpr CPUSpec cpus[] = { {"znver1", CPU::amd_znver1, CPU::generic, 0, Feature::znver1}, {"znver2", CPU::amd_znver2, CPU::generic, 0, Feature::znver2}, {"znver3", CPU::amd_znver3, CPU::amd_znver2, 120000, Feature::znver3}, + {"znver4", CPU::amd_znver4, CPU::amd_znver3, 160000, Feature::znver4}, }; static constexpr size_t ncpu_names = sizeof(cpus) / sizeof(cpus[0]); @@ -562,9 +567,15 @@ static CPU get_amd_processor_name(uint32_t family, uint32_t model, const uint32_ if (model >= 0x30) return CPU::amd_znver2; return CPU::amd_znver1; - case 0x19: // AMD Family 19h - if (model <= 0x0f || model == 0x21) + case 25: // AMD Family 19h + if (model <= 0x0f || (model >= 0x20 && model <= 0x5f)) return CPU::amd_znver3; // 00h-0Fh, 21h: Zen3 + if ((model >= 0x10 && model <= 0x1f) || + (model >= 0x60 && model <= 0x74) || + (model >= 0x78 && model <= 0x7b) || + (model >= 0xA0 && model <= 0xAf)) { + return CPU::amd_znver4; + } return CPU::amd_znver3; // fallback } } @@ -775,7 +786,7 @@ static const llvm::SmallVector, 0> &get_cmdline_targets(v { auto feature_cb = [] (const char *str, size_t len, FeatureList &list) { auto fbit = find_feature_bit(feature_names, nfeature_names, str, len); - if (fbit == (uint32_t)-1) + if (fbit == UINT32_MAX) return false; set_bit(list, fbit, true); return true; @@ -869,7 +880,7 @@ static uint32_t sysimg_init_cb(const void *id, jl_value_t** rejection_reason) "https://docs.julialang.org/en/v1/devdocs/sysimg/ for more."); } auto match = match_sysimg_targets(sysimg, target, max_vector_size, rejection_reason); - if (match.best_idx == (uint32_t)-1) + if (match.best_idx == UINT32_MAX) return match.best_idx; // Now we've decided on which sysimg version to use. // Make sure the JIT target is compatible with it and save the JIT target. @@ -1047,19 +1058,19 @@ JL_DLLEXPORT jl_value_t* jl_check_pkgimage_clones(char *data) JL_GC_PUSH1(&rejection_reason); uint32_t match_idx = pkgimg_init_cb(data, &rejection_reason); JL_GC_POP(); - if (match_idx == (uint32_t)-1) + if (match_idx == UINT32_MAX) return rejection_reason; return jl_nothing; } -JL_DLLEXPORT jl_value_t *jl_get_cpu_name(void) +JL_DLLEXPORT jl_value_t *jl_cpu_has_fma(int bits) { - return jl_cstr_to_string(host_cpu_name().c_str()); -} - -JL_DLLEXPORT jl_value_t *jl_get_cpu_features(void) -{ - return jl_cstr_to_string(jl_get_cpu_features_llvm().c_str()); + TargetData target = jit_targets.front(); + FeatureList features = target.en.features; + if ((bits == 32 || bits == 64) && (test_nbit(features, Feature::fma) || test_nbit(features, Feature::fma4))) + return jl_true; + else + return jl_false; } jl_image_t jl_init_processor_sysimg(void *hdl) @@ -1078,21 +1089,21 @@ jl_image_t jl_init_processor_pkgimg(void *hdl) return parse_sysimg(hdl, pkgimg_init_cb); } -extern "C" JL_DLLEXPORT std::pair> jl_get_llvm_target(bool imaging, uint32_t &flags) +std::pair> jl_get_llvm_target(bool imaging, uint32_t &flags) { ensure_jit_target(imaging); flags = jit_targets[0].en.flags; return get_llvm_target_vec(jit_targets[0]); } -extern "C" JL_DLLEXPORT const std::pair &jl_get_llvm_disasm_target(void) +const std::pair &jl_get_llvm_disasm_target(void) { static const auto res = get_llvm_target_str(TargetData{"generic", "", {feature_masks, 0}, {{}, 0}, 0}); return res; } -extern "C" JL_DLLEXPORT llvm::SmallVector jl_get_llvm_clone_targets(void) +llvm::SmallVector jl_get_llvm_clone_targets(void) { if (jit_targets.empty()) jl_error("JIT targets not initialized"); diff --git a/src/rtutils.c b/src/rtutils.c index afe8d24678a61..88109374cf061 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -127,11 +127,29 @@ JL_DLLEXPORT void JL_NORETURN jl_type_error(const char *fname, jl_type_error_rt(fname, "", expected, got); } -JL_DLLEXPORT void JL_NORETURN jl_undefined_var_error(jl_sym_t *var) +JL_DLLEXPORT void JL_NORETURN jl_undefined_var_error(jl_sym_t *var, jl_value_t *scope) { - if (!jl_undefvarerror_type) - jl_errorf("UndefVarError(%s)", jl_symbol_name(var)); - jl_throw(jl_new_struct(jl_undefvarerror_type, var)); + if (!jl_undefvarerror_type) { + const char *s1 = ""; + const char *s2 = ""; + if (scope) { + if (jl_is_symbol(scope)) { + s1 = ", :"; + s2 = jl_symbol_name((jl_sym_t*)scope); + } + else if (jl_is_module(scope)) { + s1 = ", module "; + s2 = jl_symbol_name(((jl_module_t*)scope)->name); + } + else { + s1 = ", "; + s2 = "unknown scope"; + } + } + jl_errorf("UndefVarError(%s%s%s)", jl_symbol_name(var), s1, s2); + } + JL_GC_PUSH1(&scope); + jl_throw(jl_new_struct(jl_undefvarerror_type, var, scope)); } JL_DLLEXPORT void JL_NORETURN jl_has_no_field_error(jl_sym_t *type_name, jl_sym_t *var) @@ -201,14 +219,6 @@ JL_DLLEXPORT void JL_NORETURN jl_bounds_error_ints(jl_value_t *v JL_MAYBE_UNROOT jl_throw(jl_new_struct((jl_datatype_t*)jl_boundserror_type, v, t)); } -JL_DLLEXPORT void JL_NORETURN jl_eof_error(void) -{ - jl_datatype_t *eof_error = - (jl_datatype_t*)jl_get_global(jl_base_module, jl_symbol("EOFError")); - assert(eof_error != NULL); - jl_throw(jl_new_struct(eof_error)); -} - JL_DLLEXPORT void jl_typeassert(jl_value_t *x, jl_value_t *t) { if (!jl_isa(x,t)) @@ -229,9 +239,8 @@ JL_DLLEXPORT void __stack_chk_fail(void) // exceptions ----------------------------------------------------------------- -JL_DLLEXPORT void jl_enter_handler(jl_handler_t *eh) +JL_DLLEXPORT void jl_enter_handler(jl_task_t *ct, jl_handler_t *eh) { - jl_task_t *ct = jl_current_task; // Must have no safepoint eh->prev = ct->eh; eh->gcstack = ct->gcstack; @@ -250,9 +259,8 @@ JL_DLLEXPORT void jl_enter_handler(jl_handler_t *eh) // * We leave a try block through normal control flow // * An exception causes a nonlocal jump to the catch block. In this case // there's additional cleanup required, eg pushing the exception stack. -JL_DLLEXPORT void jl_eh_restore_state(jl_handler_t *eh) +JL_DLLEXPORT void jl_eh_restore_state(jl_task_t *ct, jl_handler_t *eh) { - jl_task_t *ct = jl_current_task; #ifdef _OS_WINDOWS_ if (ct->ptls->needs_resetstkoflw) { _resetstkoflw(); @@ -263,7 +271,6 @@ JL_DLLEXPORT void jl_eh_restore_state(jl_handler_t *eh) // This function should **NOT** have any safepoint before the ones at the // end. sig_atomic_t old_defer_signal = ct->ptls->defer_signal; - int8_t old_gc_state = jl_atomic_load_relaxed(&ct->ptls->gc_state); ct->eh = eh->prev; ct->gcstack = eh->gcstack; small_arraylist_t *locks = &ct->ptls->locks; @@ -275,9 +282,10 @@ JL_DLLEXPORT void jl_eh_restore_state(jl_handler_t *eh) } ct->world_age = eh->world_age; ct->ptls->defer_signal = eh->defer_signal; + int8_t old_gc_state = jl_atomic_load_relaxed(&ct->ptls->gc_state); if (old_gc_state != eh->gc_state) jl_atomic_store_release(&ct->ptls->gc_state, eh->gc_state); - if (!eh->gc_state) + if (!old_gc_state || !eh->gc_state) // it was or is unsafe now jl_gc_safepoint_(ct->ptls); if (old_defer_signal && !eh->defer_signal) jl_sigint_safepoint(ct->ptls); @@ -287,27 +295,41 @@ JL_DLLEXPORT void jl_eh_restore_state(jl_handler_t *eh) } } -JL_DLLEXPORT void jl_pop_handler(int n) +JL_DLLEXPORT void jl_eh_restore_state_noexcept(jl_task_t *ct, jl_handler_t *eh) +{ + assert(ct->gcstack == eh->gcstack && "Incorrect GC usage under try catch"); + ct->eh = eh->prev; + ct->ptls->defer_signal = eh->defer_signal; // optional, but certain try-finally (in stream.jl) may be slightly harder to write without this +} + +JL_DLLEXPORT void jl_pop_handler(jl_task_t *ct, int n) { - jl_task_t *ct = jl_current_task; if (__unlikely(n <= 0)) return; jl_handler_t *eh = ct->eh; while (--n > 0) eh = eh->prev; - jl_eh_restore_state(eh); + jl_eh_restore_state(ct, eh); } -JL_DLLEXPORT size_t jl_excstack_state(void) JL_NOTSAFEPOINT +JL_DLLEXPORT void jl_pop_handler_noexcept(jl_task_t *ct, int n) +{ + if (__unlikely(n <= 0)) + return; + jl_handler_t *eh = ct->eh; + while (--n > 0) + eh = eh->prev; + jl_eh_restore_state_noexcept(ct, eh); +} + +JL_DLLEXPORT size_t jl_excstack_state(jl_task_t *ct) JL_NOTSAFEPOINT { - jl_task_t *ct = jl_current_task; jl_excstack_t *s = ct->excstack; return s ? s->top : 0; } -JL_DLLEXPORT void jl_restore_excstack(size_t state) JL_NOTSAFEPOINT +JL_DLLEXPORT void jl_restore_excstack(jl_task_t *ct, size_t state) JL_NOTSAFEPOINT { - jl_task_t *ct = jl_current_task; jl_excstack_t *s = ct->excstack; if (s) { assert(s->top >= state); @@ -322,28 +344,27 @@ static void jl_copy_excstack(jl_excstack_t *dest, jl_excstack_t *src) JL_NOTSAFE dest->top = src->top; } -static void jl_reserve_excstack(jl_task_t* task, jl_excstack_t **stack JL_REQUIRE_ROOTED_SLOT, +static void jl_reserve_excstack(jl_task_t *ct, jl_excstack_t **stack JL_REQUIRE_ROOTED_SLOT, size_t reserved_size) { jl_excstack_t *s = *stack; if (s && s->reserved_size >= reserved_size) return; size_t bufsz = sizeof(jl_excstack_t) + sizeof(uintptr_t)*reserved_size; - jl_task_t *ct = jl_current_task; jl_excstack_t *new_s = (jl_excstack_t*)jl_gc_alloc_buf(ct->ptls, bufsz); new_s->top = 0; new_s->reserved_size = reserved_size; if (s) jl_copy_excstack(new_s, s); *stack = new_s; - jl_gc_wb(task, new_s); + jl_gc_wb(ct, new_s); } -void jl_push_excstack(jl_task_t* task, jl_excstack_t **stack JL_REQUIRE_ROOTED_SLOT JL_ROOTING_ARGUMENT, +void jl_push_excstack(jl_task_t *ct, jl_excstack_t **stack JL_REQUIRE_ROOTED_SLOT JL_ROOTING_ARGUMENT, jl_value_t *exception JL_ROOTED_ARGUMENT, jl_bt_element_t *bt_data, size_t bt_size) { - jl_reserve_excstack(task, stack, (*stack ? (*stack)->top : 0) + bt_size + 2); + jl_reserve_excstack(ct, stack, (*stack ? (*stack)->top : 0) + bt_size + 2); jl_excstack_t *s = *stack; jl_bt_element_t *rawstack = jl_excstack_raw(s); memcpy(rawstack + s->top, bt_data, sizeof(jl_bt_element_t)*bt_size); @@ -362,7 +383,10 @@ JL_DLLEXPORT void *(jl_symbol_name)(jl_sym_t *s) // WARNING: THIS FUNCTION IS NEVER CALLED BUT INLINE BY CCALL JL_DLLEXPORT void *jl_array_ptr(jl_array_t *a) { - return a->data; + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(a->ref.mem))->layout; + if (layout->flags.arrayelem_isunion || layout->size == 0) + return (char*)a->ref.mem->ptr + (size_t)jl_array_data_(a); + return jl_array_data_(a); } JL_DLLEXPORT jl_value_t *jl_value_ptr(jl_value_t *a) { @@ -525,14 +549,6 @@ JL_DLLEXPORT void jl_flush_cstdio(void) JL_NOTSAFEPOINT fflush(stderr); } -JL_DLLEXPORT jl_value_t *jl_stdout_obj(void) JL_NOTSAFEPOINT -{ - if (jl_base_module == NULL) - return NULL; - jl_binding_t *stdout_obj = jl_get_module_binding(jl_base_module, jl_symbol("stdout"), 0); - return stdout_obj ? jl_atomic_load_relaxed(&stdout_obj->value) : NULL; -} - JL_DLLEXPORT jl_value_t *jl_stderr_obj(void) JL_NOTSAFEPOINT { if (jl_base_module == NULL) @@ -570,7 +586,7 @@ static size_t jl_show_svec(JL_STREAM *out, jl_svec_t *t, const char *head, const JL_DLLEXPORT int jl_id_start_char(uint32_t wc) JL_NOTSAFEPOINT; JL_DLLEXPORT int jl_id_char(uint32_t wc) JL_NOTSAFEPOINT; -JL_DLLEXPORT int jl_is_identifier(char *str) JL_NOTSAFEPOINT +JL_DLLEXPORT int jl_is_identifier(const char *str) JL_NOTSAFEPOINT { size_t i = 0; uint32_t wc = u8_nextchar(str, &i); @@ -653,22 +669,64 @@ static int is_globfunction(jl_value_t *v, jl_datatype_t *dv, jl_sym_t **globname return 0; } -static size_t jl_static_show_x_sym_escaped(JL_STREAM *out, jl_sym_t *name) JL_NOTSAFEPOINT +static size_t jl_static_show_string(JL_STREAM *out, const char *str, size_t len, int wrap) JL_NOTSAFEPOINT { size_t n = 0; - - char *sn = jl_symbol_name(name); - int hidden = 0; - if (!(jl_is_identifier(sn) || jl_is_operator(sn))) { - hidden = 1; + if (wrap) + n += jl_printf(out, "\""); + if (!u8_isvalid(str, len)) { + // alternate print algorithm that preserves data if it's not UTF-8 + static const char hexdig[] = "0123456789abcdef"; + for (size_t i = 0; i < len; i++) { + uint8_t c = str[i]; + if (c == '\\' || c == '"' || c == '$') + n += jl_printf(out, "\\%c", c); + else if (c >= 32 && c < 0x7f) + n += jl_printf(out, "%c", c); + else + n += jl_printf(out, "\\x%c%c", hexdig[c>>4], hexdig[c&0xf]); + } } - - if (hidden) { - n += jl_printf(out, "var\""); + else { + int special = 0; + for (size_t i = 0; i < len; i++) { + uint8_t c = str[i]; + if (c < 32 || c == 0x7f || c == '\\' || c == '"' || c == '$') { + special = 1; + break; + } + } + if (!special) { + jl_uv_puts(out, str, len); + n += len; + } + else { + char buf[512]; + size_t i = 0; + while (i < len) { + size_t r = u8_escape(buf, sizeof(buf), str, &i, len, "\"$", 0); + jl_uv_puts(out, buf, r - 1); + n += r - 1; + } + } } - n += jl_printf(out, "%s", sn); - if (hidden) { + if (wrap) n += jl_printf(out, "\""); + return n; +} + +static size_t jl_static_show_symbol(JL_STREAM *out, jl_sym_t *name) JL_NOTSAFEPOINT +{ + size_t n = 0; + const char *sn = jl_symbol_name(name); + int quoted = !jl_is_identifier(sn) && !jl_is_operator(sn); + if (quoted) { + n += jl_printf(out, "var"); + // TODO: this is not quite right, since repr uses String escaping rules, and Symbol uses raw string rules + n += jl_static_show_string(out, sn, strlen(sn), 1); + } + else { + n += jl_printf(out, "%s", sn); } return n; } @@ -786,11 +844,6 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt // Types are printed as a fully qualified name, with parameters, e.g. // `Base.Set{Int}`, and function types are printed as e.g. `typeof(Main.f)` jl_datatype_t *dv = (jl_datatype_t*)v; - jl_sym_t *globname; - int globfunc = is_globname_binding(v, dv) && is_globfunction(v, dv, &globname); - jl_sym_t *sym = globfunc ? globname : dv->name->name; - char *sn = jl_symbol_name(sym); - size_t quote = 0; if (dv->name == jl_tuple_typename) { if (dv == jl_tuple_type) return jl_printf(out, "Tuple"); @@ -822,9 +875,25 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt } return n; } + if (jl_genericmemory_type && dv->name == jl_genericmemory_typename) { + jl_value_t *isatomic = jl_tparam0(dv); + jl_value_t *el_type = jl_tparam1(dv); + jl_value_t *addrspace = jl_tparam2(dv); + if (isatomic == (jl_value_t*)jl_not_atomic_sym && addrspace && jl_is_addrspacecore(addrspace) && jl_unbox_uint8(addrspace) == 0) { + n += jl_printf(out, "Memory{"); + n += jl_static_show_x(out, el_type, depth, ctx); + n += jl_printf(out, "}"); + return n; + } + } if (ctx.quiet) { - return jl_printf(out, "%s", jl_symbol_name(dv->name->name)); + return jl_static_show_symbol(out, dv->name->name); } + jl_sym_t *globname; + int globfunc = is_globname_binding(v, dv) && is_globfunction(v, dv, &globname); + jl_sym_t *sym = globfunc ? globname : dv->name->name; + char *sn = jl_symbol_name(sym); + size_t quote = 0; if (globfunc) { n += jl_printf(out, "typeof("); } @@ -837,7 +906,7 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt quote = 1; } } - n += jl_static_show_x_sym_escaped(out, sym); + n += jl_static_show_symbol(out, sym); if (globfunc) { n += jl_printf(out, ")"); if (quote) { @@ -906,9 +975,7 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt n += jl_printf(out, "nothing"); } else if (vt == jl_string_type) { - n += jl_printf(out, "\""); - jl_uv_puts(out, jl_string_data(v), jl_string_len(v)); n += jl_string_len(v); - n += jl_printf(out, "\""); + n += jl_static_show_string(out, jl_string_data(v), jl_string_len(v), 1); } else if (v == jl_bottom_type) { n += jl_printf(out, "Union{}"); @@ -957,7 +1024,7 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt n += jl_printf(out, ")"); n += jl_printf(out, "<:"); } - n += jl_static_show_x_sym_escaped(out, var->name); + n += jl_static_show_symbol(out, var->name); if (showbounds && (ub != (jl_value_t*)jl_any_type || lb != jl_bottom_type)) { // show type-var upper bound if it is defined, or if we showed the lower bound int ua = jl_is_unionall(ub); @@ -975,18 +1042,11 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt n += jl_static_show_x(out, (jl_value_t*)m->parent, depth, ctx); n += jl_printf(out, "."); } - n += jl_printf(out, "%s", jl_symbol_name(m->name)); + n += jl_static_show_symbol(out, m->name); } else if (vt == jl_symbol_type) { - char *sn = jl_symbol_name((jl_sym_t*)v); - int quoted = !jl_is_identifier(sn) && jl_operator_precedence(sn) == 0; - if (quoted) - n += jl_printf(out, "Symbol(\""); - else - n += jl_printf(out, ":"); - n += jl_printf(out, "%s", sn); - if (quoted) - n += jl_printf(out, "\")"); + n += jl_printf(out, ":"); + n += jl_static_show_symbol(out, (jl_sym_t*)v); } else if (vt == jl_ssavalue_type) { n += jl_printf(out, "SSAValue(%" PRIuPTR ")", @@ -994,8 +1054,12 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt } else if (vt == jl_globalref_type) { n += jl_static_show_x(out, (jl_value_t*)jl_globalref_mod(v), depth, ctx); - char *name = jl_symbol_name(jl_globalref_name(v)); - n += jl_printf(out, jl_is_identifier(name) ? ".%s" : ".:(%s)", name); + jl_sym_t *name = jl_globalref_name(v); + n += jl_printf(out, "."); + if (jl_is_operator(jl_symbol_name(name))) + n += jl_printf(out, ":(%s)", jl_symbol_name(name)); + else + n += jl_static_show_symbol(out, name); } else if (vt == jl_gotonode_type) { n += jl_printf(out, "goto %" PRIuPTR, jl_gotonode_label(v)); @@ -1028,42 +1092,70 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt } else if (vt == jl_expr_type) { jl_expr_t *e = (jl_expr_t*)v; - if (e->head == jl_assign_sym && jl_array_len(e->args) == 2) { - n += jl_static_show_x(out, jl_exprarg(e,0), depth, ctx); + if (e->head == jl_assign_sym && jl_array_nrows(e->args) == 2) { + n += jl_static_show_x(out, jl_exprarg(e, 0), depth, ctx); n += jl_printf(out, " = "); - n += jl_static_show_x(out, jl_exprarg(e,1), depth, ctx); + n += jl_static_show_x(out, jl_exprarg(e, 1), depth, ctx); } else { - char sep = ' '; - n += jl_printf(out, "Expr(:%s", jl_symbol_name(e->head)); - size_t i, len = jl_array_len(e->args); + n += jl_printf(out, "Expr("); + n += jl_static_show_x(out, (jl_value_t*)e->head, depth, ctx); + size_t i, len = jl_array_nrows(e->args); for (i = 0; i < len; i++) { - n += jl_printf(out, ",%c", sep); - n += jl_static_show_x(out, jl_exprarg(e,i), depth, ctx); + n += jl_printf(out, ", "); + n += jl_static_show_x(out, jl_exprarg(e, i), depth, ctx); } n += jl_printf(out, ")"); } } else if (jl_array_type && jl_is_array_type(vt)) { n += jl_printf(out, "Array{"); - n += jl_static_show_x(out, (jl_value_t*)jl_tparam0(vt), depth, ctx); - n += jl_printf(out, ", ("); + jl_value_t *el_type = jl_tparam0(vt); + n += jl_static_show_x(out, el_type, depth, ctx); + jl_array_t *av = (jl_array_t*)v; size_t i, ndims = jl_array_ndims(v); + n += jl_printf(out, ", %" PRIdPTR "}(dims=(", ndims); if (ndims == 1) n += jl_printf(out, "%" PRIdPTR ",", jl_array_dim0(v)); else for (i = 0; i < ndims; i++) n += jl_printf(out, (i > 0 ? ", %" PRIdPTR : "%" PRIdPTR), jl_array_dim(v, i)); - n += jl_printf(out, ")}["); - size_t j, tlen = jl_array_len(v); - jl_array_t *av = (jl_array_t*)v; - jl_value_t *el_type = jl_tparam0(vt); - char *typetagdata = (!av->flags.ptrarray && jl_is_uniontype(el_type)) ? jl_array_typetagdata(av) : NULL; + n += jl_printf(out, "), mem="); + n += jl_static_show_x(out, (jl_value_t*)av->ref.mem, depth, ctx); + n += jl_printf(out, ")"); + } + else if (jl_genericmemoryref_type && jl_is_genericmemoryref_type(vt)) { + jl_genericmemoryref_t *ref = (jl_genericmemoryref_t*)v; + n += jl_printf(out, "GenericMemoryRef(offset="); + size_t offset = (size_t)ref->ptr_or_offset; + if (ref->mem) { + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typeof(ref->mem))->layout; + if (layout->size != 0 && !layout->flags.arrayelem_isunion) + offset = ((char*)offset - (char*)ref->mem->ptr) / layout->size; + } + n += jl_printf(out, "%" PRIdPTR, offset); + n += jl_printf(out, ", ptr_or_offset=%p, mem=", ref->ptr_or_offset); + n += jl_static_show_x(out, (jl_value_t*)ref->mem, depth, ctx); + } + else if (jl_genericmemory_type && jl_is_genericmemory_type(vt)) { + jl_genericmemory_t *m = (jl_genericmemory_t*)v; + //jl_value_t *isatomic = jl_tparam0(vt); + jl_value_t *el_type = jl_tparam1(vt); + jl_value_t *addrspace = jl_tparam2(vt); + n += jl_static_show_x(out, (jl_value_t*)vt, depth, ctx); + size_t j, tlen = m->length; + n += jl_printf(out, "(%" PRIdPTR ", %p)[", tlen, m->ptr); + if (!(addrspace && jl_is_addrspacecore(addrspace) && jl_unbox_uint8(addrspace) == 0)) { + n += jl_printf(out, "...]"); + return n; + } + const char *typetagdata = NULL; + const jl_datatype_layout_t *layout = vt->layout; int nlsep = 0; - if (av->flags.ptrarray) { + if (layout->flags.arrayelem_isboxed) { // print arrays with newlines, unless the elements are probably small for (j = 0; j < tlen; j++) { - jl_value_t **ptr = ((jl_value_t**)av->data) + j; + jl_value_t **ptr = ((jl_value_t**)m->ptr) + j; jl_value_t *p = *ptr; if (p != NULL && (uintptr_t)p >= 4096U) { jl_value_t *p_ty = jl_typeof(p); @@ -1076,21 +1168,30 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt } } } - if (nlsep && tlen > 1) - n += jl_printf(out, "\n "); - for (j = 0; j < tlen; j++) { - if (av->flags.ptrarray) { - jl_value_t **ptr = ((jl_value_t**)av->data) + j; - n += jl_static_show_x(out, *ptr, depth, ctx); - } - else { - char *ptr = ((char*)av->data) + j * av->elsize; - n += jl_static_show_x_(out, (jl_value_t*)ptr, - typetagdata ? (jl_datatype_t*)jl_nth_union_component(el_type, typetagdata[j]) : (jl_datatype_t*)el_type, - depth, ctx); + else if (layout->flags.arrayelem_isunion) { + typetagdata = jl_genericmemory_typetagdata(m); + } + if (layout->size == 0 && tlen >= 3) { + n += jl_static_show_x_(out, (jl_value_t*)m->ptr, (jl_datatype_t*)el_type, depth, ctx); + n += jl_printf(out, ", ..."); + } + else { + if (nlsep && tlen > 1) + n += jl_printf(out, "\n "); + for (size_t j = 0; j < tlen; j++) { + if (layout->flags.arrayelem_isboxed) { + jl_value_t **ptr = ((jl_value_t**)m->ptr) + j; + n += jl_static_show_x(out, *ptr, depth, ctx); + } + else { + char *ptr = ((char*)m->ptr) + j * layout->size; + n += jl_static_show_x_(out, (jl_value_t*)ptr, + (jl_datatype_t*)(typetagdata ? jl_nth_union_component(el_type, typetagdata[j]) : el_type), + depth, ctx); + } + if (j != tlen - 1) + n += jl_printf(out, nlsep ? ",\n " : ", "); } - if (j != tlen - 1) - n += jl_printf(out, nlsep ? ",\n " : ", "); } n += jl_printf(out, "]"); } @@ -1126,7 +1227,7 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt } } - n += jl_static_show_x_sym_escaped(out, sym); + n += jl_static_show_symbol(out, sym); if (globfunc) { if (quote) { @@ -1162,8 +1263,14 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt jl_value_t *names = isnamedtuple ? jl_tparam0(vt) : (jl_value_t*)jl_field_names(vt); for (; i < tlen; i++) { if (!istuple) { - jl_value_t *fname = isnamedtuple ? jl_fieldref_noalloc(names, i) : jl_svecref(names, i); - n += jl_printf(out, "%s=", jl_symbol_name((jl_sym_t*)fname)); + jl_sym_t *fname = (jl_sym_t*)(isnamedtuple ? jl_fieldref_noalloc(names, i) : jl_svecref(names, i)); + if (fname == NULL || !jl_is_symbol(fname)) + n += jl_static_show_x(out, (jl_value_t*)fname, depth, ctx); + else if (jl_is_operator(jl_symbol_name(fname))) + n += jl_printf(out, "(%s)", jl_symbol_name(fname)); + else + n += jl_static_show_symbol(out, fname); + n += jl_printf(out, "="); } size_t offs = jl_field_offset(vt, i); char *fld_ptr = (char*)v + offs; @@ -1298,7 +1405,7 @@ size_t jl_static_show_func_sig_(JL_STREAM *s, jl_value_t *type, jl_static_show_c if ((jl_nparams(ftype) == 0 || ftype == ((jl_datatype_t*)ftype)->name->wrapper) && ((jl_datatype_t*)ftype)->name->mt != jl_type_type_mt && ((jl_datatype_t*)ftype)->name->mt != jl_nonfunction_mt) { - n += jl_printf(s, "%s", jl_symbol_name(((jl_datatype_t*)ftype)->name->mt->name)); + n += jl_static_show_symbol(s, ((jl_datatype_t*)ftype)->name->mt->name); } else { n += jl_printf(s, "(::"); @@ -1397,10 +1504,10 @@ void jl_log(int level, jl_value_t *module, jl_value_t *group, jl_value_t *id, } jl_printf(str, "\n@ "); if (jl_is_string(file)) { - jl_uv_puts(str, jl_string_data(file), jl_string_len(file)); + jl_static_show_string(str, jl_string_data(file), jl_string_len(file), 0); } else if (jl_is_symbol(file)) { - jl_printf(str, "%s", jl_symbol_name((jl_sym_t*)file)); + jl_static_show_string(str, jl_symbol_name((jl_sym_t*)file), strlen(jl_symbol_name((jl_sym_t*)file)), 0); } jl_printf(str, ":"); jl_static_show(str, line); diff --git a/src/runtime_ccall.cpp b/src/runtime_ccall.cpp index a63a6dd3d6f1d..25be49d6d2f3f 100644 --- a/src/runtime_ccall.cpp +++ b/src/runtime_ccall.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include "julia.h" @@ -84,42 +83,6 @@ void *jl_lazy_load_and_lookup(jl_value_t *lib_val, const char *f_name) } // miscellany -std::string jl_get_cpu_name_llvm(void) -{ - return llvm::sys::getHostCPUName().str(); -} - -std::string jl_get_cpu_features_llvm(void) -{ - StringMap HostFeatures; - llvm::sys::getHostCPUFeatures(HostFeatures); - std::string attr; - for (auto &ele: HostFeatures) { - if (ele.getValue()) { - if (!attr.empty()) { - attr.append(",+"); - } - else { - attr.append("+"); - } - attr.append(ele.getKey().str()); - } - } - // Explicitly disabled features need to be added at the end so that - // they are not re-enabled by other features that implies them by default. - for (auto &ele: HostFeatures) { - if (!ele.getValue()) { - if (!attr.empty()) { - attr.append(",-"); - } - else { - attr.append("-"); - } - attr.append(ele.getKey().str()); - } - } - return attr; -} extern "C" JL_DLLEXPORT jl_value_t *jl_get_JIT(void) @@ -355,7 +318,7 @@ jl_value_t *jl_get_cfunction_trampoline( uv_mutex_lock(&trampoline_lock); tramp = trampoline_alloc(); ((void**)result)[0] = tramp; - tramp = init_trampoline(tramp, nval); + init_trampoline(tramp, nval); ptrhash_put(cache, (void*)fobj, result); uv_mutex_unlock(&trampoline_lock); return result; diff --git a/src/runtime_intrinsics.c b/src/runtime_intrinsics.c index b42b7d9832383..9abe9828fb07f 100644 --- a/src/runtime_intrinsics.c +++ b/src/runtime_intrinsics.c @@ -5,8 +5,6 @@ // // this file assumes a little-endian processor, although that isn't too hard to fix // it also assumes two's complement negative numbers, which might be a bit harder to fix -// -// TODO: add half-float support #include "APInt-C.h" #include "julia.h" @@ -14,7 +12,7 @@ const unsigned int host_char_bit = 8; -// float16 intrinsics +// float16 conversion helpers static inline float half_to_float(uint16_t ival) JL_NOTSAFEPOINT { @@ -185,94 +183,208 @@ static inline uint16_t float_to_half(float param) JL_NOTSAFEPOINT return h; } -JL_DLLEXPORT float julia__gnu_h2f_ieee(uint16_t param) +static inline uint16_t double_to_half(double param) JL_NOTSAFEPOINT { + float temp = (float)param; + uint32_t tempi; + memcpy(&tempi, &temp, sizeof(temp)); + + // if Float16(res) is subnormal + if ((tempi&0x7fffffffu) < 0x38800000u) { + // shift so that the mantissa lines up where it would for normal Float16 + uint32_t shift = 113u-((tempi & 0x7f800000u)>>23u); + if (shift<23u) { + tempi |= 0x00800000; // set implicit bit + tempi >>= shift; + } + } + + // if we are halfway between 2 Float16 values + if ((tempi & 0x1fffu) == 0x1000u) { + memcpy(&tempi, &temp, sizeof(temp)); + // adjust the value by 1 ULP in the direction that will make Float16(temp) give the right answer + tempi += (fabs(temp) < fabs(param)) - (fabs(param) < fabs(temp)); + memcpy(&temp, &tempi, sizeof(temp)); + } + + return float_to_half(temp); +} + +// x86-specific helpers for emulating the (B)Float16 ABI +#if defined(_CPU_X86_) || defined(_CPU_X86_64_) +#include +__attribute__((unused)) static inline __m128 return_in_xmm(uint16_t input) JL_NOTSAFEPOINT { + __m128 xmm_output; + asm ( + "movd %[input], %%xmm0\n\t" + "movss %%xmm0, %[xmm_output]\n\t" + : [xmm_output] "=x" (xmm_output) + : [input] "r" ((uint32_t)input) + : "xmm0" + ); + return xmm_output; +} +__attribute__((unused)) static inline uint16_t take_from_xmm(__m128 xmm_input) JL_NOTSAFEPOINT { + uint32_t output; + asm ( + "movss %[xmm_input], %%xmm0\n\t" + "movd %%xmm0, %[output]\n\t" + : [output] "=r" (output) + : [xmm_input] "x" (xmm_input) + : "xmm0" + ); + return (uint16_t)output; +} +#endif + +// float16 conversion API + +// for use in APInt and other soft-float ABIs (i.e. without the ABI shenanigans from below) +JL_DLLEXPORT uint16_t julia_float_to_half(float param) { + return float_to_half(param); +} +JL_DLLEXPORT uint16_t julia_double_to_half(double param) { + return double_to_half(param); +} +JL_DLLEXPORT float julia_half_to_float(uint16_t param) { return half_to_float(param); } -JL_DLLEXPORT uint16_t julia__gnu_f2h_ieee(float param) +// starting with GCC 12 and Clang 15, we have _Float16 on most platforms +// (but not on Windows; this may be a bug in the MSYS2 GCC compilers) +#if ((defined(__GNUC__) && __GNUC__ > 11) || \ + (defined(__clang__) && __clang_major__ > 14)) && \ + !defined(_CPU_PPC64_) && !defined(_CPU_PPC_) && \ + !defined(_OS_WINDOWS_) + #define FLOAT16_TYPE _Float16 + #define FLOAT16_TO_UINT16(x) (*(uint16_t*)&(x)) + #define FLOAT16_FROM_UINT16(x) (*(_Float16*)&(x)) +// on older compilers, we need to emulate the platform-specific ABI +#elif defined(_CPU_X86_) || (defined(_CPU_X86_64_) && !defined(_OS_WINDOWS_)) + // on x86, we can use __m128; except on Windows where x64 calling + // conventions expect to pass __m128 by reference. + #define FLOAT16_TYPE __m128 + #define FLOAT16_TO_UINT16(x) take_from_xmm(x) + #define FLOAT16_FROM_UINT16(x) return_in_xmm(x) +#elif defined(_CPU_PPC64_) || defined(_CPU_PPC_) + // on PPC, pass Float16 as if it were an integer, similar to the old x86 ABI + // before _Float16 + #define FLOAT16_TYPE uint16_t + #define FLOAT16_TO_UINT16(x) (x) + #define FLOAT16_FROM_UINT16(x) (x) +#else + // otherwise, pass using floating-point calling conventions + #define FLOAT16_TYPE float + #define FLOAT16_TO_UINT16(x) ((uint16_t)*(uint32_t*)&(x)) + #define FLOAT16_FROM_UINT16(x) ({ uint32_t tmp = (uint32_t)(x); *(float*)&tmp; }) +#endif + +JL_DLLEXPORT float julia__gnu_h2f_ieee(FLOAT16_TYPE param) { - return float_to_half(param); + uint16_t param16 = FLOAT16_TO_UINT16(param); + return half_to_float(param16); } -JL_DLLEXPORT uint16_t julia__truncdfhf2(double param) +JL_DLLEXPORT FLOAT16_TYPE julia__gnu_f2h_ieee(float param) { - float res = (float)param; - uint32_t resi; - memcpy(&resi, &res, sizeof(res)); - if ((resi&0x7fffffffu) < 0x38800000u){ // if Float16(res) is subnormal - // shift so that the mantissa lines up where it would for normal Float16 - uint32_t shift = 113u-((resi & 0x7f800000u)>>23u); - if (shift<23u) { - resi |= 0x00800000; // set implicit bit - resi >>= shift; - } - } - if ((resi & 0x1fffu) == 0x1000u) { // if we are halfway between 2 Float16 values - memcpy(&resi, &res, sizeof(res)); - // adjust the value by 1 ULP in the direction that will make Float16(res) give the right answer - resi += (fabs(res) < fabs(param)) - (fabs(param) < fabs(res)); - memcpy(&res, &resi, sizeof(res)); - } - return float_to_half(res); + uint16_t res = float_to_half(param); + return FLOAT16_FROM_UINT16(res); } -JL_DLLEXPORT float julia__truncsfbf2(float param) JL_NOTSAFEPOINT +JL_DLLEXPORT FLOAT16_TYPE julia__truncdfhf2(double param) { - uint16_t result; + uint16_t res = double_to_half(param); + return FLOAT16_FROM_UINT16(res); +} + +// bfloat16 conversion helpers + +static inline uint16_t float_to_bfloat(float param) JL_NOTSAFEPOINT +{ if (isnan(param)) - result = 0x7fc0; - else { - uint32_t bits = *((uint32_t*) ¶m); + return 0x7fc0; - // round to nearest even - bits += 0x7fff + ((bits >> 16) & 1); - result = (uint16_t)(bits >> 16); - } + uint32_t bits = *((uint32_t*) ¶m); - // on x86, bfloat16 needs to be returned in XMM. only GCC 13 provides the necessary ABI - // support in the form of the __bf16 type; older versions only provide __bfloat16 which - // is simply a typedef for short (i16). so use float, which is passed in XMM too. - uint32_t result_32bit = (uint32_t)result; - return *(float*)&result_32bit; + // round to nearest even + bits += 0x7fff + ((bits >> 16) & 1); + return (uint16_t)(bits >> 16); } -JL_DLLEXPORT float julia__truncdfbf2(double param) JL_NOTSAFEPOINT +static inline uint16_t double_to_bfloat(double param) JL_NOTSAFEPOINT { - float res = (float)param; - uint32_t resi; - memcpy(&resi, &res, sizeof(res)); + float temp = (float)param; + uint32_t tempi; + memcpy(&tempi, &temp, sizeof(temp)); // bfloat16 uses the same exponent as float32, so we don't need special handling // for subnormals when truncating float64 to bfloat16. - if ((resi & 0x1ffu) == 0x100u) { // if we are halfway between 2 bfloat16 values - // adjust the value by 1 ULP in the direction that will make bfloat16(res) give the right answer - resi += (fabs(res) < fabs(param)) - (fabs(param) < fabs(res)); - memcpy(&res, &resi, sizeof(res)); + // if we are halfway between 2 bfloat16 values + if ((tempi & 0x1ffu) == 0x100u) { + // adjust the value by 1 ULP in the direction that will make bfloat16(temp) give the right answer + tempi += (fabs(temp) < fabs(param)) - (fabs(param) < fabs(temp)); + memcpy(&temp, &tempi, sizeof(temp)); } - return julia__truncsfbf2(res); -} - -//JL_DLLEXPORT double julia__extendhfdf2(uint16_t n) { return (double)julia__gnu_h2f_ieee(n); } -//JL_DLLEXPORT int32_t julia__fixhfsi(uint16_t n) { return (int32_t)julia__gnu_h2f_ieee(n); } -//JL_DLLEXPORT int64_t julia__fixhfdi(uint16_t n) { return (int64_t)julia__gnu_h2f_ieee(n); } -//JL_DLLEXPORT uint32_t julia__fixunshfsi(uint16_t n) { return (uint32_t)julia__gnu_h2f_ieee(n); } -//JL_DLLEXPORT uint64_t julia__fixunshfdi(uint16_t n) { return (uint64_t)julia__gnu_h2f_ieee(n); } -//JL_DLLEXPORT uint16_t julia__floatsihf(int32_t n) { return julia__gnu_f2h_ieee((float)n); } -//JL_DLLEXPORT uint16_t julia__floatdihf(int64_t n) { return julia__gnu_f2h_ieee((float)n); } -//JL_DLLEXPORT uint16_t julia__floatunsihf(uint32_t n) { return julia__gnu_f2h_ieee((float)n); } -//JL_DLLEXPORT uint16_t julia__floatundihf(uint64_t n) { return julia__gnu_f2h_ieee((float)n); } -//HANDLE_LIBCALL(F16, F128, __extendhftf2) -//HANDLE_LIBCALL(F16, F80, __extendhfxf2) -//HANDLE_LIBCALL(F80, F16, __truncxfhf2) -//HANDLE_LIBCALL(F128, F16, __trunctfhf2) -//HANDLE_LIBCALL(PPCF128, F16, __trunctfhf2) -//HANDLE_LIBCALL(F16, I128, __fixhfti) -//HANDLE_LIBCALL(F16, I128, __fixunshfti) -//HANDLE_LIBCALL(I128, F16, __floattihf) -//HANDLE_LIBCALL(I128, F16, __floatuntihf) + + return float_to_bfloat(temp); +} + +static inline float bfloat_to_float(uint16_t param) JL_NOTSAFEPOINT +{ + uint32_t bits = ((uint32_t)param) << 16; + float result; + memcpy(&result, &bits, sizeof(result)); + return result; +} + +// bfloat16 conversion API + +// for use in APInt (without the ABI shenanigans from below) +uint16_t julia_float_to_bfloat(float param) { + return float_to_bfloat(param); +} +float julia_bfloat_to_float(uint16_t param) { + return bfloat_to_float(param); +} + +// starting with GCC 13 and Clang 17, we have __bf16 on most platforms +// (but not on Windows; this may be a bug in the MSYS2 GCC compilers) +#if ((defined(__GNUC__) && __GNUC__ > 12) || \ + (defined(__clang__) && __clang_major__ > 16)) && \ + !defined(_CPU_PPC64_) && !defined(_CPU_PPC_) && \ + !defined(_OS_WINDOWS_) + #define BFLOAT16_TYPE __bf16 + #define BFLOAT16_TO_UINT16(x) (*(uint16_t*)&(x)) + #define BFLOAT16_FROM_UINT16(x) (*(__bf16*)&(x)) +// on older compilers, we need to emulate the platform-specific ABI. +// for more details, see similar code above that deals with Float16. +#elif defined(_CPU_X86_) || (defined(_CPU_X86_64_) && !defined(_OS_WINDOWS_)) + #define BFLOAT16_TYPE __m128 + #define BFLOAT16_TO_UINT16(x) take_from_xmm(x) + #define BFLOAT16_FROM_UINT16(x) return_in_xmm(x) +#elif defined(_CPU_PPC64_) || defined(_CPU_PPC_) + #define BFLOAT16_TYPE uint16_t + #define BFLOAT16_TO_UINT16(x) (x) + #define BFLOAT16_FROM_UINT16(x) (x) +#else + #define BFLOAT16_TYPE float + #define BFLOAT16_TO_UINT16(x) ((uint16_t)*(uint32_t*)&(x)) + #define BFLOAT16_FROM_UINT16(x) ({ uint32_t tmp = (uint32_t)(x); *(float*)&tmp; }) +#endif + +JL_DLLEXPORT BFLOAT16_TYPE julia__truncsfbf2(float param) JL_NOTSAFEPOINT +{ + uint16_t res = float_to_bfloat(param); + return BFLOAT16_FROM_UINT16(res); +} + +JL_DLLEXPORT BFLOAT16_TYPE julia__truncdfbf2(double param) JL_NOTSAFEPOINT +{ + uint16_t res = double_to_bfloat(param); + return BFLOAT16_FROM_UINT16(res); +} // run time version of bitcast intrinsic @@ -472,9 +584,9 @@ JL_DLLEXPORT jl_value_t *jl_atomic_pointerreplace(jl_value_t *p, jl_value_t *exp char *pp = (char*)jl_unbox_long(p); jl_datatype_t *rettyp = jl_apply_cmpswap_type(ety); JL_GC_PROMISE_ROOTED(rettyp); // (JL_ALWAYS_LEAFTYPE) + jl_value_t *result = NULL; + JL_GC_PUSH1(&result); if (ety == (jl_value_t*)jl_any_type) { - jl_value_t *result; - JL_GC_PUSH1(&result); result = expected; int success; while (1) { @@ -483,8 +595,6 @@ JL_DLLEXPORT jl_value_t *jl_atomic_pointerreplace(jl_value_t *p, jl_value_t *exp break; } result = jl_new_struct(rettyp, result, success ? jl_true : jl_false); - JL_GC_POP(); - return result; } else { if (jl_typeof(x) != ety) @@ -492,8 +602,20 @@ 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, rettyp, pp, expected, x, nb); + int isptr = jl_field_isptr(rettyp, 0); + jl_task_t *ct = jl_current_task; + result = jl_gc_alloc(ct->ptls, isptr ? nb : jl_datatype_size(rettyp), isptr ? ety : (jl_value_t*)rettyp); + int success = jl_atomic_cmpswap_bits((jl_datatype_t*)ety, result, pp, expected, x, nb); + if (isptr) { + jl_value_t *z = jl_gc_alloc(ct->ptls, jl_datatype_size(rettyp), rettyp); + *(jl_value_t**)z = result; + result = z; + nb = sizeof(jl_value_t*); + } + *((uint8_t*)result + nb) = success ? 1 : 0; } + JL_GC_POP(); + return result; } JL_DLLEXPORT jl_value_t *jl_atomic_fence(jl_value_t *order_sym) @@ -633,25 +755,39 @@ static inline unsigned jl_##name##nbits(unsigned runtime_nbits, void *pa) JL_NOT // nbits::number of bits in the *input* // c_type::c_type corresponding to nbits #define un_fintrinsic_ctype(OP, name, c_type) \ -static inline void name(unsigned osize, void *pa, void *pr) JL_NOTSAFEPOINT \ +static inline void name(unsigned osize, jl_value_t *ty, void *pa, void *pr) JL_NOTSAFEPOINT \ { \ c_type a = *(c_type*)pa; \ - OP((c_type*)pr, a); \ + OP(ty, (c_type*)pr, a); \ } #define un_fintrinsic_half(OP, name) \ -static inline void name(unsigned osize, void *pa, void *pr) JL_NOTSAFEPOINT \ +static inline void name(unsigned osize, jl_value_t *ty, void *pa, void *pr) JL_NOTSAFEPOINT \ { \ uint16_t a = *(uint16_t*)pa; \ - float A = julia__gnu_h2f_ieee(a); \ + float A = half_to_float(a); \ if (osize == 16) { \ float R; \ - OP(&R, A); \ - *(uint16_t*)pr = julia__gnu_f2h_ieee(R); \ + OP(ty, &R, A); \ + *(uint16_t*)pr = float_to_half(R); \ } else { \ - OP((uint16_t*)pr, A); \ + OP(ty, (uint16_t*)pr, A); \ } \ - } +} + +#define un_fintrinsic_bfloat(OP, name) \ +static inline void name(unsigned osize, jl_value_t *ty, void *pa, void *pr) JL_NOTSAFEPOINT \ +{ \ + uint16_t a = *(uint16_t*)pa; \ + float A = bfloat_to_float(a); \ + if (osize == 16) { \ + float R; \ + OP(ty, &R, A); \ + *(uint16_t*)pr = float_to_bfloat(R); \ + } else { \ + OP(ty, (uint16_t*)pr, A); \ + } \ +} // float or integer inputs // OP::Function macro(inputa, inputb) @@ -671,11 +807,24 @@ static void jl_##name##16(unsigned runtime_nbits, void *pa, void *pb, void *pr) { \ uint16_t a = *(uint16_t*)pa; \ uint16_t b = *(uint16_t*)pb; \ - float A = julia__gnu_h2f_ieee(a); \ - float B = julia__gnu_h2f_ieee(b); \ + float A = half_to_float(a); \ + float B = half_to_float(b); \ + runtime_nbits = 16; \ + float R = OP(A, B); \ + *(uint16_t*)pr = float_to_half(R); \ + *(uint16_t*)pr = float_to_half(R); \ +} + +#define bi_intrinsic_bfloat(OP, name) \ +static void jl_##name##bf16(unsigned runtime_nbits, void *pa, void *pb, void *pr) JL_NOTSAFEPOINT \ +{ \ + uint16_t a = *(uint16_t*)pa; \ + uint16_t b = *(uint16_t*)pb; \ + float A = bfloat_to_float(a); \ + float B = bfloat_to_float(b); \ runtime_nbits = 16; \ float R = OP(A, B); \ - *(uint16_t*)pr = julia__gnu_f2h_ieee(R); \ + *(uint16_t*)pr = float_to_bfloat(R); \ } // float or integer inputs, bool output @@ -696,8 +845,19 @@ static int jl_##name##16(unsigned runtime_nbits, void *pa, void *pb) JL_NOTSAFEP { \ uint16_t a = *(uint16_t*)pa; \ uint16_t b = *(uint16_t*)pb; \ - float A = julia__gnu_h2f_ieee(a); \ - float B = julia__gnu_h2f_ieee(b); \ + float A = half_to_float(a); \ + float B = half_to_float(b); \ + runtime_nbits = 16; \ + return OP(A, B); \ +} + +#define bool_intrinsic_bfloat(OP, name) \ +static int jl_##name##bf16(unsigned runtime_nbits, void *pa, void *pb) JL_NOTSAFEPOINT \ +{ \ + uint16_t a = *(uint16_t*)pa; \ + uint16_t b = *(uint16_t*)pb; \ + float A = bfloat_to_float(a); \ + float B = bfloat_to_float(b); \ runtime_nbits = 16; \ return OP(A, B); \ } @@ -737,12 +897,27 @@ static void jl_##name##16(unsigned runtime_nbits, void *pa, void *pb, void *pc, uint16_t a = *(uint16_t*)pa; \ uint16_t b = *(uint16_t*)pb; \ uint16_t c = *(uint16_t*)pc; \ - float A = julia__gnu_h2f_ieee(a); \ - float B = julia__gnu_h2f_ieee(b); \ - float C = julia__gnu_h2f_ieee(c); \ + float A = half_to_float(a); \ + float B = half_to_float(b); \ + float C = half_to_float(c); \ runtime_nbits = 16; \ float R = OP(A, B, C); \ - *(uint16_t*)pr = julia__gnu_f2h_ieee(R); \ + *(uint16_t*)pr = float_to_half(R); \ + *(uint16_t*)pr = float_to_half(R); \ +} + +#define ter_intrinsic_bfloat(OP, name) \ +static void jl_##name##bf16(unsigned runtime_nbits, void *pa, void *pb, void *pc, void *pr) JL_NOTSAFEPOINT \ +{ \ + uint16_t a = *(uint16_t*)pa; \ + uint16_t b = *(uint16_t*)pb; \ + uint16_t c = *(uint16_t*)pc; \ + float A = bfloat_to_float(a); \ + float B = bfloat_to_float(b); \ + float C = bfloat_to_float(c); \ + runtime_nbits = 16; \ + float R = OP(A, B, C); \ + *(uint16_t*)pr = float_to_bfloat(R); \ } @@ -858,7 +1033,7 @@ static inline jl_value_t *jl_intrinsiclambda_u1(jl_value_t *ty, void *pa, unsign // conversion operator -typedef void (*intrinsic_cvt_t)(unsigned, void*, unsigned, void*); +typedef void (*intrinsic_cvt_t)(jl_datatype_t*, void*, jl_datatype_t*, void*); typedef unsigned (*intrinsic_cvt_check_t)(unsigned, unsigned, void*); #define cvt_iintrinsic(LLVMOP, name) \ JL_DLLEXPORT jl_value_t *jl_##name(jl_value_t *ty, jl_value_t *a) \ @@ -875,24 +1050,22 @@ static inline jl_value_t *jl_intrinsic_cvt(jl_value_t *ty, jl_value_t *a, const if (!jl_is_primitivetype(aty)) jl_errorf("%s: value is not a primitive type", name); void *pa = jl_data_ptr(a); - unsigned isize = jl_datatype_size(aty); unsigned osize = jl_datatype_size(ty); void *pr = alloca(osize); - unsigned isize_bits = isize * host_char_bit; - unsigned osize_bits = osize * host_char_bit; - op(isize_bits, pa, osize_bits, pr); + op((jl_datatype_t*)aty, pa, (jl_datatype_t*)ty, pr); return jl_new_bits(ty, pr); } // floating point #define un_fintrinsic_withtype(OP, name) \ +un_fintrinsic_bfloat(OP, jl_##name##bf16) \ un_fintrinsic_half(OP, jl_##name##16) \ un_fintrinsic_ctype(OP, jl_##name##32, float) \ un_fintrinsic_ctype(OP, jl_##name##64, double) \ JL_DLLEXPORT jl_value_t *jl_##name(jl_value_t *ty, jl_value_t *a) \ { \ - return jl_fintrinsic_1(ty, a, #name, jl_##name##16, jl_##name##32, jl_##name##64); \ + return jl_fintrinsic_1(ty, a, #name, jl_##name##bf16, jl_##name##16, jl_##name##32, jl_##name##64); \ } #define un_fintrinsic(OP, name) \ @@ -902,9 +1075,9 @@ JL_DLLEXPORT jl_value_t *jl_##name(jl_value_t *a) \ return jl_##name##_withtype(jl_typeof(a), a); \ } -typedef void (fintrinsic_op1)(unsigned, void*, void*); +typedef void (fintrinsic_op1)(unsigned, jl_value_t*, void*, void*); -static inline jl_value_t *jl_fintrinsic_1(jl_value_t *ty, jl_value_t *a, const char *name, fintrinsic_op1 *halfop, fintrinsic_op1 *floatop, fintrinsic_op1 *doubleop) +static inline jl_value_t *jl_fintrinsic_1(jl_value_t *ty, jl_value_t *a, const char *name, fintrinsic_op1 *bfloatop, fintrinsic_op1 *halfop, fintrinsic_op1 *floatop, fintrinsic_op1 *doubleop) { jl_task_t *ct = jl_current_task; if (!jl_is_primitivetype(jl_typeof(a))) @@ -918,13 +1091,16 @@ static inline jl_value_t *jl_fintrinsic_1(jl_value_t *ty, jl_value_t *a, const c switch (sz) { /* choose the right size c-type operation based on the input */ case 2: - halfop(sz2 * host_char_bit, pa, pr); + if (jl_typeof(a) == (jl_value_t*)jl_float16_type) + halfop(sz2 * host_char_bit, ty, pa, pr); + else /*if (jl_typeof(a) == (jl_value_t*)jl_bfloat16_type)*/ + bfloatop(sz2 * host_char_bit, ty, pa, pr); break; case 4: - floatop(sz2 * host_char_bit, pa, pr); + floatop(sz2 * host_char_bit, ty, pa, pr); break; case 8: - doubleop(sz2 * host_char_bit, pa, pr); + doubleop(sz2 * host_char_bit, ty, pa, pr); break; default: jl_errorf("%s: runtime floating point intrinsics are not implemented for bit sizes other than 16, 32 and 64", name); @@ -1096,6 +1272,7 @@ static inline jl_value_t *jl_intrinsiclambda_checkeddiv(jl_value_t *ty, void *pa // floating point #define bi_fintrinsic(OP, name) \ + bi_intrinsic_bfloat(OP, name) \ bi_intrinsic_half(OP, name) \ bi_intrinsic_ctype(OP, name, 32, float) \ bi_intrinsic_ctype(OP, name, 64, double) \ @@ -1113,7 +1290,10 @@ JL_DLLEXPORT jl_value_t *jl_##name(jl_value_t *a, jl_value_t *b) \ switch (sz) { \ /* choose the right size c-type operation */ \ case 2: \ - jl_##name##16(16, pa, pb, pr); \ + if ((jl_datatype_t*)ty == jl_float16_type) \ + jl_##name##16(16, pa, pb, pr); \ + else /*if ((jl_datatype_t*)ty == jl_bfloat16_type)*/ \ + jl_##name##bf16(16, pa, pb, pr); \ break; \ case 4: \ jl_##name##32(32, pa, pb, pr); \ @@ -1128,6 +1308,7 @@ JL_DLLEXPORT jl_value_t *jl_##name(jl_value_t *a, jl_value_t *b) \ } #define bool_fintrinsic(OP, name) \ + bool_intrinsic_bfloat(OP, name) \ bool_intrinsic_half(OP, name) \ bool_intrinsic_ctype(OP, name, 32, float) \ bool_intrinsic_ctype(OP, name, 64, double) \ @@ -1144,7 +1325,10 @@ JL_DLLEXPORT jl_value_t *jl_##name(jl_value_t *a, jl_value_t *b) \ switch (sz) { \ /* choose the right size c-type operation */ \ case 2: \ - cmp = jl_##name##16(16, pa, pb); \ + if ((jl_datatype_t*)ty == jl_float16_type) \ + cmp = jl_##name##16(16, pa, pb); \ + else /*if ((jl_datatype_t*)ty == jl_bfloat16_type)*/ \ + cmp = jl_##name##bf16(16, pa, pb); \ break; \ case 4: \ cmp = jl_##name##32(32, pa, pb); \ @@ -1159,6 +1343,7 @@ JL_DLLEXPORT jl_value_t *jl_##name(jl_value_t *a, jl_value_t *b) \ } #define ter_fintrinsic(OP, name) \ + ter_intrinsic_bfloat(OP, name) \ ter_intrinsic_half(OP, name) \ ter_intrinsic_ctype(OP, name, 32, float) \ ter_intrinsic_ctype(OP, name, 64, double) \ @@ -1176,7 +1361,10 @@ JL_DLLEXPORT jl_value_t *jl_##name(jl_value_t *a, jl_value_t *b, jl_value_t *c) switch (sz) { \ /* choose the right size c-type operation */ \ case 2: \ - jl_##name##16(16, pa, pb, pc, pr); \ + if ((jl_datatype_t*)ty == jl_float16_type) \ + jl_##name##16(16, pa, pb, pc, pr); \ + else /*if ((jl_datatype_t*)ty == jl_bfloat16_type)*/ \ + jl_##name##bf16(16, pa, pb, pc, pr); \ break; \ case 4: \ jl_##name##32(32, pa, pb, pc, pr); \ @@ -1192,14 +1380,12 @@ JL_DLLEXPORT jl_value_t *jl_##name(jl_value_t *a, jl_value_t *b, jl_value_t *c) // arithmetic #define neg(a) -a -#define neg_float(pr, a) *pr = -a +#define neg_float(ty, pr, a) *pr = -a un_iintrinsic_fast(LLVMNeg, neg, neg_int, u) #define add(a,b) a + b bi_iintrinsic_fast(LLVMAdd, add, add_int, u) -bi_iintrinsic_fast(LLVMAdd, add, add_ptr, u) #define sub(a,b) a - b bi_iintrinsic_fast(LLVMSub, sub, sub_int, u) -bi_iintrinsic_fast(LLVMSub, sub, sub_ptr, u) #define mul(a,b) a * b bi_iintrinsic_fast(LLVMMul, mul, mul_int, u) #define div(a,b) a / b @@ -1408,18 +1594,22 @@ cvt_iintrinsic(LLVMUItoFP, uitofp) cvt_iintrinsic(LLVMFPtoSI, fptosi) cvt_iintrinsic(LLVMFPtoUI, fptoui) -#define fptrunc(pr, a) \ +#define fptrunc(tr, pr, a) \ if (!(osize < 8 * sizeof(a))) \ jl_error("fptrunc: output bitsize must be < input bitsize"); \ - else if (osize == 16) \ - *(uint16_t*)pr = julia__gnu_f2h_ieee(a); \ + else if (osize == 16) { \ + if ((jl_datatype_t*)tr == jl_float16_type) \ + *(uint16_t*)pr = float_to_half(a); \ + else /*if ((jl_datatype_t*)tr == jl_bfloat16_type)*/ \ + *(uint16_t*)pr = float_to_bfloat(a); \ + } \ else if (osize == 32) \ *(float*)pr = a; \ else if (osize == 64) \ *(double*)pr = a; \ else \ jl_error("fptrunc: runtime floating point intrinsics are not implemented for bit sizes other than 16, 32 and 64"); -#define fpext(pr, a) \ +#define fpext(tr, pr, a) \ if (!(osize >= 8 * sizeof(a))) \ jl_error("fpext: output bitsize must be >= input bitsize"); \ if (osize == 32) \ @@ -1476,12 +1666,12 @@ checked_iintrinsic_div(LLVMRem_uov, checked_urem_int, u) #define flipsign(a, b) \ (b >= 0) ? a : -a bi_iintrinsic_fast(jl_LLVMFlipSign, flipsign, flipsign_int, ) -#define abs_float(pr, a) *pr = fp_select(a, fabs) -#define ceil_float(pr, a) *pr = fp_select(a, ceil) -#define floor_float(pr, a) *pr = fp_select(a, floor) -#define trunc_float(pr, a) *pr = fp_select(a, trunc) -#define rint_float(pr, a) *pr = fp_select(a, rint) -#define sqrt_float(pr, a) *pr = fp_select(a, sqrt) +#define abs_float(ty, pr, a) *pr = fp_select(a, fabs) +#define ceil_float(ty, pr, a) *pr = fp_select(a, ceil) +#define floor_float(ty, pr, a) *pr = fp_select(a, floor) +#define trunc_float(ty, pr, a) *pr = fp_select(a, trunc) +#define rint_float(ty, pr, a) *pr = fp_select(a, rint) +#define sqrt_float(ty, pr, a) *pr = fp_select(a, sqrt) #define copysign_float(a, b) fp_select2(a, b, copysign) un_fintrinsic(abs_float,abs_float) @@ -1492,16 +1682,31 @@ un_fintrinsic(trunc_float,trunc_llvm) un_fintrinsic(rint_float,rint_llvm) un_fintrinsic(sqrt_float,sqrt_llvm) un_fintrinsic(sqrt_float,sqrt_llvm_fast) +jl_value_t *jl_cpu_has_fma(int bits); -JL_DLLEXPORT jl_value_t *jl_arraylen(jl_value_t *a) +JL_DLLEXPORT jl_value_t *jl_have_fma(jl_value_t *typ) { - JL_TYPECHK(arraylen, array, a); - return jl_box_long(jl_array_len((jl_array_t*)a)); + JL_TYPECHK(have_fma, datatype, typ); // TODO what about float16/bfloat16? + if (typ == (jl_value_t*)jl_float32_type) + return jl_cpu_has_fma(32); + else if (typ == (jl_value_t*)jl_float64_type) + return jl_cpu_has_fma(64); + else + return jl_false; } -JL_DLLEXPORT jl_value_t *jl_have_fma(jl_value_t *typ) +JL_DLLEXPORT jl_value_t *jl_add_ptr(jl_value_t *ptr, jl_value_t *offset) +{ + JL_TYPECHK(add_ptr, pointer, ptr); + JL_TYPECHK(add_ptr, ulong, offset); + char *ptrval = (char*)jl_unbox_long(ptr) + jl_unbox_ulong(offset); + return jl_new_bits(jl_typeof(ptr), &ptrval); +} + +JL_DLLEXPORT jl_value_t *jl_sub_ptr(jl_value_t *ptr, jl_value_t *offset) { - JL_TYPECHK(have_fma, datatype, typ); - // TODO: run-time feature check? - return jl_false; + JL_TYPECHK(sub_ptr, pointer, ptr); + JL_TYPECHK(sub_ptr, ulong, offset); + char *ptrval = (char*)jl_unbox_long(ptr) - jl_unbox_ulong(offset); + return jl_new_bits(jl_typeof(ptr), &ptrval); } diff --git a/src/safepoint.c b/src/safepoint.c index 5a845496f36c6..22cda0a89444d 100644 --- a/src/safepoint.c +++ b/src/safepoint.c @@ -44,7 +44,8 @@ uint16_t jl_safepoint_enable_cnt[4] = {0, 0, 0, 0}; // load/store so that threads waiting for the GC doesn't have to also // fight on the safepoint lock... uv_mutex_t safepoint_lock; -uv_cond_t safepoint_cond; +uv_cond_t safepoint_cond_begin; +uv_cond_t safepoint_cond_end; static void jl_safepoint_enable(int idx) JL_NOTSAFEPOINT { @@ -91,7 +92,8 @@ static void jl_safepoint_disable(int idx) JL_NOTSAFEPOINT void jl_safepoint_init(void) { uv_mutex_init(&safepoint_lock); - uv_cond_init(&safepoint_cond); + uv_cond_init(&safepoint_cond_begin); + uv_cond_init(&safepoint_cond_end); // jl_page_size isn't available yet. size_t pgsz = jl_getpagesize(); #ifdef _OS_WINDOWS_ @@ -124,12 +126,51 @@ void jl_safepoint_init(void) jl_safepoint_pages = addr; } +void jl_gc_wait_for_the_world(jl_ptls_t* gc_all_tls_states, int gc_n_threads) +{ + JL_TIMING(GC, GC_Stop); +#ifdef USE_TRACY + TracyCZoneCtx ctx = JL_TIMING_DEFAULT_BLOCK->tracy_ctx; + TracyCZoneColor(ctx, 0x696969); +#endif + assert(gc_n_threads); + if (gc_n_threads > 1) + jl_wake_libuv(); + for (int i = 0; i < gc_n_threads; i++) { + jl_ptls_t ptls2 = gc_all_tls_states[i]; + if (ptls2 != NULL) { + // This acquire load pairs with the release stores + // in the signal handler of safepoint so we are sure that + // all the stores on those threads are visible. + // We're currently also using atomic store release in mutator threads + // (in jl_gc_state_set), but we may want to use signals to flush the + // memory operations on those threads lazily instead. + while (!jl_atomic_load_relaxed(&ptls2->gc_state) || !jl_atomic_load_acquire(&ptls2->gc_state)) { + // Use system mutexes rather than spin locking to minimize wasted CPU time + // while we wait for other threads reach a safepoint. + // This is particularly important when run under rr. + uv_mutex_lock(&safepoint_lock); + if (!jl_atomic_load_relaxed(&ptls2->gc_state)) + uv_cond_wait(&safepoint_cond_begin, &safepoint_lock); + uv_mutex_unlock(&safepoint_lock); + } + } + } +} + int jl_safepoint_start_gc(void) { - // The thread should have set this already + // The thread should have just set this before entry assert(jl_atomic_load_relaxed(&jl_current_task->ptls->gc_state) == JL_GC_STATE_WAITING); - jl_safepoint_wait_thread_resume(); // make sure we are permitted to run GC now (we might be required to stop instead) uv_mutex_lock(&safepoint_lock); + uv_cond_broadcast(&safepoint_cond_begin); + // make sure we are permitted to run GC now (we might be required to stop instead) + jl_task_t *ct = jl_current_task; + while (jl_atomic_load_relaxed(&ct->ptls->suspend_count)) { + uv_mutex_unlock(&safepoint_lock); + jl_safepoint_wait_thread_resume(); + uv_mutex_lock(&safepoint_lock); + } // In case multiple threads enter the GC at the same time, only allow // one of them to actually run the collection. We can't just let the // master thread do the GC since it might be running unmanaged code @@ -169,7 +210,22 @@ void jl_safepoint_end_gc(void) jl_mach_gc_end(); # endif uv_mutex_unlock(&safepoint_lock); - uv_cond_broadcast(&safepoint_cond); + uv_cond_broadcast(&safepoint_cond_end); +} + +void jl_set_gc_and_wait(void) // n.b. not used on _OS_DARWIN_ +{ + jl_task_t *ct = jl_current_task; + // reading own gc state doesn't need atomic ops since no one else + // should store to it. + int8_t state = jl_atomic_load_relaxed(&ct->ptls->gc_state); + jl_atomic_store_release(&ct->ptls->gc_state, JL_GC_STATE_WAITING); + uv_mutex_lock(&safepoint_lock); + uv_cond_broadcast(&safepoint_cond_begin); + uv_mutex_unlock(&safepoint_lock); + jl_safepoint_wait_gc(); + jl_atomic_store_release(&ct->ptls->gc_state, state); + jl_safepoint_wait_thread_resume(); // block in thread-suspend now if requested, after clearing the gc_state } // this is the core of jl_set_gc_and_wait @@ -178,7 +234,7 @@ void jl_safepoint_wait_gc(void) JL_NOTSAFEPOINT jl_task_t *ct = jl_current_task; (void)ct; JL_TIMING_SUSPEND_TASK(GC_SAFEPOINT, ct); // The thread should have set this is already - assert(jl_atomic_load_relaxed(&ct->ptls->gc_state) != 0); + assert(jl_atomic_load_relaxed(&ct->ptls->gc_state) != JL_GC_STATE_UNSAFE); // Use normal volatile load in the loop for speed until GC finishes. // Then use an acquire load to make sure the GC result is visible on this thread. while (jl_atomic_load_relaxed(&jl_gc_running) || jl_atomic_load_acquire(&jl_gc_running)) { @@ -187,7 +243,7 @@ void jl_safepoint_wait_gc(void) JL_NOTSAFEPOINT // This is particularly important when run under rr. uv_mutex_lock(&safepoint_lock); if (jl_atomic_load_relaxed(&jl_gc_running)) - uv_cond_wait(&safepoint_cond, &safepoint_lock); + uv_cond_wait(&safepoint_cond_end, &safepoint_lock); uv_mutex_unlock(&safepoint_lock); } } @@ -201,10 +257,17 @@ void jl_safepoint_wait_thread_resume(void) // will observe the change to the safepoint, even though the other thread // might have already observed our gc_state. // if (!jl_atomic_load_relaxed(&ct->ptls->suspend_count)) return; - JL_TIMING_SUSPEND_TASK(USER, ct); int8_t state = jl_atomic_load_relaxed(&ct->ptls->gc_state); jl_atomic_store_release(&ct->ptls->gc_state, JL_GC_STATE_WAITING); uv_mutex_lock(&ct->ptls->sleep_lock); + if (jl_atomic_load_relaxed(&ct->ptls->suspend_count)) { + // defer this broadcast until we determine whether uv_cond_wait is really going to be needed + uv_mutex_unlock(&ct->ptls->sleep_lock); + uv_mutex_lock(&safepoint_lock); + uv_cond_broadcast(&safepoint_cond_begin); + uv_mutex_unlock(&safepoint_lock); + uv_mutex_lock(&ct->ptls->sleep_lock); + } while (jl_atomic_load_relaxed(&ct->ptls->suspend_count)) uv_cond_wait(&ct->ptls->wake_signal, &ct->ptls->sleep_lock); // must while still holding the mutex_unlock, so we know other threads in @@ -246,11 +309,11 @@ int jl_safepoint_suspend_thread(int tid, int waitstate) } while (jl_atomic_load_acquire(&ptls2->suspend_count) != 0) { int8_t state2 = jl_atomic_load_acquire(&ptls2->gc_state); - if (waitstate <= 2 && state2 != 0) + if (waitstate <= 2 && state2 != JL_GC_STATE_UNSAFE) break; if (waitstate == 3 && state2 == JL_GC_STATE_WAITING) break; - jl_cpu_pause(); // yield? + jl_cpu_pause(); // yield (wait for safepoint_cond_begin, for example)? } } return suspend_count; @@ -263,9 +326,7 @@ int jl_safepoint_resume_thread(int tid) JL_NOTSAFEPOINT if (0 > tid || tid >= jl_atomic_load_acquire(&jl_n_threads)) return 0; jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; -# ifdef _OS_DARWIN_ uv_mutex_lock(&safepoint_lock); -# endif uv_mutex_lock(&ptls2->sleep_lock); int16_t suspend_count = jl_atomic_load_relaxed(&ptls2->suspend_count); if (suspend_count == 1) { // last to unsuspend @@ -284,9 +345,7 @@ int jl_safepoint_resume_thread(int tid) JL_NOTSAFEPOINT jl_safepoint_disable(3); } uv_mutex_unlock(&ptls2->sleep_lock); -# ifdef _OS_DARWIN_ uv_mutex_unlock(&safepoint_lock); -# endif return suspend_count; } diff --git a/src/partr.c b/src/scheduler.c similarity index 70% rename from src/partr.c rename to src/scheduler.c index a660f6be63de3..a04320beec00d 100644 --- a/src/partr.c +++ b/src/scheduler.c @@ -29,6 +29,11 @@ static const int16_t sleeping = 1; // this thread is dead. static const int16_t sleeping_like_the_dead JL_UNUSED = 2; +// a running count of how many threads are currently not_sleeping +// plus a running count of the number of in-flight wake-ups +// n.b. this may temporarily exceed jl_n_threads +static _Atomic(int) nrunning = 0; + // invariant: No thread is ever asleep unless sleep_check_state is sleeping (or we have a wakeup signal pending). // invariant: Any particular thread is not asleep unless that thread's sleep_check_state is sleeping. // invariant: The transition of a thread state to sleeping must be followed by a check that there wasn't work pending for it. @@ -64,7 +69,7 @@ JL_DLLEXPORT int jl_set_task_tid(jl_task_t *task, int16_t tid) JL_NOTSAFEPOINT if (was == tid) return 1; if (was == -1) - return jl_atomic_cmpswap(&task->tid, &was, tid); + return jl_atomic_cmpswap(&task->tid, &was, tid) || was == tid; return 0; } @@ -105,7 +110,7 @@ void jl_init_threadinginfra(void) } -void JL_NORETURN jl_finish_task(jl_task_t *t); +void JL_NORETURN jl_finish_task(jl_task_t *ct); static inline int may_mark(void) JL_NOTSAFEPOINT { @@ -124,9 +129,14 @@ void jl_parallel_gc_threadfun(void *arg) // initialize this thread (set tid and create heap) jl_ptls_t ptls = jl_init_threadtls(targ->tid); - + void *stack_lo, *stack_hi; + jl_init_stack_limits(0, &stack_lo, &stack_hi); + // warning: this changes `jl_current_task`, so be careful not to call that from this function + jl_task_t *ct = jl_init_root_task(ptls, stack_lo, stack_hi); + JL_GC_PROMISE_ROOTED(ct); + (void)jl_atomic_fetch_add_relaxed(&nrunning, -1); // wait for all threads - jl_gc_state_set(ptls, JL_GC_STATE_WAITING, 0); + jl_gc_state_set(ptls, JL_GC_STATE_WAITING, JL_GC_STATE_UNSAFE); uv_barrier_wait(targ->barrier); // free the thread argument here @@ -138,11 +148,9 @@ void jl_parallel_gc_threadfun(void *arg) uv_cond_wait(&gc_threads_cond, &gc_threads_lock); } uv_mutex_unlock(&gc_threads_lock); - if (may_mark()) { - gc_mark_loop_parallel(ptls, 0); - } + gc_mark_loop_parallel(ptls, 0); if (may_sweep(ptls)) { // not an else! - gc_sweep_pool_parallel(); + gc_sweep_pool_parallel(ptls); jl_atomic_fetch_add(&ptls->gc_sweeps_requested, -1); } } @@ -155,9 +163,14 @@ void jl_concurrent_gc_threadfun(void *arg) // initialize this thread (set tid and create heap) jl_ptls_t ptls = jl_init_threadtls(targ->tid); - + void *stack_lo, *stack_hi; + jl_init_stack_limits(0, &stack_lo, &stack_hi); + // warning: this changes `jl_current_task`, so be careful not to call that from this function + jl_task_t *ct = jl_init_root_task(ptls, stack_lo, stack_hi); + JL_GC_PROMISE_ROOTED(ct); + (void)jl_atomic_fetch_add_relaxed(&nrunning, -1); // wait for all threads - jl_gc_state_set(ptls, JL_GC_STATE_WAITING, 0); + jl_gc_state_set(ptls, JL_GC_STATE_WAITING, JL_GC_STATE_UNSAFE); uv_barrier_wait(targ->barrier); // free the thread argument here @@ -183,7 +196,7 @@ void jl_threadfun(void *arg) JL_GC_PROMISE_ROOTED(ct); // wait for all threads - jl_gc_state_set(ptls, JL_GC_STATE_SAFE, 0); + jl_gc_state_set(ptls, JL_GC_STATE_SAFE, JL_GC_STATE_UNSAFE); uv_barrier_wait(targ->barrier); // free the thread argument here @@ -194,6 +207,20 @@ void jl_threadfun(void *arg) } + +void jl_init_thread_scheduler(jl_ptls_t ptls) JL_NOTSAFEPOINT +{ + uv_mutex_init(&ptls->sleep_lock); + uv_cond_init(&ptls->wake_signal); + // record that there is now another thread that may be used to schedule work + // we will decrement this again in scheduler_delete_thread, only slightly + // in advance of pthread_join (which hopefully itself also had been + // adopted by now and is included in nrunning too) + (void)jl_atomic_fetch_add_relaxed(&nrunning, 1); + // n.b. this is the only point in the code where we ignore the invariants on the ordering of nrunning + // since we are being initialized from foreign code, we could not necessarily have expected or predicted that to happen +} + int jl_running_under_rr(int recheck) { #ifdef _OS_LINUX_ @@ -220,7 +247,7 @@ int jl_running_under_rr(int recheck) // sleep_check_after_threshold() -- if sleep_threshold ns have passed, return 1 -static int sleep_check_after_threshold(uint64_t *start_cycles) +static int sleep_check_after_threshold(uint64_t *start_cycles) JL_NOTSAFEPOINT { JULIA_DEBUG_SLEEPWAKE( return 1 ); // hammer on the sleep/wake logic much harder /** @@ -243,18 +270,31 @@ static int sleep_check_after_threshold(uint64_t *start_cycles) return 0; } +static int set_not_sleeping(jl_ptls_t ptls) JL_NOTSAFEPOINT +{ + if (jl_atomic_load_relaxed(&ptls->sleep_check_state) != not_sleeping) { + if (jl_atomic_exchange_relaxed(&ptls->sleep_check_state, not_sleeping) != not_sleeping) { + return 1; + } + } + int wasrunning = jl_atomic_fetch_add_relaxed(&nrunning, -1); // consume in-flight wakeup + assert(wasrunning > 1); (void)wasrunning; + return 0; +} static int wake_thread(int16_t tid) JL_NOTSAFEPOINT { - jl_ptls_t other = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; - int8_t state = sleeping; - - if (jl_atomic_load_relaxed(&other->sleep_check_state) == sleeping) { - if (jl_atomic_cmpswap_relaxed(&other->sleep_check_state, &state, not_sleeping)) { - JL_PROBE_RT_SLEEP_CHECK_WAKE(other, state); - uv_mutex_lock(&other->sleep_lock); - uv_cond_signal(&other->wake_signal); - uv_mutex_unlock(&other->sleep_lock); + jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; + + if (jl_atomic_load_relaxed(&ptls2->sleep_check_state) != not_sleeping) { + int8_t state = sleeping; + if (jl_atomic_cmpswap_relaxed(&ptls2->sleep_check_state, &state, not_sleeping)) { + int wasrunning = jl_atomic_fetch_add_relaxed(&nrunning, 1); // increment in-flight wakeup count + assert(wasrunning); (void)wasrunning; + JL_PROBE_RT_SLEEP_CHECK_WAKE(ptls2, state); + uv_mutex_lock(&ptls2->sleep_lock); + uv_cond_signal(&ptls2->wake_signal); + uv_mutex_unlock(&ptls2->sleep_lock); return 1; } } @@ -280,10 +320,14 @@ JL_DLLEXPORT void jl_wakeup_thread(int16_t tid) JL_NOTSAFEPOINT JULIA_DEBUG_SLEEPWAKE( wakeup_enter = cycleclock() ); if (tid == self || tid == -1) { // we're already awake, but make sure we'll exit uv_run + // and that nrunning is updated if this is now considered in-flight jl_ptls_t ptls = ct->ptls; - if (jl_atomic_load_relaxed(&ptls->sleep_check_state) == sleeping) { - jl_atomic_store_relaxed(&ptls->sleep_check_state, not_sleeping); - JL_PROBE_RT_SLEEP_CHECK_WAKEUP(ptls); + if (jl_atomic_load_relaxed(&ptls->sleep_check_state) != not_sleeping) { + if (jl_atomic_exchange_relaxed(&ptls->sleep_check_state, not_sleeping) != not_sleeping) { + int wasrunning = jl_atomic_fetch_add_relaxed(&nrunning, 1); + assert(wasrunning); (void)wasrunning; + JL_PROBE_RT_SLEEP_CHECK_WAKEUP(ptls); + } } if (uvlock == ct) uv_stop(jl_global_event_loop()); @@ -357,9 +401,16 @@ void jl_task_wait_empty(void) ct->world_age = jl_atomic_load_acquire(&jl_world_counter); if (f) jl_apply_generic(f, NULL, 0); + // we are back from jl_task_get_next now ct->world_age = lastage; wait_empty = NULL; + // TODO: move this lock acquire to before the wait_empty return and the + // unlock to the caller, so that we ensure new work (from uv_unref + // objects) didn't unexpectedly get scheduled and start running behind + // our back during the function return + JL_UV_LOCK(); jl_wait_empty_end(); + JL_UV_UNLOCK(); } } @@ -373,7 +424,6 @@ static int may_sleep(jl_ptls_t ptls) JL_NOTSAFEPOINT return jl_atomic_load_relaxed(&ptls->sleep_check_state) == sleeping; } -extern _Atomic(unsigned) _threadedregion; JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q, jl_value_t *checkempty) { @@ -394,14 +444,14 @@ JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q, jl_cpu_pause(); jl_ptls_t ptls = ct->ptls; - if (sleep_check_after_threshold(&start_cycles) || (ptls->tid == 0 && (!jl_atomic_load_relaxed(&_threadedregion) || wait_empty))) { + if (sleep_check_after_threshold(&start_cycles) || (ptls->tid == jl_atomic_load_relaxed(&io_loop_tid) && (!jl_atomic_load_relaxed(&_threadedregion) || wait_empty))) { // acquire sleep-check lock + assert(jl_atomic_load_relaxed(&ptls->sleep_check_state) == not_sleeping); jl_atomic_store_relaxed(&ptls->sleep_check_state, sleeping); jl_fence(); // [^store_buffering_1] JL_PROBE_RT_SLEEP_CHECK_SLEEP(ptls); if (!check_empty(checkempty)) { // uses relaxed loads - if (jl_atomic_load_relaxed(&ptls->sleep_check_state) != not_sleeping) { - jl_atomic_store_relaxed(&ptls->sleep_check_state, not_sleeping); // let other threads know they don't need to wake us + if (set_not_sleeping(ptls)) { JL_PROBE_RT_SLEEP_CHECK_TASKQ_WAKE(ptls); } continue; @@ -410,8 +460,7 @@ JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q, if (ptls != ct->ptls) { // sigh, a yield was detected, so let's go ahead and handle it anyway by starting over ptls = ct->ptls; - if (jl_atomic_load_relaxed(&ptls->sleep_check_state) != not_sleeping) { - jl_atomic_store_relaxed(&ptls->sleep_check_state, not_sleeping); // let other threads know they don't need to wake us + if (set_not_sleeping(ptls)) { JL_PROBE_RT_SLEEP_CHECK_TASK_WAKE(ptls); } if (task) @@ -419,14 +468,12 @@ JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q, continue; } if (task) { - if (jl_atomic_load_relaxed(&ptls->sleep_check_state) != not_sleeping) { - jl_atomic_store_relaxed(&ptls->sleep_check_state, not_sleeping); // let other threads know they don't need to wake us + if (set_not_sleeping(ptls)) { JL_PROBE_RT_SLEEP_CHECK_TASK_WAKE(ptls); } return task; } - // IO is always permitted, but outside a threaded region, only // thread 0 will process messages. // Inside a threaded region, any thread can listen for IO messages, @@ -445,7 +492,7 @@ JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q, if (jl_atomic_load_relaxed(&_threadedregion)) { uvlock = jl_mutex_trylock(&jl_uv_mutex); } - else if (ptls->tid == 0) { + else if (ptls->tid == jl_atomic_load_relaxed(&io_loop_tid)) { uvlock = 1; JL_UV_LOCK(); } @@ -480,14 +527,16 @@ JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q, // right back to sleep on the individual wake signal to let // them take it from us without conflict. if (active || !may_sleep(ptls)) { + if (set_not_sleeping(ptls)) { + JL_PROBE_RT_SLEEP_CHECK_UV_WAKE(ptls); + } start_cycles = 0; continue; } - if (!enter_eventloop && !jl_atomic_load_relaxed(&_threadedregion) && ptls->tid == 0) { + if (!enter_eventloop && !jl_atomic_load_relaxed(&_threadedregion) && ptls->tid == jl_atomic_load_relaxed(&io_loop_tid)) { // thread 0 is the only thread permitted to run the event loop // so it needs to stay alive, just spin-looping if necessary - if (jl_atomic_load_relaxed(&ptls->sleep_check_state) != not_sleeping) { - jl_atomic_store_relaxed(&ptls->sleep_check_state, not_sleeping); // let other threads know they don't need to wake us + if (set_not_sleeping(ptls)) { JL_PROBE_RT_SLEEP_CHECK_UV_WAKE(ptls); } start_cycles = 0; @@ -495,26 +544,52 @@ JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q, } } + // any thread which wants us running again will have to observe + // sleep_check_state==sleeping and increment nrunning for us + int wasrunning = jl_atomic_fetch_add_relaxed(&nrunning, -1); + assert(wasrunning); + if (wasrunning == 1) { + // This was the last running thread, and there is no thread with !may_sleep + // so make sure io_loop_tid is notified to check wait_empty + // TODO: this also might be a good time to check again that + // libuv's queue is truly empty, instead of during delete_thread + int16_t tid2 = 0; + if (ptls->tid != tid2) { + jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid2]; + uv_mutex_lock(&ptls2->sleep_lock); + uv_cond_signal(&ptls2->wake_signal); + uv_mutex_unlock(&ptls2->sleep_lock); + } + } + // the other threads will just wait for an individual wake signal to resume JULIA_DEBUG_SLEEPWAKE( ptls->sleep_enter = cycleclock() ); int8_t gc_state = jl_gc_safe_enter(ptls); uv_mutex_lock(&ptls->sleep_lock); while (may_sleep(ptls)) { - if (ptls->tid == 0 && wait_empty) { + if (ptls->tid == 0) { task = wait_empty; - if (jl_atomic_load_relaxed(&ptls->sleep_check_state) != not_sleeping) { - jl_atomic_store_relaxed(&ptls->sleep_check_state, not_sleeping); // let other threads know they don't need to wake us + if (task && jl_atomic_load_relaxed(&nrunning) == 0) { + wasrunning = jl_atomic_fetch_add_relaxed(&nrunning, 1); + assert(!wasrunning); + wasrunning = !set_not_sleeping(ptls); + assert(!wasrunning); JL_PROBE_RT_SLEEP_CHECK_TASK_WAKE(ptls); + if (!ptls->finalizers_inhibited) + ptls->finalizers_inhibited++; // this annoyingly is rather sticky (we should like to reset it at the end of jl_task_wait_empty) + break; } - break; + task = NULL; } + // else should we warn the user of certain deadlock here if tid == 0 && nrunning == 0? uv_cond_wait(&ptls->wake_signal, &ptls->sleep_lock); } assert(jl_atomic_load_relaxed(&ptls->sleep_check_state) == not_sleeping); + assert(jl_atomic_load_relaxed(&nrunning)); + start_cycles = 0; uv_mutex_unlock(&ptls->sleep_lock); JULIA_DEBUG_SLEEPWAKE( ptls->sleep_leave = cycleclock() ); jl_gc_safe_leave(ptls, gc_state); // contains jl_gc_safepoint - start_cycles = 0; if (task) { assert(task == wait_empty); wait_empty = NULL; @@ -528,6 +603,27 @@ JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q, } } +void scheduler_delete_thread(jl_ptls_t ptls) JL_NOTSAFEPOINT +{ + int notsleeping = jl_atomic_exchange_relaxed(&ptls->sleep_check_state, sleeping_like_the_dead) == not_sleeping; + jl_fence(); + if (notsleeping) { + if (jl_atomic_load_relaxed(&nrunning) == 1) { + jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[jl_atomic_load_relaxed(&io_loop_tid)]; + // This was the last running thread, and there is no thread with !may_sleep + // so make sure tid 0 is notified to check wait_empty + uv_mutex_lock(&ptls2->sleep_lock); + uv_cond_signal(&ptls2->wake_signal); + uv_mutex_unlock(&ptls2->sleep_lock); + } + } + else { + jl_atomic_fetch_add_relaxed(&nrunning, 1); + } + jl_wakeup_thread(0); // force thread 0 to see that we do not have the IO lock (and am dead) + jl_atomic_fetch_add_relaxed(&nrunning, -1); +} + #ifdef __cplusplus } #endif diff --git a/src/serialize.h b/src/serialize.h index afcdcc31d66c4..3d3eb4df5e862 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -52,20 +52,21 @@ extern "C" { #define TAG_SHORT_INT32 44 #define TAG_CALL1 45 #define TAG_CALL2 46 -#define TAG_LINEINFO 47 -#define TAG_SHORT_BACKREF 48 -#define TAG_BACKREF 49 -#define TAG_UNIONALL 50 -#define TAG_GOTONODE 51 -#define TAG_QUOTENODE 52 -#define TAG_GENERAL 53 -#define TAG_GOTOIFNOT 54 -#define TAG_RETURNNODE 55 -#define TAG_ARGUMENT 56 -#define TAG_RELOC_METHODROOT 57 -#define TAG_BINDING 58 - -#define LAST_TAG 58 +#define TAG_SHORT_BACKREF 47 +#define TAG_BACKREF 48 +#define TAG_UNIONALL 49 +#define TAG_GOTONODE 50 +#define TAG_QUOTENODE 51 +#define TAG_GENERAL 52 +#define TAG_GOTOIFNOT 53 +#define TAG_RETURNNODE 54 +#define TAG_ARGUMENT 55 +#define TAG_RELOC_METHODROOT 56 +#define TAG_BINDING 57 +#define TAG_MEMORYT 58 +#define TAG_ENTERNODE 59 + +#define LAST_TAG 59 #define write_uint8(s, n) ios_putc((n), (s)) #define read_uint8(s) ((uint8_t)ios_getc((s))) diff --git a/src/signal-handling.c b/src/signal-handling.c index 284ad359f3799..2ddbf2ad1cc8e 100644 --- a/src/signal-handling.c +++ b/src/signal-handling.c @@ -285,21 +285,27 @@ void jl_set_profile_peek_duration(double t) profile_peek_duration = t; } -uintptr_t profile_show_peek_cond_loc; -JL_DLLEXPORT void jl_set_peek_cond(uintptr_t cond) +jl_mutex_t profile_show_peek_cond_lock; +static uv_async_t *profile_show_peek_cond_loc; +JL_DLLEXPORT void jl_set_peek_cond(uv_async_t *cond) { + JL_LOCK_NOGC(&profile_show_peek_cond_lock); profile_show_peek_cond_loc = cond; + JL_UNLOCK_NOGC(&profile_show_peek_cond_lock); } static void jl_check_profile_autostop(void) { - if ((profile_autostop_time != -1.0) && (jl_hrtime() > profile_autostop_time)) { + if (profile_show_peek_cond_loc != NULL && profile_autostop_time != -1.0 && jl_hrtime() > profile_autostop_time) { profile_autostop_time = -1.0; jl_profile_stop_timer(); jl_safe_printf("\n==============================================================\n"); jl_safe_printf("Profile collected. A report will print at the next yield point\n"); jl_safe_printf("==============================================================\n\n"); - uv_async_send((uv_async_t*)profile_show_peek_cond_loc); + JL_LOCK_NOGC(&profile_show_peek_cond_lock); + if (profile_show_peek_cond_loc != NULL) + uv_async_send(profile_show_peek_cond_loc); + JL_UNLOCK_NOGC(&profile_show_peek_cond_lock); } } @@ -425,11 +431,18 @@ void jl_task_frame_noreturn(jl_task_t *ct) JL_NOTSAFEPOINT ct->gcstack = NULL; ct->eh = NULL; ct->world_age = 1; - ct->ptls->locks.len = 0; + // Force all locks to drop. Is this a good idea? Of course not. But the alternative would probably deadlock instead of crashing. + small_arraylist_t *locks = &ct->ptls->locks; + for (size_t i = locks->len; i > 0; i--) + jl_mutex_unlock_nogc((jl_mutex_t*)locks->items[i - 1]); + locks->len = 0; ct->ptls->in_pure_callback = 0; ct->ptls->in_finalizer = 0; ct->ptls->defer_signal = 0; - jl_atomic_store_release(&ct->ptls->gc_state, 0); // forceably exit GC (if we were in it) or safe into unsafe, without the mandatory safepoint + // forcibly exit GC (if we were in it) or safe into unsafe, without the mandatory safepoint + jl_atomic_store_release(&ct->ptls->gc_state, JL_GC_STATE_UNSAFE); + // allow continuing to use a Task that should have already died--unsafe necromancy! + jl_atomic_store_relaxed(&ct->_state, JL_TASK_STATE_RUNNABLE); } } @@ -463,9 +476,9 @@ void jl_critical_error(int sig, int si_code, bt_context_t *context, jl_task_t *c pthread_sigmask(SIG_UNBLOCK, &sset, NULL); #endif if (si_code) - jl_safe_printf("\n[%d] signal (%d.%d): %s\n", getpid(), sig, si_code, strsignal(sig)); + jl_safe_printf("\n[%d] signal %d (%d): %s\n", getpid(), sig, si_code, strsignal(sig)); else - jl_safe_printf("\n[%d] signal (%d): %s\n", getpid(), sig, strsignal(sig)); + jl_safe_printf("\n[%d] signal %d: %s\n", getpid(), sig, strsignal(sig)); } jl_safe_printf("in expression starting at %s:%d\n", jl_filename, jl_lineno); if (context && ct) { diff --git a/src/signals-mach.c b/src/signals-mach.c index ebc54d35a5b46..2191dc3268721 100644 --- a/src/signals-mach.c +++ b/src/signals-mach.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "mach_excServer.c" #ifdef MAC_OS_X_VERSION_10_9 @@ -45,6 +46,23 @@ static void attach_exception_port(thread_port_t thread, int segv_only); // low 16 bits are the thread id, the next 8 bits are the original gc_state static arraylist_t suspended_threads; extern uv_mutex_t safepoint_lock; +extern uv_cond_t safepoint_cond_begin; + +#define GC_STATE_SHIFT 8*sizeof(int16_t) +static inline int8_t decode_gc_state(uintptr_t item) +{ + return (int8_t)(item >> GC_STATE_SHIFT); +} + +static inline int16_t decode_tid(uintptr_t item) +{ + return (int16_t)item; +} + +static inline uintptr_t encode_item(int16_t tid, int8_t gc_state) +{ + return (uintptr_t)tid | ((uintptr_t)gc_state << GC_STATE_SHIFT); +} // see jl_safepoint_wait_thread_resume void jl_safepoint_resume_thread_mach(jl_ptls_t ptls2, int16_t tid2) @@ -52,8 +70,9 @@ void jl_safepoint_resume_thread_mach(jl_ptls_t ptls2, int16_t tid2) // must be called with uv_mutex_lock(&safepoint_lock) and uv_mutex_lock(&ptls2->sleep_lock) held (in that order) for (size_t i = 0; i < suspended_threads.len; i++) { uintptr_t item = (uintptr_t)suspended_threads.items[i]; - int16_t tid = (int16_t)item; - int8_t gc_state = (int8_t)(item >> 8); + + int16_t tid = decode_tid(item); + int8_t gc_state = decode_gc_state(item); if (tid != tid2) continue; jl_atomic_store_release(&ptls2->gc_state, gc_state); @@ -70,8 +89,8 @@ void jl_mach_gc_end(void) size_t j = 0; for (size_t i = 0; i < suspended_threads.len; i++) { uintptr_t item = (uintptr_t)suspended_threads.items[i]; - int16_t tid = (int16_t)item; - int8_t gc_state = (int8_t)(item >> 8); + int16_t tid = decode_tid(item); + int8_t gc_state = decode_gc_state(item); jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; uv_mutex_lock(&ptls2->sleep_lock); if (jl_atomic_load_relaxed(&ptls2->suspend_count) == 0) { @@ -114,14 +133,15 @@ static void jl_mach_gc_wait(jl_ptls_t ptls2, mach_port_t thread, int16_t tid) // Eventually, we should probably release this signal to the original // thread, (return KERN_FAILURE instead of KERN_SUCCESS) so that it // triggers a SIGSEGV and gets handled by the usual codepath for unix. - int8_t gc_state = ptls2->gc_state; + int8_t gc_state = jl_atomic_load_acquire(&ptls2->gc_state); jl_atomic_store_release(&ptls2->gc_state, JL_GC_STATE_WAITING); - uintptr_t item = tid | (((uintptr_t)gc_state) << 16); + uintptr_t item = encode_item(tid, gc_state); arraylist_push(&suspended_threads, (void*)item); thread_suspend(thread); } if (relaxed_suspend_count) uv_mutex_unlock(&ptls2->sleep_lock); + uv_cond_broadcast(&safepoint_cond_begin); uv_mutex_unlock(&safepoint_lock); } @@ -213,13 +233,14 @@ static void jl_call_in_state(jl_ptls_t ptls2, host_thread_state_t *state, #else #error "julia: throw-in-context not supported on this platform" #endif - if (ptls2 == NULL || ptls2->signal_stack == NULL || is_addr_on_sigstack(ptls2, (void*)rsp)) { + if (ptls2 == NULL || is_addr_on_sigstack(ptls2, (void*)rsp)) { rsp = (rsp - 256) & ~(uintptr_t)15; // redzone and re-alignment } else { - rsp = (uintptr_t)ptls2->signal_stack + sig_stack_size; + rsp = (uintptr_t)ptls2->signal_stack + (ptls2->signal_stack_size ? ptls2->signal_stack_size : sig_stack_size); } assert(rsp % 16 == 0); + rsp -= 16; #ifdef _CPU_X86_64_ rsp -= sizeof(void*); @@ -335,7 +356,7 @@ kern_return_t catch_mach_exception_raise( // jl_throw_in_thread(ptls2, thread, jl_stackovf_exception); // return KERN_SUCCESS; // } - if (ptls2->gc_state == JL_GC_STATE_WAITING) + if (jl_atomic_load_acquire(&ptls2->gc_state) == JL_GC_STATE_WAITING) return KERN_FAILURE; if (exception == EXC_ARITHMETIC) { jl_throw_in_thread(ptls2, thread, jl_diverror_exception); @@ -360,7 +381,7 @@ kern_return_t catch_mach_exception_raise( } return KERN_SUCCESS; } - if (ptls2->current_task->eh == NULL) + if (jl_atomic_load_relaxed(&ptls2->current_task)->eh == NULL) return KERN_FAILURE; jl_value_t *excpt; if (is_addr_on_stack(jl_atomic_load_relaxed(&ptls2->current_task), (void*)fault_addr)) { @@ -409,7 +430,7 @@ kern_return_t catch_mach_exception_raise_state_identity( static void attach_exception_port(thread_port_t thread, int segv_only) { kern_return_t ret; - // http://www.opensource.apple.com/source/xnu/xnu-2782.1.97/osfmk/man/thread_set_exception_ports.html + // https://www.opensource.apple.com/source/xnu/xnu-2782.1.97/osfmk/man/thread_set_exception_ports.html exception_mask_t mask = EXC_MASK_BAD_ACCESS; if (!segv_only) mask |= EXC_MASK_ARITHMETIC; diff --git a/src/signals-unix.c b/src/signals-unix.c index 07d6d0bb72cc1..91c47421669f2 100644 --- a/src/signals-unix.c +++ b/src/signals-unix.c @@ -24,10 +24,12 @@ #endif // Figure out the best signals/timers to use for this platform -#ifdef __APPLE__ // Darwin's mach ports allow signal-free thread management +#if defined(__APPLE__) // Darwin's mach ports allow signal-free thread management #define HAVE_MACH #define HAVE_KEVENT -#else // generic Linux or BSD +#elif defined(__OpenBSD__) +#define HAVE_KEVENT +#else // generic Linux or FreeBSD #define HAVE_TIMER #endif @@ -35,10 +37,8 @@ #include #endif -// 8M signal stack, same as default stack size and enough -// for reasonable finalizers. -// Should also be enough for parallel GC when we have it =) -#define sig_stack_size (8 * 1024 * 1024) +// 8M signal stack, same as default stack size (though we barely use this) +static const size_t sig_stack_size = 8 * 1024 * 1024; #include "julia_assert.h" @@ -85,6 +85,9 @@ static inline __attribute__((unused)) uintptr_t jl_get_rsp_from_ctx(const void * #elif defined(_OS_FREEBSD_) && defined(_CPU_X86_64_) const ucontext_t *ctx = (const ucontext_t*)_ctx; return ctx->uc_mcontext.mc_rsp; +#elif defined(_OS_OPENBSD_) && defined(_CPU_X86_64_) + const struct sigcontext *ctx = (const struct sigcontext *)_ctx; + return ctx->sc_rsp; #else // TODO Add support for PowerPC(64)? return 0; @@ -94,8 +97,9 @@ static inline __attribute__((unused)) uintptr_t jl_get_rsp_from_ctx(const void * static int is_addr_on_sigstack(jl_ptls_t ptls, void *ptr) { // One guard page for signal_stack. - return !((char*)ptr < (char*)ptls->signal_stack - jl_page_size || - (char*)ptr > (char*)ptls->signal_stack + sig_stack_size); + return ptls->signal_stack == NULL || + ((char*)ptr >= (char*)ptls->signal_stack - jl_page_size && + (char*)ptr <= (char*)ptls->signal_stack + (ptls->signal_stack_size ? ptls->signal_stack_size : sig_stack_size)); } // Modify signal context `_ctx` so that `fptr` will execute when the signal @@ -111,7 +115,7 @@ JL_NO_ASAN static void jl_call_in_ctx(jl_ptls_t ptls, void (*fptr)(void), int si // checks that the syscall is made in the signal handler and that // the ucontext address is valid. Hopefully the value of the ucontext // will not be part of the validation... - if (!ptls || !ptls->signal_stack) { + if (!ptls) { sigset_t sset; sigemptyset(&sset); sigaddset(&sset, sig); @@ -120,13 +124,12 @@ JL_NO_ASAN static void jl_call_in_ctx(jl_ptls_t ptls, void (*fptr)(void), int si return; } uintptr_t rsp = jl_get_rsp_from_ctx(_ctx); - if (is_addr_on_sigstack(ptls, (void*)rsp)) { + if (is_addr_on_sigstack(ptls, (void*)rsp)) rsp = (rsp - 256) & ~(uintptr_t)15; // redzone and re-alignment - } - else { - rsp = (uintptr_t)ptls->signal_stack + sig_stack_size; - } + else + rsp = (uintptr_t)ptls->signal_stack + (ptls->signal_stack_size ? ptls->signal_stack_size : sig_stack_size); assert(rsp % 16 == 0); + rsp -= 16; #if defined(_OS_LINUX_) && defined(_CPU_X86_64_) ucontext_t *ctx = (ucontext_t*)_ctx; rsp -= sizeof(void*); @@ -147,6 +150,11 @@ JL_NO_ASAN static void jl_call_in_ctx(jl_ptls_t ptls, void (*fptr)(void), int si rsp -= sizeof(void*); ctx->uc_mcontext.mc_esp = rsp; ctx->uc_mcontext.mc_eip = (uintptr_t)fptr; +#elif defined(_OS_OPENBSD_) && defined(_CPU_X86_64_) + struct sigcontext *ctx = (struct sigcontext *)_ctx; + rsp -= sizeof(void*); + ctx->sc_rsp = rsp; + ctx->sc_rip = fptr; #elif defined(_OS_LINUX_) && defined(_CPU_AARCH64_) ucontext_t *ctx = (ucontext_t*)_ctx; ctx->uc_mcontext.sp = rsp; @@ -229,15 +237,22 @@ static void sigdie_handler(int sig, siginfo_t *info, void *context) uv_tty_reset_mode(); if (sig == SIGILL) jl_show_sigill(context); - jl_critical_error(sig, info->si_code, jl_to_bt_context(context), jl_get_current_task()); + jl_task_t *ct = jl_get_current_task(); + jl_critical_error(sig, info->si_code, jl_to_bt_context(context), ct); + if (ct) + jl_atomic_store_relaxed(&ct->ptls->safepoint, (size_t*)NULL + 1); if (info->si_code == 0 || info->si_code == SI_USER || #ifdef SI_KERNEL info->si_code == SI_KERNEL || #endif info->si_code == SI_QUEUE || +#ifdef SI_MESGQ info->si_code == SI_MESGQ || +#endif +#ifdef SI_ASYNCIO info->si_code == SI_ASYNCIO || +#endif #ifdef SI_SIGIO info->si_code == SI_SIGIO || #endif @@ -252,7 +267,8 @@ static void sigdie_handler(int sig, siginfo_t *info, void *context) sig != SIGFPE && sig != SIGTRAP) raise(sig); - // fall-through return to re-execute faulting statement (but without the error handler) + // fall-through return to re-execute faulting statement (but without the + // error handler and the pgcstack having been destroyed) } #if defined(_CPU_X86_64_) || defined(_CPU_X86_) @@ -334,6 +350,11 @@ int is_write_fault(void *context) { ucontext_t *ctx = (ucontext_t*)context; return exc_reg_is_write_fault(ctx->uc_mcontext.mc_err); } +#elif defined(_OS_OPENBSD_) && defined(_CPU_X86_64_) +int is_write_fault(void *context) { + struct sigcontext *ctx = (struct sigcontext *)context; + return exc_reg_is_write_fault(ctx->sc_err); +} #else #pragma message("Implement this query for consistent PROT_NONE handling") int is_write_fault(void *context) { @@ -343,7 +364,8 @@ int is_write_fault(void *context) { static int jl_is_on_sigstack(jl_ptls_t ptls, void *ptr, void *context) { - return (is_addr_on_sigstack(ptls, ptr) && + return (ptls->signal_stack != NULL && + is_addr_on_sigstack(ptls, ptr) && is_addr_on_sigstack(ptls, (void*)jl_get_rsp_from_ctx(context))); } @@ -612,6 +634,17 @@ JL_DLLEXPORT void jl_profile_stop_timer(void) } } +#elif defined(__OpenBSD__) + +JL_DLLEXPORT int jl_profile_start_timer(void) +{ + return -1; +} + +JL_DLLEXPORT void jl_profile_stop_timer(void) +{ +} + #else #error no profile tools available @@ -635,30 +668,41 @@ static void allocate_segv_handler(void) } } -static void *alloc_sigstack(size_t *ssize) -{ - void *stk = jl_malloc_stack(ssize, NULL); - if (stk == NULL) - jl_errorf("fatal error allocating signal stack: mmap: %s", strerror(errno)); - return stk; -} - void jl_install_thread_signal_handler(jl_ptls_t ptls) { - size_t ssize = sig_stack_size; - void *signal_stack = alloc_sigstack(&ssize); - ptls->signal_stack = signal_stack; +#ifdef HAVE_MACH + attach_exception_port(pthread_mach_thread_np(ptls->system_id), 0); +#endif stack_t ss; - ss.ss_flags = 0; - ss.ss_size = ssize - 16; - ss.ss_sp = signal_stack; - if (sigaltstack(&ss, NULL) < 0) { + if (sigaltstack(NULL, &ss) < 0) jl_errorf("fatal error: sigaltstack: %s", strerror(errno)); + if ((ss.ss_flags & SS_DISABLE) != SS_DISABLE) + return; // someone else appears to have already set this up, so just use that + size_t ssize = sig_stack_size; + void *signal_stack = jl_malloc_stack(&ssize, NULL); + ss.ss_flags = 0; + ss.ss_size = ssize; + assert(ssize != 0); + +#ifndef _OS_OPENBSD_ + /* fallback to malloc(), but it isn't possible on OpenBSD */ + if (signal_stack == NULL) { + signal_stack = malloc(ssize); + ssize = 0; + if (signal_stack == NULL) + jl_safe_printf("\nwarning: julia signal alt stack could not be allocated (StackOverflowError will be fatal on this thread).\n"); + else + jl_safe_printf("\nwarning: julia signal stack allocated without guard page (launch foreign threads earlier to avoid this warning).\n"); } - -#ifdef HAVE_MACH - attach_exception_port(pthread_mach_thread_np(ptls->system_id), 0); #endif + + if (signal_stack != NULL) { + ss.ss_sp = signal_stack; + if (sigaltstack(&ss, NULL) < 0) + jl_errorf("fatal error: sigaltstack: %s", strerror(errno)); + ptls->signal_stack = signal_stack; + ptls->signal_stack_size = ssize; + } } const static int sigwait_sigs[] = { @@ -1029,7 +1073,7 @@ void jl_install_default_signal_handlers(void) memset(&actf, 0, sizeof(struct sigaction)); sigemptyset(&actf.sa_mask); actf.sa_sigaction = fpe_handler; - actf.sa_flags = SA_ONSTACK | SA_SIGINFO; + actf.sa_flags = SA_SIGINFO; if (sigaction(SIGFPE, &actf, NULL) < 0) { jl_errorf("fatal error: sigaction: %s", strerror(errno)); } @@ -1038,7 +1082,7 @@ void jl_install_default_signal_handlers(void) memset(&acttrap, 0, sizeof(struct sigaction)); sigemptyset(&acttrap.sa_mask); acttrap.sa_sigaction = sigtrap_handler; - acttrap.sa_flags = SA_ONSTACK | SA_SIGINFO; + acttrap.sa_flags = SA_SIGINFO; if (sigaction(SIGTRAP, &acttrap, NULL) < 0) { jl_errorf("fatal error: sigaction: %s", strerror(errno)); } diff --git a/src/signals-win.c b/src/signals-win.c index 10bd0dec7f480..f763b71e1cf32 100644 --- a/src/signals-win.c +++ b/src/signals-win.c @@ -4,7 +4,7 @@ // Note that this file is `#include`d by "signal-handling.c" #include // hidden by LEAN_AND_MEAN -#define sig_stack_size 131072 // 128k reserved for SEGV handling +static const size_t sig_stack_size = 131072; // 128k reserved for backtrace_fiber for stack overflow handling // Copied from MINGW_FLOAT_H which may not be found due to a collision with the builtin gcc float.h // eventually we can probably integrate this into OpenLibm. diff --git a/src/simplevector.c b/src/simplevector.c index 65217715ae55f..5f1fd744abd0c 100644 --- a/src/simplevector.c +++ b/src/simplevector.c @@ -79,7 +79,7 @@ JL_DLLEXPORT jl_svec_t *jl_svec_copy(jl_svec_t *a) { size_t n = jl_svec_len(a); jl_svec_t *c = jl_alloc_svec_uninit(n); - memmove_refs((void**)jl_svec_data(c), (void**)jl_svec_data(a), n); + memmove_refs((_Atomic(void*)*)jl_svec_data(c), (_Atomic(void*)*)jl_svec_data(a), n); return c; } @@ -96,10 +96,3 @@ JL_DLLEXPORT size_t (jl_svec_len)(jl_svec_t *t) JL_NOTSAFEPOINT { return jl_svec_len(t); } - -JL_DLLEXPORT jl_value_t *jl_svec_ref(jl_svec_t *t JL_PROPAGATES_ROOT, ssize_t i) -{ - jl_value_t *v = jl_svecref(t, (size_t)i); - assert(v != NULL); - return v; -} diff --git a/src/smallintset.c b/src/smallintset.c index fa647b57e7d3e..a80a18009c9db 100644 --- a/src/smallintset.c +++ b/src/smallintset.c @@ -24,87 +24,103 @@ extern "C" { #endif -static inline size_t jl_intref(const jl_array_t *arr, size_t idx) JL_NOTSAFEPOINT +static inline size_t ignore_tombstone(size_t val, size_t tombstone) JL_NOTSAFEPOINT { - jl_value_t *el = jl_tparam0(jl_typeof(arr)); - if (el == (jl_value_t*)jl_uint8_type) - return jl_atomic_load_relaxed(&((_Atomic(uint8_t)*)jl_array_data(arr))[idx]); - else if (el == (jl_value_t*)jl_uint16_type) - return jl_atomic_load_relaxed(&((_Atomic(uint16_t)*)jl_array_data(arr))[idx]); - else if (el == (jl_value_t*)jl_uint32_type) - return jl_atomic_load_relaxed(&((_Atomic(uint32_t)*)jl_array_data(arr))[idx]); + return val == tombstone ? 0 : val; +} +static inline size_t jl_intref(const jl_genericmemory_t *arr, size_t idx) JL_NOTSAFEPOINT +{ + jl_value_t *el = (jl_value_t*)jl_typetagof(arr); + if (el == jl_memory_uint8_type) + return ignore_tombstone(jl_atomic_load_relaxed(&((_Atomic(uint8_t)*)arr->ptr)[idx]), (uint8_t)-1); + else if (el == jl_memory_uint16_type) + return ignore_tombstone(jl_atomic_load_relaxed(&((_Atomic(uint16_t)*)arr->ptr)[idx]), (uint16_t)-1); + else if (el == jl_memory_uint32_type) + return ignore_tombstone(jl_atomic_load_relaxed(&((_Atomic(uint32_t)*)arr->ptr)[idx]), UINT32_MAX); else abort(); } -static inline size_t jl_intref_acquire(const jl_array_t *arr, size_t idx) JL_NOTSAFEPOINT +static inline size_t acquire_tombstone(size_t val, size_t tombstone) JL_NOTSAFEPOINT { - jl_value_t *el = jl_tparam0(jl_typeof(arr)); - if (el == (jl_value_t*)jl_uint8_type) - return jl_atomic_load_acquire(&((_Atomic(uint8_t)*)jl_array_data(arr))[idx]); - else if (el == (jl_value_t*)jl_uint16_type) - return jl_atomic_load_acquire(&((_Atomic(uint16_t)*)jl_array_data(arr))[idx]); - else if (el == (jl_value_t*)jl_uint32_type) - return jl_atomic_load_acquire(&((_Atomic(uint32_t)*)jl_array_data(arr))[idx]); + return val == tombstone ? (size_t)-1 : val; +} +static inline size_t jl_intref_acquire(const jl_genericmemory_t *arr, size_t idx) JL_NOTSAFEPOINT +{ + jl_value_t *el = (jl_value_t*)jl_typetagof(arr); + if (el == jl_memory_uint8_type) + return acquire_tombstone(jl_atomic_load_acquire(&((_Atomic(uint8_t)*)arr->ptr)[idx]), (uint8_t)-1); + else if (el == jl_memory_uint16_type) + return acquire_tombstone(jl_atomic_load_acquire(&((_Atomic(uint16_t)*)arr->ptr)[idx]), (uint16_t)-1); + else if (el == jl_memory_uint32_type) + return acquire_tombstone(jl_atomic_load_acquire(&((_Atomic(uint32_t)*)arr->ptr)[idx]), UINT32_MAX); else abort(); } -static inline void jl_intset_release(const jl_array_t *arr, size_t idx, size_t val) JL_NOTSAFEPOINT +static inline void jl_intset_release(const jl_genericmemory_t *arr, size_t idx, size_t val) JL_NOTSAFEPOINT { - jl_value_t *el = jl_tparam0(jl_typeof(arr)); - if (el == (jl_value_t*)jl_uint8_type) - jl_atomic_store_release(&((_Atomic(uint8_t)*)jl_array_data(arr))[idx], val); - else if (el == (jl_value_t*)jl_uint16_type) - jl_atomic_store_release(&((_Atomic(uint16_t)*)jl_array_data(arr))[idx], val); - else if (el == (jl_value_t*)jl_uint32_type) - jl_atomic_store_release(&((_Atomic(uint32_t)*)jl_array_data(arr))[idx], val); + jl_value_t *el = (jl_value_t*)jl_typetagof(arr); + if (el == jl_memory_uint8_type) + jl_atomic_store_release(&((_Atomic(uint8_t)*)arr->ptr)[idx], val); + else if (el == jl_memory_uint16_type) + jl_atomic_store_release(&((_Atomic(uint16_t)*)arr->ptr)[idx], val); + else if (el == jl_memory_uint32_type) + jl_atomic_store_release(&((_Atomic(uint32_t)*)arr->ptr)[idx], val); else abort(); } -static inline size_t jl_max_int(const jl_array_t *arr) +static inline size_t jl_max_int(const jl_genericmemory_t *arr) JL_NOTSAFEPOINT { - jl_value_t *el = jl_tparam0(jl_typeof(arr)); - if (el == (jl_value_t*)jl_uint8_type) + jl_value_t *el = (jl_value_t*)jl_typetagof(arr); + if (el == jl_memory_uint8_type) return 0xFF; - else if (el == (jl_value_t*)jl_uint16_type) + else if (el == jl_memory_uint16_type) return 0xFFFF; - else if (el == (jl_value_t*)jl_uint32_type) + else if (el == jl_memory_uint32_type) return 0xFFFFFFFF; - else if (el == (jl_value_t*)jl_any_type) + else if (el == jl_memory_any_type) return 0; else abort(); } -static jl_array_t *jl_alloc_int_1d(size_t np, size_t len) +void smallintset_empty(const jl_genericmemory_t *a) JL_NOTSAFEPOINT +{ + size_t elsize; + jl_value_t *el = (jl_value_t*)jl_typetagof(a); + if (el == jl_memory_uint8_type) + elsize = sizeof(uint8_t); + else if (el == jl_memory_uint16_type) + elsize = sizeof(uint16_t); + else if (el == jl_memory_uint32_type) + elsize = sizeof(uint32_t); + else if (el == jl_memory_any_type) + elsize = 0; + else + abort(); + memset(a->ptr, 0, a->length * elsize); +} + +static jl_genericmemory_t *jl_alloc_int_1d(size_t np, size_t len) { jl_value_t *ty; - if (np < 0xFF) { - ty = jl_array_uint8_type; - } - else if (np < 0xFFFF) { - static jl_value_t *int16 JL_ALWAYS_LEAFTYPE = NULL; - if (int16 == NULL) - int16 = jl_apply_array_type((jl_value_t*)jl_uint16_type, 1); - ty = int16; - } - else { - assert(np < 0x7FFFFFFF); - static jl_value_t *int32 JL_ALWAYS_LEAFTYPE = NULL; - if (int32 == NULL) - int32 = jl_apply_array_type((jl_value_t*)jl_uint32_type, 1); - ty = int32; - } - jl_array_t *a = jl_alloc_array_1d(ty, len); - memset(a->data, 0, len * a->elsize); + if (np < 0xFF) + ty = jl_memory_uint8_type; + else if (np < 0xFFFF) + ty = jl_memory_uint16_type; + else + ty = jl_memory_uint32_type; + assert(np < 0x7FFFFFFF); + jl_genericmemory_t *a = jl_alloc_genericmemory(ty, len); + smallintset_empty(a); return a; } -ssize_t jl_smallintset_lookup(jl_array_t *cache, smallintset_eq eq, const void *key, jl_svec_t *data, uint_t hv) +ssize_t jl_smallintset_lookup(jl_genericmemory_t *cache, smallintset_eq eq, const void *key, jl_value_t *data, uint_t hv, int pop) { - size_t sz = jl_array_len(cache); + size_t sz = cache->length; if (sz == 0) return -1; JL_GC_PUSH1(&cache); @@ -118,8 +134,10 @@ ssize_t jl_smallintset_lookup(jl_array_t *cache, smallintset_eq eq, const void * JL_GC_POP(); return -1; } - if (eq(val1 - 1, key, data, hv)) { + if (val1 != -1 && eq(val1 - 1, key, data, hv)) { JL_GC_POP(); + if (pop) + jl_intset_release(cache, index, (size_t)-1); // replace with tombstone return val1 - 1; } index = (index + 1) & (sz - 1); @@ -129,9 +147,9 @@ ssize_t jl_smallintset_lookup(jl_array_t *cache, smallintset_eq eq, const void * return -1; } -static int smallintset_insert_(jl_array_t *a, uint_t hv, size_t val1) +static int smallintset_insert_(jl_genericmemory_t *a, uint_t hv, size_t val1) JL_NOTSAFEPOINT { - size_t sz = jl_array_len(a); + size_t sz = a->length; if (sz <= 1) return 0; size_t orig, index, iter; @@ -149,16 +167,17 @@ static int smallintset_insert_(jl_array_t *a, uint_t hv, size_t val1) } while (iter <= maxprobe && index != orig); return 0; } +//} -static void smallintset_rehash(_Atomic(jl_array_t*) *pcache, jl_value_t *parent, smallintset_hash hash, jl_svec_t *data, size_t newsz, size_t np); - -void jl_smallintset_insert(_Atomic(jl_array_t*) *pcache, jl_value_t *parent, smallintset_hash hash, size_t val, jl_svec_t *data) +void jl_smallintset_insert(_Atomic(jl_genericmemory_t*) *pcache, jl_value_t *parent, smallintset_hash hash, size_t val, jl_value_t *data) { - jl_array_t *a = jl_atomic_load_relaxed(pcache); - if (val + 1 > jl_max_int(a)) - smallintset_rehash(pcache, parent, hash, data, jl_array_len(a), val + 1); + jl_genericmemory_t *a = jl_atomic_load_relaxed(pcache); + if (val + 1 >= jl_max_int(a)) { + a = smallintset_rehash(a, hash, data, a->length, val + 1); + jl_atomic_store_release(pcache, a); + if (parent) jl_gc_wb(parent, a); + } while (1) { - a = jl_atomic_load_relaxed(pcache); if (smallintset_insert_(a, hash(val, data), val + 1)) return; @@ -168,21 +187,22 @@ void jl_smallintset_insert(_Atomic(jl_array_t*) *pcache, jl_value_t *parent, sma /* lots of time rehashing all the keys over and over. */ size_t newsz; a = jl_atomic_load_relaxed(pcache); - size_t sz = jl_array_len(a); + size_t sz = a->length; if (sz < HT_N_INLINE) newsz = HT_N_INLINE; else if (sz >= (1 << 19) || (sz <= (1 << 8))) newsz = sz << 1; else newsz = sz << 2; - smallintset_rehash(pcache, parent, hash, data, newsz, 0); + a = smallintset_rehash(a, hash, data, newsz, 0); + jl_atomic_store_release(pcache, a); + if (parent) jl_gc_wb(parent, a); } } -static void smallintset_rehash(_Atomic(jl_array_t*) *pcache, jl_value_t *parent, smallintset_hash hash, jl_svec_t *data, size_t newsz, size_t np) +jl_genericmemory_t* smallintset_rehash(jl_genericmemory_t* a, smallintset_hash hash, jl_value_t *data, size_t newsz, size_t np) { - jl_array_t *a = jl_atomic_load_relaxed(pcache); - size_t sz = jl_array_len(a); + size_t sz = a->length; size_t i; for (i = 0; i < sz; i += 1) { size_t val = jl_intref(a, i); @@ -190,7 +210,7 @@ static void smallintset_rehash(_Atomic(jl_array_t*) *pcache, jl_value_t *parent, np = val; } while (1) { - jl_array_t *newa = jl_alloc_int_1d(np, newsz); + jl_genericmemory_t *newa = jl_alloc_int_1d(np + 1, newsz); JL_GC_PUSH1(&newa); for (i = 0; i < sz; i += 1) { size_t val1 = jl_intref(a, i); @@ -201,16 +221,12 @@ static void smallintset_rehash(_Atomic(jl_array_t*) *pcache, jl_value_t *parent, } } JL_GC_POP(); - if (i == sz) { - jl_atomic_store_release(pcache, newa); - jl_gc_wb(parent, newa); - return; - } + if (i == sz) + return newa; newsz <<= 1; } } - #ifdef __cplusplus } #endif diff --git a/src/stackwalk.c b/src/stackwalk.c index 1289e7d08657e..e45af4bb6634e 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -260,21 +260,21 @@ JL_DLLEXPORT jl_value_t *jl_backtrace_from_here(int returnsp, int skip) uintptr_t *sp_ptr = NULL; if (returnsp) { jl_array_grow_end(sp, maxincr); - sp_ptr = (uintptr_t*)jl_array_data(sp) + offset; + sp_ptr = jl_array_data(sp, uintptr_t) + offset; } size_t size_incr = 0; - have_more_frames = jl_unw_stepn(&cursor, (jl_bt_element_t*)jl_array_data(ip) + offset, + have_more_frames = jl_unw_stepn(&cursor, jl_array_data(ip, jl_bt_element_t) + offset, &size_incr, sp_ptr, maxincr, skip, &pgcstack, 0); skip = 0; offset += size_incr; } - jl_array_del_end(ip, jl_array_len(ip) - offset); + jl_array_del_end(ip, jl_array_nrows(ip) - offset); if (returnsp) - jl_array_del_end(sp, jl_array_len(sp) - offset); + jl_array_del_end(sp, jl_array_nrows(sp) - offset); size_t n = 0; - jl_bt_element_t *bt_data = (jl_bt_element_t*)jl_array_data(ip); - while (n < jl_array_len(ip)) { + jl_bt_element_t *bt_data = jl_array_data(ip, jl_bt_element_t); + while (n < jl_array_nrows(ip)) { jl_bt_element_t *bt_entry = bt_data + n; if (!jl_bt_is_native(bt_entry)) { size_t njlvals = jl_bt_num_jlvals(bt_entry); @@ -303,7 +303,7 @@ static void decode_backtrace(jl_bt_element_t *bt_data, size_t bt_size, bt = *btout = jl_alloc_array_1d(array_ptr_void_type, bt_size); static_assert(sizeof(jl_bt_element_t) == sizeof(void*), "jl_bt_element_t is presented as Ptr{Cvoid} on julia side"); - memcpy(bt->data, bt_data, bt_size * sizeof(jl_bt_element_t)); + memcpy(jl_array_data(bt, jl_bt_element_t), bt_data, bt_size * sizeof(jl_bt_element_t)); bt2 = *bt2out = jl_alloc_array_1d(jl_array_any_type, 0); // Scan the backtrace buffer for any gc-managed values for (size_t i = 0; i < bt_size; i += jl_bt_entry_size(bt_data + i)) { @@ -652,6 +652,80 @@ void jl_print_native_codeloc(uintptr_t ip) JL_NOTSAFEPOINT free(frames); } +const char *jl_debuginfo_file1(jl_debuginfo_t *debuginfo) +{ + jl_value_t *def = debuginfo->def; + if (jl_is_method_instance(def)) + def = ((jl_method_instance_t*)def)->def.value; + if (jl_is_method(def)) + def = (jl_value_t*)((jl_method_t*)def)->file; + if (jl_is_symbol(def)) + return jl_symbol_name((jl_sym_t*)def); + return ""; +} + +const char *jl_debuginfo_file(jl_debuginfo_t *debuginfo) +{ + jl_debuginfo_t *linetable = debuginfo->linetable; + while ((jl_value_t*)linetable != jl_nothing) { + debuginfo = linetable; + linetable = debuginfo->linetable; + } + return jl_debuginfo_file1(debuginfo); +} + +jl_module_t *jl_debuginfo_module1(jl_value_t *debuginfo_def) +{ + if (jl_is_method_instance(debuginfo_def)) + debuginfo_def = ((jl_method_instance_t*)debuginfo_def)->def.value; + if (jl_is_method(debuginfo_def)) + debuginfo_def = (jl_value_t*)((jl_method_t*)debuginfo_def)->module; + if (jl_is_module(debuginfo_def)) + return (jl_module_t*)debuginfo_def; + return NULL; +} + +const char *jl_debuginfo_name(jl_value_t *func) +{ + if (func == NULL) + return "macro expansion"; + if (jl_is_method_instance(func)) + func = ((jl_method_instance_t*)func)->def.value; + if (jl_is_method(func)) + func = (jl_value_t*)((jl_method_t*)func)->name; + if (jl_is_symbol(func)) + return jl_symbol_name((jl_sym_t*)func); + if (jl_is_module(func)) + return "top-level scope"; + return ""; +} + +// func == module : top-level +// func == NULL : macro expansion +static void jl_print_debugloc(jl_debuginfo_t *debuginfo, jl_value_t *func, size_t ip, int inlined) JL_NOTSAFEPOINT +{ + if (!jl_is_symbol(debuginfo->def)) // this is a path or + func = debuginfo->def; // this is inlined code + struct jl_codeloc_t stmt = jl_uncompress1_codeloc(debuginfo->codelocs, ip); + intptr_t edges_idx = stmt.to; + if (edges_idx) { + jl_debuginfo_t *edge = (jl_debuginfo_t*)jl_svecref(debuginfo->edges, edges_idx - 1); + assert(jl_typetagis(edge, jl_debuginfo_type)); + jl_print_debugloc(edge, NULL, stmt.pc, 1); + } + intptr_t ip2 = stmt.line; + if (ip2 >= 0 && ip > 0 && (jl_value_t*)debuginfo->linetable != jl_nothing) { + jl_print_debugloc(debuginfo->linetable, func, ip2, 0); + } + else { + if (ip2 < 0) // set broken debug info to ignored + ip2 = 0; + const char *func_name = jl_debuginfo_name(func); + const char *file = jl_debuginfo_file(debuginfo); + jl_safe_print_codeloc(func_name, file, ip2, inlined); + } +} + // Print code location for backtrace buffer entry at *bt_entry void jl_print_bt_entry_codeloc(jl_bt_element_t *bt_entry) JL_NOTSAFEPOINT { @@ -659,33 +733,18 @@ void jl_print_bt_entry_codeloc(jl_bt_element_t *bt_entry) JL_NOTSAFEPOINT jl_print_native_codeloc(bt_entry[0].uintptr); } else if (jl_bt_entry_tag(bt_entry) == JL_BT_INTERP_FRAME_TAG) { - size_t ip = jl_bt_entry_header(bt_entry); + size_t ip = jl_bt_entry_header(bt_entry); // zero-indexed jl_value_t *code = jl_bt_entry_jlvalue(bt_entry, 0); + jl_value_t *def = (jl_value_t*)jl_core_module; // just used as a token here that isa Module if (jl_is_method_instance(code)) { + def = code; // When interpreting a method instance, need to unwrap to find the code info code = jl_atomic_load_relaxed(&((jl_method_instance_t*)code)->uninferred); } if (jl_is_code_info(code)) { jl_code_info_t *src = (jl_code_info_t*)code; // See also the debug info handling in codegen.cpp. - // NB: debuginfoloc is 1-based! - intptr_t debuginfoloc = ((int32_t*)jl_array_data(src->codelocs))[ip]; - while (debuginfoloc != 0) { - jl_line_info_node_t *locinfo = (jl_line_info_node_t*) - jl_array_ptr_ref(src->linetable, debuginfoloc - 1); - assert(jl_typetagis(locinfo, jl_lineinfonode_type)); - const char *func_name = "Unknown"; - jl_value_t *method = locinfo->method; - if (jl_is_method_instance(method)) - method = ((jl_method_instance_t*)method)->def.value; - if (jl_is_method(method)) - method = (jl_value_t*)((jl_method_t*)method)->name; - if (jl_is_symbol(method)) - func_name = jl_symbol_name((jl_sym_t*)method); - jl_safe_print_codeloc(func_name, jl_symbol_name(locinfo->file), - locinfo->line, locinfo->inlined_at); - debuginfoloc = locinfo->inlined_at; - } + jl_print_debugloc(src->debuginfo, def, ip + 1, 0); } else { // If we're using this function something bad has already happened; @@ -791,7 +850,7 @@ _os_tsd_get_direct(unsigned long slot) // Unconditionally defined ptrauth_strip (instead of using the ptrauth.h header) // since libsystem will likely be compiled with -mbranch-protection, and we currently are not. // code from https://github.com/llvm/llvm-project/blob/7714e0317520207572168388f22012dd9e152e9e/compiler-rt/lib/sanitizer_common/sanitizer_ptrauth.h -static inline uint64_t ptrauth_strip(uint64_t __value, unsigned int __key) { +static inline uint64_t ptrauth_strip(uint64_t __value, unsigned int __key) JL_NOTSAFEPOINT { // On the stack the link register is protected with Pointer // Authentication Code when compiled with -mbranch-protection. // Let's strip the PAC unconditionally because xpaclri is in the NOP space, @@ -809,7 +868,7 @@ static inline uint64_t ptrauth_strip(uint64_t __value, unsigned int __key) { __attribute__((always_inline, pure)) static __inline__ void** -_os_tsd_get_base(void) +_os_tsd_get_base(void) JL_NOTSAFEPOINT { #if defined(__arm__) uintptr_t tsd; @@ -831,7 +890,7 @@ _os_tsd_get_base(void) #ifdef _os_tsd_get_base __attribute__((always_inline)) static __inline__ void* -_os_tsd_get_direct(unsigned long slot) +_os_tsd_get_direct(unsigned long slot) JL_NOTSAFEPOINT { return _os_tsd_get_base()[slot]; } @@ -839,14 +898,14 @@ _os_tsd_get_direct(unsigned long slot) __attribute__((always_inline, pure)) static __inline__ uintptr_t -_os_ptr_munge_token(void) +_os_ptr_munge_token(void) JL_NOTSAFEPOINT { return (uintptr_t)_os_tsd_get_direct(__TSD_PTR_MUNGE); } __attribute__((always_inline, pure)) JL_UNUSED static __inline__ uintptr_t -_os_ptr_munge(uintptr_t ptr) +_os_ptr_munge(uintptr_t ptr) JL_NOTSAFEPOINT { return ptr ^ _os_ptr_munge_token(); } @@ -1022,7 +1081,7 @@ static void jl_rec_backtrace(jl_task_t *t) JL_NOTSAFEPOINT mc->__r14 = ((uint64_t*)mctx)[5]; mc->__r15 = ((uint64_t*)mctx)[6]; mc->__rip = ((uint64_t*)mctx)[7]; - // added in libsystem_plaform 177.200.16 (macOS Mojave 10.14.3) + // added in libsystem_platform 177.200.16 (macOS Mojave 10.14.3) // prior to that _os_ptr_munge_token was (hopefully) typically 0, // so x ^ 0 == x and this is a no-op mc->__rbp = _OS_PTR_UNMUNGE(mc->__rbp); @@ -1087,8 +1146,6 @@ static void jl_rec_backtrace(jl_task_t *t) JL_NOTSAFEPOINT #pragma message("jl_rec_backtrace not defined for ASM/SETJMP on unknown system") (void)c; #endif -#elif defined(JL_HAVE_ASYNCIFY) - #pragma message("jl_rec_backtrace not defined for ASYNCIFY") #elif defined(JL_HAVE_SIGALTSTACK) #pragma message("jl_rec_backtrace not defined for SIGALTSTACK") #else diff --git a/src/staticdata.c b/src/staticdata.c index c684bee4c485b..a41d6b76586eb 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -71,7 +71,6 @@ External links: */ #include #include -#include #include // printf #include // PRIxPTR @@ -89,6 +88,8 @@ External links: #include "valgrind.h" #include "julia_assert.h" +static const size_t WORLD_AGE_REVALIDATION_SENTINEL = 0x1; + #include "staticdata_utils.c" #include "precompile_utils.c" @@ -99,7 +100,7 @@ extern "C" { // TODO: put WeakRefs on the weak_refs list during deserialization // TODO: handle finalizers -#define NUM_TAGS 160 +#define NUM_TAGS 189 // 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. @@ -133,6 +134,7 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_gotonode_type); INSERT_TAG(jl_quotenode_type); INSERT_TAG(jl_gotoifnot_type); + INSERT_TAG(jl_enternode_type); INSERT_TAG(jl_argument_type); INSERT_TAG(jl_returnnode_type); INSERT_TAG(jl_const_type); @@ -199,6 +201,19 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_number_type); INSERT_TAG(jl_signed_type); INSERT_TAG(jl_pair_type); + INSERT_TAG(jl_genericmemory_type); + INSERT_TAG(jl_memory_any_type); + INSERT_TAG(jl_memory_uint8_type); + INSERT_TAG(jl_memory_uint16_type); + INSERT_TAG(jl_memory_uint32_type); + INSERT_TAG(jl_memory_uint64_type); + INSERT_TAG(jl_genericmemoryref_type); + INSERT_TAG(jl_memoryref_any_type); + INSERT_TAG(jl_memoryref_uint8_type); + INSERT_TAG(jl_addrspace_type); + INSERT_TAG(jl_addrspace_typename); + INSERT_TAG(jl_addrspacecore_type); + INSERT_TAG(jl_debuginfo_type); // special typenames INSERT_TAG(jl_tuple_typename); @@ -209,6 +224,8 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_namedtuple_typename); INSERT_TAG(jl_vecelement_typename); INSERT_TAG(jl_opaque_closure_typename); + INSERT_TAG(jl_genericmemory_typename); + INSERT_TAG(jl_genericmemoryref_typename); // special exceptions INSERT_TAG(jl_errorexception_type); @@ -226,6 +243,8 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_undefref_exception); INSERT_TAG(jl_readonlymemory_exception); INSERT_TAG(jl_atomicerror_type); + INSERT_TAG(jl_missingcodeerror_type); + INSERT_TAG(jl_precompilable_error); // other special values INSERT_TAG(jl_emptysvec); @@ -234,6 +253,7 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_true); INSERT_TAG(jl_an_empty_string); INSERT_TAG(jl_an_empty_vec_any); + INSERT_TAG(jl_an_empty_memory_any); INSERT_TAG(jl_module_init_order); INSERT_TAG(jl_core_module); INSERT_TAG(jl_base_module); @@ -264,11 +284,17 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_builtin_swapfield); INSERT_TAG(jl_builtin_modifyfield); INSERT_TAG(jl_builtin_replacefield); + INSERT_TAG(jl_builtin_setfieldonce); INSERT_TAG(jl_builtin_fieldtype); - INSERT_TAG(jl_builtin_arrayref); - INSERT_TAG(jl_builtin_const_arrayref); - INSERT_TAG(jl_builtin_arrayset); - INSERT_TAG(jl_builtin_arraysize); + INSERT_TAG(jl_builtin_memoryref); + INSERT_TAG(jl_builtin_memoryrefoffset); + INSERT_TAG(jl_builtin_memoryrefget); + INSERT_TAG(jl_builtin_memoryrefset); + INSERT_TAG(jl_builtin_memoryref_isassigned); + INSERT_TAG(jl_builtin_memoryrefswap); + INSERT_TAG(jl_builtin_memoryrefmodify); + INSERT_TAG(jl_builtin_memoryrefreplace); + INSERT_TAG(jl_builtin_memoryrefsetonce); INSERT_TAG(jl_builtin_apply_type); INSERT_TAG(jl_builtin_applicable); INSERT_TAG(jl_builtin_invoke); @@ -279,6 +305,10 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_builtin_compilerbarrier); INSERT_TAG(jl_builtin_getglobal); INSERT_TAG(jl_builtin_setglobal); + INSERT_TAG(jl_builtin_swapglobal); + INSERT_TAG(jl_builtin_modifyglobal); + INSERT_TAG(jl_builtin_replaceglobal); + INSERT_TAG(jl_builtin_setglobalonce); // n.b. must update NUM_TAGS when you add something here #undef INSERT_TAG assert(i == NUM_TAGS - 1); @@ -292,11 +322,8 @@ static uintptr_t nsym_tag; // array of definitions for the predefined tagged object types // (reverse of symbol_table) static arraylist_t deser_sym; -// Predefined tags that do not have special handling in `externally_linked` -static htable_t external_objects; static htable_t serialization_order; // to break cycles, mark all objects that are serialized -static htable_t unique_ready; // as we serialize types, we need to know if all reachable objects are also already serialized. This tracks whether `immediate` has been set for all of them. static htable_t nullptrs; // FIFO queue for objects to be serialized. Anything requiring fixup upon deserialization // must be "toplevel" in this queue. For types, parameters and field types must appear @@ -310,6 +337,8 @@ static arraylist_t object_worklist; // used to mimic recursion by jl_serialize_ // jl_linkage_blobs.items[2i:2i+1] correspond to build_ids[i] (0-offset indexing) arraylist_t jl_linkage_blobs; arraylist_t jl_image_relocs; +// Keep track of which image corresponds to which top module. +arraylist_t jl_top_mods; // Eytzinger tree of images. Used for very fast jl_object_in_image queries // See https://algorithmica.org/en/eytzinger @@ -424,11 +453,23 @@ size_t external_blob_index(jl_value_t *v) JL_NOTSAFEPOINT return idx; } -uint8_t jl_object_in_image(jl_value_t *obj) JL_NOTSAFEPOINT +JL_DLLEXPORT uint8_t jl_object_in_image(jl_value_t *obj) JL_NOTSAFEPOINT { return eyt_obj_in_img(obj); } +// Map an object to it's "owning" top module +JL_DLLEXPORT jl_value_t *jl_object_top_module(jl_value_t* v) JL_NOTSAFEPOINT +{ + size_t idx = external_blob_index(v); + size_t lbids = n_linkage_blobs(); + if (idx < lbids) { + return (jl_value_t*)jl_top_mods.items[idx]; + } + // The object is runtime allocated + return (jl_value_t*)jl_nothing; +} + // hash of definitions for predefined function pointers static htable_t fptr_to_id; void *native_functions; // opaque jl_native_code_desc_t blob used for fetching data from LLVM @@ -444,14 +485,17 @@ static const jl_fptr_args_t id_to_fptrs[] = { &jl_f_typeassert, &jl_f__apply_iterate, &jl_f__apply_pure, &jl_f__call_latest, &jl_f__call_in_world, &jl_f__call_in_world_total, &jl_f_isdefined, &jl_f_tuple, &jl_f_svec, &jl_f_intrinsic_call, - &jl_f_getfield, &jl_f_setfield, &jl_f_swapfield, &jl_f_modifyfield, - &jl_f_replacefield, &jl_f_fieldtype, &jl_f_nfields, - &jl_f_arrayref, &jl_f_const_arrayref, &jl_f_arrayset, &jl_f_arraysize, &jl_f_apply_type, + &jl_f_getfield, &jl_f_setfield, &jl_f_swapfield, &jl_f_modifyfield, &jl_f_setfieldonce, + &jl_f_replacefield, &jl_f_fieldtype, &jl_f_nfields, &jl_f_apply_type, + &jl_f_memoryref, &jl_f_memoryrefoffset, &jl_f_memoryrefget, &jl_f_memoryref_isassigned, + &jl_f_memoryrefset, &jl_f_memoryrefswap, &jl_f_memoryrefmodify, &jl_f_memoryrefreplace, &jl_f_memoryrefsetonce, &jl_f_applicable, &jl_f_invoke, &jl_f_sizeof, &jl_f__expr, &jl_f__typevar, &jl_f_ifelse, &jl_f__structtype, &jl_f__abstracttype, &jl_f__primitivetype, &jl_f__typebody, &jl_f__setsuper, &jl_f__equiv_typedef, &jl_f_get_binding_type, &jl_f_set_binding_type, &jl_f_opaque_closure_call, &jl_f_donotdelete, &jl_f_compilerbarrier, - &jl_f_getglobal, &jl_f_setglobal, &jl_f_finalizer, &jl_f__compute_sparams, &jl_f__svec_ref, + &jl_f_getglobal, &jl_f_setglobal, &jl_f_swapglobal, &jl_f_modifyglobal, &jl_f_replaceglobal, &jl_f_setglobalonce, + &jl_f_finalizer, &jl_f__compute_sparams, &jl_f__svec_ref, + &jl_f_current_scope, NULL }; typedef struct { @@ -461,9 +505,12 @@ typedef struct { ios_t *relocs; // for (de)serializing relocs_list and gctags_list ios_t *gvar_record; // serialized array mapping gvid => spos ios_t *fptr_record; // serialized array mapping fptrid => spos + arraylist_t memowner_list; // a list of memory locations that have shared owners + arraylist_t memref_list; // a list of memoryref locations arraylist_t relocs_list; // a list of (location, target) pairs, see description at top arraylist_t gctags_list; // " arraylist_t uniquing_types; // a list of locations that reference types that must be de-duplicated + arraylist_t uniquing_super; // a list of datatypes, used in super fields, that need to be marked in uniquing_types once they are reached, for handling unique-ing of them on deserialization arraylist_t uniquing_objs; // a list of locations that reference non-types that must be de-duplicated arraylist_t fixup_types; // a list of locations of types requiring (re)caching arraylist_t fixup_objs; // a list of locations of objects requiring (re)caching @@ -480,13 +527,13 @@ typedef struct { jl_array_t *link_ids_gvars; jl_array_t *link_ids_external_fnvars; jl_ptls_t ptls; + // Set (implemented has a hasmap of MethodInstances to themselves) of which MethodInstances have (forward) edges + // to other MethodInstances. htable_t callers_with_edges; jl_image_t *image; int8_t incremental; } jl_serializer_state; -static jl_value_t *jl_idtable_type = NULL; -static jl_typename_t *jl_idtable_typename = NULL; static jl_value_t *jl_bigint_type = NULL; static int gmp_limb_size = 0; static jl_sym_t *jl_docmeta_sym = NULL; @@ -560,9 +607,9 @@ typedef struct { static void *jl_sysimg_handle = NULL; static jl_image_t sysimage; -static inline uintptr_t *sysimg_gvars(uintptr_t *base, const int32_t *offsets, size_t idx) +static inline uintptr_t *sysimg_gvars(const char *base, const int32_t *offsets, size_t idx) { - return base + offsets[idx] / sizeof(base[0]); + return (uintptr_t*)(base + offsets[idx]); } JL_DLLEXPORT int jl_running_on_valgrind(void) @@ -575,15 +622,8 @@ extern void * JL_WEAK_SYMBOL_OR_ALIAS_DEFAULT(system_image_data_unavailable) jl_ extern void * JL_WEAK_SYMBOL_OR_ALIAS_DEFAULT(system_image_data_unavailable) jl_system_image_size; static void jl_load_sysimg_so(void) { - int imaging_mode = jl_generating_output() && !jl_options.incremental; - // in --build mode only use sysimg data, not precompiled native code - if (!imaging_mode && jl_options.use_sysimage_native_code==JL_OPTIONS_USE_SYSIMAGE_NATIVE_CODE_YES) { - assert(sysimage.fptrs.base); - } - else { - memset(&sysimage.fptrs, 0, sizeof(sysimage.fptrs)); - } const char *sysimg_data; + assert(sysimage.fptrs.ptrs); // jl_init_processor_sysimg should already be run if (jl_sysimg_handle == jl_exe_handle && &jl_system_image_data != JL_WEAK_SYMBOL_DEFAULT(system_image_data_unavailable)) sysimg_data = (const char*)&jl_system_image_data; @@ -625,7 +665,7 @@ static int jl_needs_serialization(jl_serializer_state *s, jl_value_t *v) JL_NOTS else if (jl_typetagis(v, jl_uint8_tag << 4)) { return 0; } - else if (jl_typetagis(v, jl_task_tag << 4)) { + else if (v == (jl_value_t*)s->ptls->root_task) { return 0; } @@ -707,12 +747,12 @@ static void jl_queue_module_for_serialization(jl_serializer_state *s, jl_module_ { jl_queue_for_serialization(s, m->name); jl_queue_for_serialization(s, m->parent); - jl_queue_for_serialization(s, m->bindings); - jl_queue_for_serialization(s, m->bindingkeyset); + jl_queue_for_serialization(s, jl_atomic_load_relaxed(&m->bindings)); + jl_queue_for_serialization(s, jl_atomic_load_relaxed(&m->bindingkeyset)); if (jl_options.strip_metadata) { jl_svec_t *table = jl_atomic_load_relaxed(&m->bindings); for (size_t i = 0; i < jl_svec_len(table); i++) { - jl_binding_t *b = (jl_binding_t*)jl_svec_ref(table, i); + jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i); if ((void*)b == jl_nothing) break; jl_sym_t *name = b->globalref->name; @@ -738,24 +778,22 @@ static void jl_insert_into_serialization_queue(jl_serializer_state *s, jl_value_ { jl_datatype_t *t = (jl_datatype_t*)jl_typeof(v); jl_queue_for_serialization_(s, (jl_value_t*)t, 1, immediate); + const jl_datatype_layout_t *layout = t->layout; if (!recursive) goto done_fields; if (s->incremental && jl_is_datatype(v) && immediate) { jl_datatype_t *dt = (jl_datatype_t*)v; - // ensure super is queued (though possibly not yet handled, since it may have cycles) - jl_queue_for_serialization_(s, (jl_value_t*)dt->super, 1, 1); // ensure all type parameters are recached jl_queue_for_serialization_(s, (jl_value_t*)dt->parameters, 1, 1); - jl_value_t *singleton = dt->instance; - if (singleton && needs_uniquing(singleton)) { - assert(jl_needs_serialization(s, singleton)); // should be true, since we visited dt + if (jl_is_datatype_singleton(dt) && needs_uniquing(dt->instance)) { + assert(jl_needs_serialization(s, dt->instance)); // should be true, since we visited dt // do not visit dt->instance for our template object as it leads to unwanted cycles here // (it may get serialized from elsewhere though) record_field_change(&dt->instance, jl_nothing); } - immediate = 0; // do not handle remaining fields immediately (just field types remains) + goto done_fields; // for now } if (s->incremental && jl_is_method_instance(v)) { jl_method_instance_t *mi = (jl_method_instance_t*)v; @@ -765,7 +803,6 @@ static void jl_insert_into_serialization_queue(jl_serializer_state *s, jl_value_ jl_queue_for_serialization(s, mi->def.value); jl_queue_for_serialization(s, mi->specTypes); jl_queue_for_serialization(s, (jl_value_t*)mi->sparam_vals); - recursive = 0; goto done_fields; } else if (jl_is_method(def) && jl_object_in_image(def)) { @@ -774,7 +811,6 @@ static void jl_insert_into_serialization_queue(jl_serializer_state *s, jl_value_ // so must not be present here record_field_change((jl_value_t**)&mi->uninferred, NULL); record_field_change((jl_value_t**)&mi->backedges, NULL); - record_field_change((jl_value_t**)&mi->callbacks, NULL); record_field_change((jl_value_t**)&mi->cache, NULL); } else { @@ -796,8 +832,8 @@ static void jl_insert_into_serialization_queue(jl_serializer_state *s, jl_value_ if (jl_is_typename(v)) { jl_typename_t *tn = (jl_typename_t*)v; // don't recurse into several fields (yet) - jl_queue_for_serialization_(s, (jl_value_t*)tn->cache, 0, 1); - jl_queue_for_serialization_(s, (jl_value_t*)tn->linearcache, 0, 1); + jl_queue_for_serialization_(s, (jl_value_t*)jl_atomic_load_relaxed(&tn->cache), 0, 1); + jl_queue_for_serialization_(s, (jl_value_t*)jl_atomic_load_relaxed(&tn->linearcache), 0, 1); if (s->incremental) { assert(!jl_object_in_image((jl_value_t*)tn->module)); assert(!jl_object_in_image((jl_value_t*)tn->wrapper)); @@ -806,17 +842,20 @@ static void jl_insert_into_serialization_queue(jl_serializer_state *s, jl_value_ if (s->incremental && jl_is_code_instance(v)) { jl_code_instance_t *ci = (jl_code_instance_t*)v; // make sure we don't serialize other reachable cache entries of foreign methods + // Should this now be: + // if (ci !in ci->defs->cache) + // record_field_change((jl_value_t**)&ci->next, NULL); + // Why are we checking that the method/module this originates from is in_image? + // and then disconnect this CI? if (jl_object_in_image((jl_value_t*)ci->def->def.value)) { // TODO: if (ci in ci->defs->cache) record_field_change((jl_value_t**)&ci->next, NULL); } } - if (immediate) // must be things that can be recursively handled, and valid as type parameters assert(jl_is_immutable(t) || jl_is_typevar(v) || jl_is_symbol(v) || jl_is_svec(v)); - const jl_datatype_layout_t *layout = t->layout; if (layout->npointers == 0) { // bitstypes do not require recursion } @@ -829,22 +868,29 @@ static void jl_insert_into_serialization_queue(jl_serializer_state *s, jl_value_ } else if (jl_is_array(v)) { jl_array_t *ar = (jl_array_t*)v; - const char *data = (const char*)jl_array_data(ar); - if (ar->flags.ptrarray) { - size_t i, l = jl_array_len(ar); + jl_value_t *mem = get_replaceable_field((jl_value_t**)&ar->ref.mem, 1); + jl_queue_for_serialization_(s, mem, 1, immediate); + } + else if (jl_is_genericmemory(v)) { + jl_genericmemory_t *m = (jl_genericmemory_t*)v; + const char *data = (const char*)m->ptr; + if (jl_genericmemory_how(m) == 3) { + jl_queue_for_serialization_(s, jl_genericmemory_data_owner_field(v), 1, immediate); + } + else if (layout->flags.arrayelem_isboxed) { + size_t i, l = m->length; for (i = 0; i < l; i++) { jl_value_t *fld = get_replaceable_field(&((jl_value_t**)data)[i], 1); jl_queue_for_serialization_(s, fld, 1, immediate); } } - else if (ar->flags.hasptr) { - uint16_t elsz = ar->elsize; - size_t i, l = jl_array_len(ar); - jl_datatype_t *et = (jl_datatype_t*)jl_tparam0(jl_typeof(ar)); - size_t j, np = et->layout->npointers; + else if (layout->first_ptr >= 0) { + uint16_t elsz = layout->size; + size_t i, l = m->length; + size_t j, np = layout->npointers; for (i = 0; i < l; i++) { for (j = 0; j < np; j++) { - uint32_t ptr = jl_ptr_offset(et, j); + uint32_t ptr = jl_ptr_offset(t, j); jl_value_t *fld = get_replaceable_field(&((jl_value_t**)data)[ptr], 1); jl_queue_for_serialization_(s, fld, 1, immediate); } @@ -872,22 +918,35 @@ done_fields: ; // We've encountered an item we need to cache void **bp = ptrhash_bp(&serialization_order, v); - assert(*bp != (void*)(uintptr_t)-1); - if (s->incremental) { - void **bp2 = ptrhash_bp(&unique_ready, v); - if (*bp2 == HT_NOTFOUND) - assert(*bp == (void*)(uintptr_t)-2); - else if (*bp != (void*)(uintptr_t)-2) - return; - } - else { - assert(*bp == (void*)(uintptr_t)-2); - } + assert(*bp == (void*)(uintptr_t)-2); arraylist_push(&serialization_queue, (void*) v); size_t idx = serialization_queue.len - 1; assert(serialization_queue.len < ((uintptr_t)1 << RELOC_TAG_OFFSET) && "too many items to serialize"); - *bp = (void*)((char*)HT_NOTFOUND + 1 + idx); + + // DataType is very unusual, in that some of the fields need to be pre-order, and some + // (notably super) must not be (even if `jl_queue_for_serialization_` would otherwise + // try to promote itself to be immediate) + if (s->incremental && jl_is_datatype(v) && immediate && recursive) { + jl_datatype_t *dt = (jl_datatype_t*)v; + void **bp = ptrhash_bp(&serialization_order, (void*)dt->super); + if (*bp != (void*)-2) { + // if super is already on the stack of things to handle when this returns, do + // not try to handle it now + jl_queue_for_serialization_(s, (jl_value_t*)dt->super, 1, immediate); + } + immediate = 0; + char *data = (char*)jl_data_ptr(v); + size_t i, np = layout->npointers; + for (i = 0; i < np; i++) { + uint32_t ptr = jl_ptr_offset(t, i); + if (ptr * sizeof(jl_value_t*) == offsetof(jl_datatype_t, super)) + continue; // skip the super field, since it might not be quite validly ordered + int mutabl = 1; + jl_value_t *fld = get_replaceable_field(&((jl_value_t**)data)[ptr], mutabl); + jl_queue_for_serialization_(s, fld, 1, immediate); + } + } } static void jl_queue_for_serialization_(jl_serializer_state *s, jl_value_t *v, int recursive, int immediate) JL_GC_DISABLED @@ -906,28 +965,19 @@ static void jl_queue_for_serialization_(jl_serializer_state *s, jl_value_t *v, i } void **bp = ptrhash_bp(&serialization_order, v); - if (*bp == HT_NOTFOUND) { - *bp = (void*)(uintptr_t)(immediate ? -2 : -1); - } - else { - if (!s->incremental || !immediate || !recursive) - return; - void **bp2 = ptrhash_bp(&unique_ready, v); - if (*bp2 == HT_NOTFOUND) - *bp2 = v; // now is unique_ready - else { - assert(*bp != (void*)(uintptr_t)-1); - return; // already was unique_ready - } - assert(*bp != (void*)(uintptr_t)-2); // should be unique_ready then - if (*bp == (void*)(uintptr_t)-1) - *bp = (void*)(uintptr_t)-2; // now immediate - } + assert(!immediate || *bp != (void*)(uintptr_t)-2); + if (*bp == HT_NOTFOUND) + *bp = (void*)(uintptr_t)-1; // now enqueued + else if (!s->incremental || !immediate || !recursive || *bp != (void*)(uintptr_t)-1) + return; - if (immediate) + if (immediate) { + *bp = (void*)(uintptr_t)-2; // now immediate jl_insert_into_serialization_queue(s, v, recursive, immediate); - else + } + else { arraylist_push(&object_worklist, (void*)v); + } } // Do a pre-order traversal of the to-serialize worklist, in the identical order @@ -995,8 +1045,8 @@ static uintptr_t add_external_linkage(jl_serializer_state *s, jl_value_t *v, jl_ size_t offset = (uintptr_t)v - (uintptr_t)jl_linkage_blobs.items[2*i]; offset /= sizeof(void*); assert(offset < ((uintptr_t)1 << DEPS_IDX_OFFSET) && "offset to external image too large"); - assert(n_linkage_blobs() == jl_array_len(s->buildid_depmods_idxs)); - size_t depsidx = ((uint32_t*)jl_array_data(s->buildid_depmods_idxs))[i]; // map from build_id_idx -> deps_idx + assert(n_linkage_blobs() == jl_array_nrows(s->buildid_depmods_idxs)); + size_t depsidx = jl_array_data(s->buildid_depmods_idxs, uint32_t)[i]; // map from build_id_idx -> deps_idx assert(depsidx < INT32_MAX); if (depsidx < ((uintptr_t)1 << (RELOC_TAG_OFFSET - DEPS_IDX_OFFSET)) && offset < ((uintptr_t)1 << DEPS_IDX_OFFSET)) // if it fits in a SysimageLinkage type, use that representation @@ -1004,8 +1054,8 @@ static uintptr_t add_external_linkage(jl_serializer_state *s, jl_value_t *v, jl_ // otherwise, we store the image key in `link_ids` assert(link_ids && jl_is_array(link_ids)); jl_array_grow_end(link_ids, 1); - uint32_t *link_id_data = (uint32_t*)jl_array_data(link_ids); // wait until after the `grow` - link_id_data[jl_array_len(link_ids) - 1] = depsidx; + uint32_t *link_id_data = jl_array_data(link_ids, uint32_t); // wait until after the `grow` + link_id_data[jl_array_nrows(link_ids) - 1] = depsidx; return ((uintptr_t)ExternalLinkage << RELOC_TAG_OFFSET) + offset; } return 0; @@ -1077,8 +1127,10 @@ static void record_uniquing(jl_serializer_state *s, jl_value_t *fld, uintptr_t o if (s->incremental && jl_needs_serialization(s, fld) && needs_uniquing(fld)) { if (jl_is_datatype(fld) || jl_is_datatype_singleton((jl_datatype_t*)jl_typeof(fld))) arraylist_push(&s->uniquing_types, (void*)(uintptr_t)offset); - else + else if (jl_is_method_instance(fld)) arraylist_push(&s->uniquing_objs, (void*)(uintptr_t)offset); + else + assert(0 && "unknown object type with needs_uniquing set"); } } @@ -1122,13 +1174,12 @@ static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t newm->parent = NULL; arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_module_t, parent))); arraylist_push(&s->relocs_list, (void*)backref_id(s, m->parent, s->link_ids_relocs)); - newm->bindings = NULL; + jl_atomic_store_relaxed(&newm->bindings, NULL); arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_module_t, bindings))); - arraylist_push(&s->relocs_list, (void*)backref_id(s, m->bindings, s->link_ids_relocs)); - newm->bindingkeyset = NULL; + arraylist_push(&s->relocs_list, (void*)backref_id(s, jl_atomic_load_relaxed(&m->bindings), s->link_ids_relocs)); + jl_atomic_store_relaxed(&newm->bindingkeyset, NULL); arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_module_t, bindingkeyset))); - arraylist_push(&s->relocs_list, (void*)backref_id(s, m->bindingkeyset, s->link_ids_relocs)); - newm->primary_world = ~(size_t)0; + arraylist_push(&s->relocs_list, (void*)backref_id(s, jl_atomic_load_relaxed(&m->bindingkeyset), s->link_ids_relocs)); // write out the usings list memset(&newm->usings._space, 0, sizeof(newm->usings._space)); @@ -1159,6 +1210,36 @@ static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t assert(ios_pos(s->s) - reloc_offset == tot); } +static void record_memoryref(jl_serializer_state *s, size_t reloc_offset, jl_genericmemoryref_t ref) { + ios_t *f = s->s; + // make some header modifications in-place + jl_genericmemoryref_t *newref = (jl_genericmemoryref_t*)&f->buf[reloc_offset]; + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(ref.mem))->layout; + if (!layout->flags.arrayelem_isunion && layout->size != 0) { + newref->ptr_or_offset = (void*)((char*)ref.ptr_or_offset - (char*)ref.mem->ptr); // relocation offset (bytes) + arraylist_push(&s->memref_list, (void*)reloc_offset); // relocation location + arraylist_push(&s->memref_list, NULL); // relocation target (ignored) + } +} + +static void record_memoryrefs_inside(jl_serializer_state *s, jl_datatype_t *t, size_t reloc_offset, const char *data) +{ + assert(jl_is_datatype(t)); + size_t i, nf = jl_datatype_nfields(t); + for (i = 0; i < nf; i++) { + size_t offset = jl_field_offset(t, i); + if (jl_field_isptr(t, i)) + continue; + jl_value_t *ft = jl_field_type_concrete(t, i); + if (jl_is_uniontype(ft)) + continue; + if (jl_is_genericmemoryref_type(ft)) + record_memoryref(s, reloc_offset + offset, *(jl_genericmemoryref_t*)(data + offset)); + else + record_memoryrefs_inside(s, (jl_datatype_t*)ft, reloc_offset + offset, data + offset); + } +} + static void record_gvars(jl_serializer_state *s, arraylist_t *globals) JL_NOTSAFEPOINT { for (size_t i = 0; i < globals->len; i++) @@ -1201,25 +1282,36 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED JL_GC_PROMISE_ROOTED(v); assert(!(s->incremental && jl_object_in_image(v))); jl_datatype_t *t = (jl_datatype_t*)jl_typeof(v); - assert((t->instance == NULL || t->instance == v) && "detected singleton construction corruption"); + assert((!jl_is_datatype_singleton(t) || t->instance == v) && "detected singleton construction corruption"); + int mutabl = t->name->mutabl; ios_t *f = s->s; if (t->smalltag) { if (t->layout->npointers == 0 || t == jl_string_type) { - if (jl_datatype_nfields(t) == 0 || t->name->mutabl == 0 || t == jl_string_type) { + if (jl_datatype_nfields(t) == 0 || mutabl == 0 || t == jl_string_type) { f = s->const_data; } } } - // realign stream to expected gc alignment (16 bytes) + // realign stream to expected gc alignment (16 bytes) after tag uintptr_t skip_header_pos = ios_pos(f) + sizeof(jl_taggedvalue_t); + uintptr_t object_id_expected = mutabl && + t != jl_datatype_type && + t != jl_typename_type && + t != jl_string_type && + t != jl_simplevector_type && + t != jl_module_type; + if (object_id_expected) + skip_header_pos += sizeof(size_t); write_padding(f, LLT_ALIGN(skip_header_pos, 16) - skip_header_pos); // write header + if (object_id_expected) + write_uint(f, jl_object_id(v)); if (s->incremental && jl_needs_serialization(s, (jl_value_t*)t) && needs_uniquing((jl_value_t*)t)) arraylist_push(&s->uniquing_types, (void*)(uintptr_t)(ios_pos(f)|1)); if (f == s->const_data) - write_uint(s->const_data, ((uintptr_t)t->smalltag << 4) | GC_OLD_MARKED); + write_uint(s->const_data, ((uintptr_t)t->smalltag << 4) | GC_OLD_MARKED | GC_IN_IMAGE); else write_gctaggedfield(s, t); size_t reloc_offset = ios_pos(f); @@ -1236,7 +1328,15 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED write_pointerfield(s, (jl_value_t*)mi->sparam_vals); continue; } - else if (!jl_is_datatype(v)) { + else if (jl_is_datatype(v)) { + for (size_t i = 0; i < s->uniquing_super.len; i++) { + if (s->uniquing_super.items[i] == (void*)v) { + s->uniquing_super.items[i] = arraylist_pop(&s->uniquing_super); + arraylist_push(&s->uniquing_types, (void*)(uintptr_t)(reloc_offset|3)); + } + } + } + else { assert(jl_is_datatype_singleton(t) && "unreachable"); } } @@ -1260,102 +1360,134 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED if (jl_is_array(v)) { assert(f == s->s); // Internal data for types in julia.h with `jl_array_t` field(s) -#define JL_ARRAY_ALIGN(jl_value, nbytes) LLT_ALIGN(jl_value, nbytes) jl_array_t *ar = (jl_array_t*)v; - jl_value_t *et = jl_tparam0(jl_typeof(v)); - size_t alen = jl_array_len(ar); - size_t datasize = alen * ar->elsize; - size_t tot = datasize; - int isbitsunion = jl_array_isbitsunion(ar); - if (isbitsunion) - tot += alen; - else if (ar->elsize == 1) - tot += 1; - int ndimwords = jl_array_ndimwords(ar->flags.ndims); - size_t headersize = sizeof(jl_array_t) + ndimwords*sizeof(size_t); // copy header + size_t headersize = sizeof(jl_array_t) + jl_array_ndims(ar)*sizeof(size_t); ios_write(f, (char*)v, headersize); - size_t alignment_amt = JL_SMALL_BYTE_ALIGNMENT; - if (tot >= ARRAY_CACHE_ALIGN_THRESHOLD) - alignment_amt = JL_CACHE_BYTE_ALIGNMENT; // make some header modifications in-place jl_array_t *newa = (jl_array_t*)&f->buf[reloc_offset]; - if (newa->flags.ndims == 1) - newa->maxsize = alen; - newa->offset = 0; - newa->flags.how = 0; - newa->flags.pooled = 0; - newa->flags.isshared = 0; - - // write data - if (!ar->flags.ptrarray && !ar->flags.hasptr) { - // Non-pointer eltypes get encoded in the const_data section - uintptr_t data = LLT_ALIGN(ios_pos(s->const_data), alignment_amt); - write_padding(s->const_data, data - ios_pos(s->const_data)); - // write data and relocations - newa->data = NULL; // relocation offset - data /= sizeof(void*); - assert(data < ((uintptr_t)1 << RELOC_TAG_OFFSET) && "offset to constant data too large"); - arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_array_t, data))); // relocation location - arraylist_push(&s->relocs_list, (void*)(((uintptr_t)ConstDataRef << RELOC_TAG_OFFSET) + data)); // relocation target - if (jl_is_cpointer_type(et)) { - // reset Ptr fields to C_NULL (but keep MAP_FAILED / INVALID_HANDLE) - const intptr_t *data = (const intptr_t*)jl_array_data(ar); - size_t i; - for (i = 0; i < alen; i++) { - if (data[i] != -1) - write_pointer(s->const_data); - else - ios_write(s->const_data, (char*)&data[i], sizeof(data[i])); - } - } - else { - if (isbitsunion) { - ios_write(s->const_data, (char*)jl_array_data(ar), datasize); - ios_write(s->const_data, jl_array_typetagdata(ar), alen); + newa->ref.mem = NULL; // relocation offset + arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_array_t, ref.mem))); // relocation location + jl_value_t *mem = get_replaceable_field((jl_value_t**)&ar->ref.mem, 1); + arraylist_push(&s->relocs_list, (void*)backref_id(s, mem, s->link_ids_relocs)); // relocation target + record_memoryref(s, reloc_offset + offsetof(jl_array_t, ref), ar->ref); + } + else if (jl_is_genericmemory(v)) { + assert(f == s->s); + // Internal data for types in julia.h with `jl_genericmemory_t` field(s) + jl_genericmemory_t *m = (jl_genericmemory_t*)v; + const jl_datatype_layout_t *layout = t->layout; + size_t len = m->length; + if (jl_genericmemory_how(m) == 3 && jl_is_genericmemory(jl_genericmemory_data_owner_field(m))) { + jl_genericmemory_t *owner = (jl_genericmemory_t*)jl_genericmemory_data_owner_field(m); + size_t data = ((char*)m->ptr - (char*)owner->ptr); // relocation offset (bytes) + write_uint(f, len); + write_uint(f, data); + write_pointerfield(s, (jl_value_t*)owner); + // similar to record_memoryref, but the field is always an (offset) pointer + arraylist_push(&s->memowner_list, (void*)(reloc_offset + offsetof(jl_genericmemory_t, ptr))); // relocation location + arraylist_push(&s->memowner_list, NULL); // relocation target (ignored) + } + // else if (jl_genericmemory_how(m) == 3) { + // jl_value_t *owner = jl_genericmemory_data_owner_field(m); + // write_uint(f, len); + // write_pointerfield(s, owner); + // write_pointerfield(s, owner); + // jl_genericmemory_t *new_mem = (jl_genericmemory_t*)&f->buf[reloc_offset]; + // assert(new_mem->ptr == NULL); + // new_mem->ptr = (void*)((char*)m->ptr - (char*)owner); // relocation offset + // } + else { + size_t datasize = len * layout->size; + size_t tot = datasize; + int isbitsunion = layout->flags.arrayelem_isunion; + if (isbitsunion) + tot += len; + size_t headersize = sizeof(jl_genericmemory_t); + // copy header + ios_write(f, (char*)v, headersize); + // write data + if (!layout->flags.arrayelem_isboxed && layout->first_ptr < 0) { + // set owner to NULL + write_pointer(f); + // Non-pointer eltypes get encoded in the const_data section + size_t alignment_amt = JL_SMALL_BYTE_ALIGNMENT; + if (tot >= ARRAY_CACHE_ALIGN_THRESHOLD) + alignment_amt = JL_CACHE_BYTE_ALIGNMENT; + uintptr_t data = LLT_ALIGN(ios_pos(s->const_data), alignment_amt); + write_padding(s->const_data, data - ios_pos(s->const_data)); + // write data and relocations + jl_genericmemory_t *new_mem = (jl_genericmemory_t*)&f->buf[reloc_offset]; + new_mem->ptr = NULL; // relocation offset + data /= sizeof(void*); + assert(data < ((uintptr_t)1 << RELOC_TAG_OFFSET) && "offset to constant data too large"); + arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_genericmemory_t, ptr))); // relocation location + arraylist_push(&s->relocs_list, (void*)(((uintptr_t)ConstDataRef << RELOC_TAG_OFFSET) + data)); // relocation target + jl_value_t *et = jl_tparam1(t); + if (jl_is_cpointer_type(et)) { + // reset Ptr fields to C_NULL (but keep MAP_FAILED / INVALID_HANDLE) + const intptr_t *data = (const intptr_t*)m->ptr; + size_t i; + for (i = 0; i < len; i++) { + if (data[i] != -1) + write_pointer(s->const_data); + else + ios_write(s->const_data, (char*)&data[i], sizeof(data[i])); + } } else { - ios_write(s->const_data, (char*)jl_array_data(ar), tot); + if (isbitsunion) { + ios_write(s->const_data, (char*)m->ptr, datasize); + ios_write(s->const_data, jl_genericmemory_typetagdata(m), len); + } + else { + ios_write(s->const_data, (char*)m->ptr, tot); + } } + if (len == 0) // TODO: should we have a zero-page, instead of writing each type's fragment separately? + write_padding(s->const_data, layout->size ? layout->size : isbitsunion); + else if (jl_genericmemory_how(m) == 3 && jl_is_string(jl_genericmemory_data_owner_field(m))) + write_padding(s->const_data, 1); } - } - else { - // Pointer eltypes are encoded in the mutable data section - size_t data = LLT_ALIGN(ios_pos(f), alignment_amt); - size_t padding_amt = data - ios_pos(f); - headersize += padding_amt; - newa->data = (void*)headersize; // relocation offset - arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_array_t, data))); // relocation location - arraylist_push(&s->relocs_list, (void*)(((uintptr_t)DataRef << RELOC_TAG_OFFSET) + item)); // relocation target - write_padding(f, padding_amt); - if (ar->flags.hasptr) { - // copy all of the data first - const char *data = (const char*)jl_array_data(ar); - ios_write(f, data, datasize); - // the rewrite all of the embedded pointers to null+relocation - uint16_t elsz = ar->elsize; - size_t j, np = ((jl_datatype_t*)et)->layout->npointers; - size_t i; - for (i = 0; i < alen; i++) { - for (j = 0; j < np; j++) { - size_t offset = i * elsz + jl_ptr_offset(((jl_datatype_t*)et), j) * sizeof(jl_value_t*); - jl_value_t *fld = get_replaceable_field((jl_value_t**)&data[offset], 1); - size_t fld_pos = reloc_offset + headersize + offset; - if (fld != NULL) { - arraylist_push(&s->relocs_list, (void*)(uintptr_t)fld_pos); // relocation location - arraylist_push(&s->relocs_list, (void*)backref_id(s, fld, s->link_ids_relocs)); // relocation target - record_uniquing(s, fld, fld_pos); + else { + // Pointer eltypes are encoded in the mutable data section + headersize = LLT_ALIGN(headersize, JL_SMALL_BYTE_ALIGNMENT); + size_t data = LLT_ALIGN(ios_pos(f), JL_SMALL_BYTE_ALIGNMENT); + write_padding(f, data - ios_pos(f)); + assert(reloc_offset + headersize == ios_pos(f)); + jl_genericmemory_t *new_mem = (jl_genericmemory_t*)&f->buf[reloc_offset]; + new_mem->ptr = (void*)headersize; // relocation offset + arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_genericmemory_t, ptr))); // relocation location + arraylist_push(&s->relocs_list, (void*)(((uintptr_t)DataRef << RELOC_TAG_OFFSET) + item)); // relocation target + if (!layout->flags.arrayelem_isboxed) { + // copy all of the data first + const char *data = (const char*)m->ptr; + ios_write(f, data, datasize); + // the rewrite all of the embedded pointers to null+relocation + uint16_t elsz = layout->size; + size_t j, np = layout->first_ptr < 0 ? 0 : layout->npointers; + size_t i; + for (i = 0; i < len; i++) { + for (j = 0; j < np; j++) { + size_t offset = i * elsz + jl_ptr_offset(t, j) * sizeof(jl_value_t*); + jl_value_t *fld = get_replaceable_field((jl_value_t**)&data[offset], 1); + size_t fld_pos = reloc_offset + headersize + offset; + if (fld != NULL) { + arraylist_push(&s->relocs_list, (void*)(uintptr_t)fld_pos); // relocation location + arraylist_push(&s->relocs_list, (void*)backref_id(s, fld, s->link_ids_relocs)); // relocation target + record_uniquing(s, fld, fld_pos); + } + memset(&f->buf[fld_pos], 0, sizeof(fld)); // relocation offset (none) } - memset(&f->buf[fld_pos], 0, sizeof(fld)); // relocation offset (none) } } - } - else { - jl_value_t **data = (jl_value_t**)jl_array_data(ar); - size_t i; - for (i = 0; i < alen; i++) { - jl_value_t *e = get_replaceable_field(&data[i], 1); - write_pointerfield(s, e); + else { + jl_value_t **data = (jl_value_t**)m->ptr; + size_t i; + for (i = 0; i < len; i++) { + jl_value_t *e = get_replaceable_field(&data[i], 1); + write_pointerfield(s, e); + } } } } @@ -1385,7 +1517,6 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED } else if (jl_datatype_nfields(t) == 0) { // The object has no fields, so we just snapshot its byte representation - assert(!t->layout->npointers); assert(t->layout->npointers == 0); ios_write(f, (char*)v, jl_datatype_size(t)); } @@ -1419,7 +1550,7 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED write_padding(f, offset - tot); tot = offset; size_t fsz = jl_field_size(t, i); - if (t->name->mutabl && jl_is_cpointer_type(jl_field_type(t, i)) && *(intptr_t*)slot != -1) { + if (t->name->mutabl && jl_is_cpointer_type(jl_field_type_concrete(t, i)) && *(intptr_t*)slot != -1) { // reset Ptr fields to C_NULL (but keep MAP_FAILED / INVALID_HANDLE) assert(!jl_field_isptr(t, i)); write_pointer(f); @@ -1446,21 +1577,24 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED memset(&f->buf[fld_pos], 0, sizeof(fld)); // relocation offset (none) } - // A few objects need additional handling beyond the generic serialization above + // Need do a tricky fieldtype walk an record all memoryref we find inlined in this value + record_memoryrefs_inside(s, t, reloc_offset, data); + // A few objects need additional handling beyond the generic serialization above if (s->incremental && jl_typetagis(v, jl_typemap_entry_type)) { assert(f == s->s); jl_typemap_entry_t *newentry = (jl_typemap_entry_t*)&s->s->buf[reloc_offset]; - if (newentry->max_world == ~(size_t)0) { - if (newentry->min_world > 1) { - newentry->min_world = ~(size_t)0; + if (jl_atomic_load_relaxed(&newentry->max_world) == ~(size_t)0) { + if (jl_atomic_load_relaxed(&newentry->min_world) > 1) { + jl_atomic_store_release(&newentry->min_world, ~(size_t)0); + jl_atomic_store_release(&newentry->max_world, WORLD_AGE_REVALIDATION_SENTINEL); arraylist_push(&s->fixup_objs, (void*)reloc_offset); } } else { // garbage newentry - delete it :( - newentry->min_world = 1; - newentry->max_world = 0; + jl_atomic_store_release(&newentry->min_world, 1); + jl_atomic_store_release(&newentry->max_world, 0); } } else if (jl_is_method(v)) { @@ -1469,12 +1603,19 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED jl_method_t *m = (jl_method_t*)v; jl_method_t *newm = (jl_method_t*)&f->buf[reloc_offset]; if (s->incremental) { - if (newm->deleted_world != ~(size_t)0) - newm->deleted_world = 1; - else - arraylist_push(&s->fixup_objs, (void*)reloc_offset); - newm->primary_world = ~(size_t)0; - } else { + if (jl_atomic_load_relaxed(&newm->deleted_world) == ~(size_t)0) { + if (jl_atomic_load_relaxed(&newm->primary_world) > 1) { + jl_atomic_store_relaxed(&newm->primary_world, ~(size_t)0); // min-world + jl_atomic_store_relaxed(&newm->deleted_world, 1); // max_world + arraylist_push(&s->fixup_objs, (void*)reloc_offset); + } + } + else { + jl_atomic_store_relaxed(&newm->primary_world, 1); + jl_atomic_store_relaxed(&newm->deleted_world, 0); + } + } + else { newm->nroots_sysimg = m->roots ? jl_array_len(m->roots) : 0; } if (m->ccallable) @@ -1487,38 +1628,36 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED } else if (jl_is_code_instance(v)) { assert(f == s->s); + // Handle the native-code pointers - assert(f == s->s); - jl_code_instance_t *m = (jl_code_instance_t*)v; - jl_code_instance_t *newm = (jl_code_instance_t*)&f->buf[reloc_offset]; + jl_code_instance_t *ci = (jl_code_instance_t*)v; + jl_code_instance_t *newci = (jl_code_instance_t*)&f->buf[reloc_offset]; if (s->incremental) { - arraylist_push(&s->fixup_objs, (void*)reloc_offset); - if (m->min_world > 1) - newm->min_world = ~(size_t)0; // checks that we reprocess this upon deserialization - if (m->max_world != ~(size_t)0) - newm->max_world = 0; - else { - if (m->inferred && ptrhash_has(&s->callers_with_edges, m->def)) - newm->max_world = 1; // sentinel value indicating this will need validation - if (m->min_world > 0 && m->inferred) { - // TODO: also check if this object is part of the codeinst cache - // will check on deserialize if this cache entry is still valid + if (jl_atomic_load_relaxed(&ci->max_world) == ~(size_t)0) { + if (jl_atomic_load_relaxed(&newci->min_world) > 1) { + jl_atomic_store_release(&newci->min_world, ~(size_t)0); + jl_atomic_store_release(&newci->max_world, WORLD_AGE_REVALIDATION_SENTINEL); + arraylist_push(&s->fixup_objs, (void*)reloc_offset); } } + else { + // garbage object - delete it :( + jl_atomic_store_release(&newci->min_world, 1); + jl_atomic_store_release(&newci->max_world, 0); + } } - - newm->invoke = NULL; - newm->specsigflags = 0; - newm->specptr.fptr = NULL; + jl_atomic_store_relaxed(&newci->invoke, NULL); + jl_atomic_store_relaxed(&newci->specsigflags, 0); + jl_atomic_store_relaxed(&newci->specptr.fptr, NULL); int8_t fptr_id = JL_API_NULL; int8_t builtin_id = 0; - if (m->invoke == jl_fptr_const_return) { + if (jl_atomic_load_relaxed(&ci->invoke) == jl_fptr_const_return) { fptr_id = JL_API_CONST; } else { - if (jl_is_method(m->def->def.method)) { - builtin_id = jl_fptr_id(m->specptr.fptr); + if (jl_is_method(ci->def->def.method)) { + builtin_id = jl_fptr_id(jl_atomic_load_relaxed(&ci->specptr.fptr)); if (builtin_id) { // found in the table of builtins assert(builtin_id >= 2); fptr_id = JL_API_BUILTIN; @@ -1526,7 +1665,7 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED else { int32_t invokeptr_id = 0; int32_t specfptr_id = 0; - jl_get_function_id(native_functions, m, &invokeptr_id, &specfptr_id); // see if we generated code for it + jl_get_function_id(native_functions, ci, &invokeptr_id, &specfptr_id); // see if we generated code for it if (invokeptr_id) { if (invokeptr_id == -1) { fptr_id = JL_API_BOXED; @@ -1558,7 +1697,7 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED } } } - newm->invoke = NULL; // relocation offset + jl_atomic_store_relaxed(&newci->invoke, NULL); // relocation offset if (fptr_id != JL_API_NULL) { assert(fptr_id < BuiltinFunctionTag && "too many functions to serialize"); arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_code_instance_t, invoke))); // relocation location @@ -1578,14 +1717,14 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED size_t nf = dt->layout->nfields; size_t np = dt->layout->npointers; size_t fieldsize = 0; - uint8_t is_foreign_type = dt->layout->fielddesc_type == 3; + uint8_t is_foreign_type = dt->layout->flags.fielddesc_type == 3; if (!is_foreign_type) { - fieldsize = jl_fielddesc_size(dt->layout->fielddesc_type); + fieldsize = jl_fielddesc_size(dt->layout->flags.fielddesc_type); } char *flddesc = (char*)dt->layout; size_t fldsize = sizeof(jl_datatype_layout_t) + nf * fieldsize; if (!is_foreign_type && dt->layout->first_ptr != -1) - fldsize += np << dt->layout->fielddesc_type; + fldsize += np << dt->layout->flags.fielddesc_type; uintptr_t layout = LLT_ALIGN(ios_pos(s->const_data), sizeof(void*)); write_padding(s->const_data, layout - ios_pos(s->const_data)); // realign stream newdt->layout = NULL; // relocation offset @@ -1601,6 +1740,9 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED ios_write(s->const_data, (char*)&dyn, sizeof(jl_fielddescdyn_t)); } } + void *superidx = ptrhash_get(&serialization_order, dt->super); + if (s->incremental && superidx != HT_NOTFOUND && (char*)superidx - 1 - (char*)HT_NOTFOUND > item && needs_uniquing((jl_value_t*)dt->super)) + arraylist_push(&s->uniquing_super, dt->super); } else if (jl_is_typename(v)) { assert(f == s->s); @@ -1635,16 +1777,16 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED arraylist_push(&s->fixup_objs, (void*)reloc_offset); } } - else if (((jl_datatype_t*)(jl_typeof(v)))->name == jl_idtable_typename) { + else if (jl_is_genericmemoryref(v)) { assert(f == s->s); - // will need to rehash this, later (after types are fully constructed) - arraylist_push(&s->fixup_objs, (void*)reloc_offset); + record_memoryref(s, reloc_offset, *(jl_genericmemoryref_t*)v); } else { write_padding(f, jl_datatype_size(t) - tot); } } } + assert(s->uniquing_super.len == 0); } // In deserialization, create Symbols and set up the @@ -1765,11 +1907,11 @@ static inline uintptr_t get_item_for_reloc(jl_serializer_state *s, uintptr_t bas } switch ((jl_callingconv_t)offset) { case JL_API_BOXED: - if (s->image->fptrs.base) + if (s->image->fptrs.nptrs) return (uintptr_t)jl_fptr_args; JL_FALLTHROUGH; case JL_API_WITH_PARAMETERS: - if (s->image->fptrs.base) + if (s->image->fptrs.nptrs) return (uintptr_t)jl_fptr_sparam; return (uintptr_t)NULL; case JL_API_CONST: @@ -1791,7 +1933,7 @@ static inline uintptr_t get_item_for_reloc(jl_serializer_state *s, uintptr_t bas size_t depsidx = 0; #endif assert(s->buildid_depmods_idxs && depsidx < jl_array_len(s->buildid_depmods_idxs)); - size_t i = ((uint32_t*)jl_array_data(s->buildid_depmods_idxs))[depsidx]; + size_t i = jl_array_data(s->buildid_depmods_idxs, uint32_t)[depsidx]; assert(2*i < jl_linkage_blobs.len); return (uintptr_t)jl_linkage_blobs.items[2*i] + offset*sizeof(void*); } @@ -1799,10 +1941,10 @@ static inline uintptr_t get_item_for_reloc(jl_serializer_state *s, uintptr_t bas assert(link_ids); assert(link_index); assert(0 <= *link_index && *link_index < jl_array_len(link_ids)); - uint32_t depsidx = ((uint32_t*)jl_array_data(link_ids))[*link_index]; + uint32_t depsidx = jl_array_data(link_ids, uint32_t)[*link_index]; *link_index += 1; assert(depsidx < jl_array_len(s->buildid_depmods_idxs)); - size_t i = ((uint32_t*)jl_array_data(s->buildid_depmods_idxs))[depsidx]; + size_t i = jl_array_data(s->buildid_depmods_idxs, uint32_t)[depsidx]; assert(2*i < jl_linkage_blobs.len); return (uintptr_t)jl_linkage_blobs.items[2*i] + offset*sizeof(void*); } @@ -1891,6 +2033,37 @@ static void jl_read_reloclist(jl_serializer_state *s, jl_array_t *link_ids, uint assert(!link_ids || link_index == jl_array_len(link_ids)); } +static void jl_read_memreflist(jl_serializer_state *s) +{ + uintptr_t base = (uintptr_t)s->s->buf; + uintptr_t last_pos = 0; + uint8_t *current = (uint8_t *)(s->relocs->buf + s->relocs->bpos); + while (1) { + // Read the offset of the next object + size_t pos_diff = 0; + size_t cnt = 0; + while (1) { + assert(s->relocs->bpos <= s->relocs->size); + assert((char *)current <= (char *)(s->relocs->buf + s->relocs->size)); + int8_t c = *current++; + s->relocs->bpos += 1; + + pos_diff |= ((size_t)c & 0x7F) << (7 * cnt++); + if ((c >> 7) == 0) + break; + } + if (pos_diff == 0) + break; + + uintptr_t pos = last_pos + pos_diff; + last_pos = pos; + jl_genericmemoryref_t *pv = (jl_genericmemoryref_t*)(base + pos); + size_t offset = (size_t)pv->ptr_or_offset; + pv->ptr_or_offset = (void*)((char*)pv->mem->ptr + offset); + } +} + + static void jl_read_arraylist(ios_t *s, arraylist_t *list) { size_t list_len = read_uint(s); @@ -1993,8 +2166,7 @@ static void jl_update_all_fptrs(jl_serializer_state *s, jl_image_t *image) jl_image_fptrs_t fvars = image->fptrs; // make these NULL now so we skip trying to restore GlobalVariable pointers later image->gvars_base = NULL; - image->fptrs.base = NULL; - if (fvars.base == NULL) + if (fvars.nptrs == 0) return; memcpy(image->jl_small_typeof, &jl_small_typeof, sizeof(jl_small_typeof)); @@ -2017,26 +2189,24 @@ static void jl_update_all_fptrs(jl_serializer_state *s, jl_image_t *image) offset = ~offset; } jl_code_instance_t *codeinst = (jl_code_instance_t*)(base + offset); - uintptr_t base = (uintptr_t)fvars.base; - assert(jl_is_method(codeinst->def->def.method) && codeinst->invoke != jl_fptr_const_return); - assert(specfunc ? codeinst->invoke != NULL : codeinst->invoke == NULL); + assert(jl_is_method(codeinst->def->def.method) && jl_atomic_load_relaxed(&codeinst->invoke) != jl_fptr_const_return); + assert(specfunc ? jl_atomic_load_relaxed(&codeinst->invoke) != NULL : jl_atomic_load_relaxed(&codeinst->invoke) == NULL); linfos[i] = codeinst->def; // now it's a MethodInstance - int32_t offset = fvars.offsets[i]; + void *fptr = fvars.ptrs[i]; for (; clone_idx < fvars.nclones; clone_idx++) { uint32_t idx = fvars.clone_idxs[clone_idx] & jl_sysimg_val_mask; if (idx < i) continue; if (idx == i) - offset = fvars.clone_offsets[clone_idx]; + fptr = fvars.clone_ptrs[clone_idx]; break; } - void *fptr = (void*)(base + offset); if (specfunc) { - codeinst->specptr.fptr = fptr; - codeinst->specsigflags = 0b111; // TODO: set only if confirmed to be true + jl_atomic_store_relaxed(&codeinst->specptr.fptr, fptr); + jl_atomic_store_relaxed(&codeinst->specsigflags, 0b111); // TODO: set only if confirmed to be true } else { - codeinst->invoke = (jl_callptr_t)fptr; + jl_atomic_store_relaxed(&codeinst->invoke,(jl_callptr_t)fptr); } } } @@ -2104,11 +2274,12 @@ static void jl_root_new_gvars(jl_serializer_state *s, jl_image_t *image, uint32_ uintptr_t v = *gv; if (i < external_fns_begin) { if (!jl_is_binding(v)) - v = (uintptr_t)jl_as_global_root((jl_value_t*)v); - } else { + v = (uintptr_t)jl_as_global_root((jl_value_t*)v, 1); + } + else { jl_code_instance_t *codeinst = (jl_code_instance_t*) v; - assert(codeinst && (codeinst->specsigflags & 0b01) && codeinst->specptr.fptr); - v = (uintptr_t)codeinst->specptr.fptr; + assert(codeinst && (jl_atomic_load_relaxed(&codeinst->specsigflags) & 0b01) && jl_atomic_load_relaxed(&codeinst->specptr.fptr)); + v = (uintptr_t)jl_atomic_load_relaxed(&codeinst->specptr.fptr); } *gv = v; } @@ -2142,6 +2313,7 @@ static void jl_reinit_ccallable(arraylist_t *ccallable_list, char *base, void *s static jl_svec_t *jl_prune_type_cache_hash(jl_svec_t *cache) JL_GC_DISABLED { size_t l = jl_svec_len(cache), i; + size_t sz = 0; if (l == 0) return cache; for (i = 0; i < l; i++) { @@ -2150,11 +2322,16 @@ static jl_svec_t *jl_prune_type_cache_hash(jl_svec_t *cache) JL_GC_DISABLED continue; if (ptrhash_get(&serialization_order, ti) == HT_NOTFOUND) jl_svecset(cache, i, jl_nothing); + else + sz += 1; } + if (sz < HT_N_INLINE) + sz = HT_N_INLINE; + void *idx = ptrhash_get(&serialization_order, cache); assert(idx != HT_NOTFOUND && idx != (void*)(uintptr_t)-1); assert(serialization_queue.items[(char*)idx - 1 - (char*)HT_NOTFOUND] == cache); - cache = cache_rehash_set(cache, l); + cache = cache_rehash_set(cache, sz); // redirect all references to the old cache to relocate to the new cache object ptrhash_put(&serialization_order, cache, idx); serialization_queue.items[(char*)idx - 1 - (char*)HT_NOTFOUND] = cache; @@ -2175,23 +2352,18 @@ static void jl_prune_type_cache_linear(jl_svec_t *cache) jl_svecset(cache, ins++, jl_nothing); } -static jl_value_t *strip_codeinfo_meta(jl_method_t *m, jl_value_t *ci_, int orig) +static jl_value_t *strip_codeinfo_meta(jl_method_t *m, jl_value_t *ci_, jl_code_instance_t *codeinst, int orig) { jl_code_info_t *ci = NULL; JL_GC_PUSH1(&ci); int compressed = 0; if (!jl_is_code_info(ci_)) { compressed = 1; - ci = jl_uncompress_ir(m, NULL, (jl_value_t*)ci_); + ci = jl_uncompress_ir(m, codeinst, (jl_value_t*)ci_); } else { ci = (jl_code_info_t*)ci_; } - // leave codelocs length the same so the compiler can assume that; just zero it - memset(jl_array_data(ci->codelocs), 0, jl_array_len(ci->codelocs)*sizeof(int32_t)); - // empty linetable - if (jl_is_array(ci->linetable)) - jl_array_del_end((jl_array_t*)ci->linetable, jl_array_len(ci->linetable)); // replace slot names with `?`, except unused_sym since the compiler looks at it jl_sym_t *questionsym = jl_symbol("?"); int i, l = jl_array_len(ci->slotnames); @@ -2222,7 +2394,7 @@ static void strip_specializations_(jl_method_instance_t *mi) record_field_change((jl_value_t**)&codeinst->inferred, jl_nothing); } else if (jl_options.strip_metadata) { - jl_value_t *stripped = strip_codeinfo_meta(mi->def.method, inferred, 0); + jl_value_t *stripped = strip_codeinfo_meta(mi->def.method, inferred, codeinst, 0); if (jl_atomic_cmpswap_relaxed(&codeinst->inferred, &inferred, stripped)) { jl_gc_wb(codeinst, stripped); } @@ -2233,7 +2405,6 @@ static void strip_specializations_(jl_method_instance_t *mi) if (jl_options.strip_ir) { record_field_change((jl_value_t**)&mi->uninferred, NULL); record_field_change((jl_value_t**)&mi->backedges, NULL); - record_field_change((jl_value_t**)&mi->callbacks, NULL); } } @@ -2243,8 +2414,8 @@ static int strip_all_codeinfos__(jl_typemap_entry_t *def, void *_env) if (m->source) { int stripped_ir = 0; if (jl_options.strip_ir) { - if (m->unspecialized) { - jl_code_instance_t *unspec = jl_atomic_load_relaxed(&m->unspecialized->cache); + if (jl_atomic_load_relaxed(&m->unspecialized)) { + jl_code_instance_t *unspec = jl_atomic_load_relaxed(&jl_atomic_load_relaxed(&m->unspecialized)->cache); if (unspec && jl_atomic_load_relaxed(&unspec->invoke)) { // we have a generic compiled version, so can remove the IR record_field_change(&m->source, jl_nothing); @@ -2261,11 +2432,15 @@ static int strip_all_codeinfos__(jl_typemap_entry_t *def, void *_env) } } if (jl_options.strip_metadata && !stripped_ir) { - m->source = strip_codeinfo_meta(m, m->source, 1); + m->source = strip_codeinfo_meta(m, m->source, NULL, 1); jl_gc_wb(m, m->source); } } - jl_value_t *specializations = m->specializations; + if (jl_options.strip_metadata) { + record_field_change((jl_value_t**)&m->file, (jl_value_t*)jl_empty_sym); + m->line = 0; + } + jl_value_t *specializations = jl_atomic_load_relaxed(&m->specializations); if (!jl_is_svec(specializations)) { strip_specializations_((jl_method_instance_t*)specializations); } @@ -2277,8 +2452,8 @@ static int strip_all_codeinfos__(jl_typemap_entry_t *def, void *_env) strip_specializations_((jl_method_instance_t*)mi); } } - if (m->unspecialized) - strip_specializations_(m->unspecialized); + if (jl_atomic_load_relaxed(&m->unspecialized)) + strip_specializations_(jl_atomic_load_relaxed(&m->unspecialized)); if (jl_options.strip_ir && m->root_blocks) record_field_change((jl_value_t**)&m->root_blocks, NULL); return 1; @@ -2288,7 +2463,7 @@ static int strip_all_codeinfos_(jl_methtable_t *mt, void *_env) { if (jl_options.strip_ir && mt->backedges) record_field_change((jl_value_t**)&mt->backedges, NULL); - return jl_typemap_visitor(mt->defs, strip_all_codeinfos__, NULL); + return jl_typemap_visitor(jl_atomic_load_relaxed(&mt->defs), strip_all_codeinfos__, NULL); } static void jl_strip_all_codeinfos(void) @@ -2298,12 +2473,21 @@ static void jl_strip_all_codeinfos(void) // --- entry points --- -jl_array_t *jl_global_roots_table; +jl_genericmemory_t *jl_global_roots_list; +jl_genericmemory_t *jl_global_roots_keyset; jl_mutex_t global_roots_lock; +extern jl_mutex_t world_counter_lock; +extern size_t jl_require_world; JL_DLLEXPORT int jl_is_globally_rooted(jl_value_t *val JL_MAYBE_UNROOTED) JL_NOTSAFEPOINT { - if (jl_is_concrete_type(val) || jl_is_bool(val) || jl_is_symbol(val) || + if (jl_is_datatype(val)) { + jl_datatype_t *dt = (jl_datatype_t*)val; + if (jl_unwrap_unionall(dt->name->wrapper) == val) + return 1; + return (jl_is_tuple_type(val) ? dt->isconcretetype : !dt->hasfreetypevars); // aka is_cacheable from jltypes.c + } + if (jl_is_bool(val) || jl_is_symbol(val) || val == (jl_value_t*)jl_any_type || val == (jl_value_t*)jl_bottom_type || val == (jl_value_t*)jl_core_module) return 1; if (val == ((jl_datatype_t*)jl_typeof(val))->instance) @@ -2311,10 +2495,21 @@ JL_DLLEXPORT int jl_is_globally_rooted(jl_value_t *val JL_MAYBE_UNROOTED) JL_NOT return 0; } -JL_DLLEXPORT jl_value_t *jl_as_global_root(jl_value_t *val JL_MAYBE_UNROOTED) +static jl_value_t *extract_wrapper(jl_value_t *t JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT JL_GLOBALLY_ROOTED +{ + t = jl_unwrap_unionall(t); + if (jl_is_datatype(t)) + return ((jl_datatype_t*)t)->name->wrapper; + return NULL; +} + +JL_DLLEXPORT jl_value_t *jl_as_global_root(jl_value_t *val, int insert) { if (jl_is_globally_rooted(val)) return val; + jl_value_t *tw = extract_wrapper(val); + if (tw && (val == tw || jl_types_egal(val, tw))) + return tw; if (jl_is_uint8(val)) return jl_box_uint8(jl_unbox_uint8(val)); if (jl_is_int32(val)) { @@ -2327,22 +2522,29 @@ JL_DLLEXPORT jl_value_t *jl_as_global_root(jl_value_t *val JL_MAYBE_UNROOTED) if ((uint64_t)(n+512) < 1024) return jl_box_int64(n); } - JL_GC_PUSH1(&val); + // check table before acquiring lock to reduce writer contention + jl_value_t *rval = jl_idset_get(jl_global_roots_list, jl_global_roots_keyset, val); + if (rval) + return rval; JL_LOCK(&global_roots_lock); - jl_value_t *rval = jl_eqtable_getkey(jl_global_roots_table, val, NULL); + rval = jl_idset_get(jl_global_roots_list, jl_global_roots_keyset, val); if (rval) { val = rval; } + else if (insert) { + ssize_t idx; + jl_global_roots_list = jl_idset_put_key(jl_global_roots_list, val, &idx); + jl_global_roots_keyset = jl_idset_put_idx(jl_global_roots_list, jl_global_roots_keyset, idx); + } else { - jl_global_roots_table = jl_eqtable_put(jl_global_roots_table, val, jl_nothing, NULL); + val = NULL; } JL_UNLOCK(&global_roots_lock); - JL_GC_POP(); return val; } static void jl_prepare_serialization_data(jl_array_t *mod_array, jl_array_t *newly_inferred, uint64_t worklist_key, - /* outputs */ jl_array_t **extext_methods, jl_array_t **new_specializations, + /* outputs */ jl_array_t **extext_methods, jl_array_t **new_ext_cis, jl_array_t **method_roots_list, jl_array_t **ext_targets, jl_array_t **edges) { // extext_methods: [method1, ...], worklist-owned "extending external" methods added to functions owned by modules outside the worklist @@ -2354,12 +2556,12 @@ static void jl_prepare_serialization_data(jl_array_t *mod_array, jl_array_t *new assert(edges_map == NULL); // Save the inferred code from newly inferred, external methods - *new_specializations = queue_external_cis(newly_inferred); + *new_ext_cis = queue_external_cis(newly_inferred); // Collect method extensions and edges data JL_GC_PUSH1(&edges_map); if (edges) - edges_map = jl_alloc_vec_any(0); + edges_map = jl_alloc_memory_any(0); *extext_methods = jl_alloc_vec_any(0); jl_collect_methtable_from_mod(jl_type_type_mt, *extext_methods); jl_collect_methtable_from_mod(jl_nonfunction_mt, *extext_methods); @@ -2380,9 +2582,9 @@ static void jl_prepare_serialization_data(jl_array_t *mod_array, jl_array_t *new *ext_targets = jl_alloc_vec_any(0); *edges = jl_alloc_vec_any(0); *method_roots_list = jl_alloc_vec_any(0); - // Collect the new method roots - jl_collect_new_roots(*method_roots_list, *new_specializations, worklist_key); - jl_collect_edges(*edges, *ext_targets, *new_specializations, world); + // Collect the new method roots for external specializations + jl_collect_new_roots(*method_roots_list, *new_ext_cis, worklist_key); + jl_collect_edges(*edges, *ext_targets, *new_ext_cis, world); } assert(edges_map == NULL); // jl_collect_edges clears this when done @@ -2392,7 +2594,7 @@ static void jl_prepare_serialization_data(jl_array_t *mod_array, jl_array_t *new // In addition to the system image (where `worklist = NULL`), this can also save incremental images with external linkage static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, jl_array_t *worklist, jl_array_t *extext_methods, - jl_array_t *new_specializations, jl_array_t *method_roots_list, + jl_array_t *new_ext_cis, jl_array_t *method_roots_list, jl_array_t *ext_targets, jl_array_t *edges) JL_GC_DISABLED { htable_new(&field_replace, 0); @@ -2409,7 +2611,6 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, ptrhash_put(&fptr_to_id, (void*)(uintptr_t)id_to_fptrs[i], (void*)(i + 2)); } htable_new(&serialization_order, 25000); - htable_new(&unique_ready, 0); htable_new(&nullptrs, 0); arraylist_new(&object_worklist, 0); arraylist_new(&serialization_queue, 0); @@ -2429,9 +2630,12 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, s.gvar_record = &gvar_record; s.fptr_record = &fptr_record; s.ptls = jl_current_task->ptls; + arraylist_new(&s.memowner_list, 0); + arraylist_new(&s.memref_list, 0); arraylist_new(&s.relocs_list, 0); arraylist_new(&s.gctags_list, 0); arraylist_new(&s.uniquing_types, 0); + arraylist_new(&s.uniquing_super, 0); arraylist_new(&s.uniquing_objs, 0); arraylist_new(&s.fixup_types, 0); arraylist_new(&s.fixup_objs, 0); @@ -2462,8 +2666,6 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, } } } - jl_idtable_type = jl_base_module ? jl_get_global(jl_base_module, jl_symbol("IdDict")) : NULL; - jl_idtable_typename = jl_base_module ? ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_idtable_type))->name : NULL; jl_bigint_type = jl_base_module ? jl_get_global(jl_base_module, jl_symbol("BigInt")) : NULL; if (jl_bigint_type) { gmp_limb_size = jl_unbox_long(jl_get_global((jl_module_t*)jl_get_global(jl_base_module, jl_symbol("GMP")), @@ -2475,6 +2677,8 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, jl_docmeta_sym = (jl_sym_t*)jl_get_global((jl_module_t*)docs, jl_symbol("META")); } } + jl_genericmemory_t *global_roots_list = NULL; + jl_genericmemory_t *global_roots_keyset = NULL; { // step 1: record values (recursively) that need to go in the image size_t i; @@ -2483,17 +2687,9 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, jl_value_t *tag = *tags[i]; jl_queue_for_serialization(&s, tag); } - jl_queue_for_serialization(&s, jl_global_roots_table); jl_queue_for_serialization(&s, s.ptls->root_task->tls); } else { - // To ensure we don't have to manually update the list, go through all tags and queue any that are not otherwise - // judged to be externally-linked - htable_new(&external_objects, NUM_TAGS); - for (size_t i = 0; tags[i] != NULL; i++) { - jl_value_t *tag = *tags[i]; - ptrhash_put(&external_objects, tag, tag); - } // Queue the worklist itself as the first item we serialize jl_queue_for_serialization(&s, worklist); jl_queue_for_serialization(&s, jl_module_init_order); @@ -2507,7 +2703,7 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, // Queue method extensions jl_queue_for_serialization(&s, extext_methods); // Queue the new specializations - jl_queue_for_serialization(&s, new_specializations); + jl_queue_for_serialization(&s, new_ext_cis); // Queue the new roots jl_queue_for_serialization(&s, method_roots_list); // Queue the edges @@ -2519,14 +2715,31 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, record_gvars(&s, &gvars); record_external_fns(&s, &external_fns); jl_serialize_reachable(&s); - // step 1.3: prune (garbage collect) some special weak references from - // built-in type caches + // step 1.3: prune (garbage collect) special weak references from the jl_global_roots_list + if (worklist == NULL) { + global_roots_list = jl_alloc_memory_any(0); + global_roots_keyset = jl_alloc_memory_any(0); + for (size_t i = 0; i < jl_global_roots_list->length; i++) { + jl_value_t *val = jl_genericmemory_ptr_ref(jl_global_roots_list, i); + if (val && ptrhash_get(&serialization_order, val) != HT_NOTFOUND) { + ssize_t idx; + global_roots_list = jl_idset_put_key(global_roots_list, val, &idx); + global_roots_keyset = jl_idset_put_idx(global_roots_list, global_roots_keyset, idx); + } + } + jl_queue_for_serialization(&s, global_roots_list); + jl_queue_for_serialization(&s, global_roots_keyset); + jl_serialize_reachable(&s); + } + // step 1.4: prune (garbage collect) some special weak references from + // built-in type caches too for (i = 0; i < serialization_queue.len; i++) { jl_typename_t *tn = (jl_typename_t*)serialization_queue.items[i]; if (jl_is_typename(tn)) { - tn->cache = jl_prune_type_cache_hash(tn->cache); - jl_gc_wb(tn, tn->cache); - jl_prune_type_cache_linear(tn->linearcache); + jl_atomic_store_relaxed(&tn->cache, + jl_prune_type_cache_hash(jl_atomic_load_relaxed(&tn->cache))); + jl_gc_wb(tn, jl_atomic_load_relaxed(&tn->cache)); + jl_prune_type_cache_linear(jl_atomic_load_relaxed(&tn->linearcache)); } } } @@ -2592,6 +2805,8 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, jl_finish_relocs(base + sysimg_offset, sysimg_size, &s.relocs_list); jl_write_offsetlist(s.relocs, sysimg_size, &s.gctags_list); jl_write_offsetlist(s.relocs, sysimg_size, &s.relocs_list); + jl_write_offsetlist(s.relocs, sysimg_size, &s.memowner_list); + jl_write_offsetlist(s.relocs, sysimg_size, &s.memref_list); if (s.incremental) { jl_write_arraylist(s.relocs, &s.uniquing_types); jl_write_arraylist(s.relocs, &s.uniquing_objs); @@ -2625,7 +2840,8 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, jl_value_t *tag = *tags[i]; jl_write_value(&s, tag); } - jl_write_value(&s, jl_global_roots_table); + jl_write_value(&s, global_roots_list); + jl_write_value(&s, global_roots_keyset); jl_write_value(&s, s.ptls->root_task->tls); write_uint32(f, jl_get_gs_ctr()); write_uint(f, jl_atomic_load_acquire(&jl_world_counter)); @@ -2643,19 +2859,19 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, } jl_write_value(&s, jl_module_init_order); jl_write_value(&s, extext_methods); - jl_write_value(&s, new_specializations); + jl_write_value(&s, new_ext_cis); jl_write_value(&s, method_roots_list); jl_write_value(&s, ext_targets); jl_write_value(&s, edges); } write_uint32(f, jl_array_len(s.link_ids_gctags)); - ios_write(f, (char*)jl_array_data(s.link_ids_gctags), jl_array_len(s.link_ids_gctags) * sizeof(uint32_t)); + ios_write(f, (char*)jl_array_data(s.link_ids_gctags, uint32_t), jl_array_len(s.link_ids_gctags) * sizeof(uint32_t)); write_uint32(f, jl_array_len(s.link_ids_relocs)); - ios_write(f, (char*)jl_array_data(s.link_ids_relocs), jl_array_len(s.link_ids_relocs) * sizeof(uint32_t)); + ios_write(f, (char*)jl_array_data(s.link_ids_relocs, uint32_t), jl_array_len(s.link_ids_relocs) * sizeof(uint32_t)); write_uint32(f, jl_array_len(s.link_ids_gvars)); - ios_write(f, (char*)jl_array_data(s.link_ids_gvars), jl_array_len(s.link_ids_gvars) * sizeof(uint32_t)); + ios_write(f, (char*)jl_array_data(s.link_ids_gvars, uint32_t), jl_array_len(s.link_ids_gvars) * sizeof(uint32_t)); write_uint32(f, jl_array_len(s.link_ids_external_fnvars)); - ios_write(f, (char*)jl_array_data(s.link_ids_external_fnvars), jl_array_len(s.link_ids_external_fnvars) * sizeof(uint32_t)); + ios_write(f, (char*)jl_array_data(s.link_ids_external_fnvars, uint32_t), jl_array_len(s.link_ids_external_fnvars) * sizeof(uint32_t)); write_uint32(f, external_fns_begin); jl_write_arraylist(s.s, &s.ccallable_list); } @@ -2664,16 +2880,20 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, arraylist_free(&object_worklist); arraylist_free(&serialization_queue); arraylist_free(&layout_table); + arraylist_free(&s.uniquing_types); + arraylist_free(&s.uniquing_super); + arraylist_free(&s.uniquing_objs); + arraylist_free(&s.fixup_types); + arraylist_free(&s.fixup_objs); arraylist_free(&s.ccallable_list); + arraylist_free(&s.memowner_list); + arraylist_free(&s.memref_list); arraylist_free(&s.relocs_list); arraylist_free(&s.gctags_list); arraylist_free(&gvars); arraylist_free(&external_fns); htable_free(&field_replace); - if (worklist) - htable_free(&external_objects); htable_free(&serialization_order); - htable_free(&unique_ready); htable_free(&nullptrs); htable_free(&symbol_table); htable_free(&fptr_to_id); @@ -2691,8 +2911,9 @@ static void jl_write_header_for_incremental(ios_t *f, jl_array_t *worklist, jl_a write_uint8(f, jl_cache_flags()); // write description of contents (name, uuid, buildid) write_worklist_for_header(f, worklist); - // Determine unique (module, abspath, mtime) dependencies for the files defining modules in the worklist - // (see Base._require_dependencies). These get stored in `udeps` and written to the ji-file header. + // Determine unique (module, abspath, fsize, hash, mtime) dependencies for the files defining modules in the worklist + // (see Base._require_dependencies). These get stored in `udeps` and written to the ji-file header + // (abspath will be converted to a relocateable @depot path before writing, cf. Base.replace_depot_path). // Also write Preferences. // last word of the dependency list is the end of the data / start of the srctextpos *srctextpos = write_dependency_list(f, worklist, udeps); // srctextpos: position of srctext entry in header index (update later) @@ -2724,24 +2945,24 @@ JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *workli ff = f; } - jl_array_t *mod_array = NULL, *extext_methods = NULL, *new_specializations = NULL; + jl_array_t *mod_array = NULL, *extext_methods = NULL, *new_ext_cis = NULL; jl_array_t *method_roots_list = NULL, *ext_targets = NULL, *edges = NULL; int64_t checksumpos = 0; int64_t checksumpos_ff = 0; int64_t datastartpos = 0; - JL_GC_PUSH6(&mod_array, &extext_methods, &new_specializations, &method_roots_list, &ext_targets, &edges); + JL_GC_PUSH6(&mod_array, &extext_methods, &new_ext_cis, &method_roots_list, &ext_targets, &edges); if (worklist) { mod_array = jl_get_loaded_modules(); // __toplevel__ modules loaded in this session (from Base.loaded_modules_array) // Generate _native_data` if (_native_data != NULL) { jl_prepare_serialization_data(mod_array, newly_inferred, jl_worklist_key(worklist), - &extext_methods, &new_specializations, NULL, NULL, NULL); + &extext_methods, &new_ext_cis, NULL, NULL, NULL); jl_precompile_toplevel_module = (jl_module_t*)jl_array_ptr_ref(worklist, jl_array_len(worklist)-1); - *_native_data = jl_precompile_worklist(worklist, extext_methods, new_specializations); + *_native_data = jl_precompile_worklist(worklist, extext_methods, new_ext_cis); jl_precompile_toplevel_module = NULL; extext_methods = NULL; - new_specializations = NULL; + new_ext_cis = NULL; } jl_write_header_for_incremental(f, worklist, mod_array, udeps, srctextpos, &checksumpos); if (emit_split) { @@ -2764,7 +2985,7 @@ JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *workli ct->reentrant_timing |= 0b1000; if (worklist) { jl_prepare_serialization_data(mod_array, newly_inferred, jl_worklist_key(worklist), - &extext_methods, &new_specializations, &method_roots_list, &ext_targets, &edges); + &extext_methods, &new_ext_cis, &method_roots_list, &ext_targets, &edges); if (!emit_split) { write_int32(f, 0); // No clone_targets write_padding(f, LLT_ALIGN(ios_pos(f), JL_CACHE_BYTE_ALIGNMENT) - ios_pos(f)); @@ -2776,7 +2997,7 @@ JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *workli } if (_native_data != NULL) native_functions = *_native_data; - jl_save_system_image_to_stream(ff, mod_array, worklist, extext_methods, new_specializations, method_roots_list, ext_targets, edges); + jl_save_system_image_to_stream(ff, mod_array, worklist, extext_methods, new_ext_cis, method_roots_list, ext_targets, edges); if (_native_data != NULL) native_functions = NULL; // make sure we don't run any Julia code concurrently before this point @@ -2855,10 +3076,15 @@ JL_DLLEXPORT void jl_set_sysimg_so(void *handle) extern void rebuild_image_blob_tree(void); extern void export_jl_small_typeof(void); +// When an image is loaded with ignore_native, all subsequent image loads must ignore +// native code in the cache-file since we can't gurantuee that there are no call edges +// into the native code of the image. See https://github.com/JuliaLang/julia/pull/52123#issuecomment-1959965395. +int IMAGE_NATIVE_CODE_TAINTED = 0; + static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl_array_t *depmods, uint64_t checksum, /* outputs */ jl_array_t **restored, jl_array_t **init_order, - jl_array_t **extext_methods, - jl_array_t **new_specializations, jl_array_t **method_roots_list, + jl_array_t **extext_methods, jl_array_t **internal_methods, + jl_array_t **new_ext_cis, jl_array_t **method_roots_list, jl_array_t **ext_targets, jl_array_t **edges, char **base, arraylist_t *ccallable_list, pkgcachesizes *cachesizes) JL_GC_DISABLED { @@ -2879,6 +3105,14 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl htable_new(&new_dt_objs, 0); arraylist_new(&deser_sym, 0); + // in --build mode only use sysimg data, not precompiled native code + int imaging_mode = jl_generating_output() && !jl_options.incremental; + if (imaging_mode || jl_options.use_sysimage_native_code != JL_OPTIONS_USE_SYSIMAGE_NATIVE_CODE_YES || IMAGE_NATIVE_CODE_TAINTED) { + memset(&image->fptrs, 0, sizeof(image->fptrs)); + image->gvars_base = NULL; + IMAGE_NATIVE_CODE_TAINTED = 1; + } + // step 1: read section map assert(ios_pos(f) == 0 && f->bm == bm_mem); size_t sizeof_sysdata = read_uint(f); @@ -2920,7 +3154,7 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl ios_seek(f, LLT_ALIGN(ios_pos(f), 8)); assert(!ios_eof(f)); s.s = f; - uintptr_t offset_restored = 0, offset_init_order = 0, offset_extext_methods = 0, offset_new_specializations = 0, offset_method_roots_list = 0; + uintptr_t offset_restored = 0, offset_init_order = 0, offset_extext_methods = 0, offset_new_ext_cis = 0, offset_method_roots_list = 0; uintptr_t offset_ext_targets = 0, offset_edges = 0; if (!s.incremental) { size_t i; @@ -2933,7 +3167,8 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl JL_SMALL_TYPEOF(XX) #undef XX export_jl_small_typeof(); - jl_global_roots_table = (jl_array_t*)jl_read_value(&s); + jl_global_roots_list = (jl_genericmemory_t*)jl_read_value(&s); + jl_global_roots_keyset = (jl_genericmemory_t*)jl_read_value(&s); // set typeof extra-special values now that we have the type set by tags above jl_astaggedvalue(jl_nothing)->header = (uintptr_t)jl_nothing_type | jl_astaggedvalue(jl_nothing)->header; s.ptls->root_task->tls = jl_read_value(&s); @@ -2942,16 +3177,16 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl jl_init_box_caches(); uint32_t gs_ctr = read_uint32(f); - jl_atomic_store_release(&jl_world_counter, read_uint(f)); + jl_require_world = read_uint(f); + jl_atomic_store_release(&jl_world_counter, jl_require_world); jl_typeinf_world = read_uint(f); jl_set_gs_ctr(gs_ctr); } else { - jl_atomic_fetch_add(&jl_world_counter, 1); offset_restored = jl_read_offset(&s); offset_init_order = jl_read_offset(&s); offset_extext_methods = jl_read_offset(&s); - offset_new_specializations = jl_read_offset(&s); + offset_new_ext_cis = jl_read_offset(&s); offset_method_roots_list = jl_read_offset(&s); offset_ext_targets = jl_read_offset(&s); offset_edges = jl_read_offset(&s); @@ -2960,36 +3195,35 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl size_t nlinks_gctags = read_uint32(f); if (nlinks_gctags > 0) { s.link_ids_gctags = jl_alloc_array_1d(jl_array_int32_type, nlinks_gctags); - ios_read(f, (char*)jl_array_data(s.link_ids_gctags), nlinks_gctags * sizeof(uint32_t)); + ios_read(f, (char*)jl_array_data(s.link_ids_gctags, uint32_t), nlinks_gctags * sizeof(uint32_t)); } size_t nlinks_relocs = read_uint32(f); if (nlinks_relocs > 0) { s.link_ids_relocs = jl_alloc_array_1d(jl_array_int32_type, nlinks_relocs); - ios_read(f, (char*)jl_array_data(s.link_ids_relocs), nlinks_relocs * sizeof(uint32_t)); + ios_read(f, (char*)jl_array_data(s.link_ids_relocs, uint32_t), nlinks_relocs * sizeof(uint32_t)); } size_t nlinks_gvars = read_uint32(f); if (nlinks_gvars > 0) { s.link_ids_gvars = jl_alloc_array_1d(jl_array_int32_type, nlinks_gvars); - ios_read(f, (char*)jl_array_data(s.link_ids_gvars), nlinks_gvars * sizeof(uint32_t)); + ios_read(f, (char*)jl_array_data(s.link_ids_gvars, uint32_t), nlinks_gvars * sizeof(uint32_t)); } size_t nlinks_external_fnvars = read_uint32(f); if (nlinks_external_fnvars > 0) { s.link_ids_external_fnvars = jl_alloc_array_1d(jl_array_int32_type, nlinks_external_fnvars); - ios_read(f, (char*)jl_array_data(s.link_ids_external_fnvars), nlinks_external_fnvars * sizeof(uint32_t)); + ios_read(f, (char*)jl_array_data(s.link_ids_external_fnvars, uint32_t), nlinks_external_fnvars * sizeof(uint32_t)); } uint32_t external_fns_begin = read_uint32(f); jl_read_arraylist(s.s, ccallable_list ? ccallable_list : &s.ccallable_list); if (s.incremental) { - assert(restored && init_order && extext_methods && new_specializations && method_roots_list && ext_targets && edges); + assert(restored && init_order && extext_methods && internal_methods && new_ext_cis && method_roots_list && ext_targets && edges); *restored = (jl_array_t*)jl_delayed_reloc(&s, offset_restored); *init_order = (jl_array_t*)jl_delayed_reloc(&s, offset_init_order); *extext_methods = (jl_array_t*)jl_delayed_reloc(&s, offset_extext_methods); - *new_specializations = (jl_array_t*)jl_delayed_reloc(&s, offset_new_specializations); + *new_ext_cis = (jl_array_t*)jl_delayed_reloc(&s, offset_new_ext_cis); *method_roots_list = (jl_array_t*)jl_delayed_reloc(&s, offset_method_roots_list); *ext_targets = (jl_array_t*)jl_delayed_reloc(&s, offset_ext_targets); *edges = (jl_array_t*)jl_delayed_reloc(&s, offset_edges); - if (!*new_specializations) - *new_specializations = jl_alloc_vec_any(0); + *internal_methods = jl_alloc_vec_any(0); } s.s = NULL; @@ -3008,6 +3242,8 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl size_t sizeof_tags = ios_pos(&relocs); (void)sizeof_tags; jl_read_reloclist(&s, s.link_ids_relocs, 0); // general relocs + jl_read_memreflist(&s); // memowner_list relocs (must come before memref_list reads the pointers and after general relocs computes the pointers) + jl_read_memreflist(&s); // memref_list relocs // s.link_ids_gvars will be processed in `jl_update_all_gvars` // s.link_ids_external_fns will be processed in `jl_update_all_gvars` jl_update_all_gvars(&s, image, external_fns_begin); // gvars relocs @@ -3040,31 +3276,43 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl uintptr_t item = (uintptr_t)s.uniquing_types.items[i]; // check whether we are operating on the typetag // (needing to ignore GC bits) or a regular field - int tag = (item & 1) == 1; - // check whether this is a gvar index - int gvar = (item & 2) == 2; + // and check whether this is a gvar index + int tag = (item & 3); item &= ~(uintptr_t)3; uintptr_t *pfld; jl_value_t **obj, *newobj; - if (gvar) { + if (tag == 3) { + obj = (jl_value_t**)(image_base + item); + pfld = NULL; + for (size_t i = 0; i < delay_list.len; i += 2) { + if (obj == (jl_value_t **)delay_list.items[i + 0]) { + pfld = (uintptr_t*)delay_list.items[i + 1]; + delay_list.items[i + 1] = arraylist_pop(&delay_list); + delay_list.items[i + 0] = arraylist_pop(&delay_list); + break; + } + } + assert(pfld); + } + else if (tag == 2) { if (image->gvars_base == NULL) continue; item >>= 2; assert(item < s.gvar_record->size / sizeof(reloc_t)); pfld = sysimg_gvars(image->gvars_base, image->gvars_offsets, item); obj = *(jl_value_t***)pfld; - assert(tag == 0); } else { pfld = (uintptr_t*)(image_base + item); - if (tag) + if (tag == 1) obj = (jl_value_t**)jl_typeof(jl_valueof(pfld)); else obj = *(jl_value_t***)pfld; if ((char*)obj > (char*)pfld) { + // this must be the super field assert(tag == 0); - arraylist_push(&delay_list, pfld); arraylist_push(&delay_list, obj); + arraylist_push(&delay_list, pfld); ptrhash_put(&new_dt_objs, (void*)obj, obj); // mark obj as invalid *pfld = (uintptr_t)NULL; continue; @@ -3093,8 +3341,9 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl // leave most fields undefined for now, but we may need instance later, // and we overwrite the name field (field 0) now so preserve it too if (dt->instance) { - assert(dt->instance == jl_nothing); - newdt->instance = dt->instance = jl_gc_permobj(0, newdt); + if (dt->instance == jl_nothing) + dt->instance = jl_gc_permobj(0, newdt); + newdt->instance = dt->instance; } static_assert(offsetof(jl_datatype_t, name) == 0, ""); newdt->name = dt->name; @@ -3109,30 +3358,18 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl } else { assert(!(image_base < (char*)otyp && (char*)otyp <= image_base + sizeof_sysimg)); - assert(jl_is_datatype_singleton((jl_datatype_t*)otyp) && "unreachable"); newobj = ((jl_datatype_t*)otyp)->instance; - assert(newobj != jl_nothing); + assert(newobj && newobj != jl_nothing); arraylist_push(&cleanup_list, (void*)obj); } - if (tag) + if (tag == 1) *pfld = (uintptr_t)newobj | GC_OLD | GC_IN_IMAGE; else *pfld = (uintptr_t)newobj; assert(!(image_base < (char*)newobj && (char*)newobj <= image_base + sizeof_sysimg)); assert(jl_typetagis(obj, otyp)); } - // A few fields (reached via super) might be self-recursive. This is rare, but handle them now. - // They cannot be instances though, since the type must fully exist before the singleton field can be allocated - for (size_t i = 0; i < delay_list.len; ) { - uintptr_t *pfld = (uintptr_t*)delay_list.items[i++]; - jl_value_t **obj = (jl_value_t **)delay_list.items[i++]; - assert(jl_is_datatype(obj)); - jl_datatype_t *dt = (jl_datatype_t*)obj[0]; - assert(jl_is_datatype(dt)); - jl_value_t *newobj = (jl_value_t*)dt; - *pfld = (uintptr_t)newobj; - assert(!(image_base < (char*)newobj && (char*)newobj <= image_base + sizeof_sysimg)); - } + assert(delay_list.len == 0); arraylist_free(&delay_list); // now that all the fields of dt are assigned and unique, copy them into // their final newdt memory location: this ensures we do not accidentally @@ -3176,15 +3413,15 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl jl_cache_type_((jl_datatype_t*)obj); } // Perform fixups: things like updating world ages, inserting methods & specializations, etc. - size_t world = jl_atomic_load_acquire(&jl_world_counter); for (size_t i = 0; i < s.uniquing_objs.len; i++) { uintptr_t item = (uintptr_t)s.uniquing_objs.items[i]; // check whether this is a gvar index - int gvar = (item & 2) == 2; + int tag = (item & 3); + assert(tag == 0 || tag == 2); item &= ~(uintptr_t)3; uintptr_t *pfld; jl_value_t **obj, *newobj; - if (gvar) { + if (tag == 2) { if (image->gvars_base == NULL) continue; item >>= 2; @@ -3232,36 +3469,29 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl for (size_t i = 0; i < s.fixup_objs.len; i++) { uintptr_t item = (uintptr_t)s.fixup_objs.items[i]; jl_value_t *obj = (jl_value_t*)(image_base + item); - if (jl_typetagis(obj, jl_typemap_entry_type)) { - jl_typemap_entry_t *entry = (jl_typemap_entry_t*)obj; - entry->min_world = world; - } - else if (jl_is_method(obj)) { - jl_method_t *m = (jl_method_t*)obj; - m->primary_world = world; + if (jl_typetagis(obj, jl_typemap_entry_type) || jl_is_method(obj) || jl_is_code_instance(obj)) { + jl_array_ptr_1d_push(*internal_methods, obj); + assert(s.incremental); } else if (jl_is_method_instance(obj)) { jl_method_instance_t *newobj = jl_specializations_get_or_insert((jl_method_instance_t*)obj); assert(newobj == (jl_method_instance_t*)obj); // strict insertion expected (void)newobj; } - else if (jl_is_code_instance(obj)) { - jl_code_instance_t *ci = (jl_code_instance_t*)obj; - assert(s.incremental); - ci->min_world = world; - if (ci->max_world != 0) - jl_array_ptr_1d_push(*new_specializations, (jl_value_t*)ci); - } else if (jl_is_globalref(obj)) { - continue; // wait until all the module binding tables have been initialized + jl_globalref_t *r = (jl_globalref_t*)obj; + if (r->binding == NULL) { + jl_globalref_t *gr = (jl_globalref_t*)jl_module_globalref(r->mod, r->name); + r->binding = gr->binding; + jl_gc_wb(r, gr->binding); + } } else if (jl_is_module(obj)) { - // rebuild the binding table for module v + // rebuild the usings table for module v // TODO: maybe want to hold the lock on `v`, but that only strongly matters for async / thread safety // and we are already bad at that jl_module_t *mod = (jl_module_t*)obj; mod->build_id.hi = checksum; - mod->primary_world = world; if (mod->usings.items != &mod->usings._space[0]) { // arraylist_t assumes we called malloc to get this memory, so make that true now void **newitems = (void**)malloc_s(mod->usings.max * sizeof(void*)); @@ -3270,25 +3500,7 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl } } else { - // rehash IdDict - //assert(((jl_datatype_t*)(jl_typeof(obj)))->name == jl_idtable_typename); - jl_array_t **a = (jl_array_t**)obj; - assert(jl_typetagis(*a, jl_array_any_type)); - *a = jl_idtable_rehash(*a, jl_array_len(*a)); - jl_gc_wb(obj, *a); - } - } - // Now pick up the globalref binding pointer field - for (size_t i = 0; i < s.fixup_objs.len; i++) { - uintptr_t item = (uintptr_t)s.fixup_objs.items[i]; - jl_value_t *obj = (jl_value_t*)(image_base + item); - if (jl_is_globalref(obj)) { - jl_globalref_t *r = (jl_globalref_t*)obj; - if (r->binding == NULL) { - jl_globalref_t *gr = (jl_globalref_t*)jl_module_globalref(r->mod, r->name); - r->binding = gr->binding; - jl_gc_wb(r, gr->binding); - } + abort(); } } arraylist_free(&s.fixup_types); @@ -3352,11 +3564,23 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl arraylist_push(&jl_linkage_blobs, (void*)image_base); arraylist_push(&jl_linkage_blobs, (void*)(image_base + sizeof_sysimg)); arraylist_push(&jl_image_relocs, (void*)relocs_base); + if (restored == NULL) { + arraylist_push(&jl_top_mods, (void*)jl_top_module); + } else { + size_t len = jl_array_nrows(*restored); + assert(len > 0); + jl_module_t *topmod = (jl_module_t*)jl_array_ptr_ref(*restored, len-1); + assert(jl_is_module(topmod)); + arraylist_push(&jl_top_mods, (void*)topmod); + } jl_timing_counter_inc(JL_TIMING_COUNTER_ImageSize, sizeof_sysimg + sizeof(uintptr_t)); rebuild_image_blob_tree(); // jl_printf(JL_STDOUT, "%ld blobs to link against\n", jl_linkage_blobs.len >> 1); jl_gc_enable(en); + + if (s.incremental) + jl_add_methods(*extext_methods); } static jl_value_t *jl_validate_cache_file(ios_t *f, jl_array_t *depmods, uint64_t *checksum, int64_t *dataendpos, int64_t *datastartpos) @@ -3367,7 +3591,7 @@ static jl_value_t *jl_validate_cache_file(ios_t *f, jl_array_t *depmods, uint64_ "Precompile file header verification checks failed."); } uint8_t flags = read_uint8(f); - if (pkgimage && !jl_match_cache_flags(flags)) { + if (pkgimage && !jl_match_cache_flags_current(flags)) { return jl_get_exceptionf(jl_errorexception_type, "Pkgimage flags mismatch"); } if (!pkgimage) { @@ -3386,7 +3610,7 @@ static jl_value_t *jl_validate_cache_file(ios_t *f, jl_array_t *depmods, uint64_ } // TODO?: refactor to make it easier to create the "package inspector" -static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, ios_t *f, jl_image_t *image, jl_array_t *depmods, int completeinfo, const char *pkgname, bool needs_permalloc) +static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, ios_t *f, jl_image_t *image, jl_array_t *depmods, int completeinfo, const char *pkgname, int needs_permalloc) { JL_TIMING(LOAD_IMAGE, LOAD_Pkgimg); jl_timing_printf(JL_TIMING_DEFAULT_BLOCK, pkgname); @@ -3400,19 +3624,20 @@ static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, i assert(datastartpos > 0 && datastartpos < dataendpos); needs_permalloc = jl_options.permalloc_pkgimg || needs_permalloc; - jl_value_t *restored = NULL; - jl_array_t *init_order = NULL, *extext_methods = NULL, *new_specializations = NULL, *method_roots_list = NULL, *ext_targets = NULL, *edges = NULL; - jl_svec_t *cachesizes_sv = NULL; char *base; arraylist_t ccallable_list; - JL_GC_PUSH8(&restored, &init_order, &extext_methods, &new_specializations, &method_roots_list, &ext_targets, &edges, &cachesizes_sv); + + jl_value_t *restored = NULL; + jl_array_t *init_order = NULL, *extext_methods = NULL, *internal_methods = NULL, *new_ext_cis = NULL, *method_roots_list = NULL, *ext_targets = NULL, *edges = NULL; + jl_svec_t *cachesizes_sv = NULL; + JL_GC_PUSH9(&restored, &init_order, &extext_methods, &internal_methods, &new_ext_cis, &method_roots_list, &ext_targets, &edges, &cachesizes_sv); { // make a permanent in-memory copy of f (excluding the header) ios_bufmode(f, bm_none); JL_SIGATOMIC_BEGIN(); size_t len = dataendpos - datastartpos; char *sysimg; - bool success = !needs_permalloc; + int success = !needs_permalloc; ios_seek(f, datastartpos); if (needs_permalloc) sysimg = (char*)jl_gc_perm_alloc(len, 0, 64, 0); @@ -3429,17 +3654,24 @@ static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, i ios_close(f); ios_static_buffer(f, sysimg, len); pkgcachesizes cachesizes; - jl_restore_system_image_from_stream_(f, image, depmods, checksum, (jl_array_t**)&restored, &init_order, &extext_methods, &new_specializations, &method_roots_list, &ext_targets, &edges, &base, &ccallable_list, &cachesizes); + jl_restore_system_image_from_stream_(f, image, depmods, checksum, (jl_array_t**)&restored, &init_order, &extext_methods, &internal_methods, &new_ext_cis, &method_roots_list, + &ext_targets, &edges, &base, &ccallable_list, &cachesizes); JL_SIGATOMIC_END(); - // Insert method extensions - jl_insert_methods(extext_methods); - // No special processing of `new_specializations` is required because recaching handled it + // No special processing of `new_ext_cis` is required because recaching handled it // Add roots to methods jl_copy_roots(method_roots_list, jl_worklist_key((jl_array_t*)restored)); - // Handle edges - size_t world = jl_atomic_load_acquire(&jl_world_counter); - jl_insert_backedges((jl_array_t*)edges, (jl_array_t*)ext_targets, (jl_array_t*)new_specializations, world); // restore external backedges (needs to be last) + // Insert method extensions and handle edges + JL_LOCK(&world_counter_lock); + // allocate a world for the new methods, and insert them there, invalidating content as needed + size_t world = jl_atomic_load_relaxed(&jl_world_counter) + 1; + jl_activate_methods(extext_methods, internal_methods, world); + // allow users to start running in this updated world + jl_atomic_store_release(&jl_world_counter, world); + // but one of those immediate users is going to be our cache updates + jl_insert_backedges((jl_array_t*)edges, (jl_array_t*)ext_targets, (jl_array_t*)new_ext_cis, world); // restore external backedges (needs to be last) + // now permit more methods to be added again + JL_UNLOCK(&world_counter_lock); // reinit ccallables jl_reinit_ccallable(&ccallable_list, base, pkgimage_handle); arraylist_free(&ccallable_list); @@ -3453,8 +3685,9 @@ static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, i jl_svecset(cachesizes_sv, 4, jl_box_long(cachesizes.reloclist)); jl_svecset(cachesizes_sv, 5, jl_box_long(cachesizes.gvarlist)); jl_svecset(cachesizes_sv, 6, jl_box_long(cachesizes.fptrlist)); - restored = (jl_value_t*)jl_svec(8, restored, init_order, extext_methods, new_specializations, method_roots_list, - ext_targets, edges, cachesizes_sv); + restored = (jl_value_t*)jl_svec(8, restored, init_order, extext_methods, + new_ext_cis ? (jl_value_t*)new_ext_cis : jl_nothing, + method_roots_list, ext_targets, edges, cachesizes_sv); } else { restored = (jl_value_t*)jl_svec(2, restored, init_order); @@ -3469,10 +3702,10 @@ static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, i static void jl_restore_system_image_from_stream(ios_t *f, jl_image_t *image, uint32_t checksum) { JL_TIMING(LOAD_IMAGE, LOAD_Sysimg); - jl_restore_system_image_from_stream_(f, image, NULL, checksum | ((uint64_t)0xfdfcfbfa << 32), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + jl_restore_system_image_from_stream_(f, image, NULL, checksum | ((uint64_t)0xfdfcfbfa << 32), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); } -JL_DLLEXPORT jl_value_t *jl_restore_incremental_from_buf(void* pkgimage_handle, const char *buf, jl_image_t *image, size_t sz, jl_array_t *depmods, int completeinfo, const char *pkgname, bool needs_permalloc) +JL_DLLEXPORT jl_value_t *jl_restore_incremental_from_buf(void* pkgimage_handle, const char *buf, jl_image_t *image, size_t sz, jl_array_t *depmods, int completeinfo, const char *pkgname, int needs_permalloc) { ios_t f; ios_static_buffer(&f, (char*)buf, sz); @@ -3489,7 +3722,7 @@ JL_DLLEXPORT jl_value_t *jl_restore_incremental(const char *fname, jl_array_t *d "Cache file \"%s\" not found.\n", fname); } jl_image_t pkgimage = {}; - jl_value_t *ret = jl_restore_package_image_from_stream(NULL, &f, &pkgimage, depmods, completeinfo, pkgname, true); + jl_value_t *ret = jl_restore_package_image_from_stream(NULL, &f, &pkgimage, depmods, completeinfo, pkgname, 1); ios_close(&f); return ret; } @@ -3539,7 +3772,7 @@ JL_DLLEXPORT void jl_restore_system_image_data(const char *buf, size_t len) JL_SIGATOMIC_END(); } -JL_DLLEXPORT jl_value_t *jl_restore_package_image_from_file(const char *fname, jl_array_t *depmods, int completeinfo, const char *pkgname) +JL_DLLEXPORT jl_value_t *jl_restore_package_image_from_file(const char *fname, jl_array_t *depmods, int completeinfo, const char *pkgname, int ignore_native) { void *pkgimg_handle = jl_dlopen(fname, JL_RTLD_LAZY); if (!pkgimg_handle) { @@ -3560,7 +3793,14 @@ JL_DLLEXPORT jl_value_t *jl_restore_package_image_from_file(const char *fname, j jl_image_t pkgimage = jl_init_processor_pkgimg(pkgimg_handle); - jl_value_t* mod = jl_restore_incremental_from_buf(pkgimg_handle, pkgimg_data, &pkgimage, *plen, depmods, completeinfo, pkgname, false); + if (ignore_native) { + // Must disable using native code in possible downstream users of this code: + // https://github.com/JuliaLang/julia/pull/52123#issuecomment-1959965395. + // The easiest way to do that is to disable it in all of them. + IMAGE_NATIVE_CODE_TAINTED = 1; + } + + jl_value_t* mod = jl_restore_incremental_from_buf(pkgimg_handle, pkgimg_data, &pkgimage, *plen, depmods, completeinfo, pkgname, 0); return mod; } diff --git a/src/staticdata_utils.c b/src/staticdata_utils.c index a4cbc3fd5ebc4..9c7a810b65ad9 100644 --- a/src/staticdata_utils.c +++ b/src/staticdata_utils.c @@ -1,5 +1,5 @@ // inverse of backedges graph (caller=>callees hash) -jl_array_t *edges_map JL_GLOBALLY_ROOTED = NULL; // rooted for the duration of our uses of this +jl_genericmemory_t *edges_map JL_GLOBALLY_ROOTED = NULL; // rooted for the duration of our uses of this static void write_float64(ios_t *s, double x) JL_NOTSAFEPOINT { @@ -45,16 +45,15 @@ int must_be_new_dt(jl_value_t *t, htable_t *news, char *image_base, size_t sizeo jl_datatype_t *dt = (jl_datatype_t*)t; assert(jl_object_in_image((jl_value_t*)dt->name) && "type_in_worklist mistake?"); jl_datatype_t *super = dt->super; - // check if super is news, since then we must be new also - // (it is also possible that super is indeterminate now, wait for `t` - // to be resolved, then will be determined later and fixed up by the - // delay_list, for this and any other references to it). - while (super != jl_any_type) { - assert(super); + // fast-path: check if super is in news, since then we must be new also + // (it is also possible that super is indeterminate or NULL right now, + // waiting for `t` to be resolved, then will be determined later as + // soon as possible afterwards). + while (super != NULL && super != jl_any_type) { if (ptrhash_has(news, (void*)super)) return 1; if (!(image_base < (char*)super && (char*)super <= image_base + sizeof_sysimg)) - break; // fast-path for rejection of super + break; // the rest must all be non-new // otherwise super might be something that was not cached even though a later supertype might be // for example while handling `Type{Mask{4, U} where U}`, if we have `Mask{4, U} <: AbstractSIMDVector{4}` super = super->super; @@ -74,7 +73,7 @@ int must_be_new_dt(jl_value_t *t, htable_t *news, char *image_base, size_t sizeo static uint64_t jl_worklist_key(jl_array_t *worklist) JL_NOTSAFEPOINT { assert(jl_is_array(worklist)); - size_t len = jl_array_len(worklist); + size_t len = jl_array_nrows(worklist); if (len > 0) { jl_module_t *topmod = (jl_module_t*)jl_array_ptr_ref(worklist, len-1); assert(jl_is_module(topmod)); @@ -86,6 +85,7 @@ static uint64_t jl_worklist_key(jl_array_t *worklist) JL_NOTSAFEPOINT static jl_array_t *newly_inferred JL_GLOBALLY_ROOTED /*FIXME*/; // Mutex for newly_inferred jl_mutex_t newly_inferred_mutex; +extern jl_mutex_t world_counter_lock; // Register array of newly-inferred MethodInstances // This gets called as the first step of Base.include_package_for_output @@ -98,9 +98,9 @@ JL_DLLEXPORT void jl_set_newly_inferred(jl_value_t* _newly_inferred) JL_DLLEXPORT void jl_push_newly_inferred(jl_value_t* ci) { JL_LOCK(&newly_inferred_mutex); - size_t end = jl_array_len(newly_inferred); + size_t end = jl_array_nrows(newly_inferred); jl_array_grow_end(newly_inferred, 1); - jl_arrayset(newly_inferred, ci, end); + jl_array_ptr_set(newly_inferred, end, ci); JL_UNLOCK(&newly_inferred_mutex); } @@ -159,7 +159,7 @@ static int has_backedge_to_worklist(jl_method_instance_t *mi, htable_t *visited, if (jl_is_method(mod)) mod = ((jl_method_t*)mod)->module; assert(jl_is_module(mod)); - if (mi->precompiled || !jl_object_in_image((jl_value_t*)mod) || type_in_worklist(mi->specTypes)) { + if (jl_atomic_load_relaxed(&mi->precompiled) || !jl_object_in_image((jl_value_t*)mod) || type_in_worklist(mi->specTypes)) { return 1; } if (!mi->backedges) { @@ -169,7 +169,7 @@ static int has_backedge_to_worklist(jl_method_instance_t *mi, htable_t *visited, // HT_NOTFOUND: not yet analyzed // HT_NOTFOUND + 1: no link back // HT_NOTFOUND + 2: does link back - // HT_NOTFOUND + 3: does link back, and included in new_specializations already + // HT_NOTFOUND + 3: does link back, and included in new_ext_cis already // HT_NOTFOUND + 4 + depth: in-progress int found = (char*)*bp - (char*)HT_NOTFOUND; if (found) @@ -177,7 +177,7 @@ static int has_backedge_to_worklist(jl_method_instance_t *mi, htable_t *visited, arraylist_push(stack, (void*)mi); int depth = stack->len; *bp = (void*)((char*)HT_NOTFOUND + 4 + depth); // preliminarily mark as in-progress - size_t i = 0, n = jl_array_len(mi->backedges); + size_t i = 0, n = jl_array_nrows(mi->backedges); int cycle = depth; while (i < n) { jl_method_instance_t *be; @@ -222,11 +222,11 @@ static jl_array_t *queue_external_cis(jl_array_t *list) htable_t visited; arraylist_t stack; assert(jl_is_array(list)); - size_t n0 = jl_array_len(list); + size_t n0 = jl_array_nrows(list); htable_new(&visited, n0); arraylist_new(&stack, 0); - jl_array_t *new_specializations = jl_alloc_vec_any(0); - JL_GC_PUSH1(&new_specializations); + jl_array_t *new_ext_cis = jl_alloc_vec_any(0); + JL_GC_PUSH1(&new_ext_cis); for (i = n0; i-- > 0; ) { jl_code_instance_t *ci = (jl_code_instance_t*)jl_array_ptr_ref(list, i); assert(jl_is_code_instance(ci)); @@ -234,41 +234,37 @@ static jl_array_t *queue_external_cis(jl_array_t *list) continue; jl_method_instance_t *mi = ci->def; jl_method_t *m = mi->def.method; - if (ci->inferred && jl_is_method(m) && jl_object_in_image((jl_value_t*)m->module)) { + if (jl_atomic_load_relaxed(&ci->inferred) && jl_is_method(m) && jl_object_in_image((jl_value_t*)m->module)) { int found = has_backedge_to_worklist(mi, &visited, &stack); assert(found == 0 || found == 1 || found == 2); assert(stack.len == 0); - if (found == 1 && ci->max_world == ~(size_t)0) { - void **bp = ptrhash_bp(&visited, mi); - if (*bp != (void*)((char*)HT_NOTFOUND + 3)) { - *bp = (void*)((char*)HT_NOTFOUND + 3); - jl_array_ptr_1d_push(new_specializations, (jl_value_t*)ci); - } + if (found == 1 && jl_atomic_load_relaxed(&ci->max_world) == ~(size_t)0) { + jl_array_ptr_1d_push(new_ext_cis, (jl_value_t*)ci); } } } htable_free(&visited); arraylist_free(&stack); JL_GC_POP(); - // reverse new_specializations - n0 = jl_array_len(new_specializations); - jl_value_t **news = (jl_value_t**)jl_array_data(new_specializations); + // reverse new_ext_cis + n0 = jl_array_nrows(new_ext_cis); + jl_value_t **news = jl_array_data(new_ext_cis, jl_value_t*); for (i = 0; i < n0; i++) { jl_value_t *temp = news[i]; news[i] = news[n0 - i - 1]; news[n0 - i - 1] = temp; } - return new_specializations; + return new_ext_cis; } // New roots for external methods -static void jl_collect_new_roots(jl_array_t *roots, jl_array_t *new_specializations, uint64_t key) +static void jl_collect_new_roots(jl_array_t *roots, jl_array_t *new_ext_cis, uint64_t key) { htable_t mset; htable_new(&mset, 0); - size_t l = new_specializations ? jl_array_len(new_specializations) : 0; + size_t l = new_ext_cis ? jl_array_nrows(new_ext_cis) : 0; for (size_t i = 0; i < l; i++) { - jl_code_instance_t *ci = (jl_code_instance_t*)jl_array_ptr_ref(new_specializations, i); + jl_code_instance_t *ci = (jl_code_instance_t*)jl_array_ptr_ref(new_ext_cis, i); assert(jl_is_code_instance(ci)); jl_method_t *m = ci->def->def.method; assert(jl_is_method(m)); @@ -289,10 +285,10 @@ static void jl_collect_new_roots(jl_array_t *roots, jl_array_t *new_specializati jl_array_ptr_1d_push(roots, (jl_value_t*)newroots); rle_iter_state rootiter = rle_iter_init(0); uint64_t *rletable = NULL; - size_t nblocks2 = 0, nroots = jl_array_len(m->roots), k = 0; + size_t nblocks2 = 0, nroots = jl_array_nrows(m->roots), k = 0; if (m->root_blocks) { - rletable = (uint64_t*)jl_array_data(m->root_blocks); - nblocks2 = jl_array_len(m->root_blocks); + rletable = jl_array_data(m->root_blocks, uint64_t); + nblocks2 = jl_array_nrows(m->root_blocks); } while (rle_iter_increment(&rootiter, nroots, rletable, nblocks2)) if (rootiter.key == key) @@ -314,7 +310,7 @@ static void jl_collect_missing_backedges(jl_methtable_t *mt) { jl_array_t *backedges = mt->backedges; if (backedges) { - size_t i, l = jl_array_len(backedges); + size_t i, l = jl_array_nrows(backedges); for (i = 1; i < l; i += 2) { jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(backedges, i); jl_value_t *missing_callee = jl_array_ptr_ref(backedges, i - 1); // signature of abstract callee @@ -337,7 +333,7 @@ static void collect_backedges(jl_method_instance_t *callee, int internal) { jl_array_t *backedges = callee->backedges; if (backedges) { - size_t i = 0, l = jl_array_len(backedges); + size_t i = 0, l = jl_array_nrows(backedges); while (i < l) { jl_value_t *invokeTypes; jl_method_instance_t *caller; @@ -411,7 +407,7 @@ static void jl_record_edges(jl_method_instance_t *caller, arraylist_t *wq, jl_ar if (callees != NULL) { jl_array_ptr_1d_push(edges, (jl_value_t*)caller); jl_array_ptr_1d_push(edges, (jl_value_t*)callees); - size_t i, l = jl_array_len(callees); + size_t i, l = jl_array_nrows(callees); for (i = 1; i < l; i += 2) { jl_method_instance_t *c = (jl_method_instance_t*)jl_array_ptr_ref(callees, i); if (c && jl_is_method_instance(c)) { @@ -431,7 +427,7 @@ static void jl_collect_edges(jl_array_t *edges, jl_array_t *ext_targets, jl_arra htable_t external_mis; htable_new(&external_mis, 0); if (external_cis) { - for (size_t i = 0; i < jl_array_len(external_cis); i++) { + for (size_t i = 0; i < jl_array_nrows(external_cis); i++) { jl_code_instance_t *ci = (jl_code_instance_t*)jl_array_ptr_ref(external_cis, i); jl_method_instance_t *mi = ci->def; ptrhash_put(&external_mis, (void*)mi, (void*)mi); @@ -439,10 +435,10 @@ static void jl_collect_edges(jl_array_t *edges, jl_array_t *ext_targets, jl_arra } arraylist_t wq; arraylist_new(&wq, 0); - void **table = (void**)jl_array_data(edges_map); // edges_map is caller => callees - size_t table_size = jl_array_len(edges_map); + void **table = (void**) edges_map->ptr; // edges_map is caller => callees + size_t table_size = edges_map->length; for (size_t i = 0; i < table_size; i += 2) { - assert(table == jl_array_data(edges_map) && table_size == jl_array_len(edges_map) && + assert(table == edges_map->ptr && table_size == edges_map->length && "edges_map changed during iteration"); jl_method_instance_t *caller = (jl_method_instance_t*)table[i]; jl_array_t *callees = (jl_array_t*)table[i + 1]; @@ -464,7 +460,7 @@ static void jl_collect_edges(jl_array_t *edges, jl_array_t *ext_targets, jl_arra htable_t edges_map2; htable_new(&edges_map2, 0); htable_t edges_ids; - size_t l = edges ? jl_array_len(edges) : 0; + size_t l = edges ? jl_array_nrows(edges) : 0; htable_new(&edges_ids, l); for (size_t i = 0; i < l / 2; i++) { jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, i * 2); @@ -479,9 +475,9 @@ static void jl_collect_edges(jl_array_t *edges, jl_array_t *ext_targets, jl_arra JL_GC_PUSH3(&matches, &callee_ids, &sig); for (size_t i = 0; i < l; i += 2) { jl_array_t *callees = (jl_array_t*)jl_array_ptr_ref(edges, i + 1); - size_t l = jl_array_len(callees); + size_t l = jl_array_nrows(callees); callee_ids = jl_alloc_array_1d(jl_array_int32_type, l + 1); - int32_t *idxs = (int32_t*)jl_array_data(callee_ids); + int32_t *idxs = jl_array_data(callee_ids, int32_t); idxs[0] = 0; size_t nt = 0; for (size_t j = 0; j < l; j += 2) { @@ -534,7 +530,7 @@ static void jl_collect_edges(jl_array_t *edges, jl_array_t *ext_targets, jl_arra break; } size_t k; - for (k = 0; k < jl_array_len(matches); k++) { + for (k = 0; k < jl_array_nrows(matches); k++) { jl_method_match_t *match = (jl_method_match_t *)jl_array_ptr_ref(matches, k); jl_array_ptr_set(matches, k, match->method); } @@ -542,7 +538,7 @@ static void jl_collect_edges(jl_array_t *edges, jl_array_t *ext_targets, jl_arra jl_array_ptr_1d_push(ext_targets, invokeTypes); jl_array_ptr_1d_push(ext_targets, callee); jl_array_ptr_1d_push(ext_targets, matches); - target = (void*)((char*)HT_NOTFOUND + jl_array_len(ext_targets) / 3); + target = (void*)((char*)HT_NOTFOUND + jl_array_nrows(ext_targets) / 3); ptrhash_put(&edges_map2, (void*)callee, target); } idxs[++nt] = (char*)target - (char*)HT_NOTFOUND - 1; @@ -574,7 +570,7 @@ static void jl_collect_edges(jl_array_t *edges, jl_array_t *ext_targets, jl_arra static void write_mod_list(ios_t *s, jl_array_t *a) { size_t i; - size_t len = jl_array_len(a); + size_t len = jl_array_nrows(a); for (i = 0; i < len; i++) { jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(a, i); assert(jl_is_module(m)); @@ -607,28 +603,63 @@ JL_DLLEXPORT uint8_t jl_cache_flags(void) return flags; } -JL_DLLEXPORT uint8_t jl_match_cache_flags(uint8_t flags) + +JL_DLLEXPORT uint8_t jl_match_cache_flags(uint8_t requested_flags, uint8_t actual_flags) { - // 1. Check which flags are relevant - uint8_t current_flags = jl_cache_flags(); - uint8_t supports_pkgimage = (current_flags & 1); - uint8_t is_pkgimage = (flags & 1); + uint8_t supports_pkgimage = (requested_flags & 1); + uint8_t is_pkgimage = (actual_flags & 1); // For .ji packages ignore other flags if (!supports_pkgimage && !is_pkgimage) { return 1; } - // 2. Check all flags, execept opt level must be exact + // If package images are optional, ignore that bit (it will be unset in requested_flags) + if (jl_options.use_pkgimages == JL_OPTIONS_USE_PKGIMAGES_EXISTING) { + actual_flags &= ~1; + } + + // 2. Check all flags, except opt level must be exact uint8_t mask = (1 << OPT_LEVEL)-1; - if ((flags & mask) != (current_flags & mask)) + if ((actual_flags & mask) != (requested_flags & mask)) return 0; // 3. allow for higher optimization flags in cache - flags >>= OPT_LEVEL; - current_flags >>= OPT_LEVEL; - return flags >= current_flags; + actual_flags >>= OPT_LEVEL; + requested_flags >>= OPT_LEVEL; + return actual_flags >= requested_flags; +} + +JL_DLLEXPORT uint8_t jl_match_cache_flags_current(uint8_t flags) +{ + return jl_match_cache_flags(jl_cache_flags(), flags); } +// return char* from String field in Base.GIT_VERSION_INFO +static const char *git_info_string(const char *fld) +{ + static jl_value_t *GIT_VERSION_INFO = NULL; + if (!GIT_VERSION_INFO) + GIT_VERSION_INFO = jl_get_global(jl_base_module, jl_symbol("GIT_VERSION_INFO")); + jl_value_t *f = jl_get_field(GIT_VERSION_INFO, fld); + assert(jl_is_string(f)); + return jl_string_data(f); +} + +static const char *jl_git_branch(void) +{ + static const char *branch = NULL; + if (!branch) branch = git_info_string("branch"); + return branch; +} + +static const char *jl_git_commit(void) +{ + static const char *commit = NULL; + if (!commit) commit = git_info_string("commit"); + return commit; +} + + // "magic" string and version header of .ji file static const int JI_FORMAT_VERSION = 12; static const char JI_MAGIC[] = "\373jli\r\n\032\n"; // based on PNG signature @@ -656,7 +687,7 @@ static int64_t write_header(ios_t *s, uint8_t pkgimage) // serialize information about the result of deserializing this file static void write_worklist_for_header(ios_t *s, jl_array_t *worklist) { - int i, l = jl_array_len(worklist); + int i, l = jl_array_nrows(worklist); for (i = 0; i < l; i++) { jl_module_t *workmod = (jl_module_t*)jl_array_ptr_ref(worklist, i); if (workmod->parent == jl_main_module || workmod->parent == workmod) { @@ -706,24 +737,44 @@ static int64_t write_dependency_list(ios_t *s, jl_array_t* worklist, jl_array_t jl_array_t *udeps = (*udepsp = deps && unique_func ? (jl_array_t*)jl_apply(uniqargs, 2) : NULL); ct->world_age = last_age; + static jl_value_t *replace_depot_func = NULL; + if (!replace_depot_func) + replace_depot_func = jl_get_global(jl_base_module, jl_symbol("replace_depot_path")); + // write a placeholder for total size so that we can quickly seek past all of the // dependencies if we don't need them initial_pos = ios_pos(s); write_uint64(s, 0); - size_t i, l = udeps ? jl_array_len(udeps) : 0; + size_t i, l = udeps ? jl_array_nrows(udeps) : 0; for (i = 0; i < l; i++) { jl_value_t *deptuple = jl_array_ptr_ref(udeps, i); - jl_value_t *dep = jl_fieldref(deptuple, 1); // file abspath - size_t slen = jl_string_len(dep); + jl_value_t *deppath = jl_fieldref(deptuple, 1); + + if (replace_depot_func) { + jl_value_t **replace_depot_args; + JL_GC_PUSHARGS(replace_depot_args, 2); + replace_depot_args[0] = replace_depot_func; + replace_depot_args[1] = deppath; + ct = jl_current_task; + size_t last_age = ct->world_age; + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); + deppath = (jl_value_t*)jl_apply(replace_depot_args, 2); + ct->world_age = last_age; + JL_GC_POP(); + } + + size_t slen = jl_string_len(deppath); write_int32(s, slen); - ios_write(s, jl_string_data(dep), slen); - write_float64(s, jl_unbox_float64(jl_fieldref(deptuple, 2))); // mtime + ios_write(s, jl_string_data(deppath), slen); + write_uint64(s, jl_unbox_uint64(jl_fieldref(deptuple, 2))); // fsize + write_uint32(s, jl_unbox_uint32(jl_fieldref(deptuple, 3))); // hash + write_float64(s, jl_unbox_float64(jl_fieldref(deptuple, 4))); // mtime jl_module_t *depmod = (jl_module_t*)jl_fieldref(deptuple, 0); // evaluating module jl_module_t *depmod_top = depmod; while (depmod_top->parent != jl_main_module && depmod_top->parent != depmod_top) depmod_top = depmod_top->parent; unsigned provides = 0; - size_t j, lj = jl_array_len(worklist); + size_t j, lj = jl_array_nrows(worklist); for (j = 0; j < lj; j++) { jl_module_t *workmod = (jl_module_t*)jl_array_ptr_ref(worklist, j); if (workmod->parent == jl_main_module || workmod->parent == workmod) { @@ -770,7 +821,7 @@ static int64_t write_dependency_list(ios_t *s, jl_array_t* worklist, jl_array_t // If we successfully got the preferences, write it out, otherwise write `0` for this `.ji` file. if (prefs_hash != NULL && prefs_list != NULL) { - size_t i, l = jl_array_len(prefs_list); + size_t i, l = jl_array_nrows(prefs_list); for (i = 0; i < l; i++) { jl_value_t *pref_name = jl_array_ptr_ref(prefs_list, i); size_t slen = jl_string_len(pref_name); @@ -804,22 +855,63 @@ static int64_t write_dependency_list(ios_t *s, jl_array_t* worklist, jl_array_t // Deserialization // Add methods to external (non-worklist-owned) functions -static void jl_insert_methods(jl_array_t *list) +// mutating external to point at the new methodtable entry instead of the new method +static void jl_add_methods(jl_array_t *external) { - size_t i, l = jl_array_len(list); + size_t i, l = jl_array_nrows(external); for (i = 0; i < l; i++) { - jl_method_t *meth = (jl_method_t*)jl_array_ptr_ref(list, i); + jl_method_t *meth = (jl_method_t*)jl_array_ptr_ref(external, i); assert(jl_is_method(meth)); assert(!meth->is_for_opaque_closure); jl_methtable_t *mt = jl_method_get_table(meth); assert((jl_value_t*)mt != jl_nothing); - jl_method_table_insert(mt, meth, NULL); + jl_typemap_entry_t *entry = jl_method_table_add(mt, meth, NULL); + jl_array_ptr_set(external, i, entry); + } +} + +static void jl_activate_methods(jl_array_t *external, jl_array_t *internal, size_t world) +{ + size_t i, l = jl_array_nrows(internal); + for (i = 0; i < l; i++) { + jl_value_t *obj = jl_array_ptr_ref(internal, i); + if (jl_typetagis(obj, jl_typemap_entry_type)) { + jl_typemap_entry_t *entry = (jl_typemap_entry_t*)obj; + assert(jl_atomic_load_relaxed(&entry->min_world) == ~(size_t)0); + assert(jl_atomic_load_relaxed(&entry->max_world) == WORLD_AGE_REVALIDATION_SENTINEL); + jl_atomic_store_release(&entry->min_world, world); + jl_atomic_store_release(&entry->max_world, ~(size_t)0); + } + else if (jl_is_method(obj)) { + jl_method_t *m = (jl_method_t*)obj; + assert(jl_atomic_load_relaxed(&m->primary_world) == ~(size_t)0); + assert(jl_atomic_load_relaxed(&m->deleted_world) == WORLD_AGE_REVALIDATION_SENTINEL); + jl_atomic_store_release(&m->primary_world, world); + jl_atomic_store_release(&m->deleted_world, ~(size_t)0); + } + else if (jl_is_code_instance(obj)) { + jl_code_instance_t *ci = (jl_code_instance_t*)obj; + assert(jl_atomic_load_relaxed(&ci->min_world) == ~(size_t)0); + assert(jl_atomic_load_relaxed(&ci->max_world) == WORLD_AGE_REVALIDATION_SENTINEL); + jl_atomic_store_relaxed(&ci->min_world, world); + // n.b. ci->max_world is not updated until edges are verified + } + else { + abort(); + } + } + l = jl_array_nrows(external); + for (i = 0; i < l; i++) { + jl_typemap_entry_t *entry = (jl_typemap_entry_t*)jl_array_ptr_ref(external, i); + jl_methtable_t *mt = jl_method_get_table(entry->func.method); + assert((jl_value_t*)mt != jl_nothing); + jl_method_table_activate(mt, entry); } } static void jl_copy_roots(jl_array_t *method_roots_list, uint64_t key) { - size_t i, l = jl_array_len(method_roots_list); + size_t i, l = jl_array_nrows(method_roots_list); for (i = 0; i < l; i+=2) { jl_method_t *m = (jl_method_t*)jl_array_ptr_ref(method_roots_list, i); jl_array_t *roots = (jl_array_t*)jl_array_ptr_ref(method_roots_list, i+1); @@ -835,12 +927,12 @@ static void jl_copy_roots(jl_array_t *method_roots_list, uint64_t key) static jl_array_t *jl_verify_edges(jl_array_t *targets, size_t minworld) { JL_TIMING(VERIFY_IMAGE, VERIFY_Edges); - size_t i, l = jl_array_len(targets) / 3; + size_t i, l = jl_array_nrows(targets) / 3; static jl_value_t *ulong_array JL_ALWAYS_LEAFTYPE = NULL; if (ulong_array == NULL) ulong_array = jl_apply_array_type((jl_value_t*)jl_ulong_type, 1); jl_array_t *maxvalids = jl_alloc_array_1d(ulong_array, l); - memset(jl_array_data(maxvalids), 0, l * sizeof(size_t)); + memset(jl_array_data(maxvalids, size_t), 0, l * sizeof(size_t)); jl_value_t *loctag = NULL; jl_value_t *matches = NULL; jl_value_t *sig = NULL; @@ -856,7 +948,7 @@ static jl_array_t *jl_verify_edges(jl_array_t *targets, size_t minworld) jl_method_t *m = ((jl_method_instance_t*)callee)->def.method; if (jl_egal(invokesig, m->sig)) { // the invoke match is `m` for `m->sig`, unless `m` is invalid - if (m->deleted_world < max_valid) + if (jl_atomic_load_relaxed(&m->deleted_world) < max_valid) max_valid = 0; } else { @@ -891,7 +983,7 @@ static jl_array_t *jl_verify_edges(jl_array_t *targets, size_t minworld) // TODO: possibly need to included ambiguities too (for the optimizer correctness)? // len + 1 is to allow us to log causes of invalidation (SnoopCompile's @snoopr) matches = jl_matching_methods((jl_tupletype_t*)sig, jl_nothing, - _jl_debug_method_invalidation ? INT32_MAX : jl_array_len(expected), + _jl_debug_method_invalidation ? INT32_MAX : jl_array_nrows(expected), 0, minworld, &min_valid, &max_valid, &ambig); sig = NULL; if (matches == jl_nothing) { @@ -900,12 +992,12 @@ static jl_array_t *jl_verify_edges(jl_array_t *targets, size_t minworld) else { // setdiff!(matches, expected) size_t j, k, ins = 0; - if (jl_array_len(matches) != jl_array_len(expected)) { + if (jl_array_nrows(matches) != jl_array_nrows(expected)) { max_valid = 0; } - for (k = 0; k < jl_array_len(matches); k++) { + for (k = 0; k < jl_array_nrows(matches); k++) { jl_method_t *match = ((jl_method_match_t*)jl_array_ptr_ref(matches, k))->method; - size_t l = jl_array_len(expected); + size_t l = jl_array_nrows(expected); for (j = 0; j < l; j++) if (match == (jl_method_t*)jl_array_ptr_ref(expected, j)) break; @@ -920,10 +1012,10 @@ static jl_array_t *jl_verify_edges(jl_array_t *targets, size_t minworld) } } if (max_valid != ~(size_t)0 && _jl_debug_method_invalidation) - jl_array_del_end((jl_array_t*)matches, jl_array_len(matches) - ins); + jl_array_del_end((jl_array_t*)matches, jl_array_nrows(matches) - ins); } } - ((size_t*)(jl_array_data(maxvalids)))[i] = max_valid; + jl_array_data(maxvalids, size_t)[i] = max_valid; if (max_valid != ~(size_t)0 && _jl_debug_method_invalidation) { jl_array_ptr_1d_push(_jl_debug_method_invalidation, invokesig ? (jl_value_t*)invokesig : callee); loctag = jl_cstr_to_string("insert_backedges_callee"); @@ -934,7 +1026,7 @@ static jl_array_t *jl_verify_edges(jl_array_t *targets, size_t minworld) } //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)invokesig); //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)callee); - //ios_puts(valid ? "valid\n" : "INVALID\n", ios_stderr); + //ios_puts(max_valid == ~(size_t)0 ? "valid\n" : "INVALID\n", ios_stderr); } JL_GC_POP(); return maxvalids; @@ -947,9 +1039,9 @@ static jl_array_t *jl_verify_methods(jl_array_t *edges, jl_array_t *maxvalids) jl_value_t *loctag = NULL; jl_array_t *maxvalids2 = NULL; JL_GC_PUSH2(&loctag, &maxvalids2); - size_t i, l = jl_array_len(edges) / 2; + size_t i, l = jl_array_nrows(edges) / 2; maxvalids2 = jl_alloc_array_1d(jl_typeof(maxvalids), l); - size_t *maxvalids2_data = (size_t*)jl_array_data(maxvalids2); + size_t *maxvalids2_data = jl_array_data(maxvalids2, size_t); memset(maxvalids2_data, 0, l * sizeof(size_t)); for (i = 0; i < l; i++) { jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, 2 * i); @@ -961,12 +1053,12 @@ static jl_array_t *jl_verify_methods(jl_array_t *edges, jl_array_t *maxvalids) maxvalids2_data[i] = 0; } else { - int32_t *idxs = (int32_t*)jl_array_data(callee_ids); + int32_t *idxs = jl_array_data(callee_ids, int32_t); size_t j; maxvalids2_data[i] = ~(size_t)0; for (j = 0; j < idxs[0]; j++) { int32_t idx = idxs[j + 1]; - size_t max_valid = ((size_t*)(jl_array_data(maxvalids)))[idx]; + size_t max_valid = jl_array_data(maxvalids, size_t)[idx]; if (max_valid != ~(size_t)0 && _jl_debug_method_invalidation) { jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)caller); loctag = jl_cstr_to_string("verify_methods"); @@ -981,7 +1073,7 @@ static jl_array_t *jl_verify_methods(jl_array_t *edges, jl_array_t *maxvalids) } } //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)caller); - //ios_puts(maxvalid2_data[i] == ~(size_t)0 ? "valid\n" : "INVALID\n", ios_stderr); + //ios_puts(maxvalids2_data[i] == ~(size_t)0 ? "valid\n" : "INVALID\n", ios_stderr); } JL_GC_POP(); return maxvalids2; @@ -1006,8 +1098,8 @@ static int jl_verify_graph_edge(size_t *maxvalids2_data, jl_array_t *edges, size visited->items[idx] = (void*)(1 + depth); jl_array_t *callee_ids = (jl_array_t*)jl_array_ptr_ref(edges, idx * 2 + 1); assert(jl_typetagis((jl_value_t*)callee_ids, jl_array_int32_type)); - int32_t *idxs = (int32_t*)jl_array_data(callee_ids); - size_t i, n = jl_array_len(callee_ids); + int32_t *idxs = jl_array_data(callee_ids, int32_t); + size_t i, n = jl_array_nrows(callee_ids); cycle = depth; for (i = idxs[0] + 1; i < n; i++) { int32_t childidx = idxs[i]; @@ -1061,10 +1153,10 @@ static void jl_verify_graph(jl_array_t *edges, jl_array_t *maxvalids2) JL_TIMING(VERIFY_IMAGE, VERIFY_Graph); arraylist_t stack, visited; arraylist_new(&stack, 0); - size_t i, n = jl_array_len(edges) / 2; + size_t i, n = jl_array_nrows(edges) / 2; arraylist_new(&visited, n); memset(visited.items, 0, n * sizeof(size_t)); - size_t *maxvalids2_data = (size_t*)jl_array_data(maxvalids2); + size_t *maxvalids2_data = jl_array_data(maxvalids2, size_t); for (i = 0; i < n; i++) { assert(visited.items[i] == (void*)0 || visited.items[i] == (void*)1); int child_cycle = jl_verify_graph_edge(maxvalids2_data, edges, i, &visited, &stack); @@ -1079,45 +1171,56 @@ static void jl_verify_graph(jl_array_t *edges, jl_array_t *maxvalids2) // Restore backedges to external targets // `edges` = [caller1, targets_indexes1, ...], the list of worklist-owned methods calling external methods. // `ext_targets` is [invokesig1, callee1, matches1, ...], the global set of non-worklist callees of worklist-owned methods. -static void jl_insert_backedges(jl_array_t *edges, jl_array_t *ext_targets, jl_array_t *ci_list, size_t minworld) +static void jl_insert_backedges(jl_array_t *edges, jl_array_t *ext_targets, jl_array_t *ext_ci_list, size_t minworld) { // determine which CodeInstance objects are still valid in our image jl_array_t *valids = jl_verify_edges(ext_targets, minworld); JL_GC_PUSH1(&valids); valids = jl_verify_methods(edges, valids); // consumes edges valids, initializes methods valids jl_verify_graph(edges, valids); // propagates methods valids for each edge - size_t i, l; + + size_t n_ext_cis = ext_ci_list ? jl_array_nrows(ext_ci_list) : 0; + htable_t cis_pending_validation; + htable_new(&cis_pending_validation, n_ext_cis); // next build a map from external MethodInstances to their CodeInstance for insertion - l = jl_array_len(ci_list); - htable_t visited; - htable_new(&visited, l); - for (i = 0; i < l; i++) { - jl_code_instance_t *ci = (jl_code_instance_t*)jl_array_ptr_ref(ci_list, i); - assert(ci->min_world == minworld); - if (ci->max_world == 1) { // sentinel value: has edges to external callables - ptrhash_put(&visited, (void*)ci->def, (void*)ci); + for (size_t i = 0; i < n_ext_cis; i++) { + jl_code_instance_t *ci = (jl_code_instance_t*)jl_array_ptr_ref(ext_ci_list, i); + if (jl_atomic_load_relaxed(&ci->max_world) == WORLD_AGE_REVALIDATION_SENTINEL) { + assert(jl_atomic_load_relaxed(&ci->min_world) == minworld); + void **bp = ptrhash_bp(&cis_pending_validation, (void*)ci->def); + assert(!jl_atomic_load_relaxed(&ci->next)); + if (*bp == HT_NOTFOUND) + *bp = (void*)ci; + else { + // Do ci->owner bifurcates the cache, we temporarily + // form a linked list of all the CI that need to be connected later + jl_code_instance_t *prev_ci = (jl_code_instance_t *)*bp; + jl_atomic_store_relaxed(&ci->next, prev_ci); + *bp = (void*)ci; + } } else { - assert(ci->max_world == ~(size_t)0); + assert(jl_atomic_load_relaxed(&ci->min_world) == 1); + assert(jl_atomic_load_relaxed(&ci->max_world) == ~(size_t)0); jl_method_instance_t *caller = ci->def; - if (ci->inferred && jl_rettype_inferred(caller, minworld, ~(size_t)0) == jl_nothing) { + if (jl_atomic_load_relaxed(&ci->inferred) && jl_rettype_inferred(ci->owner, caller, minworld, ~(size_t)0) == jl_nothing) { jl_mi_cache_insert(caller, ci); } - //jl_static_show((jl_stream*)ios_stderr, (jl_value_t*)caller); + //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)caller); //ios_puts("free\n", ios_stderr); } } // next enable any applicable new codes - l = jl_array_len(edges) / 2; - for (i = 0; i < l; i++) { + size_t nedges = jl_array_nrows(edges) / 2; + for (size_t i = 0; i < nedges; i++) { jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, 2 * i); - size_t maxvalid = ((size_t*)(jl_array_data(valids)))[i]; + size_t maxvalid = jl_array_data(valids, size_t)[i]; if (maxvalid == ~(size_t)0) { // if this callee is still valid, add all the backedges jl_array_t *callee_ids = (jl_array_t*)jl_array_ptr_ref(edges, 2 * i + 1); - int32_t *idxs = (int32_t*)jl_array_data(callee_ids); + int32_t *idxs = jl_array_data(callee_ids, int32_t); for (size_t j = 0; j < idxs[0]; j++) { int32_t idx = idxs[j + 1]; jl_value_t *invokesig = jl_array_ptr_ref(ext_targets, idx * 3); @@ -1138,27 +1241,50 @@ static void jl_insert_backedges(jl_array_t *edges, jl_array_t *ext_targets, jl_a } } // then enable any methods associated with it - void *ci = ptrhash_get(&visited, (void*)caller); + void *ci = ptrhash_get(&cis_pending_validation, (void*)caller); //assert(ci != HT_NOTFOUND); if (ci != HT_NOTFOUND) { - // have some new external code to use + // Update any external CIs and add them to the cache. assert(jl_is_code_instance(ci)); jl_code_instance_t *codeinst = (jl_code_instance_t*)ci; - assert(codeinst->min_world == minworld && codeinst->inferred); - codeinst->max_world = maxvalid; - if (jl_rettype_inferred(caller, minworld, maxvalid) == jl_nothing) { - jl_mi_cache_insert(caller, codeinst); + while (codeinst) { + jl_code_instance_t *next_ci = jl_atomic_load_relaxed(&codeinst->next); + jl_atomic_store_relaxed(&codeinst->next, NULL); + + jl_value_t *owner = codeinst->owner; + JL_GC_PROMISE_ROOTED(owner); + + assert(jl_atomic_load_relaxed(&codeinst->min_world) == minworld); + assert(jl_atomic_load_relaxed(&codeinst->max_world) == WORLD_AGE_REVALIDATION_SENTINEL); + assert(jl_atomic_load_relaxed(&codeinst->inferred)); + jl_atomic_store_relaxed(&codeinst->max_world, maxvalid); + + if (jl_rettype_inferred(owner, caller, minworld, maxvalid) != jl_nothing) { + // We already got a code instance for this world age range from somewhere else - we don't need + // this one. + } else { + jl_mi_cache_insert(caller, codeinst); + } + codeinst = next_ci; + } + } + else { + // Likely internal. Find the CI already in the cache hierarchy. + for (jl_code_instance_t *codeinst = jl_atomic_load_relaxed(&caller->cache); codeinst; codeinst = jl_atomic_load_relaxed(&codeinst->next)) { + if (jl_atomic_load_relaxed(&codeinst->min_world) == minworld && jl_atomic_load_relaxed(&codeinst->max_world) == WORLD_AGE_REVALIDATION_SENTINEL) { + jl_atomic_store_relaxed(&codeinst->max_world, maxvalid); + } } } } + htable_free(&cis_pending_validation); - htable_free(&visited); JL_GC_POP(); } static void classify_callers(htable_t *callers_with_edges, jl_array_t *edges) { - size_t l = edges ? jl_array_len(edges) / 2 : 0; + size_t l = edges ? jl_array_nrows(edges) / 2 : 0; for (size_t i = 0; i < l; i++) { jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, 2 * i); ptrhash_put(callers_with_edges, (void*)caller, (void*)caller); @@ -1171,7 +1297,7 @@ static jl_value_t *read_verify_mod_list(ios_t *s, jl_array_t *depmods) return jl_get_exceptionf(jl_errorexception_type, "Main module uuid state is invalid for module deserialization."); } - size_t i, l = jl_array_len(depmods); + size_t i, l = jl_array_nrows(depmods); for (i = 0; ; i++) { size_t len = read_int32(s); if (len == 0 && i == l) @@ -1233,11 +1359,11 @@ static jl_array_t *image_to_depmodidx(jl_array_t *depmods) { if (!depmods) return NULL; - assert(jl_array_len(depmods) < INT32_MAX && "too many dependencies to serialize"); + assert(jl_array_nrows(depmods) < INT32_MAX && "too many dependencies to serialize"); size_t lbids = n_linkage_blobs(); - size_t ldeps = jl_array_len(depmods); + size_t ldeps = jl_array_nrows(depmods); jl_array_t *depmodidxs = jl_alloc_array_1d(jl_array_int32_type, lbids); - int32_t *dmidxs = (int32_t*)jl_array_data(depmodidxs); + int32_t *dmidxs = jl_array_data(depmodidxs, int32_t); memset(dmidxs, -1, lbids * sizeof(int32_t)); dmidxs[0] = 0; // the sysimg can also be found at idx 0, by construction for (size_t i = 0, j = 0; i < ldeps; i++) { @@ -1257,9 +1383,9 @@ static jl_array_t *depmod_to_imageidx(jl_array_t *depmods) { if (!depmods) return NULL; - size_t ldeps = jl_array_len(depmods); + size_t ldeps = jl_array_nrows(depmods); jl_array_t *imageidxs = jl_alloc_array_1d(jl_array_int32_type, ldeps + 1); - int32_t *imgidxs = (int32_t*)jl_array_data(imageidxs); + int32_t *imgidxs = jl_array_data(imageidxs, int32_t); imgidxs[0] = 0; for (size_t i = 0; i < ldeps; i++) { jl_value_t *depmod = jl_array_ptr_ref(depmods, i); diff --git a/src/subtype.c b/src/subtype.c index d8177f0fd21ff..f9bdf9d3982d8 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -69,6 +69,8 @@ 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 + int8_t max_offset; // record the maximum positive offset of the variable (up to 32) + // max_offset < 0 if this variable occurs outside VarargNum. // constraintkind: in covariant position, we try three different ways to compute var ∩ type: // let ub = var.ub ∩ type // 0 - var.ub <: type ? var : ub @@ -77,6 +79,7 @@ typedef struct jl_varbinding_t { int8_t constraintkind; int8_t intvalued; // intvalued: must be integer-valued; i.e. occurs as N in Vararg{_,N} int8_t limited; + int8_t intersected; // whether this variable has been intersected int16_t depth0; // # of invariant constructors nested around the UnionAll type for this var // array of typevars that our bounds depend on, whose UnionAlls need to be // moved outside ours. @@ -84,6 +87,14 @@ typedef struct jl_varbinding_t { struct jl_varbinding_t *prev; } jl_varbinding_t; +typedef struct jl_ivarbinding_t { + jl_tvar_t **var; + jl_value_t **lb; + jl_value_t **ub; + jl_varbinding_t *root; + struct jl_ivarbinding_t *next; +} jl_ivarbinding_t; + // subtype algorithm state typedef struct jl_stenv_t { // N.B.: varbindings are created on the stack and rooted there @@ -168,9 +179,9 @@ static int current_env_length(jl_stenv_t *e) typedef struct { int8_t *buf; int rdepth; - int8_t _space[24]; // == 8 * 3 + int8_t _space[32]; // == 8 * 4 jl_gcframe_t gcframe; - jl_value_t *roots[24]; + jl_value_t *roots[24]; // == 8 * 3 } jl_savedenv_t; static void re_save_env(jl_stenv_t *e, jl_savedenv_t *se, int root) @@ -200,6 +211,7 @@ static void re_save_env(jl_stenv_t *e, jl_savedenv_t *se, int root) se->buf[j++] = v->occurs; se->buf[j++] = v->occurs_inv; se->buf[j++] = v->occurs_cov; + se->buf[j++] = v->max_offset; v = v->prev; } assert(i == nroots); (void)nroots; @@ -231,7 +243,7 @@ static void alloc_env(jl_stenv_t *e, jl_savedenv_t *se, int root) ct->gcstack = &se->gcframe; } } - se->buf = (len > 8 ? (int8_t*)malloc_s(len * 3) : se->_space); + se->buf = (len > 8 ? (int8_t*)malloc_s(len * 4) : se->_space); #ifdef __clang_gcanalyzer__ memset(se->buf, 0, len * 3); #endif @@ -281,6 +293,7 @@ static void restore_env(jl_stenv_t *e, jl_savedenv_t *se, int root) JL_NOTSAFEPO v->occurs = se->buf[j++]; v->occurs_inv = se->buf[j++]; v->occurs_cov = se->buf[j++]; + v->max_offset = se->buf[j++]; v = v->prev; } assert(i == nroots); (void)nroots; @@ -677,6 +690,10 @@ static void record_var_occurrence(jl_varbinding_t *vb, jl_stenv_t *e, int param) else if (vb->occurs_cov < 2) { vb->occurs_cov++; } + // Always set `max_offset` to `-1` during the 1st round intersection. + // Would be recovered in `intersect_varargs`/`subtype_tuple_varargs` if needed. + if (!vb->intersected) + vb->max_offset = -1; } } @@ -796,7 +813,7 @@ static int subtype_var(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int R, int pa // check that a type is concrete or quasi-concrete (Type{T}). // this is used to check concrete typevars: // issubtype is false if the lower bound of a concrete type var is not concrete. -static int is_leaf_bound(jl_value_t *v) JL_NOTSAFEPOINT +int is_leaf_bound(jl_value_t *v) JL_NOTSAFEPOINT { if (v == jl_bottom_type) return 1; @@ -846,7 +863,7 @@ static jl_value_t *fix_inferred_var_bound(jl_tvar_t *var, jl_value_t *ty JL_MAYB JL_GC_PUSH2(&ans, &vs); vs = jl_find_free_typevars(ty); int i; - for (i = 0; i < jl_array_len(vs); i++) { + for (i = 0; i < jl_array_nrows(vs); i++) { ans = jl_type_unionall((jl_tvar_t*)jl_array_ptr_ref(vs, i), ans); } ans = (jl_value_t*)jl_new_typevar(var->name, jl_bottom_type, ans); @@ -872,10 +889,20 @@ static jl_unionall_t *unalias_unionall(jl_unionall_t *u, jl_stenv_t *e) // in the environment, rename to get a fresh var. JL_GC_PUSH1(&u); while (btemp != NULL) { - if (btemp->var == u->var || - // outer var can only refer to inner var if bounds changed + int aliased = btemp->var == u->var || + // outer var can only refer to inner var if bounds changed (mainly for subtyping path) (btemp->lb != btemp->var->lb && jl_has_typevar(btemp->lb, u->var)) || - (btemp->ub != btemp->var->ub && jl_has_typevar(btemp->ub, u->var))) { + (btemp->ub != btemp->var->ub && jl_has_typevar(btemp->ub, u->var)); + if (!aliased && btemp->innervars != NULL) { + for (size_t i = 0; i < jl_array_len(btemp->innervars); i++) { + jl_tvar_t *ivar = (jl_tvar_t*)jl_array_ptr_ref(btemp->innervars, i); + if (ivar == u->var) { + aliased = 1; + break; + } + } + } + if (aliased) { u = jl_rename_unionall(u); break; } @@ -888,7 +915,7 @@ 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, 0, 0, 0, + jl_varbinding_t vb = { u->var, u->var->lb, u->var->ub, R, 0, 0, 0, 0, 0, 0, 0, 0, 0, e->invdepth, NULL, e->vars }; JL_GC_PUSH4(&u, &vb.lb, &vb.ub, &vb.innervars); e->vars = &vb; @@ -1008,39 +1035,30 @@ static int subtype_tuple_varargs( jl_value_t *xp0 = jl_unwrap_vararg(vtx); jl_value_t *xp1 = jl_unwrap_vararg_num(vtx); jl_value_t *yp0 = jl_unwrap_vararg(vty); jl_value_t *yp1 = jl_unwrap_vararg_num(vty); + jl_varbinding_t *xlv = NULL, *ylv = NULL; + if (xp1 && jl_is_typevar(xp1)) + xlv = lookup(e, (jl_tvar_t*)xp1); + if (yp1 && jl_is_typevar(yp1)) + ylv = lookup(e, (jl_tvar_t*)yp1); + + int8_t max_offsetx = xlv ? xlv->max_offset : 0; + int8_t max_offsety = ylv ? ylv->max_offset : 0; + + jl_value_t *xl = xlv ? xlv->lb : xp1; + jl_value_t *yl = ylv ? ylv->lb : yp1; + if (!xp1) { - jl_value_t *yl = yp1; - if (yl) { - // Unconstrained on the left, constrained on the right - if (jl_is_typevar(yl)) { - jl_varbinding_t *ylv = lookup(e, (jl_tvar_t*)yl); - if (ylv) - yl = ylv->lb; - } - if (jl_is_long(yl)) { - return 0; - } - } + // Unconstrained on the left, constrained on the right + if (yl && jl_is_long(yl)) + return 0; } else { - jl_value_t *xl = jl_unwrap_vararg_num(vtx); - if (jl_is_typevar(xl)) { - jl_varbinding_t *xlv = lookup(e, (jl_tvar_t*)xl); - if (xlv) - xl = xlv->lb; - } if (jl_is_long(xl)) { if (jl_unbox_long(xl) + 1 == vx) { // LHS is exhausted. We're a subtype if the RHS is either // exhausted as well or unbounded (in which case we need to // set it to 0). - jl_value_t *yl = jl_unwrap_vararg_num(vty); if (yl) { - if (jl_is_typevar(yl)) { - jl_varbinding_t *ylv = lookup(e, (jl_tvar_t*)yl); - if (ylv) - yl = ylv->lb; - } if (jl_is_long(yl)) { return jl_unbox_long(yl) + 1 == vy; } @@ -1090,6 +1108,8 @@ static int subtype_tuple_varargs( // appropriately. e->invdepth++; int ans = subtype((jl_value_t*)jl_any_type, yp1, e, 2); + if (ylv && !ylv->intersected) + ylv->max_offset = max_offsety; e->invdepth--; return ans; } @@ -1130,6 +1150,10 @@ static int subtype_tuple_varargs( e->Loffset = 0; } JL_GC_POP(); + if (ylv && !ylv->intersected) + ylv->max_offset = max_offsety; + if (xlv && !xlv->intersected) + xlv->max_offset = max_offsetx; e->invdepth--; return ans; } @@ -1506,6 +1530,23 @@ static int may_contain_union_decision(jl_value_t *x, jl_stenv_t *e, jl_typeenv_t may_contain_union_decision(xb ? xb->ub : ((jl_tvar_t *)x)->ub, e, &newlog); } +static int has_exists_typevar(jl_value_t *x, jl_stenv_t *e) JL_NOTSAFEPOINT +{ + jl_typeenv_t *env = NULL; + jl_varbinding_t *v = e->vars; + while (v != NULL) { + if (v->right) { + jl_typeenv_t *newenv = (jl_typeenv_t*)alloca(sizeof(jl_typeenv_t)); + newenv->var = v->var; + newenv->val = NULL; + newenv->prev = env; + env = newenv; + } + v = v->prev; + } + return env != NULL && jl_has_bound_typevars(x, env); +} + static int local_forall_exists_subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param, int limit_slow) { int16_t oldRmore = e->Runions.more; @@ -1525,13 +1566,19 @@ static int local_forall_exists_subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t int count = 0, noRmore = 0; sub = _forall_exists_subtype(x, y, e, param, &count, &noRmore); pop_unionstate(&e->Runions, &oldRunions); - // we should not try the slow path if `forall_exists_subtype` has tested all cases; - // Once limit_slow == 1, also skip it if - // 1) `forall_exists_subtype` return false + // We could skip the slow path safely if + // 1) `_∀_∃_subtype` has tested all cases + // 2) `_∀_∃_subtype` returns 1 && `x` and `y` contain no ∃ typevar + // Once `limit_slow == 1`, also skip it if + // 1) `_∀_∃_subtype` returns 0 // 2) the left `Union` looks big + // TODO: `limit_slow` ignores complexity from inner `local_∀_exists_subtype`. if (limit_slow == -1) limit_slow = kindx || kindy; - if (noRmore || (limit_slow && (count > 3 || !sub))) + int skip = noRmore || (limit_slow && (count > 3 || !sub)) || + (sub && (kindx || !has_exists_typevar(x, e)) && + (kindy || !has_exists_typevar(y, e))); + if (skip) e->Runions.more = oldRmore; } else { @@ -1991,7 +2038,7 @@ static int obvious_subtype(jl_value_t *x, jl_value_t *y, jl_value_t *y0, int *su if (var_occurs_invariant(body, (jl_tvar_t*)b)) return 0; } - if (nparams_expanded_x > npy && jl_is_typevar(b) && concrete_min(a1) > 1) { + if (nparams_expanded_x > npy && jl_is_typevar(b) && is_leaf_typevar((jl_tvar_t *)b) && concrete_min(a1) > 1) { // diagonal rule for 2 or more elements: they must all be concrete on the LHS *subtype = 0; return 1; @@ -2002,7 +2049,7 @@ static int obvious_subtype(jl_value_t *x, jl_value_t *y, jl_value_t *y0, int *su } for (; i < nparams_expanded_x; i++) { jl_value_t *a = (vx != JL_VARARG_NONE && i >= npx - 1) ? vxt : jl_tparam(x, i); - if (i > npy && jl_is_typevar(b)) { // i == npy implies a == a1 + if (i > npy && jl_is_typevar(b) && is_leaf_typevar((jl_tvar_t *)b)) { // i == npy implies a == a1 // diagonal rule: all the later parameters are also constrained to be type-equal to the first jl_value_t *a2 = a; jl_value_t *au = jl_unwrap_unionall(a); @@ -2772,8 +2819,8 @@ static jl_value_t *omit_bad_union(jl_value_t *u, jl_tvar_t *t) 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, *ilb = NULL, *iub = NULL, *nivar = NULL; - jl_tvar_t *newvar = vb->var; - JL_GC_PUSH5(&res, &newvar, &ilb, &iub, &nivar); + jl_tvar_t *newvar = vb->var, *ivar = NULL; + JL_GC_PUSH6(&res, &newvar, &ivar, &nivar, &ilb, &iub); // try to reduce var to a single value if (jl_is_long(vb->ub) && jl_is_typevar(vb->lb)) { varval = vb->ub; @@ -2806,162 +2853,242 @@ static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbind 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 - - // I. Handle indirect innervars (make them behave like direct innervars). - // 1) record if btemp->lb/ub has indirect innervars. - // 2) subtitute `vb->var` with `varval`/`varval` - // note: We only store the innervar in the outmost `varbinding`, - // thus we must check all inner env to ensure the recording/subtitution - // is complete - int len = current_env_length(e); - int8_t *blinding_has_innerdep = (int8_t *)alloca(len); - memset(blinding_has_innerdep, 0, len); + // flatten all innervar into a (reversed) list + size_t icount = 0; + if (vb->innervars) + icount += jl_array_nrows(vb->innervars); for (jl_varbinding_t *btemp = e->vars; btemp != NULL; btemp = btemp->prev) { - if (btemp->innervars != NULL) { - for (size_t i = 0; i < jl_array_len(btemp->innervars); i++) { - jl_tvar_t *ivar = (jl_tvar_t*)jl_array_ptr_ref(btemp->innervars, i); - ilb = ivar->lb; iub = ivar->ub; - int has_innerdep = 0; - if (jl_has_typevar(ilb, vb->var)) { - has_innerdep = 1; - if (varval) { - JL_TRY { - ilb = jl_substitute_var(ilb, vb->var, varval); - } - JL_CATCH { - res = jl_bottom_type; - } - } - else if (newvar != vb->var) { - ilb = jl_substitute_var(ilb, vb->var, (jl_value_t*)newvar); - } - } - if (jl_has_typevar(iub, vb->var)) { - has_innerdep = 1; - if (varval) { - JL_TRY { - iub = jl_substitute_var(iub, vb->var, varval); - } - JL_CATCH { - res = jl_bottom_type; - } - } - else if (newvar != vb->var) { - iub = jl_substitute_var(iub, vb->var, (jl_value_t*)newvar); - } - } - if (!has_innerdep) continue; - int need_subtitution = 0; - if (ilb != ivar->lb || iub != ivar->ub) { - need_subtitution = 1; - nivar = (jl_value_t *)jl_new_typevar(ivar->name, ilb, iub); - jl_array_ptr_set(btemp->innervars, i, nivar); - if (jl_has_typevar(res, ivar)) - res = jl_substitute_var(res, ivar, nivar); - } - int envind = 0; - for (jl_varbinding_t *btemp2 = e->vars; btemp2 != btemp->prev; btemp2 = btemp2->prev) { - if (jl_has_typevar(btemp2->lb, ivar)) { - if (need_subtitution) - btemp2->lb = jl_substitute_var(btemp2->lb, ivar, nivar); - blinding_has_innerdep[envind] |= 1; - } - if (jl_has_typevar(btemp2->ub, ivar)) { - if (need_subtitution) - btemp2->ub = jl_substitute_var(btemp2->ub, ivar, nivar); - blinding_has_innerdep[envind] |= 2; - } - envind++; - } - } + if (btemp->innervars != NULL) + icount += jl_array_nrows(btemp->innervars); + } + jl_svec_t *p = NULL; + jl_value_t **iparams; + jl_value_t **roots; + JL_GC_PUSHARGS(roots, icount < 22 ? 3*icount : 1); + if (icount < 22) { + iparams = roots; + } + else { + p = jl_alloc_svec(3*icount); + roots[0] = (jl_value_t*)p; + iparams = jl_svec_data(p); + } + jl_ivarbinding_t *allvars = NULL; + size_t niparams = 0; + if (vb->innervars) { + for (size_t i = 0; i < jl_array_nrows(vb->innervars); i++) { + jl_tvar_t *ivar = (jl_tvar_t *)jl_array_ptr_ref(vb->innervars, i); + jl_ivarbinding_t *inew = (jl_ivarbinding_t *)alloca(sizeof(jl_ivarbinding_t)); + inew->var = (jl_tvar_t **)&iparams[niparams++]; *inew->var = ivar; + inew->lb = &iparams[niparams++]; *inew->lb = ivar->lb; + inew->ub = &iparams[niparams++]; *inew->ub = ivar->ub; + inew->root = vb; + inew->next = allvars; + allvars = inew; } } - // II. Handle direct innervars. - jl_varbinding_t *wrap = NULL; - int envind = 0; for (jl_varbinding_t *btemp = e->vars; btemp != NULL; btemp = btemp->prev) { - int has_innerdep = blinding_has_innerdep[envind++]; - int lb_has_innerdep = has_innerdep & 1; - int ub_has_innerdep = has_innerdep & 2; - assert(!has_innerdep || btemp->depth0 == vb->depth0); - int lb_has_dep = jl_has_typevar(btemp->lb, vb->var); - int ub_has_dep = jl_has_typevar(btemp->ub, vb->var); - if (lb_has_innerdep || lb_has_dep) { - if (vb->lb == (jl_value_t*)btemp->var) { + jl_ivarbinding_t *inew = (jl_ivarbinding_t *)alloca(sizeof(jl_ivarbinding_t)); + inew->var = &btemp->var; + inew->lb = &btemp->lb; + inew->ub = &btemp->ub; + inew->root = btemp; + inew->next = allvars; + allvars = inew; + if (btemp->innervars) { + for (size_t i = 0; i < jl_array_nrows(btemp->innervars); i++) { + jl_tvar_t *ivar = (jl_tvar_t *)jl_array_ptr_ref(btemp->innervars, i); + jl_ivarbinding_t *inew = (jl_ivarbinding_t *)alloca(sizeof(jl_ivarbinding_t)); + inew->var = (jl_tvar_t **)&iparams[niparams++]; *inew->var = ivar; + inew->lb = &iparams[niparams++]; *inew->lb = ivar->lb; + inew->ub = &iparams[niparams++]; *inew->ub = ivar->ub; + inew->root = btemp; + inew->next = allvars; + allvars = inew; + } + } + } + + // remove/replace/rewrap free occurrences of this var in the environment + int wrapped = 0; + jl_ivarbinding_t *pwrap = NULL; + for (jl_ivarbinding_t *btemp = allvars, *pbtemp = NULL; btemp != NULL; btemp = btemp->next) { + int bdepth0 = btemp->root->depth0; + ivar = *btemp->var; + ilb = *btemp->lb; + iub = *btemp->ub; + if (jl_has_typevar(ilb, vb->var)) { + assert(btemp->root->var == ivar || bdepth0 == vb->depth0); + if (vb->lb == (jl_value_t*)ivar) { + JL_GC_POP(); JL_GC_POP(); return jl_bottom_type; } if (varval) { - if (lb_has_dep) { // inner substitution has been handled - JL_TRY { - btemp->lb = jl_substitute_var(btemp->lb, vb->var, varval); - } - JL_CATCH { - res = jl_bottom_type; - } + JL_TRY { + *btemp->lb = jl_substitute_var(ilb, vb->var, varval); + } + JL_CATCH { + res = jl_bottom_type; } } - else if (btemp->lb == (jl_value_t*)vb->var) { - btemp->lb = vb->lb; + else if (ilb == (jl_value_t*)vb->var) { + *btemp->lb = vb->lb; } - else if (btemp->depth0 == vb->depth0 && !jl_has_typevar(vb->lb, btemp->var) && !jl_has_typevar(vb->ub, btemp->var)) { + else if (bdepth0 == vb->depth0 && !jl_has_typevar(vb->lb, ivar) && !jl_has_typevar(vb->ub, ivar)) { // if our variable is T, and some outer variable has constraint S = Ref{T}, // move the `where T` outside `where S` instead of putting it here. issue #21243. - if (newvar != vb->var && lb_has_dep) // inner substitution has been handled - btemp->lb = jl_substitute_var(btemp->lb, vb->var, (jl_value_t*)newvar); - wrap = btemp; + if (newvar != vb->var) + *btemp->lb = jl_substitute_var(ilb, vb->var, (jl_value_t*)newvar); + if (!wrapped) pwrap = pbtemp; + wrapped = 1; } else { - btemp->lb = jl_new_struct(jl_unionall_type, vb->var, btemp->lb); + *btemp->lb = jl_new_struct(jl_unionall_type, vb->var, ilb); } - assert((jl_value_t*)btemp->var != btemp->lb); + assert((jl_value_t*)ivar != *btemp->lb); } - if (ub_has_innerdep || ub_has_dep) { - if (vb->ub == (jl_value_t*)btemp->var) { - // TODO: handle `omit_bad_union` correctly if `ub_has_innerdep` - btemp->ub = omit_bad_union(btemp->ub, vb->var); - if (btemp->ub == jl_bottom_type && btemp->ub != btemp->lb) { + if (jl_has_typevar(iub, vb->var)) { + assert(btemp->root->var == ivar || bdepth0 == vb->depth0); + if (vb->ub == (jl_value_t*)ivar) { + *btemp->ub = omit_bad_union(iub, vb->var); + if (*btemp->ub == jl_bottom_type && *btemp->ub != *btemp->lb) { + JL_GC_POP(); JL_GC_POP(); return jl_bottom_type; } } if (varval) { - if (ub_has_dep) { // inner substitution has been handled - JL_TRY { - btemp->ub = jl_substitute_var(btemp->ub, vb->var, varval); - } - JL_CATCH { - res = jl_bottom_type; - } + JL_TRY { + *btemp->ub = jl_substitute_var(iub, vb->var, varval); + } + JL_CATCH { + res = jl_bottom_type; } } - else if (btemp->ub == (jl_value_t*)vb->var) { + else if (iub == (jl_value_t*)vb->var) { // TODO: this loses some constraints, such as in this test, where we replace T4<:S3 (e.g. T4==S3 since T4 only appears covariantly once) with T4<:Any // a = Tuple{Float64,T3,T4} where T4 where T3 // b = Tuple{S2,Tuple{S3},S3} where S2 where S3 // Tuple{Float64, T3, T4} where {S3, T3<:Tuple{S3}, T4<:S3} - btemp->ub = vb->ub; + *btemp->ub = vb->ub; } - else if (btemp->depth0 == vb->depth0 && !jl_has_typevar(vb->lb, btemp->var) && !jl_has_typevar(vb->ub, btemp->var)) { - if (newvar != vb->var && ub_has_dep) // inner substitution has been handled - btemp->ub = jl_substitute_var(btemp->ub, vb->var, (jl_value_t*)newvar); - wrap = btemp; + else if (bdepth0 == vb->depth0 && !jl_has_typevar(vb->lb, ivar) && !jl_has_typevar(vb->ub, ivar)) { + if (newvar != vb->var) + *btemp->ub = jl_substitute_var(iub, vb->var, (jl_value_t*)newvar); + if (!wrapped) pwrap = pbtemp; + wrapped = 1; } else - btemp->ub = jl_new_struct(jl_unionall_type, vb->var, btemp->ub); - assert((jl_value_t*)btemp->var != btemp->ub); + *btemp->ub = jl_new_struct(jl_unionall_type, vb->var, iub); + assert((jl_value_t*)ivar != *btemp->ub); + } + pbtemp = btemp; + } + + // Insert the newvar into the (reversed) var list if needed. + if (wrapped) { + jl_ivarbinding_t *wrap = pwrap == NULL ? allvars : pwrap->next; + jl_ivarbinding_t *inew = (jl_ivarbinding_t *)alloca(sizeof(jl_ivarbinding_t)); + inew->var = &newvar; + inew->lb = &newvar->lb; + inew->ub = &newvar->ub;; + inew->root = wrap->root; + inew->next = wrap; + if (pwrap != NULL) + pwrap->next = inew; + else + allvars = inew; + } + + // Re-sort the innervar inside the (reversed) var list. + // `jl_has_typevar` is used as the partial-ordering predicate. + // If this is slow, we could possibly switch to a simpler graph sort, such as Tarjan's SCC. + if (icount > 0) { + jl_ivarbinding_t *pib1 = NULL; + while (1) { + jl_ivarbinding_t *ib1 = pib1 == NULL ? allvars : pib1->next; + if (ib1 == NULL) break; + if (jl_has_free_typevars(*ib1->lb) || jl_has_free_typevars(*ib1->ub)) { + int changed = 0; + jl_ivarbinding_t *pib2 = ib1, *ib2 = ib1->next; + while (ib2 != NULL) { + int isinnervar = ib2->root->var != *ib2->var; + if (isinnervar && ib1->root->depth0 == ib2->root->depth0 && + (jl_has_typevar(*ib1->lb, *ib2->var) || + jl_has_typevar(*ib1->ub, *ib2->var))) { + pib2->next = ib2->next; + ib2->next = ib1; + ib2->root = ib1->root; + if (pib1) + pib1->next = ib2; + else + allvars = ib2; + changed = 1; + break; + } + pib2 = ib2; + ib2 = ib2->next; + } + if (changed) continue; + } + pib1 = ib1; + } + } + + // Freeze the innervars' lb/ub and perform substitution if needed. + for (jl_ivarbinding_t *btemp1 = allvars; btemp1 != NULL; btemp1 = btemp1->next) { + ivar = *btemp1->var; + ilb = *btemp1->lb; + iub = *btemp1->ub; + int isinnervar = btemp1->root->var != ivar; + if (isinnervar && (ivar->lb != ilb || ivar->ub != iub)) { + nivar = (jl_value_t *)jl_new_typevar(ivar->name, ilb, iub); + if (jl_has_typevar(res, ivar)) + res = jl_substitute_var(res, ivar, nivar); + for (jl_ivarbinding_t *btemp2 = btemp1->next; btemp2 != NULL; btemp2 = btemp2->next) { + ilb = *btemp2->lb; + iub = *btemp2->ub; + if (jl_has_typevar(ilb, ivar)) + *btemp2->lb = jl_substitute_var(ilb, ivar, nivar); + if (jl_has_typevar(iub, ivar)) + *btemp2->ub = jl_substitute_var(iub, ivar, nivar); + } + *btemp1->var = (jl_tvar_t *)nivar; } } - if (wrap) { - // We only assign the newvar with the outmost var. - // This make sure we never create a UnionAll with 2 identical vars. - if (wrap->innervars == NULL) - wrap->innervars = jl_alloc_array_1d(jl_array_any_type, 0); - jl_array_ptr_1d_push(wrap->innervars, (jl_value_t*)newvar); - // TODO: should we move all the innervars here too? + // Switch back the innervars' storage. + while (1) { + jl_ivarbinding_t *btemp = allvars; + jl_varbinding_t *root = btemp ? btemp->root : vb; + size_t icount = 0; + while (btemp && btemp->root == root) { + btemp = btemp->next; + icount++; + } + if (root != vb) icount--; + if (root->innervars != NULL) { + size_t len = jl_array_nrows(root->innervars); + if (icount > len) + jl_array_grow_end(root->innervars, icount - len); + if (icount < len) + jl_array_del_end(root->innervars, len - icount); + } + else if (icount > 0) { + root->innervars = jl_alloc_array_1d(jl_array_any_type, icount); + } + btemp = allvars; + for (size_t i = icount; i > 0; i--) { + jl_array_ptr_set(root->innervars, i - 1, (jl_value_t*)*btemp->var); + btemp = btemp->next; + } + if (root == vb) break; + assert(*btemp->var == root->var); + allvars = btemp->next; + assert(allvars == NULL || allvars->root != root); } + JL_GC_POP(); // if `v` still occurs, re-wrap body in `UnionAll v` or eliminate the UnionAll if (jl_has_typevar(res, vb->var)) { @@ -2983,32 +3110,15 @@ static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbind if (newvar != vb->var) res = jl_substitute_var(res, vb->var, (jl_value_t*)newvar); varval = (jl_value_t*)newvar; - if (!wrap) + if (!wrapped) res = jl_type_unionall((jl_tvar_t*)newvar, res); } } if (vb->innervars != NULL) { - for (size_t i = 0; i < jl_array_len(vb->innervars); i++) { + for (size_t i = 0; i < jl_array_nrows(vb->innervars); i++) { jl_tvar_t *var = (jl_tvar_t*)jl_array_ptr_ref(vb->innervars, i); - // the `btemp->prev` walk is only giving a sort of post-order guarantee (since we are - // iterating 2 trees at once), so once we set `wrap`, there might remain other branches - // of the type walk that now still may have incomplete bounds: finish those now too - jl_varbinding_t *wrap = NULL; - for (jl_varbinding_t *btemp = e->vars; btemp != NULL; btemp = btemp->prev) { - if (btemp->depth0 == vb->depth0 && (jl_has_typevar(btemp->lb, var) || jl_has_typevar(btemp->ub, var))) { - wrap = btemp; - } - } - if (wrap) { - if (wrap->innervars == NULL) - wrap->innervars = jl_alloc_array_1d(jl_array_any_type, 0); - jl_array_ptr_1d_push(wrap->innervars, (jl_value_t*)var); - } - else if (res != jl_bottom_type) { - if (jl_has_typevar(res, var)) - res = jl_type_unionall((jl_tvar_t*)var, res); - } + res = jl_type_unionall(var, res); } } @@ -3027,9 +3137,6 @@ static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbind static jl_value_t *intersect_unionall_(jl_value_t *t, jl_unionall_t *u, jl_stenv_t *e, int8_t R, int param, jl_varbinding_t *vb) { jl_varbinding_t *btemp = e->vars; - // if the var for this unionall (based on identity) already appears somewhere - // in the environment, rename to get a fresh var. - // TODO: might need to look inside types in btemp->lb and btemp->ub int envsize = 0; while (btemp != NULL) { envsize++; @@ -3037,13 +3144,9 @@ static jl_value_t *intersect_unionall_(jl_value_t *t, jl_unionall_t *u, jl_stenv 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 = jl_rename_unionall(u); - break; - } btemp = btemp->prev; } + u = unalias_unionall(u, e); JL_GC_PUSH1(&u); vb->var = u->var; e->vars = vb; @@ -3134,7 +3237,7 @@ static jl_value_t *intersect_unionall(jl_value_t *t, jl_unionall_t *u, jl_stenv_ { jl_value_t *res = NULL; jl_savedenv_t se; - jl_varbinding_t vb = { u->var, u->var->lb, u->var->ub, R, 0, 0, 0, 0, 0, 0, 0, + jl_varbinding_t vb = { u->var, u->var->lb, u->var->ub, R, 0, 0, 0, 0, 0, 0, 0, 0, 0, e->invdepth, NULL, e->vars }; JL_GC_PUSH4(&res, &vb.lb, &vb.ub, &vb.innervars); save_env(e, &se, 1); @@ -3142,6 +3245,7 @@ static jl_value_t *intersect_unionall(jl_value_t *t, jl_unionall_t *u, jl_stenv_ if (is_leaf_typevar(u->var) && noinv && always_occurs_cov(u->body, u->var, param)) vb.constraintkind = 1; res = intersect_unionall_(t, u, e, R, param, &vb); + vb.intersected = 1; if (vb.limited) { // if the environment got too big, avoid tree recursion and propagate the flag if (e->vars) @@ -3218,10 +3322,12 @@ static jl_value_t *intersect_varargs(jl_vararg_t *vmx, jl_vararg_t *vmy, ssize_t assert(e->Loffset == 0); e->Loffset = offset; jl_varbinding_t *xb = NULL, *yb = NULL; + int8_t max_offsetx = 0, max_offsety = 0; if (xp2) { assert(jl_is_typevar(xp2)); xb = lookup(e, (jl_tvar_t*)xp2); if (xb) xb->intvalued = 1; + if (xb) max_offsetx = xb->max_offset; if (!yp2) i2 = bound_var_below((jl_tvar_t*)xp2, xb, e, 0); } @@ -3229,6 +3335,7 @@ static jl_value_t *intersect_varargs(jl_vararg_t *vmx, jl_vararg_t *vmy, ssize_t assert(jl_is_typevar(yp2)); yb = lookup(e, (jl_tvar_t*)yp2); if (yb) yb->intvalued = 1; + if (yb) max_offsety = yb->max_offset; if (!xp2) i2 = bound_var_below((jl_tvar_t*)yp2, yb, e, 1); } @@ -3243,14 +3350,27 @@ static jl_value_t *intersect_varargs(jl_vararg_t *vmx, jl_vararg_t *vmy, ssize_t } assert(e->Loffset == offset); e->Loffset = 0; - if (i2 == jl_bottom_type) + if (i2 == jl_bottom_type) { ii = (jl_value_t*)jl_bottom_type; - else if (xp2 && obviously_egal(xp1, ii) && obviously_egal(xp2, i2)) - ii = (jl_value_t*)vmx; - else if (yp2 && obviously_egal(yp1, ii) && obviously_egal(yp2, i2)) - ii = (jl_value_t*)vmy; - else - ii = (jl_value_t*)jl_wrap_vararg(ii, i2, 1); + } + else { + if (xb && !xb->intersected) { + xb->max_offset = max_offsetx; + if (offset > xb->max_offset && xb->max_offset >= 0) + xb->max_offset = offset > 32 ? 32 : offset; + } + if (yb && !yb->intersected) { + yb->max_offset = max_offsety; + if (-offset > yb->max_offset && yb->max_offset >= 0) + yb->max_offset = -offset > 32 ? 32 : -offset; + } + if (xp2 && obviously_egal(xp1, ii) && obviously_egal(xp2, i2)) + ii = (jl_value_t*)vmx; + else if (yp2 && obviously_egal(yp1, ii) && obviously_egal(yp2, i2)) + ii = (jl_value_t*)vmy; + else + ii = (jl_value_t*)jl_wrap_vararg(ii, i2, 1); + } JL_GC_POP(); return ii; } @@ -3269,6 +3389,24 @@ static jl_value_t *intersect_tuple(jl_datatype_t *xd, jl_datatype_t *yd, jl_sten llx += jl_unbox_long(jl_unwrap_vararg_num((jl_vararg_t *)jl_tparam(xd, lx-1))) - 1; if (vvy == JL_VARARG_INT) lly += jl_unbox_long(jl_unwrap_vararg_num((jl_vararg_t *)jl_tparam(yd, ly-1))) - 1; + if (vvx == JL_VARARG_BOUND && (vvy == JL_VARARG_BOUND || vvy == JL_VARARG_UNBOUND)) { + jl_value_t *xlen = jl_unwrap_vararg_num((jl_vararg_t*)jl_tparam(xd, lx-1)); + assert(xlen && jl_is_typevar(xlen)); + jl_varbinding_t *xb = lookup(e, (jl_tvar_t*)xlen); + if (xb && xb->intersected && xb->max_offset > 0) { + assert(xb->max_offset <= 32); + llx += xb->max_offset; + } + } + if (vvy == JL_VARARG_BOUND && (vvx == JL_VARARG_BOUND || vvx == JL_VARARG_UNBOUND)) { + jl_value_t *ylen = jl_unwrap_vararg_num((jl_vararg_t*)jl_tparam(yd, ly-1)); + assert(ylen && jl_is_typevar(ylen)); + jl_varbinding_t *yb = lookup(e, (jl_tvar_t*)ylen); + if (yb && yb->intersected && yb->max_offset > 0) { + assert(yb->max_offset <= 32); + lly += yb->max_offset; + } + } if ((vvx == JL_VARARG_NONE || vvx == JL_VARARG_INT) && (vvy == JL_VARARG_NONE || vvy == JL_VARARG_INT)) { @@ -3301,8 +3439,8 @@ static jl_value_t *intersect_tuple(jl_datatype_t *xd, jl_datatype_t *yd, jl_sten assert(i == j && i == np); break; } - if (xi && jl_is_vararg(xi)) vx = vvx != JL_VARARG_INT; - if (yi && jl_is_vararg(yi)) vy = vvy != JL_VARARG_INT; + if (xi && jl_is_vararg(xi)) vx = vvx == JL_VARARG_UNBOUND || (vvx == JL_VARARG_BOUND && i == llx - 1); + if (yi && jl_is_vararg(yi)) vy = vvy == JL_VARARG_UNBOUND || (vvy == JL_VARARG_BOUND && j == lly - 1); if (xi == NULL || yi == NULL) { if (vx && intersect_vararg_length(xi, lly+1-llx, e, 0)) { np = j; @@ -3637,7 +3775,7 @@ static jl_value_t *intersect(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int pa if (xlb == xub && ylb == yub && jl_has_typevar(xlb, (jl_tvar_t *)y) && jl_has_typevar(ylb, (jl_tvar_t *)x)) { - // specical case for e.g. + // special case for e.g. // 1) Val{Y}<:X<:Val{Y} && Val{X}<:Y<:Val{X} // 2) Y<:X<:Y && Val{X}<:Y<:Val{X} => Val{Y}<:Y<:Val{Y} ccheck = 0; @@ -3845,19 +3983,19 @@ static int merge_env(jl_stenv_t *e, jl_savedenv_t *se, int count) roots = se->roots; nroots = se->gcframe.nroots >> 2; } - int n = 0; + int m = 0, n = 0; jl_varbinding_t *v = e->vars; - v = e->vars; while (v != NULL) { if (count == 0) { // need to initialize this - se->buf[n] = 0; - se->buf[n+1] = 0; - se->buf[n+2] = 0; + se->buf[m] = 0; + se->buf[m+1] = 0; + se->buf[m+2] = 0; + se->buf[m+3] = v->max_offset; } + jl_value_t *b1, *b2; if (v->occurs) { - // only merge lb/ub/innervars if this var occurs. - jl_value_t *b1, *b2; + // only merge lb/ub if this var occurs. b1 = roots[n]; JL_GC_PROMISE_ROOTED(b1); // clang-sagc doesn't know this came from our GC frame b2 = v->lb; @@ -3868,24 +4006,30 @@ static int merge_env(jl_stenv_t *e, jl_savedenv_t *se, int count) b2 = v->ub; JL_GC_PROMISE_ROOTED(b2); // clang-sagc doesn't know the fields of this are stack GC roots roots[n+1] = b1 ? simple_join(b1, b2) : b2; - b1 = roots[n+2]; - JL_GC_PROMISE_ROOTED(b1); // clang-sagc doesn't know this came from our GC frame - b2 = (jl_value_t*)v->innervars; - JL_GC_PROMISE_ROOTED(b2); // clang-sagc doesn't know the fields of this are stack GC roots - if (b2 && b1 != b2) { - if (b1) - jl_array_ptr_1d_append((jl_array_t*)b1, (jl_array_t*)b2); - else - roots[n+2] = b2; - } // record the meeted vars. - se->buf[n] = 1; + se->buf[m] = 1; + } + // `innervars` might be re-sorted inside `finish_unionall`. + // We'd better always merge it. + b1 = roots[n+2]; + JL_GC_PROMISE_ROOTED(b1); // clang-sagc doesn't know this came from our GC frame + b2 = (jl_value_t*)v->innervars; + JL_GC_PROMISE_ROOTED(b2); // clang-sagc doesn't know the fields of this are stack GC roots + if (b2 && b1 != b2) { + if (b1) + jl_array_ptr_1d_append((jl_array_t*)b1, (jl_array_t*)b2); + else + roots[n+2] = b2; } // always merge occurs_inv/cov by max (never decrease) - if (v->occurs_inv > se->buf[n+1]) - se->buf[n+1] = v->occurs_inv; - if (v->occurs_cov > se->buf[n+2]) - se->buf[n+2] = v->occurs_cov; + if (v->occurs_inv > se->buf[m+1]) + se->buf[m+1] = v->occurs_inv; + if (v->occurs_cov > se->buf[m+2]) + se->buf[m+2] = v->occurs_cov; + // always merge max_offset by min + if (!v->intersected && v->max_offset < se->buf[m+3]) + se->buf[m+3] = v->max_offset; + m = m + 4; n = n + 3; v = v->prev; } @@ -3917,7 +4061,7 @@ static void final_merge_env(jl_stenv_t *e, jl_savedenv_t *me, jl_savedenv_t *se) } assert(nroots == current_env_length(e) * 3); assert(nroots % 3 == 0); - for (int n = 0; n < nroots; n = n + 3) { + for (int n = 0, m = 0; n < nroots; n += 3, m += 4) { if (merged[n] == NULL) merged[n] = saved[n]; if (merged[n+1] == NULL) @@ -3933,7 +4077,7 @@ static void final_merge_env(jl_stenv_t *e, jl_savedenv_t *me, jl_savedenv_t *se) else merged[n+2] = b2; } - me->buf[n] |= se->buf[n]; + me->buf[m] |= se->buf[m]; } } @@ -4304,6 +4448,211 @@ int jl_subtype_matching(jl_value_t *a, jl_value_t *b, jl_svec_t **penv) return sub; } +// type utils +static void check_diagonal(jl_value_t *t, jl_varbinding_t *troot, int param) +{ + if (jl_is_uniontype(t)) { + int i, len = 0; + jl_varbinding_t *v; + for (v = troot; v != NULL; v = v->prev) + len++; + int8_t *occurs = (int8_t *)alloca(len); + for (v = troot, i = 0; v != NULL; v = v->prev, i++) + occurs[i] = v->occurs_inv | (v->occurs_cov << 2); + check_diagonal(((jl_uniontype_t *)t)->a, troot, param); + for (v = troot, i = 0; v != NULL; v = v->prev, i++) { + int8_t occurs_inv = occurs[i] & 3; + int8_t occurs_cov = occurs[i] >> 2; + occurs[i] = v->occurs_inv | (v->occurs_cov << 2); + v->occurs_inv = occurs_inv; + v->occurs_cov = occurs_cov; + } + check_diagonal(((jl_uniontype_t *)t)->b, troot, param); + for (v = troot, i = 0; v != NULL; v = v->prev, i++) { + if (v->occurs_inv < (occurs[i] & 3)) + v->occurs_inv = occurs[i] & 3; + if (v->occurs_cov < (occurs[i] >> 2)) + v->occurs_cov = occurs[i] >> 2; + } + } + else if (jl_is_unionall(t)) { + assert(troot != NULL); + jl_varbinding_t *v1 = troot, *v2 = troot->prev; + while (v2 != NULL) { + if (v2->var == ((jl_unionall_t *)t)->var) { + v1->prev = v2->prev; + break; + } + v1 = v2; + v2 = v2->prev; + } + check_diagonal(((jl_unionall_t *)t)->body, troot, param); + v1->prev = v2; + } + else if (jl_is_datatype(t)) { + int nparam = jl_is_tuple_type(t) ? 1 : 2; + if (nparam < param) nparam = param; + for (size_t i = 0; i < jl_nparams(t); i++) { + check_diagonal(jl_tparam(t, i), troot, nparam); + } + } + else if (jl_is_vararg(t)) { + jl_value_t *T = jl_unwrap_vararg(t); + jl_value_t *N = jl_unwrap_vararg_num(t); + int n = (N && jl_is_long(N)) ? jl_unbox_long(N) : 2; + if (T && n > 0) check_diagonal(T, troot, param); + if (T && n > 1) check_diagonal(T, troot, param); + if (N) check_diagonal(N, troot, 2); + } + else if (jl_is_typevar(t)) { + jl_varbinding_t *v = troot; + for (; v != NULL; v = v->prev) { + if (v->var == (jl_tvar_t *)t) { + if (param == 1 && v->occurs_cov < 2) v->occurs_cov++; + if (param == 2 && v->occurs_inv < 2) v->occurs_inv++; + break; + } + } + if (v == NULL) + check_diagonal(((jl_tvar_t *)t)->ub, troot, 0); + } +} + +static jl_value_t *insert_nondiagonal(jl_value_t *type, jl_varbinding_t *troot, int widen2ub) +{ + if (jl_is_typevar(type)) { + int concretekind = widen2ub > 1 ? 0 : 1; + jl_varbinding_t *v = troot; + for (; v != NULL; v = v->prev) { + if (v->occurs_inv == 0 && + v->occurs_cov > concretekind && + v->var == (jl_tvar_t *)type) + break; + } + if (v != NULL) { + if (widen2ub) { + type = insert_nondiagonal(((jl_tvar_t *)type)->ub, troot, 2); + } + else { + // we must replace each covariant occurrence of newvar with a different newvar2<:newvar (diagonal rule) + if (v->innervars == NULL) + v->innervars = jl_alloc_array_1d(jl_array_any_type, 0); + jl_value_t *newvar = NULL, *lb = v->var->lb, *ub = (jl_value_t *)v->var; + jl_array_t *innervars = v->innervars; + JL_GC_PUSH4(&newvar, &lb, &ub, &innervars); + newvar = (jl_value_t *)jl_new_typevar(v->var->name, lb, ub); + jl_array_ptr_1d_push(innervars, newvar); + JL_GC_POP(); + type = newvar; + } + } + } + else if (jl_is_unionall(type)) { + jl_value_t *body = ((jl_unionall_t*)type)->body; + jl_tvar_t *var = ((jl_unionall_t*)type)->var; + jl_varbinding_t *v = troot; + for (; v != NULL; v = v->prev) { + if (v->var == var) + break; + } + if (v) v->var = NULL; // Temporarily remove `type->var` from binding list. + jl_value_t *newbody = insert_nondiagonal(body, troot, widen2ub); + if (v) v->var = var; // And restore it after inner insertation. + jl_value_t *newvar = NULL; + JL_GC_PUSH3(&newbody, &newvar, &type); + if (body == newbody || jl_has_typevar(newbody, var)) { + if (body != newbody) + type = jl_new_struct(jl_unionall_type, var, newbody); + // n.b. we do not widen lb, since that would be the wrong direction + newvar = insert_nondiagonal(var->ub, troot, widen2ub); + if (newvar != var->ub) { + newvar = (jl_value_t*)jl_new_typevar(var->name, var->lb, newvar); + newbody = jl_apply_type1(type, newvar); + type = jl_type_unionall((jl_tvar_t*)newvar, newbody); + } + } + JL_GC_POP(); + } + else if (jl_is_uniontype(type)) { + jl_value_t *a = ((jl_uniontype_t*)type)->a; + jl_value_t *b = ((jl_uniontype_t*)type)->b; + jl_value_t *newa = NULL; + jl_value_t *newb = NULL; + JL_GC_PUSH2(&newa, &newb); + newa = insert_nondiagonal(a, troot, widen2ub); + newb = insert_nondiagonal(b, troot, widen2ub); + if (newa != a || newb != b) + type = simple_union(newa, newb); + JL_GC_POP(); + } + else if (jl_is_vararg(type)) { + // As for Vararg we'd better widen it's var to ub as otherwise they are still diagonal + jl_value_t *t = jl_unwrap_vararg(type); + jl_value_t *n = jl_unwrap_vararg_num(type); + if (widen2ub == 0) + widen2ub = !(n && jl_is_long(n)) || jl_unbox_long(n) > 1; + jl_value_t *newt; + JL_GC_PUSH2(&newt, &n); + newt = insert_nondiagonal(t, troot, widen2ub); + if (t != newt) + type = (jl_value_t *)jl_wrap_vararg(newt, n, 0); + JL_GC_POP(); + } + else if (jl_is_datatype(type)) { + if (jl_is_tuple_type(type)) { + jl_svec_t *newparams = NULL; + jl_value_t *newelt = NULL; + JL_GC_PUSH2(&newparams, &newelt); + for (size_t i = 0; i < jl_nparams(type); i++) { + jl_value_t *elt = jl_tparam(type, i); + newelt = insert_nondiagonal(elt, troot, widen2ub); + if (elt != newelt) { + if (!newparams) + newparams = jl_svec_copy(((jl_datatype_t*)type)->parameters); + jl_svecset(newparams, i, newelt); + } + } + if (newparams) + type = (jl_value_t*)jl_apply_tuple_type(newparams, 1); + JL_GC_POP(); + } + } + return type; +} + +static jl_value_t *_widen_diagonal(jl_value_t *t, jl_varbinding_t *troot) { + check_diagonal(t, troot, 0); + int any_concrete = 0; + for (jl_varbinding_t *v = troot; v != NULL; v = v->prev) + any_concrete |= v->occurs_cov > 1 && v->occurs_inv == 0; + if (!any_concrete) + return t; // no diagonal + return insert_nondiagonal(t, troot, 0); +} + +static jl_value_t *widen_diagonal(jl_value_t *t, jl_unionall_t *u, jl_varbinding_t *troot) +{ + jl_varbinding_t vb = { u->var, NULL, NULL, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, troot }; + jl_value_t *nt; + JL_GC_PUSH2(&vb.innervars, &nt); + if (jl_is_unionall(u->body)) + nt = widen_diagonal(t, (jl_unionall_t *)u->body, &vb); + else + nt = _widen_diagonal(t, &vb); + if (vb.innervars != NULL) { + for (size_t i = 0; i < jl_array_nrows(vb.innervars); i++) { + jl_tvar_t *var = (jl_tvar_t*)jl_array_ptr_ref(vb.innervars, i); + nt = jl_type_unionall(var, nt); + } + } + JL_GC_POP(); + return nt; +} + +JL_DLLEXPORT jl_value_t *jl_widen_diagonal(jl_value_t *t, jl_unionall_t *ua) +{ + return widen_diagonal(t, ua, NULL); +} // specificity comparison @@ -4877,6 +5226,26 @@ JL_DLLEXPORT int jl_type_morespecific_no_subtype(jl_value_t *a, jl_value_t *b) return type_morespecific_(a, b, a, b, 0, NULL); } +// Equivalent to `jl_type_morespecific` of the signatures, except that more recent +// methods are more specific, iff the methods signatures are type-equal +JL_DLLEXPORT int jl_method_morespecific(jl_method_t *ma, jl_method_t *mb) +{ + jl_value_t *a = (jl_value_t*)ma->sig; + jl_value_t *b = (jl_value_t*)mb->sig; + if (obviously_disjoint(a, b, 1)) + return 0; + if (jl_has_free_typevars(a) || jl_has_free_typevars(b)) + return 0; + if (jl_subtype(b, a)) { + if (jl_types_equal(a, b)) + return jl_atomic_load_relaxed(&ma->primary_world) > jl_atomic_load_relaxed(&mb->primary_world); + return 0; + } + if (jl_subtype(a, b)) + return 1; + return type_morespecific_(a, b, a, b, 0, NULL); +} + #ifdef __cplusplus } #endif diff --git a/src/support/dtypes.h b/src/support/dtypes.h index da570921c101c..57f4fa99f0016 100644 --- a/src/support/dtypes.h +++ b/src/support/dtypes.h @@ -96,27 +96,23 @@ typedef intptr_t ssize_t; #include #define LITTLE_ENDIAN __LITTLE_ENDIAN #define BIG_ENDIAN __BIG_ENDIAN -#define PDP_ENDIAN __PDP_ENDIAN #define BYTE_ORDER __BYTE_ORDER #endif -#if defined(__APPLE__) || defined(__FreeBSD__) +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) #include #define __LITTLE_ENDIAN LITTLE_ENDIAN #define __BIG_ENDIAN BIG_ENDIAN -#define __PDP_ENDIAN PDP_ENDIAN #define __BYTE_ORDER BYTE_ORDER #endif #ifdef _OS_WINDOWS_ #define __LITTLE_ENDIAN 1234 #define __BIG_ENDIAN 4321 -#define __PDP_ENDIAN 3412 #define __BYTE_ORDER __LITTLE_ENDIAN #define __FLOAT_WORD_ORDER __LITTLE_ENDIAN #define LITTLE_ENDIAN __LITTLE_ENDIAN #define BIG_ENDIAN __BIG_ENDIAN -#define PDP_ENDIAN __PDP_ENDIAN #define BYTE_ORDER __BYTE_ORDER #endif diff --git a/src/support/ios.c b/src/support/ios.c index c98c529991642..7f70112c82cc0 100644 --- a/src/support/ios.c +++ b/src/support/ios.c @@ -210,8 +210,10 @@ static char *_buf_realloc(ios_t *s, size_t sz) if (temp == NULL) return NULL; s->ownbuf = 1; - if (s->size > 0) + if (s->size > 0) { + assert(s->buf != NULL); memcpy(temp, s->buf, (size_t)s->size); + } } s->buf = temp; @@ -600,12 +602,12 @@ int ios_eof(ios_t *s) { if (s->state == bst_rd && s->bpos < s->size) return 0; + if (s->_eof) + return 1; if (s->bm == bm_mem) - return (s->_eof ? 1 : 0); + return 0; if (s->fd == -1) return 1; - if (s->_eof) - return 1; return 0; /* if (_fd_available(s->fd)) @@ -615,6 +617,12 @@ int ios_eof(ios_t *s) */ } +void ios_reseteof(ios_t *s) +{ + if (s->bm != bm_mem && s->fd != -1) + s->_eof = 0; +} + int ios_eof_blocking(ios_t *s) { if (s->state == bst_rd && s->bpos < s->size) @@ -721,8 +729,10 @@ char *ios_take_buffer(ios_t *s, size_t *psize) buf = (char*)LLT_ALLOC((size_t)s->size + 1); if (buf == NULL) return NULL; - if (s->size) + if (s->size) { + assert(s->buf != NULL); memcpy(buf, s->buf, (size_t)s->size); + } } else if (s->size == s->maxsize) { buf = (char*)LLT_REALLOC(s->buf, (size_t)s->size + 1); @@ -1219,7 +1229,9 @@ char *ios_readline(ios_t *s) ios_mem(&dest, 0); ios_copyuntil(&dest, s, '\n', 1); size_t n; - return ios_take_buffer(&dest, &n); + char * ret = ios_take_buffer(&dest, &n); + ios_close(&dest); + return ret; } extern int vasprintf(char **strp, const char *fmt, va_list ap); diff --git a/src/support/platform.h b/src/support/platform.h index 56f8cafbc89fa..a0dd84c9c20b6 100644 --- a/src/support/platform.h +++ b/src/support/platform.h @@ -8,7 +8,7 @@ * based of compiler-specific pre-defined macros. It is based on the * information that can be found at the following address: * - * http://sourceforge.net/p/predef/wiki/Home/ + * https://sourceforge.net/p/predef/wiki/Home/ * * Possible values include: * Compiler: @@ -16,6 +16,7 @@ * _COMPILER_GCC_ * OS: * _OS_FREEBSD_ + * _OS_OPENBSD_ * _OS_LINUX_ * _OS_WINDOWS_ * _OS_DARWIN_ @@ -81,6 +82,8 @@ #if defined(__FreeBSD__) #define _OS_FREEBSD_ +#elif defined(__OpenBSD__) +#define _OS_OPENBSD_ #elif defined(__linux__) #define _OS_LINUX_ #elif defined(_WIN32) || defined(_WIN64) diff --git a/src/support/strptime.c b/src/support/strptime.c index ab75ee05ee8db..27c86c9e4f2b8 100644 --- a/src/support/strptime.c +++ b/src/support/strptime.c @@ -134,7 +134,7 @@ static const char * const nadt[5] = { /* * Table to determine the ordinal date for the start of a month. - * Ref: http://en.wikipedia.org/wiki/ISO_week_date + * Ref: https://en.wikipedia.org/wiki/ISO_week_date */ static const int start_of_month[2][13] = { /* non-leap year */ @@ -147,7 +147,7 @@ static const int start_of_month[2][13] = { * Calculate the week day of the first day of a year. Valid for * the Gregorian calendar, which began Sept 14, 1752 in the UK * and its colonies. Ref: - * http://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week + * https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week */ static int diff --git a/src/support/strtod.c b/src/support/strtod.c index 24f556d0c086b..e0ad1bf33435a 100644 --- a/src/support/strtod.c +++ b/src/support/strtod.c @@ -11,7 +11,7 @@ extern "C" { #endif -#if !defined(_OS_WINDOWS_) +#if !defined(_OS_WINDOWS_) && !defined(__OpenBSD__) // This code path should be used for systems that support the strtod_l function // Cache locale object diff --git a/src/support/utf8.c b/src/support/utf8.c index 42a420fb0c499..02f541492b0f0 100644 --- a/src/support/utf8.c +++ b/src/support/utf8.c @@ -29,9 +29,9 @@ #include #define snprintf _snprintf #else -#ifndef __FreeBSD__ +#if !defined(__FreeBSD__) && !defined(__OpenBSD__) #include -#endif /* __FreeBSD__ */ +#endif /* !__FreeBSD__ && !__OpenBSD__ */ #endif #include @@ -410,7 +410,7 @@ int u8_escape_wchar(char *buf, size_t sz, uint32_t ch) } size_t u8_escape(char *buf, size_t sz, const char *src, size_t *pi, size_t end, - int escape_quotes, int ascii) + const char *escapes, int ascii) { size_t i = *pi, i0; uint32_t ch; @@ -420,12 +420,9 @@ size_t u8_escape(char *buf, size_t sz, const char *src, size_t *pi, size_t end, while (i 0x9f) return 0; - // Check for overlong encoding - if (byt == 0xe0 && *pnt < 0xa0) return 0; + // Check for overlong encoding + if (byt == 0xe0 && *pnt < 0xa0) return 0; pnt += 2; } else { // 4-byte sequence // Must have 3 valid continuation characters diff --git a/src/support/utf8.h b/src/support/utf8.h index 1d8e31c043838..eab86f602ee61 100644 --- a/src/support/utf8.h +++ b/src/support/utf8.h @@ -12,7 +12,7 @@ extern "C" { /* is c the start of a utf8 sequence? */ #define isutf(c) (((c)&0xC0)!=0x80) -#define UEOF ((uint32_t)-1) +#define UEOF (UINT32_MAX) /* convert UTF-8 data to wide character */ size_t u8_toucs(uint32_t *dest, size_t sz, const char *src, size_t srcsz); @@ -63,7 +63,7 @@ int u8_escape_wchar(char *buf, size_t sz, uint32_t ch); sz is buf size in bytes. must be at least 12. - if escape_quotes is nonzero, quote characters will be escaped. + if escapes is given, given characters will also be escaped (in addition to \\). if ascii is nonzero, the output is 7-bit ASCII, no UTF-8 survives. @@ -75,7 +75,7 @@ int u8_escape_wchar(char *buf, size_t sz, uint32_t ch); returns number of bytes placed in buf, including a NUL terminator. */ size_t u8_escape(char *buf, size_t sz, const char *src, size_t *pi, size_t end, - int escape_quotes, int ascii); + const char *escapes, int ascii); /* utility predicates used by the above */ int octal_digit(char c); diff --git a/src/support/win32-clang-ABI-bug/optional b/src/support/win32-clang-ABI-bug/optional new file mode 100644 index 0000000000000..135888d415f73 --- /dev/null +++ b/src/support/win32-clang-ABI-bug/optional @@ -0,0 +1,531 @@ +//===- optional.h - Simple variant for passing optional values --*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file provides optional, a template class modeled in the spirit of +/// OCaml's 'opt' variant. The idea is to strongly type whether or not +/// a value can be optional. +/// +//===----------------------------------------------------------------------===// + +#ifndef JL_OPTIONAL_H +#define JL_OPTIONAL_H + +//#include "llvm/ADT/STLForwardCompat.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/type_traits.h" +#include +#include +#include +#include + +namespace std { + +//#include "llvm/ADT/None.h" +/// A simple null object to allow implicit construction of optional +/// and similar types without having to spell out the specialization's name. +// (constant value 1 in an attempt to workaround MSVC build issue... ) +enum class nullopt_t { nullopt = 1 }; +const nullopt_t nullopt = nullopt_t::nullopt; + +class raw_ostream; + +namespace optional_detail { + +/// Storage for any type. +// +// The specialization condition intentionally uses +// llvm::is_trivially_{copy/move}_constructible instead of +// std::is_trivially_{copy/move}_constructible. GCC versions prior to 7.4 may +// instantiate the copy/move constructor of `T` when +// std::is_trivially_{copy/move}_constructible is instantiated. This causes +// compilation to fail if we query the trivially copy/move constructible +// property of a class which is not copy/move constructible. +// +// The current implementation of OptionalStorage insists that in order to use +// the trivial specialization, the value_type must be trivially copy +// constructible and trivially copy assignable due to =default implementations +// of the copy/move constructor/assignment. It does not follow that this is +// necessarily the case std::is_trivially_copyable is true (hence the expanded +// specialization condition). +// +// The move constructible / assignable conditions emulate the remaining behavior +// of std::is_trivially_copyable. +template ::value && + std::is_trivially_copy_assignable::value && + (llvm::is_trivially_move_constructible::value || + !std::is_move_constructible::value) && + (std::is_trivially_move_assignable::value || + !std::is_move_assignable::value))> +class OptionalStorage { + union { + char empty; + T val; + }; + bool hasVal = false; + +public: + ~OptionalStorage() { reset(); } + + constexpr OptionalStorage() noexcept : empty() {} + + constexpr OptionalStorage(OptionalStorage const &other) : OptionalStorage() { + if (other.has_value()) { + emplace(other.val); + } + } + constexpr OptionalStorage(OptionalStorage &&other) : OptionalStorage() { + if (other.has_value()) { + emplace(std::move(other.val)); + } + } + + template + constexpr explicit OptionalStorage(in_place_t, Args &&...args) + : val(std::forward(args)...), hasVal(true) {} + + void reset() noexcept { + if (hasVal) { + val.~T(); + hasVal = false; + } + } + + constexpr bool has_value() const noexcept { return hasVal; } + constexpr bool hasValue() const noexcept { return hasVal; } + + T &value() &noexcept { + assert(hasVal); + return val; + } + T &getValue() &noexcept { + assert(hasVal); + return val; + } + constexpr T const &value() const &noexcept { + assert(hasVal); + return val; + } + constexpr T const &getValue() const &noexcept { + assert(hasVal); + return val; + } + T &&value() &&noexcept { + assert(hasVal); + return std::move(val); + } + T &&getValue() &&noexcept { + assert(hasVal); + return std::move(val); + } + + template void emplace(Args &&...args) { + reset(); + ::new ((void *)std::addressof(val)) T(std::forward(args)...); + hasVal = true; + } + + OptionalStorage &operator=(T const &y) { + if (has_value()) { + val = y; + } else { + ::new ((void *)std::addressof(val)) T(y); + hasVal = true; + } + return *this; + } + OptionalStorage &operator=(T &&y) { + if (has_value()) { + val = std::move(y); + } else { + ::new ((void *)std::addressof(val)) T(std::move(y)); + hasVal = true; + } + return *this; + } + + OptionalStorage &operator=(OptionalStorage const &other) { + if (other.has_value()) { + if (has_value()) { + val = other.val; + } else { + ::new ((void *)std::addressof(val)) T(other.val); + hasVal = true; + } + } else { + reset(); + } + return *this; + } + + OptionalStorage &operator=(OptionalStorage &&other) { + if (other.has_value()) { + if (has_value()) { + val = std::move(other.val); + } else { + ::new ((void *)std::addressof(val)) T(std::move(other.val)); + hasVal = true; + } + } else { + reset(); + } + return *this; + } +}; + +template class OptionalStorage { + union { + char empty; + T val; + }; + bool hasVal = false; + +public: + ~OptionalStorage() = default; + + constexpr OptionalStorage() noexcept : empty{} {} + + constexpr OptionalStorage(OptionalStorage const &other) = default; + constexpr OptionalStorage(OptionalStorage &&other) = default; + + OptionalStorage &operator=(OptionalStorage const &other) = default; + OptionalStorage &operator=(OptionalStorage &&other) = default; + + template + constexpr explicit OptionalStorage(in_place_t, Args &&...args) + : val(std::forward(args)...), hasVal(true) {} + + void reset() noexcept { + if (hasVal) { + val.~T(); + hasVal = false; + } + } + + constexpr bool has_value() const noexcept { return hasVal; } + constexpr bool hasValue() const noexcept { return hasVal; } + + T &value() &noexcept { + assert(hasVal); + return val; + } + T &getValue() &noexcept { + assert(hasVal); + return val; + } + constexpr T const &value() const &noexcept { + assert(hasVal); + return val; + } + constexpr T const &getValue() const &noexcept { + assert(hasVal); + return val; + } + T &&value() &&noexcept { + assert(hasVal); + return std::move(val); + } + T &&getValue() &&noexcept { + assert(hasVal); + return std::move(val); + } + + template void emplace(Args &&...args) { + reset(); + ::new ((void *)std::addressof(val)) T(std::forward(args)...); + hasVal = true; + } + + OptionalStorage &operator=(T const &y) { + if (has_value()) { + val = y; + } else { + ::new ((void *)std::addressof(val)) T(y); + hasVal = true; + } + return *this; + } + OptionalStorage &operator=(T &&y) { + if (has_value()) { + val = std::move(y); + } else { + ::new ((void *)std::addressof(val)) T(std::move(y)); + hasVal = true; + } + return *this; + } +}; + +} // namespace optional_detail + +template class optional { + optional_detail::OptionalStorage Storage; + +public: + using value_type = T; + + constexpr optional() = default; + constexpr optional(nullopt_t) {} + + constexpr optional(const T &y) : Storage(in_place, y) {} + constexpr optional(const optional &O) = default; + + constexpr optional(T &&y) : Storage(in_place, std::move(y)) {} + constexpr optional(optional &&O) = default; + + template + constexpr optional(in_place_t, ArgTypes &&...Args) + : Storage(in_place, std::forward(Args)...) {} + + optional &operator=(T &&y) { + Storage = std::move(y); + return *this; + } + optional &operator=(optional &&O) = default; + + /// Create a new object by constructing it in place with the given arguments. + template void emplace(ArgTypes &&... Args) { + Storage.emplace(std::forward(Args)...); + } + + static constexpr optional create(const T *y) { + return y ? optional(*y) : optional(); + } + + optional &operator=(const T &y) { + Storage = y; + return *this; + } + optional &operator=(const optional &O) = default; + + void reset() { Storage.reset(); } + + constexpr const T *getPointer() const { return &Storage.value(); } + T *getPointer() { return &Storage.value(); } + constexpr const T &value() const & { return Storage.value(); } + constexpr const T &getValue() const & { return Storage.value(); } + T &value() & { return Storage.value(); } + T &getValue() & { return Storage.value(); } + + constexpr explicit operator bool() const { return has_value(); } + constexpr bool has_value() const { return Storage.has_value(); } + constexpr bool hasValue() const { return Storage.has_value(); } + constexpr const T *operator->() const { return getPointer(); } + T *operator->() { return getPointer(); } + constexpr const T &operator*() const & { return value(); } + T &operator*() & { return value(); } + + template constexpr T value_or(U &&alt) const & { + return has_value() ? value() : std::forward(alt); + } + template + [[deprecated("Use value_or instead.")]] constexpr T + getValueOr(U &&alt) const & { + return has_value() ? value() : std::forward(alt); + } + + /// Apply a function to the value if present; otherwise return nullopt. + template + auto map(const Function &F) const & -> optional { + if (*this) + return F(value()); + return nullopt; + } + + T &&value() && { return std::move(Storage.value()); } + T &&getValue() && { return std::move(Storage.value()); } + T &&operator*() && { return std::move(Storage.value()); } + + template T value_or(U &&alt) && { + return has_value() ? std::move(value()) : std::forward(alt); + } + template + [[deprecated("Use value_or instead.")]] T getValueOr(U &&alt) && { + return has_value() ? std::move(value()) : std::forward(alt); + } + + /// Apply a function to the value if present; otherwise return nullopt. + template + auto map(const Function &F) + && -> optional { + if (*this) + return F(std::move(*this).value()); + return nullopt; + } +}; + +//template llvm::hash_code hash_value(const optional &O) { +// return O ? hash_combine(true, *O) : hash_value(false); +//} + +template +constexpr bool operator==(const optional &X, const optional &Y) { + if (X && Y) + return *X == *Y; + return X.has_value() == Y.has_value(); +} + +template +constexpr bool operator!=(const optional &X, const optional &Y) { + return !(X == Y); +} + +template +constexpr bool operator<(const optional &X, const optional &Y) { + if (X && Y) + return *X < *Y; + return X.has_value() < Y.has_value(); +} + +template +constexpr bool operator<=(const optional &X, const optional &Y) { + return !(Y < X); +} + +template +constexpr bool operator>(const optional &X, const optional &Y) { + return Y < X; +} + +template +constexpr bool operator>=(const optional &X, const optional &Y) { + return !(X < Y); +} + +template +constexpr bool operator==(const optional &X, nullopt_t) { + return !X; +} + +template +constexpr bool operator==(nullopt_t, const optional &X) { + return X == nullopt; +} + +template +constexpr bool operator!=(const optional &X, nullopt_t) { + return !(X == nullopt); +} + +template +constexpr bool operator!=(nullopt_t, const optional &X) { + return X != nullopt; +} + +template constexpr bool operator<(const optional &, nullopt_t) { + return false; +} + +template constexpr bool operator<(nullopt_t, const optional &X) { + return X.has_value(); +} + +template +constexpr bool operator<=(const optional &X, nullopt_t) { + return !(nullopt < X); +} + +template +constexpr bool operator<=(nullopt_t, const optional &X) { + return !(X < nullopt); +} + +template constexpr bool operator>(const optional &X, nullopt_t) { + return nullopt < X; +} + +template constexpr bool operator>(nullopt_t, const optional &X) { + return X < nullopt; +} + +template +constexpr bool operator>=(const optional &X, nullopt_t) { + return nullopt <= X; +} + +template +constexpr bool operator>=(nullopt_t, const optional &X) { + return X <= nullopt; +} + +template +constexpr bool operator==(const optional &X, const T &Y) { + return X && *X == Y; +} + +template +constexpr bool operator==(const T &X, const optional &Y) { + return Y && X == *Y; +} + +template +constexpr bool operator!=(const optional &X, const T &Y) { + return !(X == Y); +} + +template +constexpr bool operator!=(const T &X, const optional &Y) { + return !(X == Y); +} + +template +constexpr bool operator<(const optional &X, const T &Y) { + return !X || *X < Y; +} + +template +constexpr bool operator<(const T &X, const optional &Y) { + return Y && X < *Y; +} + +template +constexpr bool operator<=(const optional &X, const T &Y) { + return !(Y < X); +} + +template +constexpr bool operator<=(const T &X, const optional &Y) { + return !(Y < X); +} + +template +constexpr bool operator>(const optional &X, const T &Y) { + return Y < X; +} + +template +constexpr bool operator>(const T &X, const optional &Y) { + return Y < X; +} + +template +constexpr bool operator>=(const optional &X, const T &Y) { + return !(X < Y); +} + +template +constexpr bool operator>=(const T &X, const optional &Y) { + return !(X < Y); +} + +raw_ostream &operator<<(raw_ostream &OS, nullopt_t); + +template () + << std::declval())> +raw_ostream &operator<<(raw_ostream &OS, const optional &O) { + if (O) + OS << *O; + else + OS << nullopt; + return OS; +} + +} // end namespace + +#endif // JL_OPTIONAL_H diff --git a/src/symbol.c b/src/symbol.c index c9c0c0e533924..b488a0cc77065 100644 --- a/src/symbol.c +++ b/src/symbol.c @@ -129,7 +129,7 @@ JL_DLLEXPORT jl_sym_t *jl_gensym(void) { char name[16]; char *n; - uint32_t ctr = jl_atomic_fetch_add(&gs_ctr, 1); + uint32_t ctr = jl_atomic_fetch_add_relaxed(&gs_ctr, 1); n = uint2str(&name[2], sizeof(name)-2, ctr, 10); *(--n) = '#'; *(--n) = '#'; return jl_symbol(n); @@ -153,7 +153,7 @@ JL_DLLEXPORT jl_sym_t *jl_tagged_gensym(const char *str, size_t len) name[1] = '#'; name[2 + len] = '#'; memcpy(name + 2, str, len); - uint32_t ctr = jl_atomic_fetch_add(&gs_ctr, 1); + uint32_t ctr = jl_atomic_fetch_add_relaxed(&gs_ctr, 1); n = uint2str(gs_name, sizeof(gs_name), ctr, 10); memcpy(name + 3 + len, n, sizeof(gs_name) - (n - gs_name)); jl_sym_t *sym = _jl_symbol(name, alloc_len - (n - gs_name)- 1); diff --git a/src/sys.c b/src/sys.c index 6da4231c9b7e2..107a8f7637763 100644 --- a/src/sys.c +++ b/src/sys.c @@ -280,14 +280,15 @@ JL_DLLEXPORT jl_value_t *jl_readuntil(ios_t *s, uint8_t delim, uint8_t str, uint return str; } a = jl_alloc_array_1d(jl_array_uint8_type, n - nchomp); - memcpy(jl_array_data(a), s->buf + s->bpos, n - nchomp); + memcpy(jl_array_data(a, uint8_t), s->buf + s->bpos, n - nchomp); s->bpos += n; } else { a = jl_alloc_array_1d(jl_array_uint8_type, 80); ios_t dest; ios_mem(&dest, 0); - ios_setbuf(&dest, (char*)a->data, 80, 0); + char *mem = jl_array_data(a, char); + ios_setbuf(&dest, (char*)mem, 80, 0); size_t n = ios_copyuntil(&dest, s, delim, 1); if (chomp && n > 0 && dest.buf[n - 1] == delim) { n--; @@ -298,12 +299,11 @@ JL_DLLEXPORT jl_value_t *jl_readuntil(ios_t *s, uint8_t delim, uint8_t str, uint assert(truncret == 0); (void)truncret; // ensure the variable is used to avoid warnings } - if (dest.buf != a->data) { + if (dest.buf != mem) { a = jl_take_buffer(&dest); } else { - a->length = n; - a->nrows = n; + a->dimsize[0] = n; } if (str) { JL_GC_PUSH1(&a); @@ -631,10 +631,42 @@ JL_DLLEXPORT long jl_SC_CLK_TCK(void) #ifndef _OS_WINDOWS_ return sysconf(_SC_CLK_TCK); #else - return 0; + return 1000; /* uv_cpu_info returns times in ms on Windows */ #endif } +#ifdef _OS_OPENBSD_ +// Helper for jl_pathname_for_handle() +struct dlinfo_data { + void *searched; + const char *result; +}; + +static int dlinfo_helper(struct dl_phdr_info *info, size_t size, void *vdata) +{ + struct dlinfo_data *data = (struct dlinfo_data *)vdata; + void *handle; + + /* ensure dl_phdr_info at compile-time to be compatible with the one at runtime */ + if (sizeof(*info) < size) + return -1; + + /* dlopen the name */ + handle = dlopen(info->dlpi_name, RTLD_LAZY | RTLD_NOLOAD); + if (handle == NULL) + return 0; + + /* check if the opened library is the same as the searched handle */ + if (data->searched == handle) + data->result = info->dlpi_name; + + dlclose(handle); + + /* continue if still not found */ + return (data->result != NULL); +} +#endif + // Takes a handle (as returned from dlopen()) and returns the absolute path to the image loaded JL_DLLEXPORT const char *jl_pathname_for_handle(void *handle) { @@ -677,6 +709,14 @@ JL_DLLEXPORT const char *jl_pathname_for_handle(void *handle) free(pth16); return filepath; +#elif defined(_OS_OPENBSD_) + struct dlinfo_data data = { + .searched = handle, + .result = NULL, + }; + dl_iterate_phdr(&dlinfo_helper, &data); + return data.result; + #else // Linux, FreeBSD, ... struct link_map *map; @@ -754,11 +794,11 @@ JL_DLLEXPORT size_t jl_maxrss(void) // FIXME: `rusage` is available on OpenBSD, DragonFlyBSD and NetBSD as well. // All of them return `ru_maxrss` in kilobytes. -#elif defined(_OS_LINUX_) || defined(_OS_DARWIN_) || defined (_OS_FREEBSD_) +#elif defined(_OS_LINUX_) || defined(_OS_DARWIN_) || defined (_OS_FREEBSD_) || defined (_OS_OPENBSD_) struct rusage rusage; getrusage( RUSAGE_SELF, &rusage ); -#if defined(_OS_LINUX_) || defined(_OS_FREEBSD_) +#if defined(_OS_LINUX_) || defined(_OS_FREEBSD_) || defined (_OS_OPENBSD_) return (size_t)(rusage.ru_maxrss * 1024); #else return (size_t)rusage.ru_maxrss; diff --git a/src/task.c b/src/task.c index dca924c3ae09c..5f28c3c243bfe 100644 --- a/src/task.c +++ b/src/task.c @@ -42,6 +42,13 @@ extern "C" { #endif #if defined(_COMPILER_ASAN_ENABLED_) +#if __GLIBC__ +#include +// Bypass the ASAN longjmp wrapper - we are unpoisoning the stack ourselves, +// since ASAN normally unpoisons far too much. +// c.f. interceptor in jl_dlopen as well +void (*real_siglongjmp)(jmp_buf _Buf, int _Value) = NULL; +#endif static inline void sanitizer_start_switch_fiber(jl_ptls_t ptls, jl_task_t *from, jl_task_t *to) { if (to->copy_stack) __sanitizer_start_switch_fiber(&from->ctx.asan_fake_stack, (char*)ptls->stackbase-ptls->stacksize, ptls->stacksize); @@ -127,18 +134,11 @@ static inline void sanitizer_finish_switch_fiber(jl_task_t *last, jl_task_t *cur #define ROOT_TASK_STACK_ADJUSTMENT 3000000 #endif -#ifdef JL_HAVE_ASYNCIFY -// Switching logic is implemented in JavaScript -#define STATIC_OR_JS JL_DLLEXPORT -#else -#define STATIC_OR_JS static -#endif - static char *jl_alloc_fiber(_jl_ucontext_t *t, size_t *ssize, jl_task_t *owner) JL_NOTSAFEPOINT; -STATIC_OR_JS void jl_set_fiber(jl_ucontext_t *t); -STATIC_OR_JS void jl_swap_fiber(jl_ucontext_t *lastt, jl_ucontext_t *t); -STATIC_OR_JS void jl_start_fiber_swap(jl_ucontext_t *savet, jl_ucontext_t *t); -STATIC_OR_JS void jl_start_fiber_set(jl_ucontext_t *t); +static void jl_set_fiber(jl_ucontext_t *t); +static void jl_swap_fiber(jl_ucontext_t *lastt, jl_ucontext_t *t); +static void jl_start_fiber_swap(jl_ucontext_t *savet, jl_ucontext_t *t); +static void jl_start_fiber_set(jl_ucontext_t *t); #ifdef ALWAYS_COPY_STACKS # ifndef COPY_STACKS @@ -197,7 +197,7 @@ static void JL_NO_ASAN JL_NO_MSAN memcpy_stack_a16(uint64_t *to, uint64_t *from, memcpy_noasan((char*)to_addr, (char*)from_addr, shadow_nb); memcpy_a16_noasan(jl_assume_aligned(to, 16), jl_assume_aligned(from, 16), nb); #elif defined(_COMPILER_MSAN_ENABLED_) -# warning This function is imcompletely implemented for MSAN (TODO). +# warning This function is incompletely implemented for MSAN (TODO). memcpy((char*)jl_assume_aligned(to, 16), (char*)jl_assume_aligned(from, 16), nb); #else memcpy((char*)jl_assume_aligned(to, 16), (char*)jl_assume_aligned(from, 16), nb); @@ -290,18 +290,17 @@ JL_NO_ASAN static void restore_stack2(jl_task_t *t, jl_ptls_t ptls, jl_task_t *l /* Rooted by the base module */ static _Atomic(jl_function_t*) task_done_hook_func JL_GLOBALLY_ROOTED = NULL; -void JL_NORETURN jl_finish_task(jl_task_t *t) +void JL_NORETURN jl_finish_task(jl_task_t *ct) { - jl_task_t *ct = jl_current_task; JL_PROBE_RT_FINISH_TASK(ct); JL_SIGATOMIC_BEGIN(); - if (jl_atomic_load_relaxed(&t->_isexception)) - jl_atomic_store_release(&t->_state, JL_TASK_STATE_FAILED); + if (jl_atomic_load_relaxed(&ct->_isexception)) + jl_atomic_store_release(&ct->_state, JL_TASK_STATE_FAILED); else - jl_atomic_store_release(&t->_state, JL_TASK_STATE_DONE); - if (t->copy_stack) { // early free of stkbuf - asan_free_copy_stack(t->stkbuf, t->bufsz); - t->stkbuf = NULL; + jl_atomic_store_release(&ct->_state, JL_TASK_STATE_DONE); + if (ct->copy_stack) { // early free of stkbuf + asan_free_copy_stack(ct->stkbuf, ct->bufsz); + ct->stkbuf = NULL; } // ensure that state is cleared ct->ptls->in_finalizer = 0; @@ -315,12 +314,12 @@ void JL_NORETURN jl_finish_task(jl_task_t *t) jl_atomic_store_release(&task_done_hook_func, done); } if (done != NULL) { - jl_value_t *args[2] = {done, (jl_value_t*)t}; + jl_value_t *args[2] = {done, (jl_value_t*)ct}; JL_TRY { jl_apply(args, 2); } JL_CATCH { - jl_no_exc_handler(jl_current_exception(), ct); + jl_no_exc_handler(jl_current_exception(ct), ct); } } jl_gc_debug_critical_error(); @@ -688,7 +687,7 @@ JL_DLLEXPORT JL_NORETURN void jl_no_exc_handler(jl_value_t *e, jl_task_t *ct) // NULL exception objects are used when rethrowing. we don't have a handler to process // the exception stack, so at least report the exception at the top of the stack. if (!e) - e = jl_current_exception(); + e = jl_current_exception(ct); jl_printf((JL_STREAM*)STDERR_FILENO, "fatal: error thrown and no exception handler available.\n"); jl_static_show((JL_STREAM*)STDERR_FILENO, e); @@ -721,8 +720,8 @@ JL_DLLEXPORT JL_NORETURN void jl_no_exc_handler(jl_value_t *e, jl_task_t *ct) /* The temporary ptls->bt_data is rooted by special purpose code in the\ GC. This exists only for the purpose of preserving bt_data until we \ set ptls->bt_size=0 below. */ \ - jl_push_excstack(ct, &ct->excstack, exception, \ - ptls->bt_data, ptls->bt_size); \ + jl_push_excstack(ct, &ct->excstack, exception, \ + ptls->bt_data, ptls->bt_size); \ ptls->bt_size = 0; \ } \ assert(ct->excstack && ct->excstack->top); \ @@ -856,202 +855,143 @@ guaranteed to avoid collisions between the RNG streams of all tasks. The main RNG is the xoshiro256++ RNG whose state is stored in rngState[0..3]. There is also a small internal RNG used for task forking stored in rngState[4]. This state is used to iterate a linear congruential generator (LCG), which is then -put through four different variations of the strongest PCG output function, -referred to as PCG-RXS-M-XS-64 [1]. This output function is invertible: it maps -a 64-bit state to 64-bit output. This is one of the reasons it's not recommended -for general purpose RNGs unless space is at an absolute premium, but in our -usage invertibility is actually a benefit (as is explained below) and adding as -little additional memory overhead to each task object as possible is preferred. +combined with xoshiro256's state and put through four different variations of +the strongest PCG output function, referred to as PCG-RXS-M-XS-64 [1]. The goal of jl_rng_split is to perturb the state of each child task's RNG in -such a way each that for an entire tree of tasks spawned starting with a given -state in a root task, no two tasks have the same RNG state. Moreover, we want to -do this in a way that is deterministic and repeatable based on (1) the root -task's seed, (2) how many random numbers are generated, and (3) the task tree -structure. The RNG state of a parent task is allowed to affect the initial RNG -state of a child task, but the mere fact that a child was spawned should not -alter the RNG output of the parent. This second requirement rules out using the -main RNG to seed children: if we use the main RNG, we either advance it, which -affects the parent's RNG stream or, if we don't advance it, then every child -would have an identical RNG stream. Therefore some separate state must be -maintained and changed upon forking a child task while leaving the main RNG -state unchanged. - -The basic approach is that used by the DotMix [2] and SplitMix [3] RNG systems: -each task is uniquely identified by a sequence of "pedigree" numbers, indicating -where in the task tree it was spawned. This vector of pedigree coordinates is -then reduced to a single value by computing a dot product with a shared vector -of random weights. The weights are common but each pedigree of each task is -distinct, so the dot product of each task is unlikely to be the same. The DotMix -paper provides a proof that this dot product hash value (referred to as a -"compression function") is collision resistant in the sense the the pairwise -collision probability of two distinct tasks is 1/N where N is the number of -possible weight values. Both DotMix and SplitMix use a prime value of N because -the proof requires that the difference between two distinct pedigree coordinates -have a multiplicative inverse, which is guaranteed by N being prime since all -values are invertible then. We take a somewhat different approach: instead of -assigning n-ary pedigree coordinates, we assign binary tree coordinates to -tasks, which means that our pedigree vectors have only 0/1 and differences -between them can only be -1, 0 or 1. Since the only possible non-zero coordinate -differences are ±1 which are invertible regardless of the modulus, we can use a -modulus of 2^64, which is far easier and more efficient then using a prime -modulus. It also means that when accumulating the dot product incrementally, as -described in SplitMix, we don't need to multiply weights by anything, we simply -add the random weight for the current task tree depth to the parent's dot -product to derive the child's dot product. - -we instead limit pedigree coordinates to being binary, guaranteeing -invertibility regardless of modulus. When a task spawns a child, the parent and -child share the parent's previous pedigree prefix and the parent appends a zero -to its coordinates, which doesn't affect the task's dot product value, while the -child appends a one, which does produce a new dot product. In this manner a -binary pedigree vector uniquely identifies each task and since the coordinates -are binary, the difference between coordinates is always invertible: 1 and -1 -are their own multiplicative inverses regardless of the modulus. - -How does our assignment of pedigree coordinates to tasks differ from DotMix and -SplitMix? In DotMix and SplitMix, each task has a fixed pedigree vector that -never changes. The root tasks's pedigree is `()`, its first child's pedigree is -`(0,)`, its second child's pedigree is `(2,)` and so on. The length of a task's -pedigree tuple corresponds to how many ancestors tasks it has. Our approach -instead appends 0 to the parent's pedigree when it forks a child and appends 1 -to the child's pedigree at the same time. The root task starts with a pedigree -of `()` as before, but when it spawns a child, we update its pedigree to `(0,)` -and give its child a pedigree of `(1,)`. When the root task then spawns a second -child, we update its pedigree to `(0,0)` and give it's second child a pedigree -of `(0,1)`. If the first child spawns a grandchild, the child's pedigree is -changed from `(1,)` to `(1,0)` and the grandchild is assigned a pedigree of -`(1,1)`. In other words, DotMix and SplitMix build an n-ary tree where every -node is a task: parent nodes are higher up the tree and child tasks are children -in the pedigree tree. Our approach is to build a binary tree where only leaves -are tasks and each task spawn replaces a leaf in the tree with two leaves: the -parent moves to the left/zero leaf while the child is the right/one leaf. Since -the tree is binary, the pedigree coordinates are binary. - -It may seem odd for a task's pedigree coordinates to change, but note that we -only ever append zeros to a task's pedigree, which does not change its dot -product. So while the pedigree changes, the dot product is fixed. What purpose -does appending zeros like this serve if the task's dot product doesn't change? -Changing the pedigree length (which is also the binary tree depth) ensures that -the next child spawned by that task will have new and different dot product from -the previous child since it will have a different pseudo-random weight added to -the parent's dot product value. Whereas the pedigree length in DotMix and -SplitMix is unchanging and corresponds to how many ancestors a task has, in our -scheme the pedigree length corresponds to the number of ancestors *plus* -children a task has, which increases every time it spawns another child. - -We use the LCG in rngState[4] to generate pseudorandom weights for the dot -product. Each time a child is forked, we update the LCG in both parent and child -tasks. In the parent, that's all we have to do -- the main RNG state remains -unchanged. (Recall that spawning a child should *not* affect subsequent RNG -draws in the parent). The next time the parent forks a child, the dot product -weight used will be different, corresponding to being a level deeper in the -pedigree tree. In the child, we use the LCG state to generate four pseudorandom -64-bit weights (more below) and add each weight to one of the xoshiro256 state -registers, rngState[0..3]. If we assume the main RNG remains unused in all -tasks, then each register rngState[0..3] accumulates a different dot product -hash as additional child tasks are spawned. Each one is collision resistant with -a pairwise collision chance of only 1/2^64. Assuming that the four pseudorandom -64-bit weight streams are sufficiently independent, the pairwise collision -probability for distinct tasks is 1/2^256. If we somehow managed to spawn a -trillion tasks, the probability of a collision would be on the order of 1/10^54. -In other words, practically impossible. Put another way, this is the same as the -probability of two SHA256 hash values accidentally colliding, which we generally -consider so unlikely as not to be worth worrying about. - -What about the random "junk" that's in the xoshiro256 state registers from -normal use of the RNG? For a tree of tasks spawned with no intervening samples -taken from the main RNG, all tasks start with the same junk which doesn't affect -the chance of collision. The Dot/SplitMix papers even suggest adding a random -base value to the dot product, so we can consider whatever happens to be in the -xoshiro256 registers to be that. What if the main RNG gets used between task -forks? In that case, the initial state registers will be different. The DotMix -collision resistance proof doesn't apply without modification, but we can -generalize the setup by adding a different base constant to each compression -function and observe that we still have a 1/N chance of the weight value -matching that exact difference. This proves collision resistance even between -tasks whose dot product hashes are computed with arbitrary offsets. We can -conclude that this scheme provides collision resistance even in the face of -different starting states of the main RNG. Does this seem too good to be true? -Perhaps another way of thinking about it will help. Suppose we seeded each task -completely randomly. Then there would also be a 1/2^256 chance of collision, -just as the DotMix proof gives. Essentially what the proof is telling us is that -if the weights are chosen uniformly and uncorrelated with the rest of the -compression function, then the dot product construction is a good enough way to -pseudorandomly seed each task based on its parent's RNG state and where in the -task tree it lives. From that perspective, all we need to believe is that the -dot product construction is random enough (assuming the weights are), and it -becomes easier to believe that adding an arbitrary constant to each dot product -value doesn't make its randomness any worse. - -This leaves us with the question of how to generate four pseudorandom weights to -add to the rngState[0..3] registers at each depth of the task tree. The scheme -used here is that a single 64-bit LCG state is iterated in both parent and child -at each task fork, and four different variations of the PCG-RXS-M-XS-64 output -function are applied to that state to generate four different pseudorandom -weights. Another obvious way to generate four weights would be to iterate the -LCG four times per task split. There are two main reasons we've chosen to use -four output variants instead: - -1. Advancing four times per fork reduces the set of possible weights that each - register can be perturbed by from 2^64 to 2^60. Since collision resistance is - proportional to the number of possible weight values, that would reduce - collision resistance. While it would still be strong engough, why reduce it? - -2. It's easier to compute four PCG output variants in parallel. Iterating the - LCG is inherently sequential. PCG variants can be computed independently. All - four can even be computed at once with SIMD vector instructions. The C - compiler doesn't currently choose to do that transformation, but it could. - -A key question is whether the approach of using four variations of PCG-RXS-M-XS -is sufficiently random both within and between streams to provide the collision -resistance we expect. We obviously can't test that with 256 bits, but we have -tested it with a reduced state analogue using four PCG-RXS-M-XS-8 output -variations applied to a common 8-bit LCG. Test results do indicate sufficient -independence: a single register has collisions at 2^5 while four registers only -start having collisions at 2^20. This is actually better scaling of collision -resistance than we theoretically expect. In theory, with one byte of resistance -we have a 50% chance of some collision at 20 tasks, which matches what we see, -but four bytes should give a 50% chance of collision at 2^17 tasks and our -reduced size analogue construction remains collision free at 2^19 tasks. This -may be due to the next observation, which is that the way we generate -pseudorandom weights actually guarantees collision avoidance in many common -situations rather than merely providing collision resistance and thus is better -than true randomness. - -In the specific case where a parent task spawns a sequence of child tasks with -no intervening usage of its main RNG, the parent and child tasks are actually -_guaranteed_ to have different RNG states. This is true because the four PCG -streams each produce every possible 2^64 bit output exactly once in the full -2^64 period of the LCG generator. This is considered a weakness of PCG-RXS-M-XS -when used as a general purpose RNG, but is quite beneficial in this application. -Since each of up to 2^64 children will be perturbed by different weights, they -cannot have hash collisions. What about parent colliding with child? That can -only happen if all four main RNG registers are perturbed by exactly zero. This -seems unlikely, but could it occur? Consider the core of the output function: - - p ^= p >> ((p >> 59) + 5); - p *= m[i]; - p ^= p >> 43 - -It's easy to check that this maps zero to zero. An unchanged parent RNG can only -happen if all four `p` values are zero at the end of this, which implies that -they were all zero at the beginning. However, that is impossible since the four -`p` values differ from `x` by different additive constants, so they cannot all -be zero. Stated more generally, this non-collision property: assuming the main -RNG isn't used between task forks, sibling and parent tasks cannot have RNG -collisions. If the task tree structure is more deeply nested or if there are -intervening uses of the main RNG, we're back to relying on "merely" 256 bits of -collision resistance, but it's nice to know that in what is likely the most -common case, RNG collisions are actually impossible. This fact may also explain -better-than-theoretical collision resistance observed in our experiment with a -reduced size analogue of our hashing system. +such a way that for an entire tree of tasks spawned starting with a given root +task state, no two tasks have the same RNG state. Moreover, we want to do this +in a way that is deterministic and repeatable based on (1) the root task's seed, +(2) how many random numbers are generated, and (3) the task tree structure. The +RNG state of a parent task is allowed to affect the initial RNG state of a child +task, but the mere fact that a child was spawned should not alter the RNG output +of the parent. This second requirement rules out using the main RNG to seed +children: if we use the main RNG, we either advance it, which affects the +parent's RNG stream or, if we don't advance it, then every child would have an +identical RNG stream. Therefore some separate state must be maintained and +changed upon forking a child task while leaving the main RNG state unchanged. + +The basic approach is a generalization and simplification of that used in the +DotMix [2] and SplitMix [3] RNG systems: each task is uniquely identified by a +sequence of "pedigree" numbers, indicating where in the task tree it was +spawned. This vector of pedigree coordinates is then reduced to a single value +by computing a "dot product" with a shared vector of random weights. I write +"dot product" in quotes because what we use is not an actual dot product. The +linear dot product construction used in both DotMix and SplitMix was found by +@foobar_iv2 [4] to allow easy construction of linear relationships between the +main RNG states of tasks, which was in turn reflected in observable linear +relationships between the outputs of their RNGs. This relationship was between a +minimum of four tasks, so doesn't constitute a collision, per se, but is clearly +undesirable and highlights a hazard of the plain dot product construction. + +As in DotMix and SplitMix, each task is assigned unique task "pedigree" +coordinates. Our pedigree construction is a bit different and uses only binary +coordinates rather than arbitrary integers. Each pedigree is an infinite +sequence of ones and zeros with only finitely many ones. Each task has a "fork +index": the root task has index 0; the fork index of a task the jth child task +of a parent task with fork index i is i+j. The root task's coordinates are all +zeros; each child task's coordinates are the same as its parents except at its +fork index, where the parent has a zero while the child has a one. A task's +coordinates after its fork index are all zeros. The coordinates of a tasks +ancestors are all prefixes of its own coordinates, padded with zeros. + +Also as in DotMix and SplitMix, we generate a sequence of pseudorandom weights +to combine with the coordinates of each task. This sequence is common across all +tasks, and different mix values for each task derive from their coordinates +being different. In DotMix and SplitMix, this is a literal dot product: the +pseudorandom weights are multiplied by corresponding task coordinate and added +up. While this does provably make collisions as unlikely as randomly assigned +task seeds, this linear construction can be used to create linearly correlated +states between tasks. However, it turns out that the compression construction +need not be linear, commutative, associative, etc. which allows us to avoid any +linear or other obvious correlations between related sets of tasks. + +To generalize SplitMix's optimized dot product construction, we similarly +compute each task's compression function value incrementally by combining the +parent's compression value with pseudorandom weight corresponding with the +child's fork index. Formally, if the parent's compression value is c then we can +compute the child's compression value as c′ = f(c, wᵢ) where w is the vector of +pseudorandom weights. What is f? It can be any function that is bijective in +each argument for all values of the other argument: + + * For all c: w ↦ f(c, w) is bijective + * For all w: c ↦ f(c, w) is bijective + +The proof that these requirements are sufficient to ensure collision resistance +is in the linked discussion [4]. DotMix/SplitMix are a special case where f is +just addition. Instead we use a much less simple mixing function: + + 1. We use (2c+1)(2w+1)÷2 % 2^64 to mix the bits of c and w + 2. We then apply the PCG-RXS-M-XS-64 output function + +The first step thoroughly mixes the bits of the previous compression value and +the pseudorandom weight value using multiplication, which is non-commutative +with xoshiro's operations (xor, shift, rotate). This mixing function is a +bijection on each argument witnessed by these inverses: + + * c′ ↦ (2c′+1)(2w+1)⁻¹÷2 % 2^64 + * w′ ↦ (2c+1)⁻¹(2w′+1)÷2 % 2^64 + +The second PCG output step is a bijection and designed to be significantly +non-linear -- non-linear enough to mask the linearity of the LCG that drives the +PCG-RXS-M-XS-64 RNG and allows it to pass statistical RNG test suites despite +having the same size state and output. In particular, since this mixing function +is highly non-associative and non-linear, we (hopefully) don't have any +discernible relationship between these values: + + * c₀₀ = c + * c₁₀ = f(c, wᵢ) + * c₀₁ = f(c, wⱼ) + * c₁₁ = f(f(c, wᵢ), wⱼ) + +When f is simply `+` then these have a very obvious relationship: + + c₀₀ + c₁₁ == c₁₀ + c₀₁ + +This relationship holds regardless of what wᵢ and wⱼ are and is precisely what +allows easy creation of correlated tasks with the DotMix/SplitMix construction +that we previously used. Expressing any relationship between these values with +our mixing function would require inverting the PCG output function (doable but +non-trivial), knowing the weights wᵢ and wⱼ, and then applying the inversion +functions for those weights appropriately. Since the weights are pseudo-randomly +generated and not directly observable, this is infeasible. + +We maintain an LCG in rngState[4] to generate pseudorandom weights. An LCG by +itself is a very bad RNG, but we combine this one with xoshiro256 state +registers in a non-trivial way and then apply the PCG-RXS-M-XS-64 output +function to that. Even if the xoshiro256 states are all zeros, which they should +never be, the output would be the same as PCG-RXS-M-XS-64, which is a solid +statistical RNG. + +Each time a child is forked, we update the LCG in both parent and child tasks, +corresponding to increasing the fork index. In the parent, that's all we have to +do -- the main RNG state remains unchanged. Recall that spawning a child should +*not* affect subsequent RNG draws in the parent. The next time the parent forks +a child, the mixing weight used will be different. In the child, we use the LCG +state to perturb the child's main RNG state registers, rngState[0..3]. + +Since we want these registers to behave independently, we use four different +variations on f to mix the LCG state with each of the four main RNG registers. +Each variation first xors the LCG state with a different random constant before +combining that value above with the old register state via multiplication; the +PCG-RXS-M-XS-64 output function is then applied to that mixed state, with a +different multiplier constant for each variation / register index. Xor is used +in the first step since we multiply the result with the state immediately after +and multiplication distributes over `+` and commutes with `*`, which makes both +options suspect; multiplication doesn't distribute over or commute with xor. We +also use a different odd multiplier in PCG-RXS-M-XS-64 for each RNG register. +These three sources of variation (different xor constants, different xoshiro256 +state, different PCG multipliers) are sufficient for each of the four outputs to +behave statistically independently. [1]: https://www.pcg-random.org/pdf/hmc-cs-2014-0905.pdf [2]: http://supertech.csail.mit.edu/papers/dprng.pdf [3]: https://gee.cs.oswego.edu/dl/papers/oopsla14.pdf + +[4]: +https://discourse.julialang.org/t/linear-relationship-between-xoshiro-tasks/110454 */ void jl_rng_split(uint64_t dst[JL_RNG_SIZE], uint64_t src[JL_RNG_SIZE]) JL_NOTSAFEPOINT { @@ -1060,26 +1000,30 @@ void jl_rng_split(uint64_t dst[JL_RNG_SIZE], uint64_t src[JL_RNG_SIZE]) JL_NOTSA src[4] = dst[4] = x * 0xd1342543de82ef95 + 1; // high spectrum multiplier from https://arxiv.org/abs/2001.05304 + // random xor constants static const uint64_t a[4] = { - 0xe5f8fa077b92a8a8, // random additive offsets... - 0x7a0cd918958c124d, - 0x86222f7d388588d4, - 0xd30cbd35f2b64f52 + 0x214c146c88e47cb7, + 0xa66d8cc21285aafa, + 0x68c7ef2d7b1a54d4, + 0xb053a7d7aa238c61 }; + // random odd multipliers static const uint64_t m[4] = { 0xaef17502108ef2d9, // standard PCG multiplier - 0xf34026eeb86766af, // random odd multipliers... + 0xf34026eeb86766af, 0x38fd70ad58dd9fbb, 0x6677f9b93ab0c04d }; // PCG-RXS-M-XS-64 output with four variants for (int i = 0; i < 4; i++) { - uint64_t p = x + a[i]; - p ^= p >> ((p >> 59) + 5); - p *= m[i]; - p ^= p >> 43; - dst[i] = src[i] + p; // SplitMix dot product + uint64_t c = src[i]; + uint64_t w = x ^ a[i]; + c += w*(2*c + 1); // c = (2c+1)(2w+1)÷2 % 2^64 (double bijection) + c ^= c >> ((c >> 59) + 5); + c *= m[i]; + c ^= c >> 43; + dst[i] = c; } } @@ -1165,47 +1109,6 @@ JL_DLLEXPORT jl_task_t *jl_get_current_task(void) return pgcstack == NULL ? NULL : container_of(pgcstack, jl_task_t, gcstack); } - -#ifdef JL_HAVE_ASYNCIFY -JL_DLLEXPORT jl_ucontext_t *task_ctx_ptr(jl_task_t *t) -{ - return &t->ctx.ctx; -} - -JL_DLLEXPORT jl_value_t *jl_get_root_task(void) -{ - jl_task_t *ct = jl_current_task; - return (jl_value_t*)ct->ptls->root_task; -} - -JL_DLLEXPORT void jl_task_wait() -{ - static jl_function_t *wait_func = NULL; - if (!wait_func) { - wait_func = (jl_function_t*)jl_get_global(jl_base_module, jl_symbol("wait")); - } - jl_task_t *ct = jl_current_task; - size_t last_age = ct->world_age; - ct->world_age = jl_get_world_counter(); - jl_apply(&wait_func, 1); - ct->world_age = last_age; -} - -JL_DLLEXPORT void jl_schedule_task(jl_task_t *task) -{ - static jl_function_t *sched_func = NULL; - if (!sched_func) { - sched_func = (jl_function_t*)jl_get_global(jl_base_module, jl_symbol("schedule")); - } - jl_task_t *ct = jl_current_task; - size_t last_age = ct->world_age; - ct->world_age = jl_get_world_counter(); - jl_value_t *args[] = {(jl_value_t*)sched_func, (jl_value_t*)task}; - jl_apply(args, 2); - ct->world_age = last_age; -} -#endif - // Do one-time initializations for task system void jl_init_tasks(void) JL_GC_DISABLED { @@ -1226,13 +1129,24 @@ void jl_init_tasks(void) JL_GC_DISABLED exit(1); } #endif +#if defined(_COMPILER_ASAN_ENABLED_) && __GLIBC__ + void *libc_handle = dlopen("libc.so.6", RTLD_NOW | RTLD_NOLOAD); + if (libc_handle) { + *(void**)&real_siglongjmp = dlsym(libc_handle, "siglongjmp"); + dlclose(libc_handle); + } + if (real_siglongjmp == NULL) { + jl_safe_printf("failed to get real siglongjmp\n"); + exit(1); + } +#endif } #if defined(_COMPILER_ASAN_ENABLED_) -STATIC_OR_JS void NOINLINE JL_NORETURN _start_task(void); +static void NOINLINE JL_NORETURN _start_task(void); #endif -STATIC_OR_JS void NOINLINE JL_NORETURN JL_NO_ASAN start_task(void) +static void NOINLINE JL_NORETURN JL_NO_ASAN start_task(void) { CFI_NORETURN #if defined(_COMPILER_ASAN_ENABLED_) @@ -1248,7 +1162,7 @@ CFI_NORETURN _start_task(); } -STATIC_OR_JS void NOINLINE JL_NORETURN _start_task(void) +static void NOINLINE JL_NORETURN _start_task(void) { CFI_NORETURN #endif @@ -1288,7 +1202,7 @@ CFI_NORETURN res = jl_apply(&ct->start, 1); } JL_CATCH { - res = jl_current_exception(); + res = jl_current_exception(ct); jl_atomic_store_relaxed(&ct->_isexception, 1); goto skip_pop_exception; } @@ -1735,6 +1649,7 @@ jl_task_t *jl_init_root_task(jl_ptls_t ptls, void *stack_lo, void *stack_hi) JL_GC_PROMISE_ROOTED(ct); jl_set_pgcstack(&ct->gcstack); assert(jl_current_task == ct); + assert(jl_current_task->ptls == ptls); #ifdef _COMPILER_TSAN_ENABLED_ ct->ctx.tsan_state = __tsan_get_current_fiber(); diff --git a/src/threading.c b/src/threading.c index 319a2918fab3f..eb76ac579b538 100644 --- a/src/threading.c +++ b/src/threading.c @@ -316,6 +316,7 @@ int jl_all_tls_states_size; static uv_cond_t cond; // concurrent reads are permitted, using the same pattern as mtsmall_arraylist // it is implemented separately because the API of direct jl_all_tls_states use is already widely prevalent +void jl_init_thread_scheduler(jl_ptls_t ptls) JL_NOTSAFEPOINT; // return calling thread's ID JL_DLLEXPORT int16_t jl_threadid(void) @@ -363,7 +364,7 @@ jl_ptls_t jl_init_threadtls(int16_t tid) } } #endif - jl_atomic_store_relaxed(&ptls->gc_state, 0); // GC unsafe + jl_atomic_store_relaxed(&ptls->gc_state, JL_GC_STATE_UNSAFE); // GC unsafe // Conditionally initialize the safepoint address. See comment in // `safepoint.c` if (tid == 0) { @@ -379,9 +380,7 @@ jl_ptls_t jl_init_threadtls(int16_t tid) ptls->bt_data = bt_data; small_arraylist_new(&ptls->locks, 0); jl_init_thread_heap(ptls); - - uv_mutex_init(&ptls->sleep_lock); - uv_cond_init(&ptls->wake_signal); + jl_init_thread_scheduler(ptls); uv_mutex_lock(&tls_lock); if (tid == -1) @@ -434,6 +433,9 @@ JL_DLLEXPORT jl_gcframe_t **jl_adopt_thread(void) } void jl_task_frame_noreturn(jl_task_t *ct) JL_NOTSAFEPOINT; +void scheduler_delete_thread(jl_ptls_t ptls) JL_NOTSAFEPOINT; + +void jl_free_thread_gc_state(jl_ptls_t ptls); static void jl_delete_thread(void *value) JL_NOTSAFEPOINT_ENTER { @@ -444,9 +446,31 @@ static void jl_delete_thread(void *value) JL_NOTSAFEPOINT_ENTER // safepoint until GC exit, in case GC was running concurrently while in // prior unsafe-region (before we let it release the stack memory) (void)jl_gc_unsafe_enter(ptls); - jl_atomic_store_relaxed(&ptls->sleep_check_state, 2); // dead, interpreted as sleeping and unwakeable - jl_fence(); - jl_wakeup_thread(0); // force thread 0 to see that we do not have the IO lock (and am dead) + scheduler_delete_thread(ptls); + // try to free some state we do not need anymore +#ifndef _OS_WINDOWS_ + void *signal_stack = ptls->signal_stack; + size_t signal_stack_size = ptls->signal_stack_size; + if (signal_stack != NULL) { + stack_t ss; + if (sigaltstack(NULL, &ss)) + jl_errorf("fatal error: sigaltstack: %s", strerror(errno)); + if (ss.ss_sp == signal_stack) { + ss.ss_flags = SS_DISABLE; + if (sigaltstack(&ss, NULL) != 0) { + jl_errorf("warning: sigaltstack: %s (will leak this memory)", strerror(errno)); + signal_stack = NULL; + } + } + if (signal_stack != NULL) { + if (signal_stack_size) + jl_free_stack(signal_stack, signal_stack_size); + else + free(signal_stack); + } + ptls->signal_stack = NULL; + } +#endif // Acquire the profile write lock, to ensure we are not racing with the `kill` // call in the profile code which will also try to look at this thread. // We have no control over when the user calls pthread_join, so we must do @@ -486,6 +510,12 @@ static void jl_delete_thread(void *value) JL_NOTSAFEPOINT_ENTER #else pthread_mutex_unlock(&in_signal_lock); #endif + free(ptls->bt_data); + small_arraylist_free(&ptls->locks); + ptls->previous_exception = NULL; + // allow the page root_task is on to be freed + ptls->root_task = NULL; + jl_free_thread_gc_state(ptls); // then park in safe-region (void)jl_gc_safe_enter(ptls); } @@ -657,6 +687,7 @@ void jl_init_threading(void) } } + int cpu = jl_cpu_threads(); jl_n_markthreads = jl_options.nmarkthreads - 1; jl_n_sweepthreads = jl_options.nsweepthreads; if (jl_n_markthreads == -1) { // --gcthreads not specified @@ -677,23 +708,30 @@ void jl_init_threading(void) } else { // if `--gcthreads` or ENV[NUM_GCTHREADS_NAME] was not specified, - // set the number of mark threads to half of compute threads + // set the number of mark threads to the number of compute threads // and number of sweep threads to 0 - if (nthreads <= 1) { - jl_n_markthreads = 0; - } - else { - jl_n_markthreads = (nthreads / 2) - 1; + jl_n_markthreads = nthreads - 1; // -1 for the master (mutator) thread which may also do marking + // if `--gcthreads` or ENV[NUM_GCTHREADS_NAME] was not specified, + // cap the number of threads that may run the mark phase to + // the number of CPU cores + if (jl_n_markthreads + 1 >= cpu) { + jl_n_markthreads = cpu - 1; } } } + // warn the user if they try to run with a number + // of GC threads which is larger than the number + // of physical cores + if (jl_n_markthreads + 1 > cpu) { + jl_safe_printf("WARNING: running Julia with %d GC threads on %d CPU cores\n", jl_n_markthreads + 1, cpu); + } int16_t ngcthreads = jl_n_markthreads + jl_n_sweepthreads; jl_all_tls_states_size = nthreads + nthreadsi + ngcthreads; jl_n_threads_per_pool = (int*)malloc_s(2 * sizeof(int)); jl_n_threads_per_pool[0] = nthreadsi; jl_n_threads_per_pool[1] = nthreads; - + assert(jl_all_tls_states_size > 0); jl_atomic_store_release(&jl_all_tls_states, (jl_ptls_t*)calloc(jl_all_tls_states_size, sizeof(jl_ptls_t))); jl_atomic_store_release(&jl_n_threads, jl_all_tls_states_size); jl_n_gcthreads = ngcthreads; @@ -765,7 +803,8 @@ void jl_start_threads(void) uv_barrier_wait(&thread_init_done); } -_Atomic(unsigned) _threadedregion; // HACK: keep track of whether to prioritize IO or threading +_Atomic(unsigned) _threadedregion; // keep track of whether to prioritize IO or threading +_Atomic(uint16_t) io_loop_tid; // mark which thread is assigned to run the uv_loop JL_DLLEXPORT int jl_in_threaded_region(void) { @@ -786,7 +825,27 @@ JL_DLLEXPORT void jl_exit_threaded_region(void) JL_UV_UNLOCK(); // make sure thread 0 is not using the sleep_lock // so that it may enter the libuv event loop instead - jl_wakeup_thread(0); + jl_fence(); + jl_wakeup_thread(jl_atomic_load_relaxed(&io_loop_tid)); + } +} + +JL_DLLEXPORT void jl_set_io_loop_tid(int16_t tid) +{ + if (tid < 0 || tid >= jl_atomic_load_relaxed(&jl_n_threads)) { + // TODO: do we care if this thread has exited or not started yet, + // since ptls2 might not be defined yet and visible on all threads yet + return; + } + jl_atomic_store_relaxed(&io_loop_tid, tid); + jl_fence(); + if (jl_atomic_load_relaxed(&_threadedregion) == 0) { + // make sure the previous io_loop_tid leaves the libuv event loop + JL_UV_LOCK(); + JL_UV_UNLOCK(); + // make sure thread io_loop_tid is not using the sleep_lock + // so that it may enter the libuv event loop instead + jl_wakeup_thread(tid); } } diff --git a/src/toplevel.c b/src/toplevel.c index ca2033e58727d..d7d4b04c3e67b 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -82,7 +82,7 @@ void jl_module_run_initializer(jl_module_t *m) } else { jl_rethrow_other(jl_new_struct(jl_initerror_type, m->name, - jl_current_exception())); + jl_current_exception(ct))); } } } @@ -121,7 +121,7 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex { jl_task_t *ct = jl_current_task; assert(ex->head == jl_module_sym); - if (jl_array_len(ex->args) != 3 || !jl_is_expr(jl_exprarg(ex, 2))) { + if (jl_array_nrows(ex->args) != 3 || !jl_is_expr(jl_exprarg(ex, 2))) { jl_error("syntax: malformed module expression"); } @@ -167,7 +167,7 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex jl_printf(JL_STDERR, "WARNING: replacing module %s.\n", jl_symbol_name(name)); old = jl_atomic_exchange(&b->value, (jl_value_t*)newm); } - jl_gc_wb_binding(b, newm); + jl_gc_wb(b, newm); if (old != NULL) { // create a hidden gc root for the old module JL_LOCK(&jl_modules_mutex); @@ -188,7 +188,7 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex jl_array_t *exprs = ((jl_expr_t*)jl_exprarg(ex, 2))->args; int lineno = 0; const char *filename = "none"; - if (jl_array_len(exprs) > 0) { + if (jl_array_nrows(exprs) > 0) { jl_value_t *lineex = jl_array_ptr_ref(exprs, 0); if (jl_is_linenode(lineex)) { lineno = jl_linenode_line(lineex); @@ -203,18 +203,17 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex } // add `eval` function form = jl_call_scm_on_ast_and_loc("module-default-defs", (jl_value_t*)name, newm, filename, lineno); - jl_toplevel_eval_flex(newm, form, 0, 1); + jl_toplevel_eval_flex(newm, form, 0, 1, &filename, &lineno); form = NULL; } - for (int i = 0; i < jl_array_len(exprs); i++) { + for (int i = 0; i < jl_array_nrows(exprs); i++) { // process toplevel form ct->world_age = jl_atomic_load_acquire(&jl_world_counter); - form = jl_expand_stmt_with_loc(jl_array_ptr_ref(exprs, i), newm, jl_filename, jl_lineno); + form = jl_expand_stmt_with_loc(jl_array_ptr_ref(exprs, i), newm, filename, lineno); ct->world_age = jl_atomic_load_acquire(&jl_world_counter); - (void)jl_toplevel_eval_flex(newm, form, 1, 1); + (void)jl_toplevel_eval_flex(newm, form, 1, 1, &filename, &lineno); } - newm->primary_world = jl_atomic_load_acquire(&jl_world_counter); ct->world_age = last_age; #if 0 @@ -222,7 +221,7 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex size_t i; jl_svec_t *table = jl_atomic_load_relaxed(&newm->bindings); for (size_t i = 0; i < jl_svec_len(table); i++) { - jl_binding_t *b = (jl_binding_t*)jl_svec_ref(table, i); + jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i); if ((void*)b != jl_nothing) { // remove non-exported macros if (jl_symbol_name(b->name)[0]=='@' && @@ -254,7 +253,7 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex form = NULL; if (!jl_generating_output()) { if (!ptrhash_has(&jl_current_modules, (void*)newm->parent)) { - size_t i, l = jl_array_len(jl_module_init_order); + size_t i, l = jl_array_nrows(jl_module_init_order); size_t ns = 0; form = (jl_value_t*)jl_alloc_vec_any(0); for (i = 0; i < l; i++) { @@ -273,7 +272,7 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex JL_UNLOCK(&jl_modules_mutex); if (form) { - size_t i, l = jl_array_len(form); + size_t i, l = jl_array_nrows(form); for (i = 0; i < l; i++) { jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(form, i); JL_GC_PROMISE_ROOTED(m); @@ -287,13 +286,13 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex return (jl_value_t*)newm; } -static jl_value_t *jl_eval_dot_expr(jl_module_t *m, jl_value_t *x, jl_value_t *f, int fast) +static jl_value_t *jl_eval_dot_expr(jl_module_t *m, jl_value_t *x, jl_value_t *f, int fast, const char **toplevel_filename, int *toplevel_lineno) { jl_task_t *ct = jl_current_task; jl_value_t **args; JL_GC_PUSHARGS(args, 3); - args[1] = jl_toplevel_eval_flex(m, x, fast, 0); - args[2] = jl_toplevel_eval_flex(m, f, fast, 0); + args[1] = jl_toplevel_eval_flex(m, x, fast, 0, toplevel_filename, toplevel_lineno); + args[2] = jl_toplevel_eval_flex(m, f, fast, 0, toplevel_filename, toplevel_lineno); if (jl_is_module(args[1])) { JL_TYPECHK(getglobal, symbol, args[2]); args[0] = jl_eval_global_var((jl_module_t*)args[1], (jl_sym_t*)args[2]); @@ -311,7 +310,7 @@ static jl_value_t *jl_eval_dot_expr(jl_module_t *m, jl_value_t *x, jl_value_t *f void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type) { // create uninitialized mutable binding for "global x" decl sometimes or probably - size_t i, l = jl_array_len(ex->args); + size_t i, l = jl_array_nrows(ex->args); for (i = 0; i < l; i++) { jl_value_t *arg = jl_exprarg(ex, i); jl_module_t *gm; @@ -352,7 +351,7 @@ JL_DLLEXPORT jl_module_t *jl_base_relative_to(jl_module_t *m) return jl_top_module; } -static void expr_attributes(jl_value_t *v, int *has_ccall, int *has_defs, int *has_opaque) +static void expr_attributes(jl_value_t *v, jl_array_t *body, int *has_ccall, int *has_defs, int *has_opaque) { if (!jl_is_expr(v)) return; @@ -390,6 +389,9 @@ static void expr_attributes(jl_value_t *v, int *has_ccall, int *has_defs, int *h else if (head == jl_call_sym && jl_expr_nargs(e) > 0) { jl_value_t *called = NULL; jl_value_t *f = jl_exprarg(e, 0); + if (jl_is_ssavalue(f)) { + f = jl_array_ptr_ref(body, ((jl_ssavalue_t*)f)->id - 1); + } if (jl_is_globalref(f)) { jl_module_t *mod = jl_globalref_mod(f); jl_sym_t *name = jl_globalref_name(f); @@ -414,10 +416,10 @@ static void expr_attributes(jl_value_t *v, int *has_ccall, int *has_defs, int *h return; } int i; - for (i = 0; i < jl_array_len(e->args); i++) { + for (i = 0; i < jl_array_nrows(e->args); i++) { jl_value_t *a = jl_exprarg(e, i); if (jl_is_expr(a)) - expr_attributes(a, has_ccall, has_defs, has_opaque); + expr_attributes(a, body, has_ccall, has_defs, has_opaque); } } @@ -429,9 +431,9 @@ int jl_code_requires_compiler(jl_code_info_t *src, int include_force_compile) int has_ccall = 0, has_defs = 0, has_opaque = 0; if (include_force_compile && jl_has_meta(body, jl_force_compile_sym)) return 1; - for(i=0; i < jl_array_len(body); i++) { + for(i=0; i < jl_array_nrows(body); i++) { jl_value_t *stmt = jl_array_ptr_ref(body,i); - expr_attributes(stmt, &has_ccall, &has_defs, &has_opaque); + expr_attributes(stmt, body, &has_ccall, &has_defs, &has_opaque); if (has_ccall) return 1; } @@ -442,7 +444,7 @@ static void body_attributes(jl_array_t *body, int *has_ccall, int *has_defs, int { size_t i; *has_loops = 0; - for(i=0; i < jl_array_len(body); i++) { + for(i=0; i < jl_array_nrows(body); i++) { jl_value_t *stmt = jl_array_ptr_ref(body,i); if (!*has_loops) { if (jl_is_gotonode(stmt)) { @@ -454,26 +456,29 @@ static void body_attributes(jl_array_t *body, int *has_ccall, int *has_defs, int *has_loops = 1; } } - expr_attributes(stmt, has_ccall, has_defs, has_opaque); + expr_attributes(stmt, body, has_ccall, has_defs, has_opaque); } *forced_compile = jl_has_meta(body, jl_force_compile_sym); } +size_t jl_require_world = ~(size_t)0; static jl_module_t *call_require(jl_module_t *mod, jl_sym_t *var) JL_GLOBALLY_ROOTED { JL_TIMING(LOAD_IMAGE, LOAD_Require); jl_timing_printf(JL_TIMING_DEFAULT_BLOCK, "%s", jl_symbol_name(var)); - static jl_value_t *require_func = NULL; - int build_mode = jl_generating_output(); + int build_mode = jl_options.incremental && jl_generating_output(); jl_module_t *m = NULL; jl_task_t *ct = jl_current_task; + static jl_value_t *require_func = NULL; if (require_func == NULL && jl_base_module != NULL) { require_func = jl_get_global(jl_base_module, jl_symbol("require")); } if (require_func != NULL) { size_t last_age = ct->world_age; - ct->world_age = (build_mode ? jl_base_module->primary_world : jl_atomic_load_acquire(&jl_world_counter)); + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); + if (build_mode && jl_require_world < ct->world_age) + ct->world_age = jl_require_world; jl_value_t *reqargs[3]; reqargs[0] = require_func; reqargs[1] = (jl_value_t*)mod; @@ -493,7 +498,7 @@ static jl_module_t *call_require(jl_module_t *mod, jl_sym_t *var) JL_GLOBALLY_RO static jl_module_t *eval_import_path(jl_module_t *where, jl_module_t *from JL_PROPAGATES_ROOT, jl_array_t *args, jl_sym_t **name, const char *keyword) JL_GLOBALLY_ROOTED { - if (jl_array_len(args) == 0) + if (jl_array_nrows(args) == 0) jl_errorf("malformed \"%s\" statement", keyword); jl_sym_t *var = (jl_sym_t*)jl_array_ptr_ref(args, 0); size_t i = 1; @@ -517,14 +522,14 @@ static jl_module_t *eval_import_path(jl_module_t *where, jl_module_t *from JL_PR else { m = call_require(where, var); } - if (i == jl_array_len(args)) + if (i == jl_array_nrows(args)) return m; } else { // `.A.B.C`: strip off leading dots by following parent links m = where; while (1) { - if (i >= jl_array_len(args)) + if (i >= jl_array_nrows(args)) jl_error("invalid module path"); var = (jl_sym_t*)jl_array_ptr_ref(args, i); if (var != jl_dot_sym) @@ -541,7 +546,7 @@ static jl_module_t *eval_import_path(jl_module_t *where, jl_module_t *from JL_PR jl_type_error(keyword, (jl_value_t*)jl_symbol_type, (jl_value_t*)var); if (var == jl_dot_sym) jl_errorf("invalid %s path: \".\" in identifier path", keyword); - if (i == jl_array_len(args)-1) + if (i == jl_array_nrows(args)-1) break; m = (jl_module_t*)jl_eval_global_var(m, var); JL_GC_PROMISE_ROOTED(m); @@ -582,7 +587,7 @@ int jl_needs_lowering(jl_value_t *e) JL_NOTSAFEPOINT return 0; } if (head == jl_global_sym || head == jl_const_sym) { - size_t i, l = jl_array_len(ex->args); + size_t i, l = jl_array_nrows(ex->args); for (i = 0; i < l; i++) { jl_value_t *a = jl_exprarg(ex, i); if (!jl_is_symbol(a) && !jl_is_globalref(a)) @@ -661,46 +666,49 @@ static void check_macro_rename(jl_sym_t *from, jl_sym_t *to, const char *keyword // Eval `throw(ErrorException(msg)))` in module `m`. // Used in `jl_toplevel_eval_flex` instead of `jl_throw` so that the error // location in julia code gets into the backtrace. -static void jl_eval_throw(jl_module_t *m, jl_value_t *exc) +static void jl_eval_throw(jl_module_t *m, jl_value_t *exc, const char *filename, int lineno) { jl_value_t *throw_ex = (jl_value_t*)jl_exprn(jl_call_sym, 2); JL_GC_PUSH1(&throw_ex); jl_exprargset(throw_ex, 0, jl_builtin_throw); jl_exprargset(throw_ex, 1, exc); - jl_toplevel_eval_flex(m, throw_ex, 0, 0); + jl_toplevel_eval_flex(m, throw_ex, 0, 0, &filename, &lineno); JL_GC_POP(); } // Format error message and call jl_eval -static void jl_eval_errorf(jl_module_t *m, const char* fmt, ...) +static void jl_eval_errorf(jl_module_t *m, const char *filename, int lineno, const char* fmt, ...) { va_list args; va_start(args, fmt); jl_value_t *exc = jl_vexceptionf(jl_errorexception_type, fmt, args); va_end(args); JL_GC_PUSH1(&exc); - jl_eval_throw(m, exc); + jl_eval_throw(m, exc, filename, lineno); JL_GC_POP(); } -jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int fast, int expanded) +JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int fast, int expanded, const char **toplevel_filename, int *toplevel_lineno) { jl_task_t *ct = jl_current_task; if (!jl_is_expr(e)) { if (jl_is_linenode(e)) { - jl_lineno = jl_linenode_line(e); + *toplevel_lineno = jl_linenode_line(e); jl_value_t *file = jl_linenode_file(e); if (file != jl_nothing) { assert(jl_is_symbol(file)); - jl_filename = jl_symbol_name((jl_sym_t*)file); + *toplevel_filename = jl_symbol_name((jl_sym_t*)file); } + // Not thread safe. For debugging and last resort error messages (jl_critical_error) only. + jl_filename = *toplevel_filename; + jl_lineno = *toplevel_lineno; return jl_nothing; } if (jl_is_symbol(e)) { char *n = jl_symbol_name((jl_sym_t*)e), *n0 = n; while (*n == '_') ++n; if (*n == 0 && n > n0) - jl_eval_errorf(m, "all-underscore identifiers are write-only and their values cannot be used in expressions"); + jl_eval_errorf(m, *toplevel_filename, *toplevel_lineno, "all-underscore identifiers are write-only and their values cannot be used in expressions"); } return jl_interpret_toplevel_expr_in(m, e, NULL, NULL); } @@ -709,12 +717,12 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int if (ex->head == jl_dot_sym && jl_expr_nargs(ex) != 1) { if (jl_expr_nargs(ex) != 2) - jl_eval_errorf(m, "syntax: malformed \".\" expression"); + jl_eval_errorf(m, *toplevel_filename, *toplevel_lineno, "syntax: malformed \".\" expression"); jl_value_t *lhs = jl_exprarg(ex, 0); jl_value_t *rhs = jl_exprarg(ex, 1); // only handle `a.b` syntax here, so qualified names can be eval'd in pure contexts if (jl_is_quotenode(rhs) && jl_is_symbol(jl_fieldref(rhs, 0))) { - return jl_eval_dot_expr(m, lhs, rhs, fast); + return jl_eval_dot_expr(m, lhs, rhs, fast, toplevel_filename, toplevel_lineno); } } @@ -729,7 +737,7 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int size_t last_age = ct->world_age; if (!expanded && jl_needs_lowering(e)) { ct->world_age = jl_atomic_load_acquire(&jl_world_counter); - ex = (jl_expr_t*)jl_expand_with_loc_warn(e, m, jl_filename, jl_lineno); + ex = (jl_expr_t*)jl_expand_with_loc_warn(e, m, *toplevel_filename, *toplevel_lineno); ct->world_age = last_age; } jl_sym_t *head = jl_is_expr(ex) ? ex->head : NULL; @@ -756,14 +764,15 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int if (name != NULL) u = (jl_module_t*)jl_eval_global_var(import, name); if (from) { - // `using A: B` syntax + // `using A: B` and `using A: B.c` syntax jl_module_use(m, import, name); } else { if (!jl_is_module(u)) - jl_eval_errorf(m, "invalid using path: \"%s\" does not name a module", + jl_eval_errorf(m, *toplevel_filename, *toplevel_lineno, + "invalid using path: \"%s\" does not name a module", jl_symbol_name(name)); - // `using A.B` syntax + // `using A` and `using A.B` syntax jl_module_using(m, u); if (m == jl_main_module && name == NULL) { // TODO: for now, `using A` in Main also creates an explicit binding for `A` @@ -787,7 +796,8 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int continue; } } - jl_eval_errorf(m, "syntax: malformed \"using\" statement"); + jl_eval_errorf(m, *toplevel_filename, *toplevel_lineno, + "syntax: malformed \"using\" statement"); } JL_GC_POP(); return jl_nothing; @@ -834,18 +844,20 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int continue; } } - jl_eval_errorf(m, "syntax: malformed \"import\" statement"); + jl_eval_errorf(m, *toplevel_filename, *toplevel_lineno, + "syntax: malformed \"import\" statement"); } JL_GC_POP(); return jl_nothing; } else if (head == jl_export_sym || head == jl_public_sym) { int exp = (head == jl_export_sym); - for (size_t i = 0; i < jl_array_len(ex->args); i++) { + for (size_t i = 0; i < jl_array_nrows(ex->args); i++) { jl_sym_t *name = (jl_sym_t*)jl_array_ptr_ref(ex->args, i); if (!jl_is_symbol(name)) - jl_eval_errorf(m, exp ? "syntax: malformed \"export\" statement" : - "syntax: malformed \"public\" statement"); + jl_eval_errorf(m, *toplevel_filename, *toplevel_lineno, + exp ? "syntax: malformed \"export\" statement" : + "syntax: malformed \"public\" statement"); jl_module_public(m, name, exp); } JL_GC_POP(); @@ -877,18 +889,20 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int else if (head == jl_toplevel_sym) { jl_value_t *res = jl_nothing; int i; - for (i = 0; i < jl_array_len(ex->args); i++) { - res = jl_toplevel_eval_flex(m, jl_array_ptr_ref(ex->args, i), fast, 0); + for (i = 0; i < jl_array_nrows(ex->args); i++) { + res = jl_toplevel_eval_flex(m, jl_array_ptr_ref(ex->args, i), fast, 0, toplevel_filename, toplevel_lineno); } JL_GC_POP(); return res; } else if (head == jl_error_sym || head == jl_incomplete_sym) { if (jl_expr_nargs(ex) == 0) - jl_eval_errorf(m, "malformed \"%s\" expression", jl_symbol_name(head)); + jl_eval_errorf(m, *toplevel_filename, *toplevel_lineno, + "malformed \"%s\" expression", jl_symbol_name(head)); if (jl_is_string(jl_exprarg(ex, 0))) - jl_eval_errorf(m, "syntax: %s", jl_string_data(jl_exprarg(ex, 0))); - jl_eval_throw(m, jl_exprarg(ex, 0)); + jl_eval_errorf(m, *toplevel_filename, *toplevel_lineno, + "syntax: %s", jl_string_data(jl_exprarg(ex, 0))); + jl_eval_throw(m, jl_exprarg(ex, 0), *toplevel_filename, *toplevel_lineno); } else if (jl_is_symbol(ex)) { JL_GC_POP(); @@ -903,7 +917,8 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int assert(head == jl_thunk_sym); thk = (jl_code_info_t*)jl_exprarg(ex, 0); if (!jl_is_code_info(thk) || !jl_typetagis(thk->code, jl_array_any_type)) { - jl_eval_errorf(m, "malformed \"thunk\" statement"); + jl_eval_errorf(m, *toplevel_filename, *toplevel_lineno, + "malformed \"thunk\" statement"); } body_attributes((jl_array_t*)thk->code, &has_ccall, &has_defs, &has_loops, &has_opaque, &forced_compile); @@ -924,7 +939,7 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int size_t world = jl_atomic_load_acquire(&jl_world_counter); ct->world_age = world; if (!has_defs && jl_get_module_infer(m) != 0) { - (void)jl_type_infer(mfunc, world, 0); + (void)jl_type_infer(mfunc, world, 0, SOURCE_MODE_NOT_REQUIRED); } result = jl_invoke(/*func*/NULL, /*args*/NULL, /*nargs*/0, mfunc); ct->world_age = last_age; @@ -944,7 +959,9 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int JL_DLLEXPORT jl_value_t *jl_toplevel_eval(jl_module_t *m, jl_value_t *v) { - return jl_toplevel_eval_flex(m, v, 1, 0); + const char *filename = jl_filename; + int lieno = jl_lineno; + return jl_toplevel_eval_flex(m, v, 1, 0, &filename, &lieno); } // Check module `m` is open for `eval/include`, or throw an error. @@ -957,7 +974,7 @@ JL_DLLEXPORT void jl_check_top_level_effect(jl_module_t *m, char *fname) JL_LOCK(&jl_modules_mutex); int open = ptrhash_has(&jl_current_modules, (void*)m); if (!open && jl_module_init_order != NULL) { - size_t i, l = jl_array_len(jl_module_init_order); + size_t i, l = jl_array_nrows(jl_module_init_order); for (i = 0; i < l; i++) { if (m == (jl_module_t*)jl_array_ptr_ref(jl_module_init_order, i)) { open = 1; @@ -1005,10 +1022,10 @@ JL_DLLEXPORT jl_value_t *jl_infer_thunk(jl_code_info_t *thk, jl_module_t *m) JL_GC_PUSH1(&li); jl_resolve_globals_in_ir((jl_array_t*)thk->code, m, NULL, 0); jl_task_t *ct = jl_current_task; - jl_code_info_t *src = jl_type_infer(li, ct->world_age, 0); + jl_code_instance_t *ci = jl_type_infer(li, ct->world_age, 0, SOURCE_MODE_NOT_REQUIRED); JL_GC_POP(); - if (src) - return src->rettype; + if (ci) + return ci->rettype; return (jl_value_t*)jl_any_type; } @@ -1044,7 +1061,8 @@ static jl_value_t *jl_parse_eval_all(jl_module_t *module, jl_value_t *text, size_t last_age = ct->world_age; int lineno = 0; jl_lineno = 0; - jl_filename = jl_string_data(filename); + const char *filename_str = jl_string_data(filename); + jl_filename = filename_str; int err = 0; JL_TRY { @@ -1059,7 +1077,7 @@ static jl_value_t *jl_parse_eval_all(jl_module_t *module, jl_value_t *text, expression = jl_expand_with_loc_warn(expression, module, jl_string_data(filename), lineno); ct->world_age = jl_atomic_load_acquire(&jl_world_counter); - result = jl_toplevel_eval_flex(module, expression, 1, 1); + result = jl_toplevel_eval_flex(module, expression, 1, 1, &filename_str, &lineno); } } JL_CATCH { @@ -1076,7 +1094,7 @@ static jl_value_t *jl_parse_eval_all(jl_module_t *module, jl_value_t *text, jl_rethrow(); else jl_rethrow_other(jl_new_struct(jl_loaderror_type, filename, result, - jl_current_exception())); + jl_current_exception(ct))); } JL_GC_POP(); return result; diff --git a/src/typemap.c b/src/typemap.c index 1bdbe52a974dd..b8b699e101fe5 100644 --- a/src/typemap.c +++ b/src/typemap.c @@ -277,21 +277,21 @@ static int is_cache_leaf(jl_value_t *ty, int tparam) return (jl_is_concrete_type(ty) && (tparam || !jl_is_kind(ty))); } -static _Atomic(jl_value_t*) *mtcache_hash_lookup_bp(jl_array_t *cache JL_PROPAGATES_ROOT, jl_value_t *ty) JL_NOTSAFEPOINT +static _Atomic(jl_value_t*) *mtcache_hash_lookup_bp(jl_genericmemory_t *cache JL_PROPAGATES_ROOT, jl_value_t *ty) JL_NOTSAFEPOINT { - if (cache == (jl_array_t*)jl_an_empty_vec_any) + if (cache == (jl_genericmemory_t*)jl_an_empty_memory_any) return NULL; _Atomic(jl_value_t*) *pml = jl_table_peek_bp(cache, ty); JL_GC_PROMISE_ROOTED(pml); // clang-sa doesn't trust our JL_PROPAGATES_ROOT claim return pml; } -static void mtcache_hash_insert(_Atomic(jl_array_t*) *cache, jl_value_t *parent, jl_value_t *key, jl_typemap_t *val) +static void mtcache_hash_insert(_Atomic(jl_genericmemory_t*) *cache, jl_value_t *parent, jl_value_t *key, jl_typemap_t *val) { int inserted = 0; - jl_array_t *a = jl_atomic_load_relaxed(cache); - if (a == (jl_array_t*)jl_an_empty_vec_any) { - a = jl_alloc_vec_any(16); + jl_genericmemory_t *a = jl_atomic_load_relaxed(cache); + if (a == (jl_genericmemory_t*)jl_an_empty_memory_any) { + a = jl_alloc_memory_any(16); jl_atomic_store_release(cache, a); if (parent) jl_gc_wb(parent, a); @@ -305,9 +305,9 @@ static void mtcache_hash_insert(_Atomic(jl_array_t*) *cache, jl_value_t *parent, } } -static jl_typemap_t *mtcache_hash_lookup(jl_array_t *cache JL_PROPAGATES_ROOT, jl_value_t *ty) JL_NOTSAFEPOINT +static jl_typemap_t *mtcache_hash_lookup(jl_genericmemory_t *cache JL_PROPAGATES_ROOT, jl_value_t *ty) JL_NOTSAFEPOINT { - if (cache == (jl_array_t*)jl_an_empty_vec_any) + if (cache == (jl_genericmemory_t*)jl_an_empty_memory_any) return (jl_typemap_t*)jl_nothing; jl_typemap_t *ml = (jl_typemap_t*)jl_eqtable_get(cache, ty, jl_nothing); return ml; @@ -315,17 +315,17 @@ static jl_typemap_t *mtcache_hash_lookup(jl_array_t *cache JL_PROPAGATES_ROOT, j // ----- Sorted Type Signature Lookup Matching ----- // -static int jl_typemap_array_visitor(jl_array_t *a, jl_typemap_visitor_fptr fptr, void *closure) +static int jl_typemap_memory_visitor(jl_genericmemory_t *a, jl_typemap_visitor_fptr fptr, void *closure) { - size_t i, l = jl_array_len(a); - _Atomic(jl_typemap_t*) *data = (_Atomic(jl_typemap_t*)*)jl_array_data(a); + size_t i, l = a->length; + _Atomic(jl_typemap_t*) *data = (_Atomic(jl_typemap_t*)*) a->ptr; for (i = 1; i < l; i += 2) { jl_value_t *d = jl_atomic_load_relaxed(&data[i]); JL_GC_PROMISE_ROOTED(d); if (d == NULL) continue; - if (jl_is_array(d)) { - if (!jl_typemap_array_visitor((jl_array_t*)d, fptr, closure)) + if (jl_is_genericmemory(d)) { + if (!jl_typemap_memory_visitor((jl_genericmemory_t*)d, fptr, closure)) return 0; } else { @@ -352,23 +352,23 @@ int jl_typemap_visitor(jl_typemap_t *cache, jl_typemap_visitor_fptr fptr, void * { if (jl_typeof(cache) == (jl_value_t*)jl_typemap_level_type) { jl_typemap_level_t *node = (jl_typemap_level_t*)cache; - jl_array_t *a; + jl_genericmemory_t *a; JL_GC_PUSH1(&a); a = jl_atomic_load_relaxed(&node->targ); - if (a != (jl_array_t*)jl_an_empty_vec_any) - if (!jl_typemap_array_visitor(a, fptr, closure)) + if (a != (jl_genericmemory_t*)jl_an_empty_memory_any) + if (!jl_typemap_memory_visitor(a, fptr, closure)) goto exit; a = jl_atomic_load_relaxed(&node->arg1); - if (a != (jl_array_t*)jl_an_empty_vec_any) - if (!jl_typemap_array_visitor(a, fptr, closure)) + if (a != (jl_genericmemory_t*)jl_an_empty_memory_any) + if (!jl_typemap_memory_visitor(a, fptr, closure)) goto exit; a = jl_atomic_load_relaxed(&node->tname); - if (a != (jl_array_t*)jl_an_empty_vec_any) - if (!jl_typemap_array_visitor(a, fptr, closure)) + if (a != (jl_genericmemory_t*)jl_an_empty_memory_any) + if (!jl_typemap_memory_visitor(a, fptr, closure)) goto exit; a = jl_atomic_load_relaxed(&node->name1); - if (a != (jl_array_t*)jl_an_empty_vec_any) - if (!jl_typemap_array_visitor(a, fptr, closure)) + if (a != (jl_genericmemory_t*)jl_an_empty_memory_any) + if (!jl_typemap_memory_visitor(a, fptr, closure)) goto exit; if (!jl_typemap_node_visitor(jl_atomic_load_relaxed(&node->linear), fptr, closure)) goto exit; @@ -451,12 +451,12 @@ static int concrete_intersects(jl_value_t *t, jl_value_t *ty, int8_t tparam) // tparam bit 0 is ::Type{T} (vs. T) // tparam bit 1 is typename(T) (vs. T) -static int jl_typemap_intersection_array_visitor(jl_array_t *a, jl_value_t *ty, int8_t tparam, +static int jl_typemap_intersection_memory_visitor(jl_genericmemory_t *a, jl_value_t *ty, int8_t tparam, int8_t offs, struct typemap_intersection_env *closure) { JL_GC_PUSH1(&a); - size_t i, l = jl_array_len(a); - _Atomic(jl_typemap_t*) *data = (_Atomic(jl_typemap_t*)*)jl_array_data(a); + size_t i, l = a->length; + _Atomic(jl_typemap_t*) *data = (_Atomic(jl_typemap_t*)*) a->ptr; unsigned height = 0; jl_datatype_t *tydt = jl_any_type; if (tparam & 2) { @@ -492,8 +492,8 @@ static int jl_typemap_intersection_array_visitor(jl_array_t *a, jl_value_t *ty, tname_intersection_dt(tydt, (jl_typename_t*)t, height)) { if ((tparam & 1) && t == (jl_value_t*)jl_typeofbottom_type->name) // skip Type{Union{}} and Type{typeof(Union{})}, since the caller should have already handled those continue; - if (jl_is_array(ml)) { - if (!jl_typemap_intersection_array_visitor((jl_array_t*)ml, ty, tparam & ~2, offs, closure)) + if (jl_is_genericmemory(ml)) { + if (!jl_typemap_intersection_memory_visitor((jl_genericmemory_t*)ml, ty, tparam & ~2, offs, closure)) goto exit; } else { @@ -531,9 +531,9 @@ static int jl_typemap_intersection_node_visitor(jl_typemap_entry_t *ml, struct t // that can be absolutely critical for speed register jl_typemap_intersection_visitor_fptr fptr = closure->fptr; for (; ml != (void*)jl_nothing; ml = jl_atomic_load_relaxed(&ml->next)) { - if (closure->max_valid < ml->min_world) + if (closure->max_valid < jl_atomic_load_relaxed(&ml->min_world)) continue; - if (closure->min_valid > ml->max_world) + if (closure->min_valid > jl_atomic_load_relaxed(&ml->max_world)) continue; jl_svec_t **penv = NULL; if (closure->env) { @@ -627,15 +627,15 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, if (jl_has_free_typevars(ty)) ty = jl_rewrap_unionall(ty, closure->type); JL_GC_PUSH1(&ty); - jl_array_t *targ = jl_atomic_load_relaxed(&cache->targ); - jl_array_t *tname = jl_atomic_load_relaxed(&cache->tname); + jl_genericmemory_t *targ = jl_atomic_load_relaxed(&cache->targ); + jl_genericmemory_t *tname = jl_atomic_load_relaxed(&cache->tname); int maybe_type = 0; int maybe_kind = 0; int exclude_typeofbottom = 0; jl_value_t *typetype = NULL; jl_value_t *name = NULL; // pre-check: optimized pre-intersection test to see if `ty` could intersect with any Type or Kind - if (targ != (jl_array_t*)jl_an_empty_vec_any || tname != (jl_array_t*)jl_an_empty_vec_any) { + if (targ != (jl_genericmemory_t*)jl_an_empty_memory_any || tname != (jl_genericmemory_t*)jl_an_empty_memory_any) { maybe_kind = jl_has_intersect_kind_not_type(ty); maybe_type = maybe_kind || jl_has_intersect_type_not_kind(ty); if (maybe_type && !maybe_kind) { @@ -651,7 +651,7 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, } } // First check for intersections with methods defined on Type{T}, where T was a concrete type - if (targ != (jl_array_t*)jl_an_empty_vec_any && maybe_type && + if (targ != (jl_genericmemory_t*)jl_an_empty_memory_any && maybe_type && (!typetype || jl_has_free_typevars(typetype) || is_cache_leaf(typetype, 1))) { // otherwise cannot contain this particular kind, so don't bother with checking if (!exclude_typeofbottom) { // detect Type{Union{}}, Type{Type{Union{}}}, and Type{typeof(Union{}} and do those early here @@ -680,18 +680,18 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, // attempt semi-direct lookup of types via their names // consider the type name first jl_value_t *ml = mtcache_hash_lookup(targ, (jl_value_t*)name); - if (jl_is_array(ml)) { + if (jl_is_genericmemory(ml)) { if (typetype && !jl_has_free_typevars(typetype)) { // direct lookup of leaf types if (is_cache_leaf(typetype, 1)) { - ml = mtcache_hash_lookup((jl_array_t*)ml, typetype); + ml = mtcache_hash_lookup((jl_genericmemory_t*)ml, typetype); if (ml != jl_nothing) { if (!jl_typemap_intersection_visitor((jl_typemap_t*)ml, offs+1, closure)) { JL_GC_POP(); return 0; } } } } else { - if (!jl_typemap_intersection_array_visitor((jl_array_t*)ml, ty, 1, offs, closure)) { JL_GC_POP(); return 0; } + if (!jl_typemap_intersection_memory_visitor((jl_genericmemory_t*)ml, ty, 1, offs, closure)) { JL_GC_POP(); return 0; } } } else if (ml != jl_nothing) { @@ -699,19 +699,19 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, } } else { - // else an array scan is required to consider all the possible subtypes - if (!jl_typemap_intersection_array_visitor(targ, exclude_typeofbottom && !maybe_kind ? ty : (jl_value_t*)jl_any_type, 3, offs, closure)) { JL_GC_POP(); return 0; } + // else a scan is required to consider all the possible subtypes + if (!jl_typemap_intersection_memory_visitor(targ, exclude_typeofbottom && !maybe_kind ? ty : (jl_value_t*)jl_any_type, 3, offs, closure)) { JL_GC_POP(); return 0; } } } } - jl_array_t *cachearg1 = jl_atomic_load_relaxed(&cache->arg1); - if (cachearg1 != (jl_array_t*)jl_an_empty_vec_any) { + jl_genericmemory_t *cachearg1 = jl_atomic_load_relaxed(&cache->arg1); + if (cachearg1 != (jl_genericmemory_t*)jl_an_empty_memory_any) { if (is_cache_leaf(ty, 0)) { jl_typename_t *name = ty == jl_bottom_type ? jl_typeofbottom_type->name : ((jl_datatype_t*)ty)->name; // direct lookup of leaf types jl_value_t *ml = mtcache_hash_lookup(cachearg1, (jl_value_t*)name); - if (jl_is_array(ml)) - ml = mtcache_hash_lookup((jl_array_t*)ml, ty); + if (jl_is_genericmemory(ml)) + ml = mtcache_hash_lookup((jl_genericmemory_t*)ml, ty); if (ml != jl_nothing) { if (!jl_typemap_intersection_visitor(ml, offs+1, closure)) { JL_GC_POP(); return 0; } } @@ -721,21 +721,21 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, if (name && jl_type_extract_name_precise(ty, 0)) { // direct lookup of leaf types jl_value_t *ml = mtcache_hash_lookup(cachearg1, name); - if (jl_is_array(ml)) { - if (!jl_typemap_intersection_array_visitor((jl_array_t*)ml, ty, 0, offs, closure)) { JL_GC_POP(); return 0; } + if (jl_is_genericmemory(ml)) { + if (!jl_typemap_intersection_memory_visitor((jl_genericmemory_t*)ml, ty, 0, offs, closure)) { JL_GC_POP(); return 0; } } else { if (!jl_typemap_intersection_visitor((jl_typemap_t*)ml, offs+1, closure)) { JL_GC_POP(); return 0; } } } else { - // else an array scan is required to check subtypes - if (!jl_typemap_intersection_array_visitor(cachearg1, ty, 2, offs, closure)) { JL_GC_POP(); return 0; } + // else a scan is required to check subtypes + if (!jl_typemap_intersection_memory_visitor(cachearg1, ty, 2, offs, closure)) { JL_GC_POP(); return 0; } } } } // Next check for intersections with methods defined on Type{T}, where T was not concrete (it might even have been a TypeVar), but had an extractable TypeName - if (tname != (jl_array_t*)jl_an_empty_vec_any && maybe_type) { + if (tname != (jl_genericmemory_t*)jl_an_empty_memory_any && maybe_type) { if (!exclude_typeofbottom || (!typetype && jl_isa((jl_value_t*)jl_typeofbottom_type, ty))) { // detect Type{Union{}}, Type{Type{Union{}}}, and Type{typeof(Union{}} and do those early here // otherwise the possibility of encountering `Type{Union{}}` in this intersection may @@ -775,13 +775,13 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, } } else { - // else an array scan is required to check subtypes of typetype too + // else a scan is required to check subtypes of typetype too tname = jl_atomic_load_relaxed(&cache->tname); // may be GC'd earlier - if (!jl_typemap_intersection_array_visitor(tname, exclude_typeofbottom && !maybe_kind ? ty : (jl_value_t*)jl_any_type, 3, offs, closure)) { JL_GC_POP(); return 0; } + if (!jl_typemap_intersection_memory_visitor(tname, exclude_typeofbottom && !maybe_kind ? ty : (jl_value_t*)jl_any_type, 3, offs, closure)) { JL_GC_POP(); return 0; } } } - jl_array_t *name1 = jl_atomic_load_relaxed(&cache->name1); - if (name1 != (jl_array_t*)jl_an_empty_vec_any) { + jl_genericmemory_t *name1 = jl_atomic_load_relaxed(&cache->name1); + if (name1 != (jl_genericmemory_t*)jl_an_empty_memory_any) { jl_value_t *name = jl_type_extract_name(ty); if (name && jl_type_extract_name_precise(ty, 0)) { jl_datatype_t *super = (jl_datatype_t*)jl_unwrap_unionall(((jl_typename_t*)name)->wrapper); @@ -798,8 +798,8 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, } } else { - // else an array scan is required to check subtypes - if (!jl_typemap_intersection_array_visitor(name1, ty, 2, offs, closure)) { JL_GC_POP(); return 0; } + // else a scan is required to check subtypes + if (!jl_typemap_intersection_memory_visitor(name1, ty, 2, offs, closure)) { JL_GC_POP(); return 0; } } } JL_GC_POP(); @@ -836,9 +836,7 @@ static jl_typemap_entry_t *jl_typemap_entry_assoc_by_type( size_t n = jl_nparams(unw); int typesisva = n == 0 ? 0 : jl_is_vararg(jl_tparam(unw, n-1)); for (; ml != (void*)jl_nothing; ml = jl_atomic_load_relaxed(&ml->next)) { - if (search->max_valid < ml->min_world) - continue; - if (search->min_valid > ml->max_world) + if (search->world < jl_atomic_load_relaxed(&ml->min_world) || search->world > jl_atomic_load_relaxed(&ml->max_world)) continue; size_t lensig = jl_nparams(jl_unwrap_unionall((jl_value_t*)ml->sig)); if (lensig == n || (ml->va && lensig <= n+1)) { @@ -877,24 +875,7 @@ static jl_typemap_entry_t *jl_typemap_entry_assoc_by_type( } } if (ismatch) { - if (search->world < ml->min_world) { - // ignore method table entries that are part of a later world - if (search->max_valid >= ml->min_world) - search->max_valid = ml->min_world - 1; - } - else if (search->world > ml->max_world) { - // ignore method table entries that have been replaced in the current world - if (search->min_valid <= ml->max_world) - search->min_valid = ml->max_world + 1; - } - else { - // intersect the env valid range with method's valid range - if (search->min_valid < ml->min_world) - search->min_valid = ml->min_world; - if (search->max_valid > ml->max_world) - search->max_valid = ml->max_world; - return ml; - } + return ml; } } if (resetenv) @@ -908,7 +889,7 @@ static jl_typemap_entry_t *jl_typemap_entry_lookup_by_type( jl_typemap_entry_t *ml, struct jl_typemap_assoc *search) { for (; ml != (void*)jl_nothing; ml = jl_atomic_load_relaxed(&ml->next)) { - if (search->world < ml->min_world || search->world > ml->max_world) + if (search->world < jl_atomic_load_relaxed(&ml->min_world) || search->world > jl_atomic_load_relaxed(&ml->max_world)) continue; // unroll the first few cases here, to the extent that is possible to do fast and easily jl_value_t *types = search->types; @@ -989,12 +970,12 @@ jl_typemap_entry_t *jl_typemap_assoc_by_type( if (jl_is_type_type(ty)) { jl_value_t *a0 = jl_tparam0(ty); if (is_cache_leaf(a0, 1)) { - jl_array_t *targ = jl_atomic_load_relaxed(&cache->targ); - if (targ != (jl_array_t*)jl_an_empty_vec_any) { + jl_genericmemory_t *targ = jl_atomic_load_relaxed(&cache->targ); + if (targ != (jl_genericmemory_t*)jl_an_empty_memory_any) { jl_typename_t *name = a0 == jl_bottom_type ? jl_typeofbottom_type->name : ((jl_datatype_t*)a0)->name; jl_value_t *ml = mtcache_hash_lookup(targ, (jl_value_t*)name); - if (jl_is_array(ml)) - ml = mtcache_hash_lookup((jl_array_t*)ml, a0); + if (jl_is_genericmemory(ml)) + ml = mtcache_hash_lookup((jl_genericmemory_t*)ml, a0); if (ml != jl_nothing) { jl_typemap_entry_t *li = jl_typemap_assoc_by_type((jl_typemap_t*)ml, search, offs + 1, subtype); if (li) return li; @@ -1004,12 +985,12 @@ jl_typemap_entry_t *jl_typemap_assoc_by_type( } } if (is_cache_leaf(ty, 0)) { - jl_array_t *cachearg1 = jl_atomic_load_relaxed(&cache->arg1); - if (cachearg1 != (jl_array_t*)jl_an_empty_vec_any) { + jl_genericmemory_t *cachearg1 = jl_atomic_load_relaxed(&cache->arg1); + if (cachearg1 != (jl_genericmemory_t*)jl_an_empty_memory_any) { jl_typename_t *name = ty == jl_bottom_type ? jl_typeofbottom_type->name : ((jl_datatype_t*)ty)->name; jl_value_t *ml = mtcache_hash_lookup(cachearg1, (jl_value_t*)name); - if (jl_is_array(ml)) - ml = mtcache_hash_lookup((jl_array_t*)ml, ty); + if (jl_is_genericmemory(ml)) + ml = mtcache_hash_lookup((jl_genericmemory_t*)ml, ty); if (ml != jl_nothing) { jl_typemap_entry_t *li = jl_typemap_assoc_by_type((jl_typemap_t*)ml, search, offs + 1, subtype); if (li) return li; @@ -1020,8 +1001,8 @@ jl_typemap_entry_t *jl_typemap_assoc_by_type( } if (ty || subtype) { // now look at the optimized TypeName caches - jl_array_t *tname = jl_atomic_load_relaxed(&cache->tname); - if (tname != (jl_array_t*)jl_an_empty_vec_any) { + jl_genericmemory_t *tname = jl_atomic_load_relaxed(&cache->tname); + if (tname != (jl_genericmemory_t*)jl_an_empty_memory_any) { jl_value_t *a0 = ty && jl_is_type_type(ty) ? jl_type_extract_name(jl_tparam0(ty)) : NULL; if (a0) { // TODO: if we start analyzing Union types in jl_type_extract_name, then a0 might be over-approximated here, leading us to miss possible subtypes jl_datatype_t *super = (jl_datatype_t*)jl_unwrap_unionall(((jl_typename_t*)a0)->wrapper); @@ -1039,9 +1020,10 @@ jl_typemap_entry_t *jl_typemap_assoc_by_type( } else { if (!ty || !jl_has_empty_intersection((jl_value_t*)jl_type_type, ty)) { + jl_genericmemory_t *tname = jl_atomic_load_relaxed(&cache->tname); // reload after type-intersect // couldn't figure out unique `a0` initial point, so scan all for matches - size_t i, l = jl_array_len(tname); - _Atomic(jl_typemap_t*) *data = (_Atomic(jl_typemap_t*)*)jl_array_ptr_data(tname); + size_t i, l = tname->length; + _Atomic(jl_typemap_t*) *data = (_Atomic(jl_typemap_t*)*) jl_genericmemory_ptr_data(tname); JL_GC_PUSH1(&tname); for (i = 1; i < l; i += 2) { jl_typemap_t *ml = jl_atomic_load_relaxed(&data[i]); @@ -1057,8 +1039,8 @@ jl_typemap_entry_t *jl_typemap_assoc_by_type( } } } - jl_array_t *name1 = jl_atomic_load_relaxed(&cache->name1); - if (name1 != (jl_array_t*)jl_an_empty_vec_any) { + jl_genericmemory_t *name1 = jl_atomic_load_relaxed(&cache->name1); + if (name1 != (jl_genericmemory_t*)jl_an_empty_memory_any) { if (ty) { jl_value_t *a0 = jl_type_extract_name(ty); if (a0) { // TODO: if we start analyzing Union types in jl_type_extract_name, then a0 might be over-approximated here, leading us to miss possible subtypes @@ -1079,8 +1061,8 @@ jl_typemap_entry_t *jl_typemap_assoc_by_type( } else { // doing subtype, but couldn't figure out unique `ty`, so scan all for supertypes - size_t i, l = jl_array_len(name1); - _Atomic(jl_typemap_t*) *data = (_Atomic(jl_typemap_t*)*)jl_array_ptr_data(name1); + size_t i, l = name1->length; + _Atomic(jl_typemap_t*) *data = (_Atomic(jl_typemap_t*)*) jl_genericmemory_ptr_data(name1); JL_GC_PUSH1(&name1); for (i = 1; i < l; i += 2) { jl_typemap_t *ml = jl_atomic_load_relaxed(&data[i]); @@ -1119,7 +1101,7 @@ jl_typemap_entry_t *jl_typemap_entry_assoc_exact(jl_typemap_entry_t *ml, jl_valu // some manually-unrolled common special cases while (ml->simplesig == (void*)jl_nothing && ml->guardsigs == jl_emptysvec && ml->isleafsig) { // use a tight loop for as long as possible - if (world >= ml->min_world && world <= ml->max_world) { + if (world >= jl_atomic_load_relaxed(&ml->min_world) && world <= jl_atomic_load_relaxed(&ml->max_world)) { if (n == jl_nparams(ml->sig) && jl_typeof(arg1) == jl_tparam(ml->sig, 0)) { if (n == 1) return ml; @@ -1144,7 +1126,7 @@ jl_typemap_entry_t *jl_typemap_entry_assoc_exact(jl_typemap_entry_t *ml, jl_valu } for (; ml != (void*)jl_nothing; ml = jl_atomic_load_relaxed(&ml->next)) { - if (world < ml->min_world || world > ml->max_world) + if (world < jl_atomic_load_relaxed(&ml->min_world) || world > jl_atomic_load_relaxed(&ml->max_world)) continue; // ignore replaced methods size_t lensig = jl_nparams(ml->sig); if (lensig == n || (ml->va && lensig <= n+1)) { @@ -1198,26 +1180,26 @@ jl_typemap_entry_t *jl_typemap_level_assoc_exact(jl_typemap_level_t *cache, jl_v jl_value_t *a1 = (offs == 0 ? arg1 : args[offs - 1]); jl_value_t *ty = jl_typeof(a1); assert(jl_is_datatype(ty)); - jl_array_t *targ = jl_atomic_load_relaxed(&cache->targ); - if (targ != (jl_array_t*)jl_an_empty_vec_any && is_cache_leaf(a1, 1)) { + jl_genericmemory_t *targ = jl_atomic_load_relaxed(&cache->targ); + if (targ != (jl_genericmemory_t*)jl_an_empty_memory_any && is_cache_leaf(a1, 1)) { jl_typename_t *name = a1 == jl_bottom_type ? jl_typeofbottom_type->name : ((jl_datatype_t*)a1)->name; jl_value_t *ml_or_cache = mtcache_hash_lookup(targ, (jl_value_t*)name); - if (jl_is_array(ml_or_cache)) - ml_or_cache = mtcache_hash_lookup((jl_array_t*)ml_or_cache, a1); + if (jl_is_genericmemory(ml_or_cache)) + ml_or_cache = mtcache_hash_lookup((jl_genericmemory_t*)ml_or_cache, a1); jl_typemap_entry_t *ml = jl_typemap_assoc_exact(ml_or_cache, arg1, args, n, offs+1, world); if (ml) return ml; } - jl_array_t *cachearg1 = jl_atomic_load_relaxed(&cache->arg1); - if (cachearg1 != (jl_array_t*)jl_an_empty_vec_any && is_cache_leaf(ty, 0)) { + jl_genericmemory_t *cachearg1 = jl_atomic_load_relaxed(&cache->arg1); + if (cachearg1 != (jl_genericmemory_t*)jl_an_empty_memory_any && is_cache_leaf(ty, 0)) { jl_typename_t *name = ty == jl_bottom_type ? jl_typeofbottom_type->name : ((jl_datatype_t*)ty)->name; jl_value_t *ml_or_cache = mtcache_hash_lookup(cachearg1, (jl_value_t*)name); - if (jl_is_array(ml_or_cache)) - ml_or_cache = mtcache_hash_lookup((jl_array_t*)ml_or_cache, ty); + if (jl_is_genericmemory(ml_or_cache)) + ml_or_cache = mtcache_hash_lookup((jl_genericmemory_t*)ml_or_cache, ty); jl_typemap_entry_t *ml = jl_typemap_assoc_exact((jl_typemap_t*)ml_or_cache, arg1, args, n, offs+1, world); if (ml) return ml; } - jl_array_t *tname = jl_atomic_load_relaxed(&cache->tname); - if (jl_is_kind(ty) && tname != (jl_array_t*)jl_an_empty_vec_any) { + jl_genericmemory_t *tname = jl_atomic_load_relaxed(&cache->tname); + if (jl_is_kind(ty) && tname != (jl_genericmemory_t*)jl_an_empty_memory_any) { jl_value_t *name = jl_type_extract_name(a1); if (name) { if (ty != (jl_value_t*)jl_datatype_type) @@ -1235,8 +1217,8 @@ jl_typemap_entry_t *jl_typemap_level_assoc_exact(jl_typemap_level_t *cache, jl_v } else { // couldn't figure out unique `name` initial point, so must scan all for matches - size_t i, l = jl_array_len(tname); - _Atomic(jl_typemap_t*) *data = (_Atomic(jl_typemap_t*)*)jl_array_ptr_data(tname); + size_t i, l = tname->length; + _Atomic(jl_typemap_t*) *data = (_Atomic(jl_typemap_t*)*) jl_genericmemory_ptr_data(tname); JL_GC_PUSH1(&tname); for (i = 1; i < l; i += 2) { jl_typemap_t *ml_or_cache = jl_atomic_load_relaxed(&data[i]); @@ -1251,8 +1233,8 @@ jl_typemap_entry_t *jl_typemap_level_assoc_exact(jl_typemap_level_t *cache, jl_v JL_GC_POP(); } } - jl_array_t *name1 = jl_atomic_load_relaxed(&cache->name1); - if (name1 != (jl_array_t*)jl_an_empty_vec_any) { + jl_genericmemory_t *name1 = jl_atomic_load_relaxed(&cache->name1); + if (name1 != (jl_genericmemory_t*)jl_an_empty_memory_any) { while (1) { name1 = jl_atomic_load_relaxed(&cache->name1); // reload after tree descent (which may hit safepoints) jl_typemap_t *ml_or_cache = mtcache_hash_lookup( @@ -1297,23 +1279,23 @@ static jl_typemap_level_t *jl_new_typemap_level(void) jl_typemap_level_t *cache = (jl_typemap_level_t*)jl_gc_alloc(ct->ptls, sizeof(jl_typemap_level_t), jl_typemap_level_type); - jl_atomic_store_relaxed(&cache->arg1, (jl_array_t*)jl_an_empty_vec_any); - jl_atomic_store_relaxed(&cache->targ, (jl_array_t*)jl_an_empty_vec_any); - jl_atomic_store_relaxed(&cache->name1, (jl_array_t*)jl_an_empty_vec_any); - jl_atomic_store_relaxed(&cache->tname, (jl_array_t*)jl_an_empty_vec_any); + jl_atomic_store_relaxed(&cache->arg1, (jl_genericmemory_t*)jl_an_empty_memory_any); + jl_atomic_store_relaxed(&cache->targ, (jl_genericmemory_t*)jl_an_empty_memory_any); + jl_atomic_store_relaxed(&cache->name1, (jl_genericmemory_t*)jl_an_empty_memory_any); + jl_atomic_store_relaxed(&cache->tname, (jl_genericmemory_t*)jl_an_empty_memory_any); jl_atomic_store_relaxed(&cache->linear, (jl_typemap_entry_t*)jl_nothing); jl_atomic_store_relaxed(&cache->any, jl_nothing); return cache; } -static void jl_typemap_array_insert_( - jl_typemap_t *map, _Atomic(jl_array_t*) *pcache, jl_value_t *key, jl_typemap_entry_t *newrec, +static void jl_typemap_memory_insert_( + jl_typemap_t *map, _Atomic(jl_genericmemory_t*) *pcache, jl_value_t *key, jl_typemap_entry_t *newrec, jl_value_t *parent, int8_t tparam, int8_t offs, jl_value_t *doublesplit); static jl_value_t *jl_method_convert_list_to_cache( jl_typemap_t *map, jl_typemap_entry_t *ml, int8_t tparam, int8_t offs, int8_t doublesplit) { - jl_value_t *cache = doublesplit ? jl_an_empty_vec_any : (jl_value_t*)jl_new_typemap_level(); + jl_value_t *cache = doublesplit ? jl_an_empty_memory_any : (jl_value_t*)jl_new_typemap_level(); jl_typemap_entry_t *next = NULL; JL_GC_PUSH3(&cache, &next, &ml); while (ml != (void*)jl_nothing) { @@ -1336,7 +1318,7 @@ static jl_value_t *jl_method_convert_list_to_cache( assert(jl_is_type_type(key)); key = jl_tparam0(key); } - jl_typemap_array_insert_(map, (_Atomic(jl_array_t*)*)&cache, key, ml, NULL, 0, offs, NULL); + jl_typemap_memory_insert_(map, (_Atomic(jl_genericmemory_t*)*)&cache, key, ml, NULL, 0, offs, NULL); } else jl_typemap_level_insert_(map, (jl_typemap_level_t*)cache, ml, offs); @@ -1371,9 +1353,9 @@ static void jl_typemap_insert_generic( jl_typemap_entry_t *newrec, int8_t tparam, int8_t offs, jl_value_t *doublesplit) { jl_value_t *ml = jl_atomic_load_relaxed(pml); - if (jl_is_array(ml)) { + if (jl_is_genericmemory(ml)) { assert(doublesplit); - jl_typemap_array_insert_(map, (_Atomic(jl_array_t*)*)pml, doublesplit, newrec, parent, 0, offs, NULL); + jl_typemap_memory_insert_(map, (_Atomic(jl_genericmemory_t*)*)pml, doublesplit, newrec, parent, 0, offs, NULL); return; } if (jl_typeof(ml) == (jl_value_t*)jl_typemap_level_type) { @@ -1389,7 +1371,7 @@ static void jl_typemap_insert_generic( jl_atomic_store_release(pml, ml); jl_gc_wb(parent, ml); if (doublesplit) - jl_typemap_array_insert_(map, (_Atomic(jl_array_t*)*)pml, doublesplit, newrec, parent, 0, offs, NULL); + jl_typemap_memory_insert_(map, (_Atomic(jl_genericmemory_t*)*)pml, doublesplit, newrec, parent, 0, offs, NULL); else jl_typemap_level_insert_(map, (jl_typemap_level_t*)ml, newrec, offs); return; @@ -1399,16 +1381,16 @@ static void jl_typemap_insert_generic( parent, newrec); } -static void jl_typemap_array_insert_( - jl_typemap_t *map, _Atomic(jl_array_t*) *pcache, jl_value_t *key, jl_typemap_entry_t *newrec, +static void jl_typemap_memory_insert_( + jl_typemap_t *map, _Atomic(jl_genericmemory_t*) *pcache, jl_value_t *key, jl_typemap_entry_t *newrec, jl_value_t *parent, int8_t tparam, int8_t offs, jl_value_t *doublesplit) { - jl_array_t *cache = jl_atomic_load_relaxed(pcache); + jl_genericmemory_t *cache = jl_atomic_load_relaxed(pcache); _Atomic(jl_value_t*) *pml = mtcache_hash_lookup_bp(cache, key); if (pml == NULL) mtcache_hash_insert(pcache, parent, key, (jl_typemap_t*)newrec); else - jl_typemap_insert_generic(map, pml, (jl_value_t*)cache, newrec, tparam, offs + (doublesplit ? 0 : 1), doublesplit); + jl_typemap_insert_generic(map, pml, (jl_value_t*) cache, newrec, tparam, offs + (doublesplit ? 0 : 1), doublesplit); } static void jl_typemap_level_insert_( @@ -1451,13 +1433,13 @@ static void jl_typemap_level_insert_( jl_value_t *a0 = jl_tparam0(t1); if (is_cache_leaf(a0, 1)) { jl_typename_t *name = a0 == jl_bottom_type ? jl_typeofbottom_type->name : ((jl_datatype_t*)a0)->name; - jl_typemap_array_insert_(map, &cache->targ, (jl_value_t*)name, newrec, (jl_value_t*)cache, 1, offs, jl_is_datatype(name->wrapper) ? NULL : a0); + jl_typemap_memory_insert_(map, &cache->targ, (jl_value_t*)name, newrec, (jl_value_t*)cache, 1, offs, jl_is_datatype(name->wrapper) ? NULL : a0); return; } } if (is_cache_leaf(t1, 0)) { jl_typename_t *name = t1 == jl_bottom_type ? jl_typeofbottom_type->name : ((jl_datatype_t*)t1)->name; - jl_typemap_array_insert_(map, &cache->arg1, (jl_value_t*)name, newrec, (jl_value_t*)cache, 0, offs, jl_is_datatype(name->wrapper) ? NULL : t1); + jl_typemap_memory_insert_(map, &cache->arg1, (jl_value_t*)name, newrec, (jl_value_t*)cache, 0, offs, jl_is_datatype(name->wrapper) ? NULL : t1); return; } @@ -1467,12 +1449,12 @@ static void jl_typemap_level_insert_( if (jl_is_type_type(t1)) { a0 = jl_type_extract_name(jl_tparam0(t1)); jl_datatype_t *super = a0 ? (jl_datatype_t*)jl_unwrap_unionall(((jl_typename_t*)a0)->wrapper) : jl_any_type; - jl_typemap_array_insert_(map, &cache->tname, (jl_value_t*)super->name, newrec, (jl_value_t*)cache, 1, offs, NULL); + jl_typemap_memory_insert_(map, &cache->tname, (jl_value_t*)super->name, newrec, (jl_value_t*)cache, 1, offs, NULL); return; } a0 = jl_type_extract_name(t1); if (a0 && a0 != (jl_value_t*)jl_any_type->name) { - jl_typemap_array_insert_(map, &cache->name1, a0, newrec, (jl_value_t*)cache, 0, offs, NULL); + jl_typemap_memory_insert_(map, &cache->name1, a0, newrec, (jl_value_t*)cache, 0, offs, NULL); return; } } @@ -1516,8 +1498,8 @@ jl_typemap_entry_t *jl_typemap_alloc( newrec->func.value = newvalue; newrec->guardsigs = guardsigs; jl_atomic_store_relaxed(&newrec->next, (jl_typemap_entry_t*)jl_nothing); - newrec->min_world = min_world; - newrec->max_world = max_world; + jl_atomic_store_relaxed(&newrec->min_world, min_world); + jl_atomic_store_relaxed(&newrec->max_world, max_world); newrec->va = isva; newrec->issimplesig = issimplesig; newrec->isleafsig = isleafsig; diff --git a/src/work-stealing-queue.h b/src/work-stealing-queue.h index 38429e02886e9..9ec283b610e62 100644 --- a/src/work-stealing-queue.h +++ b/src/work-stealing-queue.h @@ -3,6 +3,8 @@ #ifndef WORK_STEALING_QUEUE_H #define WORK_STEALING_QUEUE_H +#include + #include "julia_atomics.h" #include "assert.h" @@ -34,10 +36,17 @@ static inline ws_array_t *create_ws_array(size_t capacity, int32_t eltsz) JL_NOT return a; } +static inline void free_ws_array(ws_array_t *a) +{ + free(a->buffer); + free(a); +} + typedef struct { - _Atomic(int64_t) top; - _Atomic(int64_t) bottom; - _Atomic(ws_array_t *) array; + // align to JL_CACHE_BYTE_ALIGNMENT + alignas(JL_CACHE_BYTE_ALIGNMENT) _Atomic(int64_t) top; + alignas(JL_CACHE_BYTE_ALIGNMENT) _Atomic(int64_t) bottom; + alignas(JL_CACHE_BYTE_ALIGNMENT) _Atomic(ws_array_t *) array; } ws_queue_t; static inline ws_array_t *ws_queue_push(ws_queue_t *q, void *elt, int32_t eltsz) JL_NOTSAFEPOINT diff --git a/stdlib/.gitignore b/stdlib/.gitignore index f76eb3df57145..93668857189af 100644 --- a/stdlib/.gitignore +++ b/stdlib/.gitignore @@ -25,6 +25,10 @@ /LazyArtifacts /Distributed-* /Distributed +/StyledStrings-* +/StyledStrings +/JuliaSyntaxHighlighting-* +/JuliaSyntaxHighlighting /*_jll/StdlibArtifacts.toml /*/Manifest.toml /*.image diff --git a/stdlib/ArgTools.version b/stdlib/ArgTools.version index ad2febe81e46e..09090a62ce0bf 100644 --- a/stdlib/ArgTools.version +++ b/stdlib/ArgTools.version @@ -1,4 +1,4 @@ ARGTOOLS_BRANCH = master -ARGTOOLS_SHA1 = 4eccde45ddc27e4f7fc9094b2861c684e062adb2 +ARGTOOLS_SHA1 = 997089b9cd56404b40ff766759662e16dc1aab4b ARGTOOLS_GIT_URL := https://github.com/JuliaIO/ArgTools.jl.git ARGTOOLS_TAR_URL = https://api.github.com/repos/JuliaIO/ArgTools.jl/tarball/$1 diff --git a/stdlib/Artifacts/Project.toml b/stdlib/Artifacts/Project.toml index 7251b79cea8c1..c4e5cc031375c 100644 --- a/stdlib/Artifacts/Project.toml +++ b/stdlib/Artifacts/Project.toml @@ -1,5 +1,6 @@ name = "Artifacts" uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" +version = "1.11.0" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/Artifacts/docs/src/index.md b/stdlib/Artifacts/docs/src/index.md index 80f4c62cbf77f..1bd75832fb8d3 100644 --- a/stdlib/Artifacts/docs/src/index.md +++ b/stdlib/Artifacts/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/Artifacts/docs/src/index.md" +``` + # Artifacts ```@meta @@ -18,4 +22,7 @@ Artifacts.artifact_meta Artifacts.artifact_hash Artifacts.find_artifacts_toml Artifacts.@artifact_str +Artifacts.artifact_exists +Artifacts.artifact_path +Artifacts.select_downloadable_artifacts ``` diff --git a/stdlib/Artifacts/src/Artifacts.jl b/stdlib/Artifacts/src/Artifacts.jl index 70593bfadae05..968d190c2b443 100644 --- a/stdlib/Artifacts/src/Artifacts.jl +++ b/stdlib/Artifacts/src/Artifacts.jl @@ -1,5 +1,11 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +""" +Artifacts.jl is a Julia module that is used for managing and accessing +artifacts in Julia packages. Artifacts are containers for +platform-specific binaries, datasets, text, or any other kind of data +that would be convenient to place within an immutable, life-cycled datastore. +""" module Artifacts import Base: get, SHA1 @@ -18,7 +24,7 @@ function parse_toml(path::String) Base.parsed_toml(path) end -# keep in sync with Base.project_names and Base.manifest_names +# keep in sync with Base.project_names const artifact_names = ("JuliaArtifacts.toml", "Artifacts.toml") const ARTIFACTS_DIR_OVERRIDE = Ref{Union{String,Nothing}}(nothing) @@ -67,8 +73,8 @@ function parse_mapping(mapping::String, name::String, override_file::String) end return mapping end -function parse_mapping(mapping::Dict, name::String, override_file::String) - return Dict(k => parse_mapping(v, name, override_file) for (k, v) in mapping) +function parse_mapping(mapping::Dict{String, Any}, name::String, override_file::String) + return Dict{String, Any}(k => parse_mapping(v, name, override_file) for (k, v) in mapping) end # Fallthrough for invalid Overrides.toml files parse_mapping(mapping, name::String, override_file::String) = nothing @@ -96,7 +102,7 @@ overriding to another artifact by its content-hash. const ARTIFACT_OVERRIDES = Ref{Union{Dict{Symbol,Any},Nothing}}(nothing) function load_overrides(;force::Bool = false)::Dict{Symbol, Any} if ARTIFACT_OVERRIDES[] !== nothing && !force - return ARTIFACT_OVERRIDES[] + return ARTIFACT_OVERRIDES[]::Dict{Symbol,Any} end # We organize our artifact location overrides into two camps: @@ -106,13 +112,8 @@ function load_overrides(;force::Bool = false)::Dict{Symbol, Any} # Overrides per UUID/bound name are intercepted upon Artifacts.toml load, and new # entries within the "hash" overrides are generated on-the-fly. Thus, all redirects # mechanistically happen through the "hash" overrides. - overrides = Dict{Symbol,Any}( - # Overrides by UUID - :UUID => Dict{Base.UUID,Dict{String,Union{String,SHA1}}}(), - - # Overrides by hash - :hash => Dict{SHA1,Union{String,SHA1}}(), - ) + overrides_uuid = Dict{Base.UUID,Dict{String,Union{String,SHA1}}}() + overrides_hash = Dict{SHA1,Union{String,SHA1}}() for override_file in reverse(artifacts_dirs("Overrides.toml")) !isfile(override_file) && continue @@ -131,7 +132,6 @@ function load_overrides(;force::Bool = false)::Dict{Symbol, Any} # Next, determine if this is a hash override or a UUID/name override if isa(mapping, String) || isa(mapping, SHA1) # if this mapping is a direct mapping (e.g. a String), store it as a hash override - local hash_str hash = tryparse(Base.SHA1, k) if hash === nothing @error("Invalid override in '$(override_file)': Invalid SHA1 hash '$(k)'") @@ -139,12 +139,12 @@ function load_overrides(;force::Bool = false)::Dict{Symbol, Any} end # If this mapping is the empty string, un-override it - if mapping == "" - delete!(overrides[:hash], hash) + if mapping isa String && isempty(mapping) + delete!(overrides_hash, hash) else - overrides[:hash][hash] = mapping + overrides_hash[hash] = mapping end - elseif isa(mapping, Dict) + elseif isa(mapping, Dict{String, Any}) # Convert `k` into a uuid uuid = tryparse(Base.UUID, k) if uuid === nothing @@ -153,19 +153,18 @@ function load_overrides(;force::Bool = false)::Dict{Symbol, Any} end # If this mapping is itself a dict, store it as a set of UUID/artifact name overrides - ovruuid = overrides[:UUID]::Dict{Base.UUID,Dict{String,Union{String,SHA1}}} - if !haskey(ovruuid, uuid) - ovruuid[uuid] = Dict{String,Union{String,SHA1}}() + if !haskey(overrides_uuid, uuid) + overrides_uuid[uuid] = Dict{String,Union{String,SHA1}}() end # For each name in the mapping, update appropriately for (name, override_value) in mapping # If the mapping for this name is the empty string, un-override it - if override_value == "" - delete!(ovruuid[uuid], name) + if override_value isa String && isempty(override_value) + delete!(overrides_uuid[uuid], name) else # Otherwise, store it! - ovruuid[uuid][name] = override_value + overrides_uuid[uuid][name] = override_value::Union{Base.SHA1, String} end end else @@ -174,6 +173,14 @@ function load_overrides(;force::Bool = false)::Dict{Symbol, Any} end end + overrides = Dict{Symbol,Any}( + # Overrides by UUID + :UUID => overrides_uuid, + + # Overrides by hash + :hash => overrides_hash + ) + ARTIFACT_OVERRIDES[] = overrides return overrides end @@ -190,11 +197,13 @@ Query the loaded `/artifacts/Overrides.toml` settings for artifacts that redirected to a particular path or another content-hash. """ function query_override(hash::SHA1; overrides::Dict{Symbol,Any} = load_overrides()) - return map_override_path(get(overrides[:hash], hash, nothing)) + overrides_hash = overrides[:hash]::Dict{SHA1,Union{String,SHA1}} + return map_override_path(get(overrides_hash, hash, nothing)) end function query_override(pkg::Base.UUID, artifact_name::String; overrides::Dict{Symbol,Any} = load_overrides()) - if haskey(overrides[:UUID], pkg) - return map_override_path(get(overrides[:UUID][pkg], artifact_name, nothing)) + overrides_uuid = overrides[:UUID]::Dict{Base.UUID,Dict{String,Union{String,SHA1}}} + if haskey(overrides_uuid, pkg) + return map_override_path(get(overrides_uuid[pkg], artifact_name, nothing)) end return nothing end @@ -284,7 +293,7 @@ function unpack_platform(entry::Dict{String,Any}, name::String, delete!(tags, "os") delete!(tags, "arch") delete!(tags, "git-tree-sha1") - return Platform(entry["arch"], entry["os"], tags) + return Platform(entry["arch"]::String, entry["os"]::String, tags) end function pack_platform!(meta::Dict, p::AbstractPlatform) @@ -326,8 +335,11 @@ function process_overrides(artifact_dict::Dict, pkg_uuid::Base.UUID) # Insert just-in-time hash overrides by looking up the names of anything we need to # override for this UUID, and inserting new overrides for those hashes. overrides = load_overrides() - if haskey(overrides[:UUID], pkg_uuid) - pkg_overrides = overrides[:UUID][pkg_uuid]::Dict{String, <:Any} + overrides_uuid = overrides[:UUID]::Dict{Base.UUID,Dict{String,Union{String,SHA1}}} + overrides_hash = overrides[:hash]::Dict{SHA1,Union{String,SHA1}} + + if haskey(overrides_uuid, pkg_uuid) + pkg_overrides = overrides_uuid[pkg_uuid]::Dict{String, <:Any} for name in keys(artifact_dict) # Skip names that we're not overriding @@ -336,14 +348,16 @@ function process_overrides(artifact_dict::Dict, pkg_uuid::Base.UUID) end # If we've got a platform-specific friend, override all hashes: - if isa(artifact_dict[name], Array) - for entry in artifact_dict[name] - hash = SHA1(entry["git-tree-sha1"]) - overrides[:hash][hash] = overrides[:UUID][pkg_uuid][name] + artifact_dict_name = artifact_dict[name] + if isa(artifact_dict_name, Array) + for entry in artifact_dict_name + entry = entry::Dict{String,Any} + hash = SHA1(entry["git-tree-sha1"]::String) + overrides_hash[hash] = overrides_uuid[pkg_uuid][name] end - elseif isa(artifact_dict[name], Dict) - hash = SHA1(artifact_dict[name]["git-tree-sha1"]) - overrides[:hash][hash] = overrides[:UUID][pkg_uuid][name] + elseif isa(artifact_dict_name, Dict{String, Any}) + hash = SHA1(artifact_dict_name["git-tree-sha1"]::String) + overrides_hash[hash] = overrides_uuid[pkg_uuid][name] end end end @@ -388,7 +402,7 @@ function artifact_meta(name::String, artifact_dict::Dict, artifacts_toml::String if isa(meta, Vector) dl_dict = Dict{AbstractPlatform,Dict{String,Any}}() for x in meta - x::Dict{String} + x = x::Dict{String, Any} dl_dict[unpack_platform(x, name, artifacts_toml)] = x end meta = select_platform(dl_dict, platform) @@ -399,9 +413,12 @@ function artifact_meta(name::String, artifact_dict::Dict, artifacts_toml::String end # This is such a no-no, we are going to call it out right here, right now. - if meta !== nothing && !haskey(meta, "git-tree-sha1") - @error("Invalid artifacts file at $(artifacts_toml): artifact '$name' contains no `git-tree-sha1`!") - return nothing + if meta !== nothing + meta = meta::Dict{String, Any} + if !haskey(meta, "git-tree-sha1") + @error("Invalid artifacts file at $(artifacts_toml): artifact '$name' contains no `git-tree-sha1`!") + return nothing + end end # Return the full meta-dict. @@ -548,7 +565,7 @@ function _artifact_str(__module__, artifacts_toml, name, path_tail, artifact_dic if nameof(lazyartifacts) in (:Pkg, :Artifacts) Base.depwarn("using Pkg instead of using LazyArtifacts is deprecated", :var"@artifact_str", force=true) end - return jointail(lazyartifacts.ensure_artifact_installed(string(name), artifacts_toml; platform), path_tail) + return jointail(lazyartifacts.ensure_artifact_installed(string(name), meta, artifacts_toml; platform), path_tail) end error("Artifact $(repr(name)) is a lazy artifact; package developers must call `using LazyArtifacts` in $(__module__) before using lazy artifacts.") end diff --git a/stdlib/Artifacts/test/runtests.jl b/stdlib/Artifacts/test/runtests.jl index 67117217be549..a09b3c7531996 100644 --- a/stdlib/Artifacts/test/runtests.jl +++ b/stdlib/Artifacts/test/runtests.jl @@ -1,12 +1,92 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +import Base: SHA1 using Artifacts, Test, Base.BinaryPlatforms -using Artifacts: with_artifacts_directory, pack_platform!, unpack_platform +using Artifacts: with_artifacts_directory, pack_platform!, unpack_platform, load_overrides +using TOML # prepare for the package tests by ensuring the required artifacts are downloaded now artifacts_dir = mktempdir() run(addenv(`$(Base.julia_cmd()) --color=no $(joinpath(@__DIR__, "refresh_artifacts.jl")) $(artifacts_dir)`, "TERM"=>"dumb")) +@testset "Load Overrides" begin + """ + create_test_overrides_toml(temp_dir::String) + + Create "Overrides.toml" in the given `temp_dir`. + """ + function create_test_overrides_toml(temp_dir::String) + # Define the overrides + overrides = Dict( + "78f35e74ff113f02274ce60dab6e92b4546ef806" => "/path/to/replacement", + "c76f8cda85f83a06d17de6c57aabf9e294eb2537" => "fb886e813a4aed4147d5979fcdf27457d20aa35d", + "d57dbccd-ca19-4d82-b9b8-9d660942965b" => Dict( + "c_simple" => "/path/to/c_simple_dir", + "libfoo" => "fb886e813a4aed4147d5979fcdf27457d20aa35d" + ) + ) + + # Get the artifacts directory + artifacts_dir = joinpath(temp_dir, "artifacts") + + # Ensure the artifacts directory exists + isdir(artifacts_dir) || mkdir(artifacts_dir) + + # Get the path to the Overrides.toml file + overrides_path = joinpath(artifacts_dir, "Overrides.toml") + + # Create the Overrides.toml file + open(overrides_path, "w") do io + TOML.print(io, overrides) + end + end + + # Specify the expected test result when depot path does not exist or no overriding happened + empty_output = Dict{Symbol, Any}( + :UUID => Dict{Base.UUID, Dict{String, Union{SHA1, String}}}(), + :hash => Dict{SHA1, Union{SHA1, String}}() + ) + + # Specify the expected test result when overriding happened + expected_output = Dict{Symbol, Any}( + :UUID => Dict{Base.UUID, Dict{String, Union{SHA1, String}}}(Base.UUID("d57dbccd-ca19-4d82-b9b8-9d660942965b") => Dict("c_simple" => "/path/to/c_simple_dir", "libfoo" => SHA1("fb886e813a4aed4147d5979fcdf27457d20aa35d"))), + :hash => Dict{SHA1, Union{SHA1, String}}(SHA1("78f35e74ff113f02274ce60dab6e92b4546ef806") => "/path/to/replacement", SHA1("c76f8cda85f83a06d17de6c57aabf9e294eb2537") => SHA1("fb886e813a4aed4147d5979fcdf27457d20aa35d")) + ) + + # Test `load_overrides()` works with *no* "Overrides.toml" file + @test load_overrides() == empty_output + + # Create a temporary directory + mktempdir() do temp_dir + # Back up the old `DEPOT_PATH`` + old_depot_path = copy(Base.DEPOT_PATH) + + # Set `DEPOT_PATH` to that directory + empty!(Base.DEPOT_PATH) + push!(Base.DEPOT_PATH, temp_dir) + + try + # Create "Overrides.toml" for the test + create_test_overrides_toml(temp_dir) + + # Test `load_overrides()` works *with* "Overrides.toml" file but non-nothing ARTIFACT_OVERRIDES[] + @test load_overrides() == empty_output + + # Test `load_overrides()` works *with* "Overrides.toml" file with force parameter, which overrides even when `ARTIFACT_OVERRIDES[] !== nothing`` + @test load_overrides(force=true) == expected_output + finally # Make sure `DEPOT_PATH` will be restored to the status quo in the event of a bug + # Restore the old `DEPOT_PATH` to avoid messing with any other code + empty!(Base.DEPOT_PATH) + append!(Base.DEPOT_PATH, old_depot_path) + end + end + # Temporary directory and test "Overrides.toml" file will be automatically deleted when out of scope + # This means after this block, the system *should* behave like this test never happened. + + # Test the "Overrides.toml" file is cleared back to the status quo + @test load_overrides(force=true) == empty_output +end + @testset "Artifact Paths" begin mktempdir() do tempdir with_artifacts_directory(tempdir) do @@ -120,6 +200,23 @@ end end end +@testset "artifact_hash()" begin + # Use the Linus OS on an ARMv7L architecture for the tests to make tests reproducible + armv7l_linux = Platform("armv7l", "linux") + + # Check the first key in Artifacts.toml is hashed correctly + @test artifact_hash("HelloWorldC", joinpath(@__DIR__, "Artifacts.toml"); platform=armv7l_linux) == + SHA1("5a8288c8a30578c0d0f24a9cded29579517ce7a8") + + # Check the second key in Artifacts.toml is hashed correctly + @test artifact_hash("socrates", joinpath(@__DIR__, "Artifacts.toml"); platform=armv7l_linux) == + SHA1("43563e7631a7eafae1f9f8d9d332e3de44ad7239") + + # Check artifact_hash() works for any AbstractString + @test artifact_hash(SubString("HelloWorldC0", 1, 11), joinpath(@__DIR__, "Artifacts.toml"); platform=armv7l_linux) == + SHA1("5a8288c8a30578c0d0f24a9cded29579517ce7a8") +end + @testset "select_downloadable_artifacts()" begin armv7l_linux = Platform("armv7l", "linux") artifacts = select_downloadable_artifacts(joinpath(@__DIR__, "Artifacts.toml"); platform=armv7l_linux) @@ -161,6 +258,10 @@ end @testset "`Artifacts.artifact_names` and friends" begin n = length(Artifacts.artifact_names) @test length(Base.project_names) == n - @test length(Base.manifest_names) == n + @test length(Base.manifest_names) == 2n # there are two manifest names per project name @test length(Base.preferences_names) == n end + +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(Artifacts)) +end diff --git a/stdlib/Base64/Project.toml b/stdlib/Base64/Project.toml index 68d63837fc385..14796beb7e21a 100644 --- a/stdlib/Base64/Project.toml +++ b/stdlib/Base64/Project.toml @@ -1,5 +1,6 @@ name = "Base64" uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +version = "1.11.0" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/Base64/docs/src/index.md b/stdlib/Base64/docs/src/index.md index 6bc647f8a2e67..26e9d70f2ff9f 100644 --- a/stdlib/Base64/docs/src/index.md +++ b/stdlib/Base64/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/Base64/docs/src/index.md" +``` + # Base64 ```@docs diff --git a/stdlib/Base64/src/buffer.jl b/stdlib/Base64/src/buffer.jl index 44a9c0931ac95..009a6d56cfde8 100644 --- a/stdlib/Base64/src/buffer.jl +++ b/stdlib/Base64/src/buffer.jl @@ -2,37 +2,37 @@ # Data buffer for pipes. mutable struct Buffer - data::Vector{UInt8} - ptr::Ptr{UInt8} + const data::Memory{UInt8} + offset::Int size::Int function Buffer(bufsize) - data = Vector{UInt8}(undef, bufsize) - return new(data, pointer(data), 0) + data = Memory{UInt8}(undef, bufsize) + return new(data, 0, 0) end end Base.empty!(buffer::Buffer) = buffer.size = 0 -Base.getindex(buffer::Buffer, i::Integer) = unsafe_load(buffer.ptr, i) -Base.setindex!(buffer::Buffer, v::UInt8, i::Integer) = unsafe_store!(buffer.ptr, v, i) +Base.getindex(buffer::Buffer, i::Integer) = buffer.data[buffer.offset + i] +Base.setindex!(buffer::Buffer, v::UInt8, i::Integer) = buffer.data[buffer.offset + i] = v Base.firstindex(buffer::Buffer) = 1 Base.lastindex(buffer::Buffer) = buffer.size -Base.pointer(buffer::Buffer) = buffer.ptr -capacity(buffer::Buffer) = Int(pointer(buffer.data, lastindex(buffer.data) + 1) - buffer.ptr) +Base.pointer(buffer::Buffer) = pointer(buffer.data) + buffer.offset +capacity(buffer::Buffer) = length(buffer.data) - buffer.offset function consumed!(buffer::Buffer, n::Integer) @assert n ≤ buffer.size - buffer.ptr += n + buffer.offset += n buffer.size -= n end function read_to_buffer(io::IO, buffer::Buffer) - offset = buffer.ptr - pointer(buffer.data) + offset = buffer.offset copyto!(buffer.data, 1, buffer.data, offset + 1, buffer.size) - buffer.ptr = pointer(buffer.data) + buffer.offset = 0 if !eof(io) n = min(bytesavailable(io), capacity(buffer) - buffer.size) - unsafe_read(io, buffer.ptr + buffer.size, n) + unsafe_read(io, pointer(buffer) + buffer.size, n) buffer.size += n end return diff --git a/stdlib/Base64/test/runtests.jl b/stdlib/Base64/test/runtests.jl index 11d0a3cca4348..145576f6ea3f4 100644 --- a/stdlib/Base64/test/runtests.jl +++ b/stdlib/Base64/test/runtests.jl @@ -1,7 +1,8 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license using Test, Random -import Base64: +using Base64: + Base64, Base64EncodePipe, base64encode, Base64DecodePipe, @@ -142,3 +143,7 @@ end @test String(base64decode(splace(longEncodedText))) == longDecodedText end end + +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(Base64)) +end diff --git a/stdlib/CRC32c/Project.toml b/stdlib/CRC32c/Project.toml index c1de88cbc7c52..d3ab5ff019503 100644 --- a/stdlib/CRC32c/Project.toml +++ b/stdlib/CRC32c/Project.toml @@ -1,5 +1,6 @@ name = "CRC32c" uuid = "8bf52ea8-c179-5cab-976a-9e18b702a9bc" +version = "1.11.0" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/CRC32c/docs/src/index.md b/stdlib/CRC32c/docs/src/index.md index 24a073d1e3938..c00a792232c70 100644 --- a/stdlib/CRC32c/docs/src/index.md +++ b/stdlib/CRC32c/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/CRC32c/docs/src/index.md" +``` + # CRC32c Standard library module for computing the CRC-32c checksum. diff --git a/stdlib/CRC32c/test/runtests.jl b/stdlib/CRC32c/test/runtests.jl index e9e933ee2451c..e1bd75d0e15f6 100644 --- a/stdlib/CRC32c/test/runtests.jl +++ b/stdlib/CRC32c/test/runtests.jl @@ -45,6 +45,15 @@ function test_crc32c(crc32c) rm(f, force=true) end end + + # test longer arrays to cover all the code paths in crc32c.c + LONG = 8192 # from crc32c.c + SHORT = 256 # from crc32c.c + n = LONG*3+SHORT*3+SHORT*2+64+7 + big = vcat(reinterpret(UInt8, hton.(0x74d7f887 .^ (1:n÷4))), UInt8[1:n%4;]) + for (offset,crc) in [(0, 0x13a5ecd5), (1, 0xecf34b7e), (2, 0xfa71b596), (3, 0xbfd24745), (4, 0xf0cb3370), (5, 0xb0ec88b5), (6, 0x258c20a8), (7, 0xa9bd638d)] + @test crc == crc32c(@view big[1+offset:end]) + end end unsafe_crc32c_sw(a, n, crc) = ccall(:jl_crc32c_sw, UInt32, (UInt32, Ptr{UInt8}, Csize_t), crc, a, n) @@ -68,3 +77,7 @@ end crc32c_sw(io::IO, crc::UInt32=0x00000000) = crc32c_sw(io, typemax(Int64), crc) test_crc32c(crc32c) test_crc32c(crc32c_sw) + +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(CRC32c)) +end diff --git a/stdlib/CompilerSupportLibraries_jll/Project.toml b/stdlib/CompilerSupportLibraries_jll/Project.toml index 6256c69d9bc10..5aab865b5f6fc 100644 --- a/stdlib/CompilerSupportLibraries_jll/Project.toml +++ b/stdlib/CompilerSupportLibraries_jll/Project.toml @@ -2,9 +2,9 @@ name = "CompilerSupportLibraries_jll" uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" # NOTE: When updating this, also make sure to update the value -# `CSL_NEXT_GLIBCXX_VERSION` in `deps/csl.mk`, to properly disable +# `CSL_NEXT_GLIBCXX_VERSION` in `Make.inc`, to properly disable # automatic usage of BB-built CSLs on extremely up-to-date systems! -version = "1.0.5+1" +version = "1.1.1+0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl b/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl index bd7a0571f9d5a..a57b275b1862d 100644 --- a/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl +++ b/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl @@ -4,7 +4,6 @@ baremodule CompilerSupportLibraries_jll using Base, Libdl, Base.BinaryPlatforms -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] diff --git a/stdlib/Dates/Project.toml b/stdlib/Dates/Project.toml index fe225055bad98..45da6ad1a0152 100644 --- a/stdlib/Dates/Project.toml +++ b/stdlib/Dates/Project.toml @@ -1,5 +1,6 @@ name = "Dates" uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" +version = "1.11.0" [deps] Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" diff --git a/stdlib/Dates/docs/src/index.md b/stdlib/Dates/docs/src/index.md index aa46f7b827f10..35ec6771efc55 100644 --- a/stdlib/Dates/docs/src/index.md +++ b/stdlib/Dates/docs/src/index.md @@ -1,7 +1,3 @@ -```@meta -EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/Dates/docs/src/index.md" -``` - # Dates ```@meta @@ -22,7 +18,7 @@ represents a continuously increasing machine timeline based on the UT second [^1 [`DateTime`](@ref) type is not aware of time zones (*naive*, in Python parlance), analogous to a *LocalDateTime* in Java 8. Additional time zone functionality can be added through the [TimeZones.jl package](https://github.com/JuliaTime/TimeZones.jl/), which -compiles the [IANA time zone database](http://www.iana.org/time-zones). Both [`Date`](@ref) and +compiles the [IANA time zone database](https://www.iana.org/time-zones). Both [`Date`](@ref) and [`DateTime`](@ref) are based on the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) standard, which follows the proleptic Gregorian calendar. One note is that the ISO 8601 standard is particular about BC/BCE dates. In general, the last day of the BC/BCE era, 1-12-31 BC/BCE, was followed by 1-1-1 AD/CE, thus no year zero exists. @@ -343,12 +339,12 @@ First the mapping is loaded into the `LOCALES` variable: julia> french_months = ["janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"]; -julia> french_monts_abbrev = ["janv","févr","mars","avril","mai","juin", +julia> french_months_abbrev = ["janv","févr","mars","avril","mai","juin", "juil","août","sept","oct","nov","déc"]; julia> french_days = ["lundi","mardi","mercredi","jeudi","vendredi","samedi","dimanche"]; -julia> Dates.LOCALES["french"] = Dates.DateLocale(french_months, french_monts_abbrev, french_days, [""]); +julia> Dates.LOCALES["french"] = Dates.DateLocale(french_months, french_months_abbrev, french_days, [""]); ``` The above mentioned functions can then be used to perform the queries: diff --git a/stdlib/Dates/src/Dates.jl b/stdlib/Dates/src/Dates.jl index a111ea24089c4..a4600a5f82043 100644 --- a/stdlib/Dates/src/Dates.jl +++ b/stdlib/Dates/src/Dates.jl @@ -32,7 +32,7 @@ for more information. """ module Dates -import Base: ==, isless, div, fld, mod, rem, gcd, lcm, +, -, *, /, %, broadcast +import Base: ==, isless, div, fld, mod, rem, gcd, lcm, +, -, *, /, % using Printf: @sprintf using Base.Iterators @@ -77,7 +77,7 @@ export Period, DatePeriod, TimePeriod, firstdayofmonth, lastdayofmonth, firstdayofyear, lastdayofyear, firstdayofquarter, lastdayofquarter, - adjust, tonext, toprev, tofirst, tolast, + tonext, toprev, tofirst, tolast, # io.jl ISODateTimeFormat, ISODateFormat, ISOTimeFormat, DateFormat, RFC1123Format, @dateformat_str diff --git a/stdlib/Dates/src/adjusters.jl b/stdlib/Dates/src/adjusters.jl index 245e2678a9d77..0d6cea5dc3e6b 100644 --- a/stdlib/Dates/src/adjusters.jl +++ b/stdlib/Dates/src/adjusters.jl @@ -204,6 +204,41 @@ function adjust(df::DateFunction, start, step, limit) throw(ArgumentError("Adjustment limit reached: $limit iterations")) end +""" + adjust(df, start[, step, limit]) -> TimeType + adjust(df, start) -> TimeType + +Adjusts the date in `start` until the `f::Function` passed using `df` returns `true`. +The optional `step` parameter dictates the change in `start` on every iteration. +If `limit` iterations occur, then an [`ArgumentError`](@ref) is thrown. + +The default values for parameters `start` and `limit` are 1 Day and 10,000 respectively. + +# Examples +```jldoctest +julia> Dates.adjust(date -> month(date) == 10, Date(2022, 1, 1), step=Month(3), limit=10) +2022-10-01 + +julia> Dates.adjust(date -> year(date) == 2025, Date(2022, 1, 1), step=Year(1), limit=4) +2025-01-01 + +julia> Dates.adjust(date -> day(date) == 15, Date(2022, 1, 1), step=Year(1), limit=3) +ERROR: ArgumentError: Adjustment limit reached: 3 iterations +Stacktrace: +[...] + +julia> Dates.adjust(date -> month(date) == 10, Date(2022, 1, 1)) +2022-10-01 + +julia> Dates.adjust(date -> year(date) == 2025, Date(2022, 1, 1)) +2025-01-01 + +julia> Dates.adjust(date -> year(date) == 2224, Date(2022, 1, 1)) +ERROR: ArgumentError: Adjustment limit reached: 10000 iterations +Stacktrace: +[...] +``` +""" function adjust(func::Function, start; step::Period=Day(1), limit::Int=10000) return adjust(DateFunction(func, start), start, step, limit) end diff --git a/stdlib/Dates/src/arithmetic.jl b/stdlib/Dates/src/arithmetic.jl index a847f749d0154..83a2873b43409 100644 --- a/stdlib/Dates/src/arithmetic.jl +++ b/stdlib/Dates/src/arithmetic.jl @@ -7,7 +7,8 @@ # TimeType arithmetic (+)(x::TimeType) = x (-)(x::T, y::T) where {T<:TimeType} = x.instant - y.instant -(-)(x::TimeType, y::TimeType) = -(promote(x, y)...) +(-)(x::T, y::T) where {T<:AbstractDateTime} = x.instant - y.instant +(-)(x::AbstractDateTime, y::AbstractDateTime) = -(promote(x, y)...) # Date-Time arithmetic """ diff --git a/stdlib/Dates/src/conversions.jl b/stdlib/Dates/src/conversions.jl index 30f1f2581d1fa..0d413d2cf53a1 100644 --- a/stdlib/Dates/src/conversions.jl +++ b/stdlib/Dates/src/conversions.jl @@ -84,7 +84,7 @@ today() = Date(now()) Return a `DateTime` corresponding to the user's system time as UTC/GMT. For other time zones, see the TimeZones.jl package. -# Example +# Examples ```julia julia> now(UTC) 2023-01-04T10:52:24.864 diff --git a/stdlib/Dates/src/io.jl b/stdlib/Dates/src/io.jl index 257e86064c2fb..3980ad3a7245f 100644 --- a/stdlib/Dates/src/io.jl +++ b/stdlib/Dates/src/io.jl @@ -472,7 +472,7 @@ end Describes the ISO8601 formatting for a date and time. This is the default value for `Dates.format` of a `DateTime`. -# Example +# Examples ```jldoctest julia> Dates.format(DateTime(2018, 8, 8, 12, 0, 43, 1), ISODateTimeFormat) "2018-08-08T12:00:43.001" @@ -486,7 +486,7 @@ default_format(::Type{DateTime}) = ISODateTimeFormat Describes the ISO8601 formatting for a date. This is the default value for `Dates.format` of a `Date`. -# Example +# Examples ```jldoctest julia> Dates.format(Date(2018, 8, 8), ISODateFormat) "2018-08-08" @@ -500,7 +500,7 @@ default_format(::Type{Date}) = ISODateFormat Describes the ISO8601 formatting for a time. This is the default value for `Dates.format` of a `Time`. -# Example +# Examples ```jldoctest julia> Dates.format(Time(12, 0, 43, 1), ISOTimeFormat) "12:00:43.001" @@ -514,7 +514,7 @@ default_format(::Type{Time}) = ISOTimeFormat Describes the RFC1123 formatting for a date and time. -# Example +# Examples ```jldoctest julia> Dates.format(DateTime(2018, 8, 8, 12, 0, 43, 1), RFC1123Format) "Wed, 08 Aug 2018 12:00:43" @@ -538,7 +538,7 @@ pattern given in the `format` string (see [`DateFormat`](@ref) for syntax). that you create a [`DateFormat`](@ref) object instead and use that as the second argument to avoid performance loss when using the same format repeatedly. -# Example +# Examples ```jldoctest julia> DateTime("2020-01-01", "yyyy-mm-dd") 2020-01-01T00:00:00 @@ -578,7 +578,7 @@ in the `format` string (see [`DateFormat`](@ref) for syntax). that you create a [`DateFormat`](@ref) object instead and use that as the second argument to avoid performance loss when using the same format repeatedly. -# Example +# Examples ```jldoctest julia> Date("2020-01-01", "yyyy-mm-dd") 2020-01-01 @@ -618,7 +618,7 @@ in the `format` string (see [`DateFormat`](@ref) for syntax). that you create a [`DateFormat`](@ref) object instead and use that as the second argument to avoid performance loss when using the same format repeatedly. -# Example +# Examples ```jldoctest julia> Time("12:34pm", "HH:MMp") 12:34:00 diff --git a/stdlib/Dates/src/parse.jl b/stdlib/Dates/src/parse.jl index 62d44177de877..49c0234c7c9fa 100644 --- a/stdlib/Dates/src/parse.jl +++ b/stdlib/Dates/src/parse.jl @@ -207,7 +207,7 @@ function Base.parse(::Type{DateTime}, s::AbstractString, df::typeof(ISODateTimeF let val = tryparsenext_base10(s, i, end_pos, 1) val === nothing && @goto error dy, i = val - i > end_pos && @goto error + i > end_pos && @goto done end c, i = iterate(s, i)::Tuple{Char, Int} diff --git a/stdlib/Dates/src/periods.jl b/stdlib/Dates/src/periods.jl index c1d94d3d62321..c88a1bed4bba9 100644 --- a/stdlib/Dates/src/periods.jl +++ b/stdlib/Dates/src/periods.jl @@ -102,6 +102,7 @@ div(x::Period, y::Period, r::RoundingMode) = div(promote(x, y)..., r) Base.gcdx(a::T, b::T) where {T<:Period} = ((g, x, y) = gcdx(value(a), value(b)); return T(g), x, y) Base.abs(a::T) where {T<:Period} = T(abs(value(a))) Base.sign(x::Period) = sign(value(x)) +Base.signbit(x::Period) = signbit(value(x)) # return (next coarser period, conversion factor): coarserperiod(::Type{P}) where {P<:Period} = (P, 1) diff --git a/stdlib/Dates/test/accessors.jl b/stdlib/Dates/test/accessors.jl index b690a81d70e49..240de42eaa1dc 100644 --- a/stdlib/Dates/test/accessors.jl +++ b/stdlib/Dates/test/accessors.jl @@ -153,7 +153,7 @@ end @test Dates.week(Dates.Date(2010, 1, 1)) == 53 @test Dates.week(Dates.Date(2010, 1, 2)) == 53 @test Dates.week(Dates.Date(2010, 1, 2)) == 53 - # Tests from http://www.epochconverter.com/date-and-time/weeknumbers-by-year.php?year=1999 + # Tests from https://www.epochconverter.com/date-and-time/weeknumbers-by-year.php?year=1999 dt = Dates.DateTime(1999, 12, 27) dt1 = Dates.Date(1999, 12, 27) check = (52, 52, 52, 52, 52, 52, 52, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2) @@ -163,7 +163,7 @@ end dt = dt + Dates.Day(1) dt1 = dt1 + Dates.Day(1) end - # Tests from http://www.epochconverter.com/date-and-time/weeknumbers-by-year.php?year=2000 + # Tests from https://www.epochconverter.com/date-and-time/weeknumbers-by-year.php?year=2000 dt = Dates.DateTime(2000, 12, 25) dt1 = Dates.Date(2000, 12, 25) for i = 1:21 @@ -172,7 +172,7 @@ end dt = dt + Dates.Day(1) dt1 = dt1 + Dates.Day(1) end - # Test from http://www.epochconverter.com/date-and-time/weeknumbers-by-year.php?year=2030 + # Test from https://www.epochconverter.com/date-and-time/weeknumbers-by-year.php?year=2030 dt = Dates.DateTime(2030, 12, 23) dt1 = Dates.Date(2030, 12, 23) for i = 1:21 @@ -181,7 +181,7 @@ end dt = dt + Dates.Day(1) dt1 = dt1 + Dates.Day(1) end - # Tests from http://www.epochconverter.com/date-and-time/weeknumbers-by-year.php?year=2004 + # Tests from https://www.epochconverter.com/date-and-time/weeknumbers-by-year.php?year=2004 dt = Dates.DateTime(2004, 12, 20) dt1 = Dates.Date(2004, 12, 20) check = (52, 52, 52, 52, 52, 52, 52, 53, 53, 53, 53, 53, 53, 53, 1, 1, 1, 1, 1, 1, 1) diff --git a/stdlib/Dates/test/arithmetic.jl b/stdlib/Dates/test/arithmetic.jl index 110eea6d00235..333ba3a7c0088 100644 --- a/stdlib/Dates/test/arithmetic.jl +++ b/stdlib/Dates/test/arithmetic.jl @@ -11,10 +11,18 @@ using Dates @test Dates.CompoundPeriod(a - b) == Dates.Hour(12) end +struct MonthlyDate <: TimeType + instant::Dates.UTInstant{Month} +end +struct OtherTime <: Dates.AbstractDateTime + instant::Dates.UTInstant{Nanosecond} +end @testset "TimeType arithmetic" begin - a = Date(2023, 5, 1) - b = DateTime(2023, 5, 2) - @test b - a == Day(1) + @test_throws MethodError DateTime(2023, 5, 2) - Date(2023, 5, 1) + # check that - between two same-type TimeTypes works by default + @test MonthlyDate(Dates.UTInstant(Month(10))) - MonthlyDate(Dates.UTInstant(Month(1))) == Month(9) + # ... and between two same-type AbstractDateTimes + @test OtherTime(Dates.UTInstant(Nanosecond(2))) - OtherTime(Dates.UTInstant(Nanosecond(1))) == Nanosecond(1) end @testset "Wrapping arithmetic for Months" begin diff --git a/stdlib/Dates/test/io.jl b/stdlib/Dates/test/io.jl index 2c99ac45d0c58..ee102288acd3e 100644 --- a/stdlib/Dates/test/io.jl +++ b/stdlib/Dates/test/io.jl @@ -47,7 +47,7 @@ end end @testset "DateTime parsing" begin - # Useful reference for different locales: http://library.princeton.edu/departments/tsd/katmandu/reference/months.html + # Useful reference for different locales: https://library.princeton.edu/departments/tsd/katmandu/reference/months.html # Allow parsing of strings which are not representable as a TimeType str = "02/15/1996 25:00" @@ -470,6 +470,9 @@ end # Issue #44003 @test tryparse(Dates.Date, "2017", Dates.DateFormat(".s")) === nothing +# Issue #52989 +@test Dates.DateTime("2000") == Dates.DateTime(2000) + @testset "parse milliseconds, Issue #22100" begin @test Dates.DateTime("2017-Mar-17 00:00:00.0000", "y-u-d H:M:S.s") == Dates.DateTime(2017, 3, 17) @test Dates.parse_components(".1", Dates.DateFormat(".s")) == [Dates.Millisecond(100)] diff --git a/stdlib/Dates/test/periods.jl b/stdlib/Dates/test/periods.jl index 7cebfc55e7735..9c7d0deef8a11 100644 --- a/stdlib/Dates/test/periods.jl +++ b/stdlib/Dates/test/periods.jl @@ -30,6 +30,9 @@ using Test @test sign(t) == sign(t2) == 1 @test sign(-t) == sign(-t2) == -1 @test sign(Dates.Year(0)) == 0 + @test signbit(t) == signbit(t2) == false + @test signbit(-t) == signbit(-t2) == true + @test signbit(Dates.Year(0)) == false end @testset "div/mod/gcd/lcm/rem" begin @test Dates.Year(10) % Dates.Year(4) == Dates.Year(2) diff --git a/stdlib/Dates/test/runtests.jl b/stdlib/Dates/test/runtests.jl index de063135427a9..ad2ee43cedfb1 100644 --- a/stdlib/Dates/test/runtests.jl +++ b/stdlib/Dates/test/runtests.jl @@ -2,8 +2,14 @@ module DateTests +using Test, Dates + for file in readlines(joinpath(@__DIR__, "testgroups")) include(file * ".jl") end +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(Dates)) +end + end diff --git a/stdlib/Distributed.version b/stdlib/Distributed.version index faed0795fbbf9..b0acacf2367de 100644 --- a/stdlib/Distributed.version +++ b/stdlib/Distributed.version @@ -1,4 +1,4 @@ DISTRIBUTED_BRANCH = master -DISTRIBUTED_SHA1 = fdf56f429cd44da6d5d08cb5208bbf41d5b3d0a5 +DISTRIBUTED_SHA1 = 6a07d9853ab7686df7440a47d1b585c6c9f3be35 DISTRIBUTED_GIT_URL := https://github.com/JuliaLang/Distributed.jl DISTRIBUTED_TAR_URL = https://api.github.com/repos/JuliaLang/Distributed.jl/tarball/$1 diff --git a/stdlib/Downloads.version b/stdlib/Downloads.version index c5bd4d7a0d473..7805348a4b2f5 100644 --- a/stdlib/Downloads.version +++ b/stdlib/Downloads.version @@ -1,4 +1,4 @@ DOWNLOADS_BRANCH = master -DOWNLOADS_SHA1 = 8a614d592810b15d17885838dec61da244a12e09 +DOWNLOADS_SHA1 = a9d274ff6588cc5dbfa90e908ee34c2408bab84a DOWNLOADS_GIT_URL := https://github.com/JuliaLang/Downloads.jl.git DOWNLOADS_TAR_URL = https://api.github.com/repos/JuliaLang/Downloads.jl/tarball/$1 diff --git a/stdlib/FileWatching/Project.toml b/stdlib/FileWatching/Project.toml index 1da637fd4259d..5edcfdadd085d 100644 --- a/stdlib/FileWatching/Project.toml +++ b/stdlib/FileWatching/Project.toml @@ -1,5 +1,6 @@ name = "FileWatching" uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +version = "1.11.0" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/FileWatching/docs/src/index.md b/stdlib/FileWatching/docs/src/index.md index a420d49232345..1b2212fcc5a28 100644 --- a/stdlib/FileWatching/docs/src/index.md +++ b/stdlib/FileWatching/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/FileWatching/docs/src/index.md" +``` + # [File Events](@id lib-filewatching) ```@docs diff --git a/stdlib/FileWatching/src/FileWatching.jl b/stdlib/FileWatching/src/FileWatching.jl index 2a654547ae6e3..0c987ad01c828 100644 --- a/stdlib/FileWatching/src/FileWatching.jl +++ b/stdlib/FileWatching/src/FileWatching.jl @@ -164,10 +164,13 @@ mutable struct _FDWatcher @static if Sys.isunix() _FDWatcher(fd::RawFD, mask::FDEvent) = _FDWatcher(fd, mask.readable, mask.writable) function _FDWatcher(fd::RawFD, readable::Bool, writable::Bool) - if !readable && !writable + fdnum = Core.Intrinsics.bitcast(Int32, fd) + 1 + if fdnum <= 0 + throw(ArgumentError("Passed file descriptor fd=$(fd) is not a valid file descriptor")) + elseif !readable && !writable throw(ArgumentError("must specify at least one of readable or writable to create a FDWatcher")) end - fdnum = Core.Intrinsics.bitcast(Int32, fd) + 1 + iolock_begin() if fdnum > length(FDWatchers) old_len = length(FDWatchers) @@ -232,12 +235,19 @@ mutable struct _FDWatcher @static if Sys.iswindows() _FDWatcher(fd::RawFD, mask::FDEvent) = _FDWatcher(fd, mask.readable, mask.writable) function _FDWatcher(fd::RawFD, readable::Bool, writable::Bool) + fdnum = Core.Intrinsics.bitcast(Int32, fd) + 1 + if fdnum <= 0 + throw(ArgumentError("Passed file descriptor fd=$(fd) is not a valid file descriptor")) + end + handle = Libc._get_osfhandle(fd) return _FDWatcher(handle, readable, writable) end _FDWatcher(fd::WindowsRawSocket, mask::FDEvent) = _FDWatcher(fd, mask.readable, mask.writable) function _FDWatcher(fd::WindowsRawSocket, readable::Bool, writable::Bool) - if !readable && !writable + if fd == Base.INVALID_OS_HANDLE + throw(ArgumentError("Passed file descriptor fd=$(fd) is not a valid file descriptor")) + elseif !readable && !writable throw(ArgumentError("must specify at least one of readable or writable to create a FDWatcher")) end @@ -458,6 +468,11 @@ function uv_fspollcb(handle::Ptr{Cvoid}, status::Int32, prev::Ptr, curr::Ptr) nothing end +global uv_jl_pollcb::Ptr{Cvoid} +global uv_jl_fspollcb::Ptr{Cvoid} +global uv_jl_fseventscb_file::Ptr{Cvoid} +global uv_jl_fseventscb_folder::Ptr{Cvoid} + function __init__() global uv_jl_pollcb = @cfunction(uv_pollcb, Cvoid, (Ptr{Cvoid}, Cint, Cint)) global uv_jl_fspollcb = @cfunction(uv_fspollcb, Cvoid, (Ptr{Cvoid}, Cint, Ptr{Cvoid}, Ptr{Cvoid})) @@ -727,7 +742,7 @@ function poll_fd(s::Union{RawFD, Sys.iswindows() ? WindowsRawSocket : Union{}}, end end catch ex - ex isa EOFError() || rethrow() + ex isa EOFError || rethrow() return FDEvent() end else diff --git a/stdlib/FileWatching/src/pidfile.jl b/stdlib/FileWatching/src/pidfile.jl index 93217311c3183..4c821a3d897e4 100644 --- a/stdlib/FileWatching/src/pidfile.jl +++ b/stdlib/FileWatching/src/pidfile.jl @@ -7,8 +7,6 @@ using Base: IOError, UV_EEXIST, UV_ESRCH, Process -using Base.Libc: rand - using Base.Filesystem: File, open, JL_O_CREAT, JL_O_RDWR, JL_O_RDONLY, JL_O_EXCL, rename, samefile, path_separator @@ -33,7 +31,8 @@ Optional keyword arguments: - `mode`: file access mode (modified by the process umask). Defaults to world-readable. - `poll_interval`: Specify the maximum time to between attempts (if `watch_file` doesn't work) - `stale_age`: Delete an existing pidfile (ignoring the lock) if it is older than this many seconds, based on its mtime. - The file won't be deleted until 25x longer than this if the pid in the file appears that it may be valid. + The file won't be deleted until 5x longer than this if the pid in the file appears that it may be valid. + Or 25x longer if `refresh` is overridden to 0 to disable lock refreshing. By default this is disabled (`stale_age` = 0), but a typical recommended value would be about 3-5x an estimated normal completion time. - `refresh`: Keeps a lock from becoming stale by updating the mtime every interval of time that passes. @@ -64,7 +63,7 @@ mutable struct LockMonitor atdir, atname = splitdir(at) isempty(atdir) && (atdir = pwd()) at = realpath(atdir) * path_separator * atname - fd = open_exclusive(at; stale_age=stale_age, kwopts...) + fd = open_exclusive(at; stale_age, refresh, kwopts...) update = nothing try write_pidfile(fd, pid) @@ -76,6 +75,7 @@ mutable struct LockMonitor lock = new(at, fd, update) finalizer(close, lock) catch ex + update === nothing || close(update) tryrmopenfile(at) close(fd) rethrow(ex) @@ -99,10 +99,13 @@ end function mkpidlock(at::String, proc::Process; kwopts...) lock = mkpidlock(at, getpid(proc); kwopts...) closer = @async begin - wait(proc) - close(lock) + try + wait(proc) + finally + close(lock) + end end - isdefined(Base, :errormonitor) && Base.errormonitor(closer) + Base.errormonitor(closer) return lock end @@ -185,15 +188,16 @@ function isvalidpid(hostname::AbstractString, pid::Cuint) end """ - stale_pidfile(path::String, stale_age::Real) :: Bool + stale_pidfile(path::String, stale_age::Real, refresh::Real) :: Bool Helper function for `open_exclusive` for deciding if a pidfile is stale. """ -function stale_pidfile(path::String, stale_age::Real) +function stale_pidfile(path::String, stale_age::Real, refresh::Real) pid, hostname, age = parse_pidfile(path) age < -stale_age && @warn "filesystem time skew detected" path=path + longer_factor = refresh == 0 ? 25 : 5 if age > stale_age - if (age > stale_age * 25) || !isvalidpid(hostname, pid) + if (age > stale_age * longer_factor) || !isvalidpid(hostname, pid) return true end end @@ -220,7 +224,7 @@ struct PidlockedError <: Exception end """ - open_exclusive(path::String; mode, poll_interval, wait, stale_age) :: File + open_exclusive(path::String; mode, poll_interval, wait, stale_age, refresh) :: File Create a new a file for read-write advisory-exclusive access. If `wait` is `false` then error out if the lock files exist @@ -232,13 +236,14 @@ function open_exclusive(path::String; mode::Integer = 0o444 #= read-only =#, poll_interval::Real = 10 #= seconds =#, wait::Bool = true #= return on failure if false =#, - stale_age::Real = 0 #= disabled =#) + stale_age::Real = 0 #= disabled =#, + refresh::Real = stale_age/2) # fast-path: just try to open it file = tryopen_exclusive(path, mode) file === nothing || return file if !wait if file === nothing && stale_age > 0 - if stale_age > 0 && stale_pidfile(path, stale_age) + if stale_age > 0 && stale_pidfile(path, stale_age, refresh) @warn "attempting to remove probably stale pidfile" path=path tryrmopenfile(path) end @@ -264,7 +269,7 @@ function open_exclusive(path::String; file = tryopen_exclusive(path, mode) file === nothing || return file Base.wait(t) # sleep for a bit before trying again - if stale_age > 0 && stale_pidfile(path, stale_age) + if stale_age > 0 && stale_pidfile(path, stale_age, refresh) # if the file seems stale, try to remove it before attempting again # set stale_age to zero so we won't attempt again, even if the attempt fails stale_age -= stale_age @@ -275,7 +280,7 @@ function open_exclusive(path::String; end function _rand_filename(len::Int=4) # modified from Base.Libc - slug = Base.StringVector(len) + slug = Base.StringMemory(len) chars = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" for i = 1:len slug[i] = chars[(Libc.rand() % length(chars)) + 1] diff --git a/stdlib/FileWatching/test/pidfile.jl b/stdlib/FileWatching/test/pidfile.jl index c2cb0c88a1b1e..3464a24175632 100644 --- a/stdlib/FileWatching/test/pidfile.jl +++ b/stdlib/FileWatching/test/pidfile.jl @@ -203,18 +203,33 @@ end @assert !ispath("pidfile") @testset "open_exclusive: break lock" begin - # test for stale_age - t = @elapsed f = open_exclusive("pidfile", poll_interval=3, stale_age=10)::File - try - write_pidfile(f, getpid()) - finally + @testset "using stale_age without lock refreshing" begin + t = @elapsed f = open_exclusive("pidfile", poll_interval=3, stale_age=10, refresh=0)::File + try + write_pidfile(f, getpid()) + finally + close(f) + end + @test t < 2 + t = @elapsed f = open_exclusive("pidfile", poll_interval=3, stale_age=1, refresh=0)::File close(f) + @test 20 < t < 50 + rm("pidfile") + end + + @testset "using stale_age with lock refreshing on (default)" begin + t = @elapsed f = open_exclusive("pidfile", poll_interval=3, stale_age=10)::File + try + write_pidfile(f, getpid()) + finally + close(f) + end + @test t < 2 + t = @elapsed f = open_exclusive("pidfile", poll_interval=3, stale_age=5)::File + close(f) + @test 20 < t < 50 + rm("pidfile") end - @test t < 2 - t = @elapsed f = open_exclusive("pidfile", poll_interval=3, stale_age=1)::File - close(f) - @test 20 < t < 50 - rm("pidfile") t = @elapsed f = open_exclusive("pidfile", poll_interval=3, stale_age=10)::File close(f) diff --git a/stdlib/FileWatching/test/runtests.jl b/stdlib/FileWatching/test/runtests.jl index 75b17b5f0e511..2592aea024386 100644 --- a/stdlib/FileWatching/test/runtests.jl +++ b/stdlib/FileWatching/test/runtests.jl @@ -24,7 +24,7 @@ for i in 1:n uv_error("pipe", ccall(:uv_pipe, Cint, (Ptr{NTuple{2, Base.OS_HANDLE}}, Cint, Cint), Ref(pipe_fds, i), 0, 0)) end Ctype = Sys.iswindows() ? Ptr{Cvoid} : Cint - FDmax = Sys.iswindows() ? 0x7fff : (n + 60 + (isdefined(Main, :Revise) * 30)) # expectations on reasonable values + FDmax = Sys.iswindows() ? typemax(Int32) : (n + 60 + (isdefined(Main, :Revise) * 30)) # expectations on reasonable values fd_in_limits = 0 <= Int(Base.cconvert(Ctype, pipe_fds[i][1])) <= FDmax && 0 <= Int(Base.cconvert(Ctype, pipe_fds[i][2])) <= FDmax @@ -161,7 +161,7 @@ test2_12992() ####################################################################### # This section tests file watchers. # ####################################################################### -F_GETPATH = Sys.islinux() || Sys.iswindows() || Sys.isapple() # platforms where F_GETPATH is available +F_GETPATH = Sys.islinux() || Sys.iswindows() || Sys.isapple() # platforms where F_GETPATH is available F_PATH = F_GETPATH ? "afile.txt" : "" dir = mktempdir() file = joinpath(dir, "afile.txt") @@ -276,7 +276,7 @@ function test_dirmonitor_wait(tval) end end fname, events = wait(fm)::Pair - @test fname == F_PATH + @test fname == basename(file) @test events.changed && !events.timedout && !events.renamed close(fm) end @@ -443,8 +443,17 @@ unwatch_folder(dir) rm(file) rm(dir) +# Test that creating a FDWatcher with a (probably) negative FD fails +@test_throws ArgumentError FDWatcher(RawFD(-1), true, true) + @testset "Pidfile" begin include("pidfile.jl") end +@testset "Docstrings" begin + undoc = Docs.undocumented_names(FileWatching) + @test_broken isempty(undoc) + @test undoc == [:FDWatcher, :FileMonitor, :FolderMonitor, :PollingFileWatcher] +end + end # testset diff --git a/stdlib/Future/Project.toml b/stdlib/Future/Project.toml index ffdbaf94b9853..c09489812ce01 100644 --- a/stdlib/Future/Project.toml +++ b/stdlib/Future/Project.toml @@ -1,5 +1,6 @@ name = "Future" uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" +version = "1.11.0" [deps] Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" diff --git a/stdlib/Future/docs/src/index.md b/stdlib/Future/docs/src/index.md index dcb1a36541b6e..99250296f2c7d 100644 --- a/stdlib/Future/docs/src/index.md +++ b/stdlib/Future/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/Future/docs/src/index.md" +``` + # Future The `Future` module implements future behavior of already existing functions, diff --git a/stdlib/Future/test/runtests.jl b/stdlib/Future/test/runtests.jl index 6deffe74d891c..6e02f17358ab3 100644 --- a/stdlib/Future/test/runtests.jl +++ b/stdlib/Future/test/runtests.jl @@ -2,3 +2,7 @@ using Test using Future + +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(Future)) +end diff --git a/stdlib/GMP_jll/Project.toml b/stdlib/GMP_jll/Project.toml index 9f3b917257bfa..c8fcfe4f2b845 100644 --- a/stdlib/GMP_jll/Project.toml +++ b/stdlib/GMP_jll/Project.toml @@ -1,6 +1,6 @@ name = "GMP_jll" uuid = "781609d7-10c4-51f6-84f2-b8444358ff6d" -version = "6.2.1+6" +version = "6.3.0+0" [deps] Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" diff --git a/stdlib/GMP_jll/src/GMP_jll.jl b/stdlib/GMP_jll/src/GMP_jll.jl index fde2fc15acf90..ae8b3c0b3e7d5 100644 --- a/stdlib/GMP_jll/src/GMP_jll.jl +++ b/stdlib/GMP_jll/src/GMP_jll.jl @@ -3,7 +3,6 @@ ## dummy stub for https://github.com/JuliaBinaryWrappers/GMP_jll.jl baremodule GMP_jll using Base, Libdl -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] diff --git a/stdlib/GMP_jll/test/runtests.jl b/stdlib/GMP_jll/test/runtests.jl index 7c0d877945231..b2b35b98cbe17 100644 --- a/stdlib/GMP_jll/test/runtests.jl +++ b/stdlib/GMP_jll/test/runtests.jl @@ -4,5 +4,5 @@ using Test, Libdl, GMP_jll @testset "GMP_jll" begin vn = VersionNumber(unsafe_string(unsafe_load(cglobal((:__gmp_version, libgmp), Ptr{Cchar})))) - @test vn == v"6.2.1" + @test vn == v"6.3.0" end diff --git a/stdlib/InteractiveUtils/Project.toml b/stdlib/InteractiveUtils/Project.toml index e13902375e005..53cc9218eff5d 100644 --- a/stdlib/InteractiveUtils/Project.toml +++ b/stdlib/InteractiveUtils/Project.toml @@ -1,5 +1,6 @@ name = "InteractiveUtils" uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +version = "1.11.0" [deps] Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" diff --git a/stdlib/InteractiveUtils/docs/src/index.md b/stdlib/InteractiveUtils/docs/src/index.md index 5ee8e57adc848..dbfb42b9a931d 100644 --- a/stdlib/InteractiveUtils/docs/src/index.md +++ b/stdlib/InteractiveUtils/docs/src/index.md @@ -1,6 +1,12 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/InteractiveUtils/docs/src/index.md" +``` + # [Interactive Utilities](@id man-interactive-utils) -This module is intended for interactive work. It is loaded automatically in [interactive mode](@ref command-line-interface). +The `InteractiveUtils` module provides utilities for interactive use of Julia, +such as code introspection and clipboard access. +It is intended for interactive work and is loaded automatically in [interactive mode](@ref command-line-interface). ```@docs InteractiveUtils.apropos diff --git a/stdlib/InteractiveUtils/src/InteractiveUtils.jl b/stdlib/InteractiveUtils/src/InteractiveUtils.jl index 5d6114913bf71..835988ddf149f 100644 --- a/stdlib/InteractiveUtils/src/InteractiveUtils.jl +++ b/stdlib/InteractiveUtils/src/InteractiveUtils.jl @@ -1,5 +1,10 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +""" +The `InteractiveUtils` module provides utilities for interactive use of Julia, +such as code introspection and clipboard access. +It is intended for interactive work and is loaded automatically in interactive mode. +""" module InteractiveUtils Base.Experimental.@optlevel 1 @@ -10,7 +15,7 @@ export apropos, edit, less, code_warntype, code_llvm, code_native, methodswith, import Base.Docs.apropos -using Base: unwrap_unionall, rewrap_unionall, isdeprecated, Bottom, show_unquoted, summarysize, +using Base: unwrap_unionall, rewrap_unionall, isdeprecated, Bottom, summarysize, signature_type, format_bytes using Base.Libc using Markdown @@ -99,7 +104,7 @@ function versioninfo(io::IO=stdout; verbose::Bool=false) if !isempty(Base.GIT_VERSION_INFO.commit_short) println(io, "Commit $(Base.GIT_VERSION_INFO.commit_short) ($(Base.GIT_VERSION_INFO.date_string))") end - official_release = Base.TAGGED_RELEASE_BANNER == "Official https://julialang.org/ release" + official_release = Base.TAGGED_RELEASE_BANNER == "Official https://julialang.org release" if Base.isdebugbuild() || !isempty(Base.TAGGED_RELEASE_BANNER) || (Base.GIT_VERSION_INFO.tagged_commit && !official_release) println(io, "Build Info:") if Base.isdebugbuild() @@ -160,7 +165,8 @@ function versioninfo(io::IO=stdout; verbose::Bool=false) end println(io, " WORD_SIZE: ", Sys.WORD_SIZE) println(io, " LLVM: libLLVM-",Base.libllvm_version," (", Sys.JIT, ", ", Sys.CPU_NAME, ")") - println(io, " Threads: ", Threads.maxthreadid(), " on ", Sys.CPU_THREADS, " virtual cores") + println(io, """Threads: $(Threads.nthreads(:default)) default, $(Threads.nthreads(:interactive)) interactive, \ + $(Threads.ngcthreads()) GC (on $(Sys.CPU_THREADS) virtual cores)""") function is_nonverbose_env(k::String) return occursin(r"^JULIA_|^DYLD_|^LD_", k) @@ -201,6 +207,8 @@ The optional second argument restricts the search to a particular module or func If keyword `supertypes` is `true`, also return arguments with a parent type of `typ`, excluding type `Any`. + +See also: [`methods`](@ref). """ function methodswith(@nospecialize(t::Type), @nospecialize(f::Base.Callable), meths = Method[]; supertypes::Bool=false) for d in methods(f) @@ -330,7 +338,7 @@ export peakflops function peakflops(n::Integer=4096; eltype::DataType=Float64, ntrials::Integer=3, parallel::Bool=false) # Base.depwarn("`peakflops` has moved to the LinearAlgebra module, " * # "add `using LinearAlgebra` to your imports.", :peakflops) - let LinearAlgebra = Base.require(Base.PkgId( + let LinearAlgebra = Base.require_stdlib(Base.PkgId( Base.UUID((0x37e2e46d_f89d_539d,0xb4ee_838fcccc9c8e)), "LinearAlgebra")) return LinearAlgebra.peakflops(n, eltype=eltype, ntrials=ntrials, parallel=parallel) end @@ -345,7 +353,7 @@ function report_bug(kind) if Base.locate_package(BugReportingId) === nothing @info "Package `BugReporting` not found - attempting temporary installation" # Create a temporary environment and add BugReporting - let Pkg = Base.require(Base.PkgId( + let Pkg = Base.require_stdlib(Base.PkgId( Base.UUID((0x44cfe95a_1eb2_52ea,0xb672_e2afdf69b78f)), "Pkg")) mktempdir() do tmp old_load_path = copy(LOAD_PATH) diff --git a/stdlib/InteractiveUtils/src/clipboard.jl b/stdlib/InteractiveUtils/src/clipboard.jl index c2abda9a60cc3..6bcd61584a2b8 100644 --- a/stdlib/InteractiveUtils/src/clipboard.jl +++ b/stdlib/InteractiveUtils/src/clipboard.jl @@ -100,7 +100,7 @@ elseif Sys.iswindows() pdata == C_NULL && return cleanup(:GlobalAlloc) plock = ccall((:GlobalLock, "kernel32"), stdcall, Ptr{UInt16}, (Ptr{UInt16},), pdata) plock == C_NULL && return cleanup(:GlobalLock) - GC.@preserve x_u16 memcpy(plock, Base.unsafe_convert(Ptr{UInt16}, x_u16), sizeof(x_u16)) + GC.@preserve x_u16 memcpy(plock, Base.unsafe_convert(Ptr{UInt16}, Base.cconvert(Ptr{UInt16}, x_u16)), sizeof(x_u16)) unlock = ccall((:GlobalUnlock, "kernel32"), stdcall, Cint, (Ptr{UInt16},), pdata) (unlock == 0 && Libc.GetLastError() == 0) || return cleanup(:GlobalUnlock) # this should never fail pset = ccall((:SetClipboardData, "user32"), stdcall, Ptr{UInt16}, (Cuint, Ptr{UInt16}), 13, pdata) # CF_UNICODETEXT diff --git a/stdlib/InteractiveUtils/src/codeview.jl b/stdlib/InteractiveUtils/src/codeview.jl index 4e5141c0de08e..b242dea8e006e 100644 --- a/stdlib/InteractiveUtils/src/codeview.jl +++ b/stdlib/InteractiveUtils/src/codeview.jl @@ -54,6 +54,75 @@ function is_expected_union(u::Union) return true end +function print_warntype_codeinfo(io::IO, src::Core.CodeInfo, @nospecialize(rettype), nargs::Int; lineprinter) + if src.slotnames !== nothing + slotnames = Base.sourceinfo_slotnames(src) + io = IOContext(io, :SOURCE_SLOTNAMES => slotnames) + slottypes = src.slottypes + nargs > 0 && println(io, "Arguments") + for i = 1:length(slotnames) + if i == nargs + 1 + println(io, "Locals") + end + print(io, " ", slotnames[i]) + if isa(slottypes, Vector{Any}) + warntype_type_printer(io; type=slottypes[i], used=true) + end + println(io) + end + end + print(io, "Body") + warntype_type_printer(io; type=rettype, used=true) + println(io) + irshow_config = Base.IRShow.IRShowConfig(lineprinter(src), warntype_type_printer) + Base.IRShow.show_ir(io, src, irshow_config) + println(io) +end + +function print_warntype_mi(io::IO, mi::Core.MethodInstance) + println(io, mi) + print(io, " from ") + println(io, mi.def) + if !isempty(mi.sparam_vals) + println(io, "Static Parameters") + sig = mi.def.sig + warn_color = Base.warn_color() # more mild user notification + for i = 1:length(mi.sparam_vals) + sig = sig::UnionAll + name = sig.var.name + val = mi.sparam_vals[i] + print_highlighted(io::IO, v::String, color::Symbol) = + if highlighting[:warntype] + Base.printstyled(io, v; color) + else + Base.print(io, v) + end + if val isa TypeVar + if val.lb === Union{} + print(io, " ", name, " <: ") + print_highlighted(io, "$(val.ub)", warn_color) + elseif val.ub === Any + print(io, " ", sig.var.name, " >: ") + print_highlighted(io, "$(val.lb)", warn_color) + else + print(io, " ") + print_highlighted(io, "$(val.lb)", warn_color) + print(io, " <: ", sig.var.name, " <: ") + print_highlighted(io, "$(val.ub)", warn_color) + end + elseif val isa typeof(Vararg) + print(io, " ", name, "::") + print_highlighted(io, "Int", warn_color) + else + print(io, " ", sig.var.name, " = ") + print_highlighted(io, "$(val)", :cyan) # show the "good" type + end + println(io) + sig = sig.body + end + end +end + """ code_warntype([io::IO], f, types; debuginfo=:default) @@ -70,89 +139,32 @@ Small unions of concrete types are usually not a concern, so these are highlight Keyword argument `debuginfo` may be one of `:source` or `:none` (default), to specify the verbosity of code comments. -See [`@code_warntype`](@ref man-code-warntype) for more information. +See the [`@code_warntype`](@ref man-code-warntype) section in the Performance Tips page of the manual for more information. See also: [`@code_warntype`](@ref), [`code_typed`](@ref), [`code_lowered`](@ref), [`code_llvm`](@ref), [`code_native`](@ref). """ -function code_warntype(io::IO, @nospecialize(f), @nospecialize(t=Base.default_tt(f)); +function code_warntype(io::IO, @nospecialize(f), @nospecialize(tt=Base.default_tt(f)); + world=Base.get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world), debuginfo::Symbol=:default, optimize::Bool=false, kwargs...) + (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && + error("code reflection cannot be used from generated functions") debuginfo = Base.IRShow.debuginfo(debuginfo) lineprinter = Base.IRShow.__debuginfo[debuginfo] - for (src, rettype) in code_typed(f, t; optimize, kwargs...) - if !(src isa Core.CodeInfo) - println(io, src) - println(io, " failed to infer") - continue - end - lambda_io::IOContext = io - p = src.parent - nargs::Int = 0 - if p isa Core.MethodInstance - println(io, p) - print(io, " from ") - println(io, p.def) - p.def isa Method && (nargs = p.def.nargs) - if !isempty(p.sparam_vals) - println(io, "Static Parameters") - sig = p.def.sig - warn_color = Base.warn_color() # more mild user notification - for i = 1:length(p.sparam_vals) - sig = sig::UnionAll - name = sig.var.name - val = p.sparam_vals[i] - print_highlighted(io::IO, v::String, color::Symbol) = - if highlighting[:warntype] - Base.printstyled(io, v; color) - else - Base.print(io, v) - end - if val isa TypeVar - if val.lb === Union{} - print(io, " ", name, " <: ") - print_highlighted(io, "$(val.ub)", warn_color) - elseif val.ub === Any - print(io, " ", sig.var.name, " >: ") - print_highlighted(io, "$(val.lb)", warn_color) - else - print(io, " ") - print_highlighted(io, "$(val.lb)", warn_color) - print(io, " <: ", sig.var.name, " <: ") - print_highlighted(io, "$(val.ub)", warn_color) - end - elseif val isa typeof(Vararg) - print(io, " ", name, "::") - print_highlighted(io, "Int", warn_color) - else - print(io, " ", sig.var.name, " = ") - print_highlighted(io, "$(val)", :cyan) # show the "good" type - end - println(io) - sig = sig.body - end - end - end - if src.slotnames !== nothing - slotnames = Base.sourceinfo_slotnames(src) - lambda_io = IOContext(lambda_io, :SOURCE_SLOTNAMES => slotnames) - slottypes = src.slottypes - nargs > 0 && println(io, "Arguments") - for i = 1:length(slotnames) - if i == nargs + 1 - println(io, "Locals") - end - print(io, " ", slotnames[i]) - if isa(slottypes, Vector{Any}) - warntype_type_printer(io; type=slottypes[i], used=true) - end - println(io) - end - end - print(io, "Body") - warntype_type_printer(io; type=rettype, used=true) - println(io) - irshow_config = Base.IRShow.IRShowConfig(lineprinter(src), warntype_type_printer) - Base.IRShow.show_ir(lambda_io, src, irshow_config) - println(io) + nargs::Int = 0 + if isa(f, Core.OpaqueClosure) + isa(f.source, Method) && (nargs = f.nargs) + print_warntype_codeinfo(io, Base.code_typed_opaque_closure(f, tt)[1]..., nargs; lineprinter) + return nothing + end + matches = Base._methods_by_ftype(Base.signature_type(f, tt), #=lim=#-1, world)::Vector + for match in matches + match = match::Core.MethodMatch + (src, rettype) = Core.Compiler.typeinf_code(interp, match, optimize) + mi = Core.Compiler.specialize_method(match) + mi.def isa Method && (nargs = (mi.def::Method).nargs) + print_warntype_mi(io, mi) + print_warntype_codeinfo(io, src, rettype, nargs; lineprinter) end nothing end @@ -188,7 +200,7 @@ function _dump_function(@nospecialize(f), @nospecialize(t), native::Bool, wrappe else world = UInt64(f.world) tt = Base.to_tuple_type(t) - if Core.Compiler.is_source_inferred(f.source.source) + if !isdefined(f.source, :source) # OC was constructed from inferred source. There's only one # specialization and we can't infer anything more precise either. world = f.source.primary_world diff --git a/stdlib/InteractiveUtils/src/editless.jl b/stdlib/InteractiveUtils/src/editless.jl index 539e9b12f4071..5b87dc0c57d40 100644 --- a/stdlib/InteractiveUtils/src/editless.jl +++ b/stdlib/InteractiveUtils/src/editless.jl @@ -77,7 +77,7 @@ already work: - pycharm - bbedit -# Example: +# Examples The following defines the usage of terminal-based `emacs`: @@ -223,7 +223,7 @@ Edit a file or directory optionally providing a line number to edit the file at. Return to the `julia` prompt when you quit the editor. The editor can be changed by setting `JULIA_EDITOR`, `VISUAL` or `EDITOR` as an environment variable. -See also [`define_editor`](@ref). +See also [`InteractiveUtils.define_editor`](@ref). """ function edit(path::AbstractString, line::Integer=0, column::Integer=0) path isa String || (path = convert(String, path)) @@ -255,7 +255,7 @@ method to edit. For modules, open the main source file. The module needs to be l `edit` on modules requires at least Julia 1.1. To ensure that the file can be opened at the given line, you may need to call -`define_editor` first. +`InteractiveUtils.define_editor` first. """ function edit(@nospecialize f) ms = methods(f).ms diff --git a/stdlib/InteractiveUtils/src/macros.jl b/stdlib/InteractiveUtils/src/macros.jl index a840dd7ea43bb..bb56c47b4f9ca 100644 --- a/stdlib/InteractiveUtils/src/macros.jl +++ b/stdlib/InteractiveUtils/src/macros.jl @@ -36,6 +36,10 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws=Expr[]) if Meta.isexpr(ex0, :ref) ex0 = replace_ref_begin_end!(ex0) end + # assignments get bypassed: @edit a = f(x) <=> @edit f(x) + if isa(ex0, Expr) && ex0.head == :(=) && isa(ex0.args[1], Symbol) && isempty(kws) + return gen_call_with_extracted_types(__module__, fcn, ex0.args[2]) + end if isa(ex0, Expr) if ex0.head === :do && Meta.isexpr(get(ex0.args, 1, nothing), :call) if length(ex0.args) != 2 @@ -102,6 +106,11 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws=Expr[]) $(kws...)) end elseif ex0.head === :call + if ex0.args[1] === :^ && length(ex0.args) >= 3 && isa(ex0.args[3], Int) + return Expr(:call, fcn, :(Base.literal_pow), + Expr(:call, typesof, esc(ex0.args[1]), esc(ex0.args[2]), + esc(Val(ex0.args[3])))) + end return Expr(:call, fcn, esc(ex0.args[1]), Expr(:call, typesof, map(esc, ex0.args[2:end])...), kws...) diff --git a/stdlib/InteractiveUtils/test/highlighting.jl b/stdlib/InteractiveUtils/test/highlighting.jl index bac52e2945b5e..b72c9dbe72795 100644 --- a/stdlib/InteractiveUtils/test/highlighting.jl +++ b/stdlib/InteractiveUtils/test/highlighting.jl @@ -72,7 +72,7 @@ end @test occursin("\e", String(take!(io))) end -function hilight_llvm(s) +function highlight_llvm(s) io = IOBuffer() InteractiveUtils.print_llvm(IOContext(io, :color=>true), s) r = String(take!(io)) @@ -82,7 +82,7 @@ function hilight_llvm(s) flush(stdout) r end -function hilight_native(s, arch) +function highlight_native(s, arch) io = IOBuffer() InteractiveUtils.print_native(IOContext(io, :color=>true), s, arch) r = String(take!(io)) @@ -92,8 +92,8 @@ function hilight_native(s, arch) flush(stdout) r end -hilight_x86(s) = hilight_native(s, :x86) -hilight_arm(s) = hilight_native(s, :arm) +highlight_x86(s) = highlight_native(s, :x86) +highlight_arm(s) = highlight_native(s, :arm) function esc_code(s) io = IOBuffer() @@ -124,41 +124,41 @@ const XU = B * "}" * XB @testset "LLVM IR" begin @testset "comment" begin - @test hilight_llvm("; comment ; // # ") == "$(C); comment ; // # $(XC)\n" + @test highlight_llvm("; comment ; // # ") == "$(C); comment ; // # $(XC)\n" end - @testset "lavel" begin - @test hilight_llvm("top:") == "$(L)top:$(XL)\n" + @testset "label" begin + @test highlight_llvm("top:") == "$(L)top:$(XL)\n" - @test hilight_llvm("L7:\t\t; preds = %top") == + @test highlight_llvm("L7:\t\t; preds = %top") == "$(L)L7:$(XL)\t\t$(C); preds = %top$(XC)\n" end @testset "define" begin - @test hilight_llvm("define double @julia_func_1234(float) {") == + @test highlight_llvm("define double @julia_func_1234(float) {") == "$(K)define$(XK) $(T)double$(XT) " * "$(F)@julia_func_1234$(XF)$P$(T)float$(XT)$XP $U\n" - @test hilight_llvm("}") == "$XU\n" + @test highlight_llvm("}") == "$XU\n" end @testset "declare" begin - @test hilight_llvm("declare i32 @jl_setjmp(i8*) #2") == + @test highlight_llvm("declare i32 @jl_setjmp(i8*) #2") == "$(K)declare$(XK) $(T)i32$(XT) " * "$(F)@jl_setjmp$(XF)$P$(T)i8$(XT)$(D)*$(XD)$XP $(D)#2$(XD)\n" end @testset "type" begin - @test hilight_llvm("%jl_value_t = type opaque") == + @test highlight_llvm("%jl_value_t = type opaque") == "$(V)%jl_value_t$(XV) $EQU $(K)type$(XK) $(T)opaque$(XT)\n" end @testset "target" begin datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128-ni:10:11:12:13" - @test hilight_llvm("target datalayout = \"$datalayout\"") == + @test highlight_llvm("target datalayout = \"$datalayout\"") == "$(K)target$(XK) $(K)datalayout$(XK) $EQU $(V)\"$datalayout\"$(XV)\n" end @testset "attributes" begin - @test hilight_llvm( + @test highlight_llvm( """attributes #1 = { uwtable "frame-pointer"="all" }""") == "$(K)attributes$(XK) $(D)#1$(XD) $EQU " * "$U $(K)uwtable$(XK) $(V)\"frame-pointer\"$(XV)$EQU" * @@ -166,57 +166,57 @@ const XU = B * "}" * XB end @testset "terminator" begin - @test hilight_llvm(" ret i8 %12") == + @test highlight_llvm(" ret i8 %12") == " $(I)ret$(XI) $(T)i8$(XT) $(V)%12$(XV)\n" - @test hilight_llvm(" br i1 %2, label %L6, label %L4") == + @test highlight_llvm(" br i1 %2, label %L6, label %L4") == " $(I)br$(XI) $(T)i1$(XT) $(V)%2$(XV)$COM " * "$(T)label$(XT) $(L)%L6$(XL)$COM $(T)label$(XT) $(L)%L4$(XL)\n" - @test hilight_llvm(" br label %L5") == + @test highlight_llvm(" br label %L5") == " $(I)br$(XI) $(T)label$(XT) $(L)%L5$(XL)\n" - @test hilight_llvm(" unreachable") == " $(I)unreachable$(XI)\n" + @test highlight_llvm(" unreachable") == " $(I)unreachable$(XI)\n" end @testset "arithmetic" begin - @test hilight_llvm(" %11 = add nuw nsw i64 %value_phi10, 1") == + @test highlight_llvm(" %11 = add nuw nsw i64 %value_phi10, 1") == " $(V)%11$(XV) $EQU $(I)add$(XI) $(K)nuw$(XK) $(K)nsw$(XK) " * "$(T)i64$(XT) $(V)%value_phi10$(XV)$COM $(N)1$(XN)\n" - @test hilight_llvm(" %13 = fadd double %12, -2.000000e+00") == + @test highlight_llvm(" %13 = fadd double %12, -2.000000e+00") == " $(V)%13$(XV) $EQU $(I)fadd$(XI) " * "$(T)double$(XT) $(V)%12$(XV)$COM $(N)-2.000000e+00$(XN)\n" - @test hilight_llvm(" %21 = fmul contract double %20, 0x0123456789ABCDEF") == + @test highlight_llvm(" %21 = fmul contract double %20, 0x0123456789ABCDEF") == " $(V)%21$(XV) $EQU $(I)fmul$(XI) $(K)contract$(XK) " * "$(T)double$(XT) $(V)%20$(XV)$COM $(N)0x0123456789ABCDEF$(XN)\n" end @testset "bitwise" begin - @test hilight_llvm(" %31 = shl i64 %value_phi4, 52") == + @test highlight_llvm(" %31 = shl i64 %value_phi4, 52") == " $(V)%31$(XV) $EQU " * "$(I)shl$(XI) $(T)i64$(XT) $(V)%value_phi4$(XV)$COM $(N)52$(XN)\n" end @testset "aggregate" begin - @test hilight_llvm(" %4 = extractvalue { i64, i1 } %1, 0") == + @test highlight_llvm(" %4 = extractvalue { i64, i1 } %1, 0") == " $(V)%4$(XV) $EQU $(I)extractvalue$(XI) " * "$U $(T)i64$(XT)$COM $(T)i1$(XT) $XU $(V)%1$(XV)$COM $(N)0$(XN)\n" end @testset "memory access" begin - @test hilight_llvm(" %dims = alloca [1 x i64], align 8") == + @test highlight_llvm(" %dims = alloca [1 x i64], align 8") == " $(V)%dims$(XV) $EQU $(I)alloca$(XI) " * "$S$(N)1$(XN) $(D)x$(XD) $(T)i64$(XT)$XS$COM $(K)align$(XK) $(N)8$(XN)\n" - @test hilight_llvm(" %51 = load i32," * + @test highlight_llvm(" %51 = load i32," * " i32* inttoptr (i64 226995504 to i32*), align 16") == " $(V)%51$(XV) $EQU $(I)load$(XI) $(T)i32$(XT)$COM " * "$(T)i32$(XT)$(D)*$(XD) $(K)inttoptr$(XK) $P$(T)i64$(XT) $(N)226995504$(XN) " * "$(K)to$(XK) $(T)i32$(XT)$(D)*$(XD)$XP$COM $(K)align$(XK) $(N)16$(XN)\n" - @test hilight_llvm(" %53 = load %jl_value_t addrspace(10)*, " * + @test highlight_llvm(" %53 = load %jl_value_t addrspace(10)*, " * "%jl_value_t addrspace(10)* addrspace(11)* %52, align 8") == " $(V)%53$(XV) $EQU $(I)load$(XI) $(V)%jl_value_t$(XV) " * "$(K)addrspace$(XK)$P$(N)10$(XN)$XP$(D)*$(XD)$COM " * @@ -224,37 +224,37 @@ const XU = B * "}" * XB "$(K)addrspace$(XK)$P$(N)11$(XN)$XP$(D)*$(XD) " * "$(V)%52$(XV)$COM $(K)align$(XK) $(N)8$(XN)\n" - @test hilight_llvm(" store i64 %61, i64 addrspace(11)* %60, align 8") == + @test highlight_llvm(" store i64 %61, i64 addrspace(11)* %60, align 8") == " $(I)store$(XI) $(T)i64$(XT) $(V)%61$(XV)$COM " * "$(T)i64$(XT) $(K)addrspace$(XK)$P$(N)11$(XN)$XP$(D)*$(XD) " * "$(V)%60$(XV)$COM $(K)align$(XK) $(N)8$(XN)\n" - @test hilight_llvm(" store volatile %jl_value_t addrspace(10)** %62, " * + @test highlight_llvm(" store volatile %jl_value_t addrspace(10)** %62, " * "%jl_value_t addrspace(10)*** %63, align 8") == " $(I)store$(XI) $(K)volatile$(XK) $(V)%jl_value_t$(XV) " * "$(K)addrspace$(XK)$P$(N)10$(XN)$XP$(D)**$(XD) $(V)%62$(XV)$COM " * "$(V)%jl_value_t$(XV) $(K)addrspace$(XK)$P$(N)10$(XN)$XP$(D)***$(XD) " * "$(V)%63$(XV)$COM $(K)align$(XK) $(N)8$(XN)\n" - @test hilight_llvm(" %71 = getelementptr i8, i8* %70, i64 8") == + @test highlight_llvm(" %71 = getelementptr i8, i8* %70, i64 8") == " $(V)%71$(XV) $EQU $(I)getelementptr$(XI) $(T)i8$(XT)$COM " * "$(T)i8$(XT)$(D)*$(XD) $(V)%70$(XV)$COM $(T)i64$(XT) $(N)8$(XN)\n" end @testset "conversion" begin - @test hilight_llvm(" %22 = zext i1 %21 to i8") == + @test highlight_llvm(" %22 = zext i1 %21 to i8") == " $(V)%22$(XV) $EQU $(I)zext$(XI) $(T)i1$(XT) $(V)%21$(XV) " * "$(K)to$(XK) $(T)i8$(XT)\n" - @test hilight_llvm(" %24 = sitofp i64 %23 to double") == + @test highlight_llvm(" %24 = sitofp i64 %23 to double") == " $(V)%24$(XV) $EQU $(I)sitofp$(XI) $(T)i64$(XT) $(V)%23$(XV) " * "$(K)to$(XK) $(T)double$(XT)\n" - @test hilight_llvm(" %26 = ptrtoint i8* %25 to i64") == + @test highlight_llvm(" %26 = ptrtoint i8* %25 to i64") == " $(V)%26$(XV) $EQU $(I)ptrtoint$(XI) $(T)i8$(XT)$(D)*$(XD) " * "$(V)%25$(XV) $(K)to$(XK) $(T)i64$(XT)\n" - @test hilight_llvm(" %28 = bitcast %jl_value_t addrspace(10)* %27 " * + @test highlight_llvm(" %28 = bitcast %jl_value_t addrspace(10)* %27 " * "to [2 x i16] addrspace(10)*") == " $(V)%28$(XV) $EQU $(I)bitcast$(XI) $(V)%jl_value_t$(XV) " * "$(K)addrspace$(XK)$P$(N)10$(XN)$XP$(D)*$(XD) $(V)%27$(XV) " * @@ -263,20 +263,20 @@ const XU = B * "}" * XB end @testset "other" begin - @test hilight_llvm(" %31 = icmp slt i64 %30, 0") == + @test highlight_llvm(" %31 = icmp slt i64 %30, 0") == " $(V)%31$(XV) $EQU $(I)icmp$(XI) $(I)slt$(XI) " * "$(T)i64$(XT) $(V)%30$(XV)$COM $(N)0$(XN)\n" - @test hilight_llvm(" %value_phi34 = phi double [ %33, %L50 ], [ %32, %L60 ]") == + @test highlight_llvm(" %value_phi34 = phi double [ %33, %L50 ], [ %32, %L60 ]") == " $(V)%value_phi34$(XV) $EQU $(I)phi$(XI) $(T)double$(XT) " * "$S $(V)%33$(XV)$COM $(L)%L50$(XL) $XS$COM " * "$S $(V)%32$(XV)$COM $(L)%L60$(XL) $XS\n" - @test hilight_llvm(" %.v = select i1 %35, i64 %36, i64 63") == + @test highlight_llvm(" %.v = select i1 %35, i64 %36, i64 63") == " $(V)%.v$(XV) $EQU $(I)select$(XI) $(T)i1$(XT) $(V)%35$(XV)$COM " * "$(T)i64$(XT) $(V)%36$(XV)$COM $(T)i64$(XT) $(N)63$(XN)\n" - @test hilight_llvm(" %38 = call i64 @llvm.cttz.i64(i64 %37, i1 false)") == + @test highlight_llvm(" %38 = call i64 @llvm.cttz.i64(i64 %37, i1 false)") == " $(V)%38$(XV) $EQU $(I)call$(XI) $(T)i64$(XT) " * "$(F)@llvm.cttz.i64$(XF)$P$(T)i64$(XT) $(V)%37$(XV)$COM " * "$(T)i1$(XT) $(K)false$(XK)$XP\n" @@ -285,133 +285,133 @@ end @testset "x86 ASM" begin @testset "comment" begin - @test hilight_x86("; comment ; // # ") == "$(C); comment ; // # $(XC)\n" + @test highlight_x86("; comment ; // # ") == "$(C); comment ; // # $(XC)\n" end @testset "label" begin - @test hilight_x86("L123:") == "$(L)L123:$(XL)\n" + @test highlight_x86("L123:") == "$(L)L123:$(XL)\n" end @testset "directive" begin - @test hilight_x86("\t.text") == "\t$(D).text$(XD)\n" + @test highlight_x86("\t.text") == "\t$(D).text$(XD)\n" end @testset "0-operand" begin # AT&T - @test hilight_x86("\tretq") == "\t$(I)retq$(XI)\n" + @test highlight_x86("\tretq") == "\t$(I)retq$(XI)\n" # Intel - @test hilight_x86("\tret") == "\t$(I)ret$(XI)\n" + @test highlight_x86("\tret") == "\t$(I)ret$(XI)\n" end @testset "1-operand" begin # AT&T - @test hilight_x86("\tpopq\t%rax") == "\t$(I)popq$(XI)\t$(V)%rax$(XV)\n" + @test highlight_x86("\tpopq\t%rax") == "\t$(I)popq$(XI)\t$(V)%rax$(XV)\n" - @test hilight_x86("\tpushl\t\$4294967295\t# imm = 0xFFFFFFFF") == + @test highlight_x86("\tpushl\t\$4294967295\t# imm = 0xFFFFFFFF") == "\t$(I)pushl$(XI)\t$(N)\$4294967295$(XN)\t$(C)# imm = 0xFFFFFFFF$(XC)\n" - @test hilight_x86("\tja\tL234") == "\t$(I)ja$(XI)\t$(L)L234$(XL)\n" + @test highlight_x86("\tja\tL234") == "\t$(I)ja$(XI)\t$(L)L234$(XL)\n" - @test hilight_x86("\tnopw\t%cs:(%rax,%rax)") == + @test highlight_x86("\tnopw\t%cs:(%rax,%rax)") == "\t$(I)nopw$(XI)\t$(V)%cs$(XV)$COL$P$(V)%rax$(XV)$COM$(V)%rax$(XV)$XP\n" # Intel - @test hilight_x86("\tpop\trax") == "\t$(I)pop$(XI)\t$(V)rax$(XV)\n" + @test highlight_x86("\tpop\trax") == "\t$(I)pop$(XI)\t$(V)rax$(XV)\n" - @test hilight_x86("\tpush\t4294967295") == + @test highlight_x86("\tpush\t4294967295") == "\t$(I)push$(XI)\t$(N)4294967295$(XN)\n" - @test hilight_x86("\tja\tL234") == "\t$(I)ja$(XI)\t$(L)L234$(XL)\n" + @test highlight_x86("\tja\tL234") == "\t$(I)ja$(XI)\t$(L)L234$(XL)\n" - @test hilight_x86("\tnop\tword ptr cs:[rax + rax]") == + @test highlight_x86("\tnop\tword ptr cs:[rax + rax]") == "\t$(I)nop$(XI)\t$(K)word$(XK) $(K)ptr$(XK) " * "$(V)cs$(XV)$COL$S$(V)rax$(XV) $(D)+$(XD) $(V)rax$(XV)$XS\n" end @testset "2-operand" begin # AT&T - @test hilight_x86("\tshrq\t\$63, %rcx") == + @test highlight_x86("\tshrq\t\$63, %rcx") == "\t$(I)shrq$(XI)\t$(N)\$63$(XN)$COM $(V)%rcx$(XV)\n" - @test hilight_x86("\tvmovsd\t(%rsi,%rdx,8), %xmm1\t# xmm1 = mem[0],zero") == + @test highlight_x86("\tvmovsd\t(%rsi,%rdx,8), %xmm1\t# xmm1 = mem[0],zero") == "\t$(I)vmovsd$(XI)\t$P$(V)%rsi$(XV)$COM$(V)%rdx$(XV)$COM$(N)8$(XN)$XP" * "$COM $(V)%xmm1$(XV)\t$(C)# xmm1 = mem[0],zero$(XC)\n" - @test hilight_x86("\tmovabsq\t\$\"#string#338\", %rax") == + @test highlight_x86("\tmovabsq\t\$\"#string#338\", %rax") == "\t$(I)movabsq$(XI)\t$(F)\$\"#string#338\"$(XF)$COM $(V)%rax$(XV)\n" # Intel - @test hilight_x86("\tshr\trcx, 63") == + @test highlight_x86("\tshr\trcx, 63") == "\t$(I)shr$(XI)\t$(V)rcx$(XV)$COM $(N)63$(XN)\n" - @test hilight_x86( + @test highlight_x86( "\tvmovsd\txmm1, dword ptr [rsi + 8*rdx]\t# xmm1 = mem[0],zero") == "\t$(I)vmovsd$(XI)\t$(V)xmm1$(XV)$COM $(K)dword$(XK) $(K)ptr$(XK) " * "$S$(V)rsi$(XV) $(D)+$(XD) $(N)8$(XN)$(D)*$(XD)$(V)rdx$(XV)$XS" * "\t$(C)# xmm1 = mem[0],zero$(XC)\n" - @test hilight_x86("\tmovabs\trax, offset \"#string#338\"") == + @test highlight_x86("\tmovabs\trax, offset \"#string#338\"") == "\t$(I)movabs$(XI)\t$(V)rax$(XV)$COM " * "$(K)offset$(XK) $(F)\"#string#338\"$(XF)\n" end @testset "3-operand" begin # AT&T - @test hilight_x86("\tvaddsd\t(%rax), %xmm0, %xmm0") == + @test highlight_x86("\tvaddsd\t(%rax), %xmm0, %xmm0") == "\t$(I)vaddsd$(XI)\t$P$(V)%rax$(XV)$XP$COM " * "$(V)%xmm0$(XV)$COM $(V)%xmm0$(XV)\n" # Intel - @test hilight_x86("\tvaddsd\txmm0, xmm0, qword ptr [rax]") == + @test highlight_x86("\tvaddsd\txmm0, xmm0, qword ptr [rax]") == "\t$(I)vaddsd$(XI)\t$(V)xmm0$(XV)$COM $(V)xmm0$(XV)$COM " * "$(K)qword$(XK) $(K)ptr$(XK) $S$(V)rax$(XV)$XS\n" end @testset "4-operand" begin # AT&T - @test hilight_x86("\tvroundsd\t\$4, %xmm1, %xmm1, %xmm1") == + @test highlight_x86("\tvroundsd\t\$4, %xmm1, %xmm1, %xmm1") == "\t$(I)vroundsd$(XI)\t$(N)\$4$(XN)$COM " * "$(V)%xmm1$(XV)$COM $(V)%xmm1$(XV)$COM $(V)%xmm1$(XV)\n" # Intel - @test hilight_x86("\tvroundsd\txmm1, xmm1, xmm1, 4") == + @test highlight_x86("\tvroundsd\txmm1, xmm1, xmm1, 4") == "\t$(I)vroundsd$(XI)\t" * "$(V)xmm1$(XV)$COM $(V)xmm1$(XV)$COM $(V)xmm1$(XV)$COM $(N)4$(XN)\n" end @testset "AVX-512" begin # AT&T - @test hilight_x86("\tvmovaps\t(%eax), %zmm0") == + @test highlight_x86("\tvmovaps\t(%eax), %zmm0") == "\t$(I)vmovaps$(XI)\t$P$(V)%eax$(XV)$XP$COM $(V)%zmm0$(XV)\n" - @test hilight_x86("\tvpaddd\t%zmm3, %zmm1, %zmm1 {%k1}") == + @test highlight_x86("\tvpaddd\t%zmm3, %zmm1, %zmm1 {%k1}") == "\t$(I)vpaddd$(XI)\t$(V)%zmm3$(XV)$COM $(V)%zmm1$(XV)$COM " * "$(V)%zmm1$(XV) $U$(V)%k1$(XV)$XU\n" - @test hilight_x86("\tvdivpd\t%zmm3, %zmm1, %zmm0 {%k1} {z}") == + @test highlight_x86("\tvdivpd\t%zmm3, %zmm1, %zmm0 {%k1} {z}") == "\t$(I)vdivpd$(XI)\t$(V)%zmm3$(XV)$COM $(V)%zmm1$(XV)$COM " * "$(V)%zmm0$(XV) $U$(V)%k1$(XV)$XU $U$(K)z$(XK)$XU\n" - @test hilight_x86("\tvdivps\t(%ebx){1to16}, %zmm5, %zmm4") == + @test highlight_x86("\tvdivps\t(%ebx){1to16}, %zmm5, %zmm4") == "\t$(I)vdivps$(XI)\t$P$(V)%ebx$(XV)$XP$U$(K)1to16$(XK)$XU$COM " * "$(V)%zmm5$(XV)$COM $(V)%zmm4$(XV)\n" - @test hilight_x86("\tvcvtsd2si\t{rn-sae}, %xmm0, %eax") == + @test highlight_x86("\tvcvtsd2si\t{rn-sae}, %xmm0, %eax") == "\t$(I)vcvtsd2si$(XI)\t$U$(K)rn-sae$(XK)$XU$COM " * "$(V)%xmm0$(XV)$COM $(V)%eax$(XV)\n" # Intel - @test hilight_x86("\tvmovaps\tzmm0, zmmword ptr [eax]") == + @test highlight_x86("\tvmovaps\tzmm0, zmmword ptr [eax]") == "\t$(I)vmovaps$(XI)\t$(V)zmm0$(XV)$COM " * "$(K)zmmword$(XK) $(K)ptr$(XK) $S$(V)eax$(XV)$XS\n" - @test hilight_x86("\tvpaddd\tzmm1 {k1}, zmm1, zmm3") == + @test highlight_x86("\tvpaddd\tzmm1 {k1}, zmm1, zmm3") == "\t$(I)vpaddd$(XI)\t$(V)zmm1$(XV) $U$(V)k1$(XV)$XU$COM " * "$(V)zmm1$(XV)$COM $(V)zmm3$(XV)\n" - @test hilight_x86("\tvdivpd\tzmm0 {k1} {z}, zmm1, zmm3") == + @test highlight_x86("\tvdivpd\tzmm0 {k1} {z}, zmm1, zmm3") == "\t$(I)vdivpd$(XI)\t$(V)zmm0$(XV) $U$(V)k1$(XV)$XU $U$(K)z$(XK)$XU$COM " * "$(V)zmm1$(XV)$COM $(V)zmm3$(XV)\n" - @test hilight_x86("\tvdivps\tzmm4, zmm5, dword ptr [ebx]{1to16}") == + @test highlight_x86("\tvdivps\tzmm4, zmm5, dword ptr [ebx]{1to16}") == "\t$(I)vdivps$(XI)\t$(V)zmm4$(XV)$COM $(V)zmm5$(XV)$COM " * "$(K)dword$(XK) $(K)ptr$(XK) $S$(V)ebx$(XV)$XS$U$(K)1to16$(XK)$XU\n" - @test hilight_x86("\tvcvtsd2si\teax, xmm0$(XV), {rn-sae}") == + @test highlight_x86("\tvcvtsd2si\teax, xmm0$(XV), {rn-sae}") == "\t$(I)vcvtsd2si$(XI)\t$(V)eax$(XV)$COM " * "$(V)xmm0$(XV)$COM $U$(K)rn-sae$(XK)$XU\n" end @@ -419,74 +419,74 @@ end @testset "ARM ASM" begin @testset "comment" begin - @test hilight_arm("; comment ; // # ") == "$(C); comment ; // # $(XC)\n" + @test highlight_arm("; comment ; // # ") == "$(C); comment ; // # $(XC)\n" end @testset "label" begin - @test hilight_arm("L45:") == "$(L)L45:$(XL)\n" + @test highlight_arm("L45:") == "$(L)L45:$(XL)\n" end @testset "directive" begin - @test hilight_arm("\t.text") == "\t$(D).text$(XD)\n" + @test highlight_arm("\t.text") == "\t$(D).text$(XD)\n" end @testset "0-operand" begin - @test hilight_arm("\tret") == "\t$(I)ret$(XI)\n" + @test highlight_arm("\tret") == "\t$(I)ret$(XI)\n" end @testset "1-operand" begin - @test hilight_arm("\tbl\t0x12") == "\t$(I)bl$(XI)\t$(N)0x12$(XN)\n" + @test highlight_arm("\tbl\t0x12") == "\t$(I)bl$(XI)\t$(N)0x12$(XN)\n" - @test hilight_arm("\tb\tL345") == "\t$(I)b$(XI)\t$(L)L345$(XL)\n" + @test highlight_arm("\tb\tL345") == "\t$(I)b$(XI)\t$(L)L345$(XL)\n" - @test hilight_arm("\tb.gt\tL67") == "\t$(I)b.gt$(XI)\t$(L)L67$(XL)\n" + @test highlight_arm("\tb.gt\tL67") == "\t$(I)b.gt$(XI)\t$(L)L67$(XL)\n" - @test hilight_arm("\tpop\t{r11, pc}") == + @test highlight_arm("\tpop\t{r11, pc}") == "\t$(I)pop$(XI)\t$U$(V)r11$(XV)$COM $(V)pc$(XV)$XU\n" end @testset "2-operand" begin - @test hilight_arm("\tcmp\tx10, #2047\t// =2047") == + @test highlight_arm("\tcmp\tx10, #2047\t// =2047") == "\t$(I)cmp$(XI)\t$(V)x10$(XV)$COM $(N)#2047$(XN)\t$(C)// =2047$(XC)\n" - @test hilight_arm("\tldr\td1, [x10]") == + @test highlight_arm("\tldr\td1, [x10]") == "\t$(I)ldr$(XI)\t$(V)d1$(XV)$COM $S$(V)x10$(XV)$XS\n" - @test hilight_arm("\tstr\tx30, [sp, #-16]!") == + @test highlight_arm("\tstr\tx30, [sp, #-16]!") == "\t$(I)str$(XI)\t$(V)x30$(XV)$COM " * "$S$(V)sp$(XV)$COM $(N)#-16$(XN)$XS$(K)!$(XK)\n" - @test hilight_arm("\tmov\tv0.16b, v1.16b") == + @test highlight_arm("\tmov\tv0.16b, v1.16b") == "\t$(I)mov$(XI)\t$(V)v0.16b$(XV)$COM $(V)v1.16b$(XV)\n" end @testset "3-operand" begin - @test hilight_arm("\tfmul\td2, d0, d2") == + @test highlight_arm("\tfmul\td2, d0, d2") == "\t$(I)fmul$(XI)\t$(V)d2$(XV)$COM $(V)d0$(XV)$COM $(V)d2$(XV)\n" - @test hilight_arm("\tmovk\tx10, #65535, lsl #32") == + @test highlight_arm("\tmovk\tx10, #65535, lsl #32") == "\t$(I)movk$(XI)\t$(V)x10$COM $(N)#65535$(XN)$COM $(K)lsl$(XK) $(N)#32$(XN)\n" - @test hilight_arm("\tcneg\tx8, x8, ge") == + @test highlight_arm("\tcneg\tx8, x8, ge") == "\t$(I)cneg$(XI)\t$(V)x8$(XV)$COM $(V)x8$(XV)$COM $(K)ge$(XK)\n" end @testset "4-operand" begin - @test hilight_arm("\tadd\tx8, x9, x8, lsl #52") == + @test highlight_arm("\tadd\tx8, x9, x8, lsl #52") == "\t$(I)add$(XI)\t$(V)x8$(XV)$COM $(V)x9$(XV)$COM $(V)x8$(XV)$COM " * "$(K)lsl$(XK) $(N)#52$(XN)\n" - @test hilight_arm("\tfcsel\td1, d0, d1, eq") == + @test highlight_arm("\tfcsel\td1, d0, d1, eq") == "\t$(I)fcsel$(XI)\t" * "$(V)d1$(XV)$COM $(V)d0$(XV)$COM $(V)d1$(XV)$COM $(K)eq$(XK)\n" end @testset "NEON" begin - hilight_arm("\tvmul.f32\tq8, q9, q8") == + highlight_arm("\tvmul.f32\tq8, q9, q8") == "\t$(I)vmul.f32$(XI)\t$(V)q8$(XV)$COM $(V)q9$(XV)$COM $(V)q8$(XV)\n" - hilight_arm("\tvcvt.s32.f64\ts2, d20") == + highlight_arm("\tvcvt.s32.f64\ts2, d20") == "\t$(I)vcvt.s32.f64$(XI)\t$(V)s2$(XV)$COM $(V)d20$(XV)\n" - hilight_arm("\tvld1.32\t{d18, d19}, [r1]") == + highlight_arm("\tvld1.32\t{d18, d19}, [r1]") == "\t$(I)vld1.32$(XI)\t$U$(V)d18$(XV)$COM $(V)d19$(XV)$XU$COM $S$(V)r1$(XV)$XS\n" end @testset "SVE" begin - hilight_arm("\tld1d\tz1.d, p0/z, [x0, x4, lsl #3]") == + highlight_arm("\tld1d\tz1.d, p0/z, [x0, x4, lsl #3]") == "\t$(I)ld1d$(XI)\t$(V)z1.d$(XV)$COM " * "$(V)p0$(XV)$(K)/z$(XK)$COM " * "$S$(V)x0$(XV)$COM $(V)x4$(XV)$COM $(K)lsl$(XK) $(N)#3$(XN)$XS\n" - hilight_arm("\tb.first\tL123") == "\t$(I)b.first$(XI)\t$(L)L123$(XL)" + highlight_arm("\tb.first\tL123") == "\t$(I)b.first$(XI)\t$(L)L123$(XL)" end end diff --git a/stdlib/InteractiveUtils/test/runtests.jl b/stdlib/InteractiveUtils/test/runtests.jl index 5f90491fd8151..22789be2c5495 100644 --- a/stdlib/InteractiveUtils/test/runtests.jl +++ b/stdlib/InteractiveUtils/test/runtests.jl @@ -229,7 +229,7 @@ module Tmp14173 end varinfo(Tmp14173) # warm up const MEMDEBUG = ccall(:jl_is_memdebug, Bool, ()) -@test @allocated(varinfo(Tmp14173)) < (MEMDEBUG ? 300000 : 100000) +@test @allocated(varinfo(Tmp14173)) < (MEMDEBUG ? 300000 : 125000) # PR #24997: test that `varinfo` doesn't fail when encountering `missing` module A @@ -279,14 +279,50 @@ let x..y = 0 @test (@which 1..2).name === :.. end +# issue #53691 +let a = -1 + @test (@which 2^a).name === :^ + @test (@which 2^0x1).name === :^ +end + +let w = Vector{Any}(undef, 9) + @testset "@which x^literal" begin + w[1] = @which 2^0 + w[2] = @which 2^1 + w[3] = @which 2^2 + w[4] = @which 2^3 + w[5] = @which 2^-1 + w[6] = @which 2^-2 + w[7] = @which 2^10 + w[8] = @which big(2.0)^1 + w[9] = @which big(2.0)^-1 + @test all(getproperty.(w, :name) .=== :literal_pow) + @test length(Set(w)) == length(w) # all methods distinct + end +end + +# PR 53713 +if Int === Int64 + # literal_pow only for exponents x: -2^63 <= x < 2^63 #53860 (all Int) + @test (@which 2^-9223372036854775809).name === :^ + @test (@which 2^-9223372036854775808).name === :literal_pow + @test (@which 2^9223372036854775807).name === :literal_pow + @test (@which 2^9223372036854775808).name === :^ +elseif Int === Int32 + # literal_pow only for exponents x: -2^31 <= x < 2^31 #53860 (all Int) + @test (@which 2^-2147483649).name === :^ + @test (@which 2^-2147483648).name === :literal_pow + @test (@which 2^2147483647).name === :literal_pow + @test (@which 2^2147483648).name === :^ +end + # issue #13464 try @which x = 1 error("unexpected") catch err13464 - @test startswith(err13464.msg, "expression is not a function call, or is too complex") + @test startswith(err13464.msg, "expression is not a function call") end - module MacroTest export @macrotest macro macrotest(x::Int, y::Symbol) end @@ -330,7 +366,9 @@ let _true = Ref(true), f, g, h end # manually generate a broken function, which will break codegen -# and make sure Julia doesn't crash +# and make sure Julia doesn't crash (when using a non-asserts build) +is_asserts() = ccall(:jl_is_assertsbuild, Cint, ()) == 1 +if !is_asserts() @eval @noinline Base.@constprop :none f_broken_code() = 0 let m = which(f_broken_code, ()) let src = Base.uncompressed_ast(m) @@ -345,9 +383,9 @@ _true = true # and show that we can still work around it @noinline g_broken_code() = _true ? 0 : h_broken_code() @noinline h_broken_code() = (g_broken_code(); f_broken_code()) -let err = tempname(), +let errf = tempname(), old_stderr = stderr, - new_stderr = open(err, "w") + new_stderr = open(errf, "w") try redirect_stderr(new_stderr) println(new_stderr, "start") @@ -360,24 +398,27 @@ let err = tempname(), finally redirect_stderr(old_stderr) close(new_stderr) - let errstr = read(err, String) + let errstr = read(errf, String) @test startswith(errstr, """start end Internal error: encountered unexpected error during compilation of f_broken_code: - ErrorException(\"unsupported or misplaced expression \"invalid\" in function f_broken_code\") + ErrorException(\"unsupported or misplaced expression \\\"invalid\\\" in function f_broken_code\") """) || errstr @test !endswith(errstr, "\nend\n") || errstr end - rm(err) + rm(errf) end end +end # Issue #33163 A33163(x; y) = x + y B33163(x) = x -@test (@code_typed A33163(1, y=2))[1].inferred -@test !(@code_typed optimize=false A33163(1, y=2))[1].inferred -@test !(@code_typed optimize=false B33163(1))[1].inferred +let + (@code_typed A33163(1, y=2))[1] + (@code_typed optimize=false A33163(1, y=2))[1] + (@code_typed optimize=false B33163(1))[1] +end @test_throws MethodError (@code_lowered wrongkeyword=true 3 + 4) @@ -411,10 +452,11 @@ a14637 = A14637(0) @test (@code_typed max.(1 .+ 3, 5 - 7))[2] == Int f36261(x,y) = 3x + 4y A36261 = Float64[1.0, 2.0, 3.0] -@test (@code_typed f36261.(A36261, pi))[1].inferred -@test (@code_typed f36261.(A36261, 1 .+ pi))[1].inferred -@test (@code_typed f36261.(A36261, 1 + pi))[1].inferred - +let + @code_typed f36261.(A36261, pi)[1] + @code_typed f36261.(A36261, 1 .+ pi)[1] + @code_typed f36261.(A36261, 1 + pi)[1] +end module ReflectionTest using Test, Random, InteractiveUtils @@ -640,7 +682,8 @@ end # macro options should accept both literals and variables let opt = false - @test !(first(@code_typed optimize=opt sum(1:10)).inferred) + @test length(first(@code_typed optimize=opt sum(1:10)).code) == + length((@code_lowered sum(1:10)).code) end @testset "@time_imports" begin @@ -700,6 +743,8 @@ end @testset "code_llvm on opaque_closure" begin let ci = code_typed(+, (Int, Int))[1][1] ir = Core.Compiler.inflate_ir(ci) + @test ir.debuginfo.def === nothing + ir.debuginfo.def = Symbol(@__FILE__) oc = Core.OpaqueClosure(ir) @test (code_llvm(devnull, oc, Tuple{Int, Int}); true) let io = IOBuffer() @@ -721,3 +766,7 @@ end end @test Base.infer_effects(sin, (Int,)) == InteractiveUtils.@infer_effects sin(42) + +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(InteractiveUtils)) +end diff --git a/stdlib/JuliaSyntaxHighlighting.version b/stdlib/JuliaSyntaxHighlighting.version new file mode 100644 index 0000000000000..4a819d056e70e --- /dev/null +++ b/stdlib/JuliaSyntaxHighlighting.version @@ -0,0 +1,4 @@ +JULIASYNTAXHIGHLIGHTING_BRANCH = main +JULIASYNTAXHIGHLIGHTING_SHA1 = 4110caaf4fcdf0c614fd3ecd7c5bf589ca82ac63 +JULIASYNTAXHIGHLIGHTING_GIT_URL := https://github.com/julialang/JuliaSyntaxHighlighting.jl.git +JULIASYNTAXHIGHLIGHTING_TAR_URL = https://api.github.com/repos/julialang/JuliaSyntaxHighlighting.jl/tarball/$1 diff --git a/stdlib/LLD_jll/Project.toml b/stdlib/LLD_jll/Project.toml index 0a0cd4aa6924f..fccea03ebd80e 100644 --- a/stdlib/LLD_jll/Project.toml +++ b/stdlib/LLD_jll/Project.toml @@ -1,6 +1,6 @@ name = "LLD_jll" uuid = "d55e3150-da41-5e91-b323-ecfd1eec6109" -version = "15.0.7+8" +version = "16.0.6+4" [deps] Zlib_jll = "83775a58-1f1d-513f-b197-d71354ab007a" @@ -9,8 +9,8 @@ libLLVM_jll = "8f36deef-c2a5-5394-99ed-8e07531fb29a" Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" [compat] -julia = "1.9" -libLLVM_jll = "15.0.7" +julia = "1.11" +libLLVM_jll = "16.0.6" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/LLD_jll/src/LLD_jll.jl b/stdlib/LLD_jll/src/LLD_jll.jl index 55ccec9cc4005..9b8365dddcf0b 100644 --- a/stdlib/LLD_jll/src/LLD_jll.jl +++ b/stdlib/LLD_jll/src/LLD_jll.jl @@ -4,7 +4,6 @@ baremodule LLD_jll using Base, Libdl -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] diff --git a/stdlib/LLVMLibUnwind_jll/src/LLVMLibUnwind_jll.jl b/stdlib/LLVMLibUnwind_jll/src/LLVMLibUnwind_jll.jl index 5c4026291a673..429e35b91d3f2 100644 --- a/stdlib/LLVMLibUnwind_jll/src/LLVMLibUnwind_jll.jl +++ b/stdlib/LLVMLibUnwind_jll/src/LLVMLibUnwind_jll.jl @@ -4,7 +4,6 @@ baremodule LLVMLibUnwind_jll using Base, Libdl -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] diff --git a/stdlib/LibCURL_jll/Project.toml b/stdlib/LibCURL_jll/Project.toml index aa84637e0dc82..d17090a1e5c3b 100644 --- a/stdlib/LibCURL_jll/Project.toml +++ b/stdlib/LibCURL_jll/Project.toml @@ -1,6 +1,6 @@ name = "LibCURL_jll" uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" -version = "8.4.0+0" +version = "8.6.0+0" [deps] LibSSH2_jll = "29816b5a-b9ab-546f-933c-edad1886dfa8" @@ -17,4 +17,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" test = ["Test"] [compat] -julia = "1.8" +julia = "1.11" diff --git a/stdlib/LibCURL_jll/src/LibCURL_jll.jl b/stdlib/LibCURL_jll/src/LibCURL_jll.jl index cd67bfac0006a..3291c97d811cb 100644 --- a/stdlib/LibCURL_jll/src/LibCURL_jll.jl +++ b/stdlib/LibCURL_jll/src/LibCURL_jll.jl @@ -4,7 +4,6 @@ baremodule LibCURL_jll using Base, Libdl, nghttp2_jll -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] diff --git a/stdlib/LibGit2/Project.toml b/stdlib/LibGit2/Project.toml index 1205716369e09..8432a32cd240b 100644 --- a/stdlib/LibGit2/Project.toml +++ b/stdlib/LibGit2/Project.toml @@ -1,8 +1,8 @@ name = "LibGit2" uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" +version = "1.11.0" [deps] -Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" LibGit2_jll = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" NetworkOptions = "ca575930-c2e3-43a9-ace4-1e988b2c1908" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" diff --git a/stdlib/LibGit2/docs/src/index.md b/stdlib/LibGit2/docs/src/index.md index 3205c4c5d6987..aa4ebf2e784b6 100644 --- a/stdlib/LibGit2/docs/src/index.md +++ b/stdlib/LibGit2/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/LibGit2/docs/src/index.md" +``` + # LibGit2 The LibGit2 module provides bindings to [libgit2](https://libgit2.org/), a portable C library that diff --git a/stdlib/LibGit2/src/LibGit2.jl b/stdlib/LibGit2/src/LibGit2.jl index 43b960e8d509d..04435dd577c19 100644 --- a/stdlib/LibGit2/src/LibGit2.jl +++ b/stdlib/LibGit2/src/LibGit2.jl @@ -6,8 +6,7 @@ Interface to [libgit2](https://libgit2.org/). module LibGit2 import Base: == -using Base: something, notnothing -using Base64: base64decode +using Base: something using NetworkOptions using Printf: @printf using SHA: sha1, sha256 diff --git a/stdlib/LibGit2/src/callbacks.jl b/stdlib/LibGit2/src/callbacks.jl index f4df26cdc30d8..043e04e0dfad6 100644 --- a/stdlib/LibGit2/src/callbacks.jl +++ b/stdlib/LibGit2/src/callbacks.jl @@ -195,9 +195,9 @@ function authenticate_userpass(libgit2credptr::Ptr{Ptr{Cvoid}}, p::CredentialPay if p.use_git_helpers && (!revised || !isfilled(cred)) git_cred = GitCredential(p.config, p.url) - # Use `deepcopy` to ensure shredding the `git_cred` does not shred the `cred`s copy + # Use `copy` to ensure shredding the `git_cred` does not shred the `cred`s copy cred.user = something(git_cred.username, "") - cred.pass = deepcopy(something(git_cred.password, "")) + cred.pass = git_cred.password !== nothing ? copy(git_cred.password) : "" Base.shred!(git_cred) revised = true @@ -292,7 +292,7 @@ function credentials_callback(libgit2credptr::Ptr{Ptr{Cvoid}}, url_ptr::Cstring, cred = explicit # Copy explicit credentials to avoid mutating approved credentials. - # invalidation fix from cred being non-inferrable + # invalidation fix from cred being non-inferable p.credential = Base.invokelatest(deepcopy, cred) if isa(cred, SSHCredential) @@ -307,7 +307,7 @@ function credentials_callback(libgit2credptr::Ptr{Ptr{Cvoid}}, url_ptr::Cstring, # Perform a deepcopy as we do not want to mutate approved cached credentials if haskey(cache, cred_id) - # invalidation fix from cache[cred_id] being non-inferrable + # invalidation fix from cache[cred_id] being non-inferable p.credential = Base.invokelatest(deepcopy, cache[cred_id]) end end diff --git a/stdlib/LibGit2/src/consts.jl b/stdlib/LibGit2/src/consts.jl index 1f28a3bdbe887..8c140e8c2aa30 100644 --- a/stdlib/LibGit2/src/consts.jl +++ b/stdlib/LibGit2/src/consts.jl @@ -2,7 +2,7 @@ module Consts -import ..LibGit2: version, ensure_initialized +import ..LibGit2: version const HEAD_FILE = "HEAD" const FETCH_HEAD = "FETCH_HEAD" @@ -417,7 +417,32 @@ Option flags for `GitRepo`. FEATURE_SSH = Cuint(1 << 2), FEATURE_NSEC = Cuint(1 << 3)) -if version() >= v"0.24.0" +if version() >= v"1.8.0" + @doc """ + Priority level of a config file. + + These priority levels correspond to the natural escalation logic (from higher to lower) when searching for config entries in git. + + * `CONFIG_LEVEL_DEFAULT` - Open the global, XDG and system configuration files if any available. + * `CONFIG_LEVEL_PROGRAMDATA` - System-wide on Windows, for compatibility with portable git + * `CONFIG_LEVEL_SYSTEM` - System-wide configuration file; `/etc/gitconfig` on Linux systems + * `CONFIG_LEVEL_XDG` - XDG compatible configuration file; typically `~/.config/git/config` + * `CONFIG_LEVEL_GLOBAL` - User-specific configuration file (also called Global configuration file); typically `~/.gitconfig` + * `CONFIG_LEVEL_LOCAL` - Repository specific configuration file; `\$WORK_DIR/.git/config` on non-bare repos + * `CONFIG_LEVEL_WORKTREE` - Worktree specific configuration file; `\$GIT_DIR/config.worktree` + * `CONFIG_LEVEL_APP` - Application specific configuration file; freely defined by applications + * `CONFIG_HIGHEST_LEVEL` - Represents the highest level available config file (i.e. the most specific config file available that actually is loaded) + """ + @enum(GIT_CONFIG, CONFIG_LEVEL_DEFAULT = 0, + CONFIG_LEVEL_PROGRAMDATA = 1, + CONFIG_LEVEL_SYSTEM = 2, + CONFIG_LEVEL_XDG = 3, + CONFIG_LEVEL_GLOBAL = 4, + CONFIG_LEVEL_LOCAL = 5, + CONFIG_LEVEL_WORKTREE = 6, + CONFIG_LEVEL_APP = 7, + CONFIG_HIGHEST_LEVEL =-1) +elseif version() >= v"0.24.0" @doc """ Priority level of a config file. diff --git a/stdlib/LibGit2/src/types.jl b/stdlib/LibGit2/src/types.jl index 96cea96d013e5..b0b463c69e2f1 100644 --- a/stdlib/LibGit2/src/types.jl +++ b/stdlib/LibGit2/src/types.jl @@ -678,6 +678,8 @@ The fields represent: for more information. * `custom_headers`: only relevant if the LibGit2 version is greater than or equal to `0.24.0`. Extra headers needed for the push operation. + * `remote_push_options`: only relevant if the LibGit2 version is greater than or equal to `1.8.0`. + "Push options" to deliver to the remote. """ @kwdef struct PushOptions version::Cuint = Cuint(1) @@ -692,6 +694,9 @@ The fields represent: @static if LibGit2.VERSION >= v"0.24.0" custom_headers::StrArrayStruct = StrArrayStruct() end + @static if LibGit2.VERSION >= v"1.8.0" + remote_push_options::StrArrayStruct = StrArrayStruct() + end end @assert Base.allocatedinline(PushOptions) @@ -913,10 +918,17 @@ Matches the [`git_config_entry`](https://libgit2.org/libgit2/#HEAD/type/git_conf struct ConfigEntry name::Cstring value::Cstring + @static if LibGit2.VERSION >= v"1.8.0" + backend_type::Cstring + origin_path::Cstring + end include_depth::Cuint level::GIT_CONFIG free::Ptr{Cvoid} - payload::Ptr{Cvoid} # User is not permitted to read or write this field + @static if LibGit2.VERSION < v"1.8.0" + # In 1.8.0, the unused payload value has been removed + payload::Ptr{Cvoid} + end end @assert Base.allocatedinline(ConfigEntry) diff --git a/stdlib/LibGit2/test/runtests.jl b/stdlib/LibGit2/test/runtests.jl index 88aea77f25671..4d2f4f9104c4e 100644 --- a/stdlib/LibGit2/test/runtests.jl +++ b/stdlib/LibGit2/test/runtests.jl @@ -1,6 +1,11 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -using Test +using Test, LibGit2 + @testset verbose=true "LibGit2 $test" for test in eachline(joinpath(@__DIR__, "testgroups")) include("$test.jl") end + +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(LibGit2)) +end diff --git a/stdlib/LibGit2_jll/Project.toml b/stdlib/LibGit2_jll/Project.toml index e64d86dc6c9b7..ceeb394f26231 100644 --- a/stdlib/LibGit2_jll/Project.toml +++ b/stdlib/LibGit2_jll/Project.toml @@ -1,6 +1,6 @@ name = "LibGit2_jll" uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" -version = "1.7.1+0" +version = "1.8.0+0" [deps] MbedTLS_jll = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" diff --git a/stdlib/LibGit2_jll/src/LibGit2_jll.jl b/stdlib/LibGit2_jll/src/LibGit2_jll.jl index ff625a6494a26..15d303dfea6ee 100644 --- a/stdlib/LibGit2_jll/src/LibGit2_jll.jl +++ b/stdlib/LibGit2_jll/src/LibGit2_jll.jl @@ -4,7 +4,6 @@ baremodule LibGit2_jll using Base, Libdl, MbedTLS_jll, LibSSH2_jll -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] @@ -21,9 +20,9 @@ libgit2_path::String = "" if Sys.iswindows() const libgit2 = "libgit2.dll" elseif Sys.isapple() - const libgit2 = "@rpath/libgit2.1.7.dylib" + const libgit2 = "@rpath/libgit2.1.8.dylib" else - const libgit2 = "libgit2.so.1.7" + const libgit2 = "libgit2.so.1.8" end function __init__() diff --git a/stdlib/LibGit2_jll/test/runtests.jl b/stdlib/LibGit2_jll/test/runtests.jl index 86dcf659e7ab2..5bc74760b1603 100644 --- a/stdlib/LibGit2_jll/test/runtests.jl +++ b/stdlib/LibGit2_jll/test/runtests.jl @@ -7,5 +7,5 @@ using Test, Libdl, LibGit2_jll minor = Ref{Cint}(0) patch = Ref{Cint}(0) @test ccall((:git_libgit2_version, libgit2), Cint, (Ref{Cint}, Ref{Cint}, Ref{Cint}), major, minor, patch) == 0 - @test VersionNumber(major[], minor[], patch[]) == v"1.7.1" + @test VersionNumber(major[], minor[], patch[]) == v"1.8.0" end diff --git a/stdlib/LibSSH2_jll/src/LibSSH2_jll.jl b/stdlib/LibSSH2_jll/src/LibSSH2_jll.jl index a809f7a912d6b..351cbe0e3729b 100644 --- a/stdlib/LibSSH2_jll/src/LibSSH2_jll.jl +++ b/stdlib/LibSSH2_jll/src/LibSSH2_jll.jl @@ -4,7 +4,6 @@ baremodule LibSSH2_jll using Base, Libdl, MbedTLS_jll -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] diff --git a/stdlib/LibUV_jll/Project.toml b/stdlib/LibUV_jll/Project.toml index 605c1115b3d34..7c61fdf89df70 100644 --- a/stdlib/LibUV_jll/Project.toml +++ b/stdlib/LibUV_jll/Project.toml @@ -1,6 +1,6 @@ name = "LibUV_jll" uuid = "183b4373-6708-53ba-ad28-60e28bb38547" -version = "2.0.1+14" +version = "2.0.1+16" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/stdlib/LibUV_jll/src/LibUV_jll.jl b/stdlib/LibUV_jll/src/LibUV_jll.jl index 767f055eb019f..febc47f168ab9 100644 --- a/stdlib/LibUV_jll/src/LibUV_jll.jl +++ b/stdlib/LibUV_jll/src/LibUV_jll.jl @@ -4,7 +4,6 @@ baremodule LibUV_jll using Base, Libdl -Base.Experimental.@compiler_options compile=min optimize=0 infer=false # NOTE: This file is currently empty, as we link libuv statically for now. diff --git a/stdlib/LibUnwind_jll/src/LibUnwind_jll.jl b/stdlib/LibUnwind_jll/src/LibUnwind_jll.jl index 12abeaf598151..f97b18443b6fd 100644 --- a/stdlib/LibUnwind_jll/src/LibUnwind_jll.jl +++ b/stdlib/LibUnwind_jll/src/LibUnwind_jll.jl @@ -4,7 +4,6 @@ baremodule LibUnwind_jll using Base, Libdl -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] diff --git a/stdlib/Libdl/Project.toml b/stdlib/Libdl/Project.toml index 26e5bf0cdefd7..7fab4b9334260 100644 --- a/stdlib/Libdl/Project.toml +++ b/stdlib/Libdl/Project.toml @@ -1,5 +1,6 @@ name = "Libdl" uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +version = "1.11.0" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/Libdl/docs/src/index.md b/stdlib/Libdl/docs/src/index.md index 62f9837831d55..2d7ef2fffc41a 100644 --- a/stdlib/Libdl/docs/src/index.md +++ b/stdlib/Libdl/docs/src/index.md @@ -1,3 +1,11 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/Libdl/docs/src/index.md" +``` + +```@docs +Libdl +``` + # Dynamic Linker ```@docs diff --git a/stdlib/Libdl/src/Libdl.jl b/stdlib/Libdl/src/Libdl.jl index 0df70ea1daac5..2a8f800c69194 100644 --- a/stdlib/Libdl/src/Libdl.jl +++ b/stdlib/Libdl/src/Libdl.jl @@ -1,5 +1,10 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license - +""" +The Libdl module in Julia provides specialized and lower-level facilities for dynamic linking with shared libraries. While Julia +inherently supports linking to runtime shared libraries through the `ccall` intrinsic, `Libdl` extends this capability by offering additional, more +granular control. It enables users to search for shared libraries both in memory and the filesystem, manually load them with specific runtime linker options, and look up +library symbols as low-level pointers. +""" module Libdl # Just re-export Base.Libc.Libdl: export DL_LOAD_PATH, RTLD_DEEPBIND, RTLD_FIRST, RTLD_GLOBAL, RTLD_LAZY, RTLD_LOCAL, diff --git a/stdlib/Libdl/test/runtests.jl b/stdlib/Libdl/test/runtests.jl index e500b68dec34b..ef7b8abf83337 100644 --- a/stdlib/Libdl/test/runtests.jl +++ b/stdlib/Libdl/test/runtests.jl @@ -329,3 +329,7 @@ end lazy_name_lazy_lib = LazyLibrary(libname) @test dlpath(lazy_name_lazy_lib) == realpath(string(libname)) end; end + +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(Libdl)) +end diff --git a/stdlib/LinearAlgebra/Project.toml b/stdlib/LinearAlgebra/Project.toml index 46653aa795209..892de0397c219 100644 --- a/stdlib/LinearAlgebra/Project.toml +++ b/stdlib/LinearAlgebra/Project.toml @@ -1,5 +1,6 @@ name = "LinearAlgebra" uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +version = "1.11.0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/stdlib/LinearAlgebra/docs/src/index.md b/stdlib/LinearAlgebra/docs/src/index.md index 00ce21ed6fcae..da756f56e12b8 100644 --- a/stdlib/LinearAlgebra/docs/src/index.md +++ b/stdlib/LinearAlgebra/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/LinearAlgebra/docs/src/index.md" +``` + # [Linear Algebra](@id man-linalg) ```@meta @@ -157,7 +161,7 @@ sorts of systems of linear equations. ## Special matrices -[Matrices with special symmetries and structures](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=3274) +[Matrices with special symmetries and structures](https://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=3274) arise often in linear algebra and are frequently associated with various matrix factorizations. Julia features a rich collection of special matrix types, which allow for fast computation with specialized routines that are specially developed for particular matrix types. @@ -184,8 +188,8 @@ as well as whether hooks to various optimized methods for them in LAPACK are ava | Matrix type | `+` | `-` | `*` | `\` | Other functions with optimized methods | |:----------------------------- |:--- |:--- |:--- |:--- |:----------------------------------------------------------- | -| [`Symmetric`](@ref) | | | | MV | [`inv`](@ref), [`sqrt`](@ref), [`exp`](@ref) | -| [`Hermitian`](@ref) | | | | MV | [`inv`](@ref), [`sqrt`](@ref), [`exp`](@ref) | +| [`Symmetric`](@ref) | | | | MV | [`inv`](@ref), [`sqrt`](@ref), [`cbrt`](@ref), [`exp`](@ref) | +| [`Hermitian`](@ref) | | | | MV | [`inv`](@ref), [`sqrt`](@ref), [`cbrt`](@ref), [`exp`](@ref) | | [`UpperTriangular`](@ref) | | | MV | MV | [`inv`](@ref), [`det`](@ref), [`logdet`](@ref) | | [`UnitUpperTriangular`](@ref) | | | MV | MV | [`inv`](@ref), [`det`](@ref), [`logdet`](@ref) | | [`LowerTriangular`](@ref) | | | MV | MV | [`inv`](@ref), [`det`](@ref), [`logdet`](@ref) | @@ -300,7 +304,7 @@ of the Linear Algebra documentation. | `QRCompactWY` | Compact WY form of the QR factorization | | `QRPivoted` | Pivoted [QR factorization](https://en.wikipedia.org/wiki/QR_decomposition) | | `LQ` | [QR factorization](https://en.wikipedia.org/wiki/QR_decomposition) of `transpose(A)` | -| `Hessenberg` | [Hessenberg decomposition](http://mathworld.wolfram.com/HessenbergDecomposition.html) | +| `Hessenberg` | [Hessenberg decomposition](https://mathworld.wolfram.com/HessenbergDecomposition.html) | | `Eigen` | [Spectral decomposition](https://en.wikipedia.org/wiki/Eigendecomposition_of_a_matrix) | | `GeneralizedEigen` | [Generalized spectral decomposition](https://en.wikipedia.org/wiki/Eigendecomposition_of_a_matrix#Generalized_eigenvalue_problem) | | `SVD` | [Singular value decomposition](https://en.wikipedia.org/wiki/Singular_value_decomposition) | @@ -400,19 +404,51 @@ generally broadcasting over elements in the matrix representation fail because t be highly inefficient. For such use cases, consider computing the matrix representation up front and cache it for future reuse. +## [Pivoting Strategies](@id man-linalg-pivoting-strategies) + +Several of Julia's [matrix factorizations](@ref man-linalg-factorizations) support +[pivoting](https://en.wikipedia.org/wiki/Pivot_element), which can be used to improve their +numerical stability. In fact, some matrix factorizations, such as the LU +factorization, may fail without pivoting. + +In pivoting, first, a [pivot element](https://en.wikipedia.org/wiki/Pivot_element) +with good numerical properties is chosen based on a pivoting strategy. Next, the rows and +columns of the original matrix are permuted to bring the chosen element in place for +subsequent computation. Furthermore, the process is repeated for each stage of the factorization. + +Consequently, besides the conventional matrix factors, the outputs of +pivoted factorization schemes also include permutation matrices. + +In the following, the pivoting strategies implemented in Julia are briefly described. Note +that not all matrix factorizations may support them. Consult the documentation of the +respective [matrix factorization](@ref man-linalg-factorizations) for details on the +supported pivoting strategies. + +See also [`LinearAlgebra.ZeroPivotException`](@ref). + +```@docs +LinearAlgebra.NoPivot +LinearAlgebra.RowNonZero +LinearAlgebra.RowMaximum +LinearAlgebra.ColumnNorm +``` + ## Standard functions -Linear algebra functions in Julia are largely implemented by calling functions from [LAPACK](http://www.netlib.org/lapack/). +Linear algebra functions in Julia are largely implemented by calling functions from [LAPACK](https://www.netlib.org/lapack/). Sparse matrix factorizations call functions from [SuiteSparse](http://suitesparse.com). Other sparse solvers are available as Julia packages. ```@docs Base.:*(::AbstractMatrix, ::AbstractMatrix) +Base.:*(::AbstractMatrix, ::AbstractMatrix, ::AbstractVector) Base.:\(::AbstractMatrix, ::AbstractVecOrMat) Base.:/(::AbstractVecOrMat, ::AbstractVecOrMat) LinearAlgebra.SingularException LinearAlgebra.PosDefException LinearAlgebra.ZeroPivotException +LinearAlgebra.RankDeficientException +LinearAlgebra.LAPACKException LinearAlgebra.dot LinearAlgebra.dot(::Any, ::Any, ::Any) LinearAlgebra.cross @@ -516,6 +552,7 @@ Base.:^(::AbstractMatrix, ::Number) Base.:^(::Number, ::AbstractMatrix) LinearAlgebra.log(::StridedMatrix) LinearAlgebra.sqrt(::StridedMatrix) +LinearAlgebra.cbrt(::AbstractMatrix{<:Real}) LinearAlgebra.cos(::StridedMatrix{<:Real}) LinearAlgebra.sin(::StridedMatrix{<:Real}) LinearAlgebra.sincos(::StridedMatrix{<:Real}) @@ -565,6 +602,8 @@ LinearAlgebra.checksquare LinearAlgebra.peakflops LinearAlgebra.hermitianpart LinearAlgebra.hermitianpart! +LinearAlgebra.copy_adjoint! +LinearAlgebra.copy_transpose! ``` ## Low-level matrix operations @@ -585,8 +624,8 @@ LinearAlgebra.rdiv! ## BLAS functions In Julia (as in much of scientific computation), dense linear-algebra operations are based on -the [LAPACK library](http://www.netlib.org/lapack/), which in turn is built on top of basic linear-algebra -building-blocks known as the [BLAS](http://www.netlib.org/blas/). There are highly optimized +the [LAPACK library](https://www.netlib.org/lapack/), which in turn is built on top of basic linear-algebra +building-blocks known as the [BLAS](https://www.netlib.org/blas/). There are highly optimized implementations of BLAS available for every computer architecture, and sometimes in high-performance linear algebra routines it is useful to call the BLAS functions directly. @@ -730,6 +769,9 @@ and define matrix-matrix operations. [Dongarra-1990]: https://dl.acm.org/doi/10.1145/77626.79170 ```@docs +LinearAlgebra.BLAS.gemmt! +LinearAlgebra.BLAS.gemmt(::Any, ::Any, ::Any, ::Any, ::Any, ::Any) +LinearAlgebra.BLAS.gemmt(::Any, ::Any, ::Any, ::Any, ::Any) LinearAlgebra.BLAS.gemm! LinearAlgebra.BLAS.gemm(::Any, ::Any, ::Any, ::Any, ::Any) LinearAlgebra.BLAS.gemm(::Any, ::Any, ::Any, ::Any) @@ -753,7 +795,7 @@ LinearAlgebra.BLAS.trsm! LinearAlgebra.BLAS.trsm ``` -## LAPACK functions +## [LAPACK functions](@id man-linalg-lapack-functions) `LinearAlgebra.LAPACK` provides wrappers for some of the LAPACK functions for linear algebra. Those functions that overwrite one of the input arrays have names ending in `'!'`. diff --git a/stdlib/LinearAlgebra/src/LinearAlgebra.jl b/stdlib/LinearAlgebra/src/LinearAlgebra.jl index ca95214b1bbd1..6ed272ab42f02 100644 --- a/stdlib/LinearAlgebra/src/LinearAlgebra.jl +++ b/stdlib/LinearAlgebra/src/LinearAlgebra.jl @@ -7,16 +7,16 @@ functionality. """ module LinearAlgebra -import Base: \, /, *, ^, +, -, == +import Base: \, /, //, *, ^, +, -, == import Base: USE_BLAS64, abs, acos, acosh, acot, acoth, acsc, acsch, adjoint, asec, asech, - asin, asinh, atan, atanh, axes, big, broadcast, ceil, cis, collect, conj, convert, copy, - copyto!, copymutable, cos, cosh, cot, coth, csc, csch, eltype, exp, fill!, floor, - getindex, hcat, getproperty, imag, inv, isapprox, isequal, isone, iszero, IndexStyle, - kron, kron!, length, log, map, ndims, one, oneunit, parent, permutedims, - power_by_squaring, promote_rule, real, sec, sech, setindex!, show, similar, sin, - sincos, sinh, size, sqrt, strides, stride, tan, tanh, transpose, trunc, typed_hcat, - vec, view, zero -using Base: IndexLinear, promote_eltype, promote_op, promote_typeof, print_matrix, + asin, asinh, atan, atanh, axes, big, broadcast, cbrt, ceil, cis, collect, conj, convert, + copy, copyto!, copymutable, cos, cosh, cot, coth, csc, csch, eltype, exp, fill!, floor, + getindex, hcat, getproperty, imag, inv, invpermuterows!, isapprox, isequal, isone, iszero, + IndexStyle, kron, kron!, length, log, map, ndims, one, oneunit, parent, permutecols!, + permutedims, permuterows!, power_by_squaring, promote_rule, real, sec, sech, setindex!, + show, similar, sin, sincos, sinh, size, sqrt, strides, stride, tan, tanh, transpose, trunc, + typed_hcat, vec, view, zero +using Base: IndexLinear, promote_eltype, promote_op, print_matrix, @propagate_inbounds, reduce, typed_hvcat, typed_vcat, require_one_based_indexing, splat using Base.Broadcast: Broadcasted, broadcasted @@ -27,132 +27,136 @@ import Libdl export # Modules - LAPACK, BLAS, + LAPACK, # Types Adjoint, - Transpose, - SymTridiagonal, - Tridiagonal, Bidiagonal, - Factorization, BunchKaufman, Cholesky, CholeskyPivoted, ColumnNorm, + Diagonal, Eigen, + Factorization, GeneralizedEigen, GeneralizedSVD, GeneralizedSchur, + Hermitian, Hessenberg, - LU, LDLt, + LQ, + LU, + LowerTriangular, NoPivot, - RowNonZero, QR, QRPivoted, - LQ, - Schur, - SVD, - Hermitian, RowMaximum, + RowNonZero, + SVD, + Schur, + SymTridiagonal, Symmetric, - LowerTriangular, - UpperTriangular, + Transpose, + Tridiagonal, + UniformScaling, UnitLowerTriangular, UnitUpperTriangular, UpperHessenberg, - Diagonal, - UniformScaling, + UpperTriangular, + # Functions - axpy!, + adjoint!, + adjoint, axpby!, - bunchkaufman, + axpy!, bunchkaufman!, - cholesky, + bunchkaufman, cholesky!, + cholesky, cond, condskeel, - copyto!, + copy_adjoint!, copy_transpose!, + copyto!, + copytrito!, cross, - adjoint, - adjoint!, det, diag, diagind, diagm, dot, - eigen, eigen!, + eigen, eigmax, eigmin, - eigvals, eigvals!, + eigvals, eigvecs, factorize, givens, - hermitianpart, hermitianpart!, - hessenberg, + hermitianpart, hessenberg!, + hessenberg, isdiag, ishermitian, - isposdef, isposdef!, + isposdef, issuccess, issymmetric, istril, istriu, - kron, kron!, + kron, ldiv!, ldlt!, ldlt, + lmul!, logabsdet, logdet, - lowrankdowndate, lowrankdowndate!, - lowrankupdate, + lowrankdowndate, lowrankupdate!, - lu, + lowrankupdate, + lq!, + lq, lu!, + lu, lyap, mul!, - lmul!, - rmul!, norm, - normalize, normalize!, + normalize, nullspace, + opnorm, ordschur!, ordschur, pinv, - qr, qr!, - lq, - lq!, - opnorm, + qr, rank, rdiv!, reflect!, + rmul!, rotate!, - schur, schur!, - svd, + schur, svd!, + svd, svdvals!, svdvals, sylvester, tr, - transpose, transpose!, - tril, - triu, + transpose, tril!, + tril, triu!, + triu, + # Operators \, @@ -161,6 +165,17 @@ export # Constants I +# not exported, but public names +public AbstractTriangular, + Givens, + checksquare, + hermitian, + hermitian_type, + isbanded, + peakflops, + symmetric, + symmetric_type + const BlasFloat = Union{Float64,Float32,ComplexF64,ComplexF32} const BlasReal = Union{Float64,Float32} const BlasComplex = Union{ComplexF64,ComplexF32} @@ -176,10 +191,54 @@ abstract type Algorithm end struct DivideAndConquer <: Algorithm end struct QRIteration <: Algorithm end +# Pivoting strategies for matrix factorization algorithms. abstract type PivotingStrategy end + +""" + NoPivot + +Pivoting is not performed. Matrix factorizations such as the LU factorization +may fail without pivoting, and may also be numerically unstable for floating-point matrices in the face of roundoff error. +This pivot strategy is mainly useful for pedagogical purposes. +""" struct NoPivot <: PivotingStrategy end + +""" + RowNonZero + +First non-zero element in the remaining rows is chosen as the pivot element. + +Beware that for floating-point matrices, the resulting LU algorithm is numerically unstable — this strategy +is mainly useful for comparison to hand calculations (which typically use this strategy) or for other +algebraic types (e.g. rational numbers) not susceptible to roundoff errors. Otherwise, the default +`RowMaximum` pivoting strategy should be generally preferred in Gaussian elimination. + +Note that the [element type](@ref eltype) of the matrix must admit an [`iszero`](@ref) +method. +""" struct RowNonZero <: PivotingStrategy end + +""" + RowMaximum + +The maximum-magnitude element in the remaining rows is chosen as the pivot element. +This is the default strategy for LU factorization of floating-point matrices, and is sometimes +referred to as the "partial pivoting" algorithm. + +Note that the [element type](@ref eltype) of the matrix must admit an [`abs`](@ref) method, +whose result type must admit a [`<`](@ref) method. +""" struct RowMaximum <: PivotingStrategy end + +""" + ColumnNorm + +The column with the maximum norm is used for subsequent computation. This +is used for pivoted QR factorization. + +Note that the [element type](@ref eltype) of the matrix must admit [`norm`](@ref) and +[`abs`](@ref) methods, whose respective result types must admit a [`<`](@ref) method. +""" struct ColumnNorm <: PivotingStrategy end # Check that stride of matrix/vector is 1 @@ -238,14 +297,14 @@ julia> LinearAlgebra.checksquare(A, B) """ function checksquare(A) m,n = size(A) - m == n || throw(DimensionMismatch("matrix is not square: dimensions are $(size(A))")) + m == n || throw(DimensionMismatch(lazy"matrix is not square: dimensions are $(size(A))")) m end function checksquare(A...) sizes = Int[] for a in A - size(a,1)==size(a,2) || throw(DimensionMismatch("matrix is not square: dimensions are $(size(a))")) + size(a,1)==size(a,2) || throw(DimensionMismatch(lazy"matrix is not square: dimensions are $(size(a))")) push!(sizes, size(a,1)) end return sizes @@ -466,21 +525,25 @@ wrapper_char(A::Hermitian{<:Real}) = A.uplo == 'U' ? 'S' : 's' wrapper_char(A::Symmetric) = A.uplo == 'U' ? 'S' : 's' Base.@constprop :aggressive function wrap(A::AbstractVecOrMat, tA::AbstractChar) - if tA == 'N' - return A + # merge the result of this before return, so that we can type-assert the return such + # that even if the tmerge is inaccurate, inference can still identify that the + # `_generic_matmatmul` signature still matches and doesn't require missing backedges + B = if tA == 'N' + A elseif tA == 'T' - return transpose(A) + transpose(A) elseif tA == 'C' - return adjoint(A) + adjoint(A) elseif tA == 'H' - return Hermitian(A, :U) + Hermitian(A, :U) elseif tA == 'h' - return Hermitian(A, :L) + Hermitian(A, :L) elseif tA == 'S' - return Symmetric(A, :U) + Symmetric(A, :U) else # tA == 's' - return Symmetric(A, :L) + Symmetric(A, :L) end + return B::AbstractVecOrMat end _unwrap(A::AbstractVecOrMat) = A @@ -509,6 +572,20 @@ _makevector(x::AbstractVector) = Vector(x) _pushzero(A) = (B = similar(A, length(A)+1); @inbounds B[begin:end-1] .= A; @inbounds B[end] = zero(eltype(B)); B) _droplast!(A) = deleteat!(A, lastindex(A)) +# destination type for matmul +matprod_dest(A::StructuredMatrix, B::StructuredMatrix, TS) = similar(B, TS, size(B)) +matprod_dest(A, B::StructuredMatrix, TS) = similar(A, TS, size(A)) +matprod_dest(A::StructuredMatrix, B, TS) = similar(B, TS, size(B)) +# diagonal is special, as it does not change the structure of the other matrix +# we call similar without a size to preserve the type of the matrix wherever possible +matprod_dest(A::StructuredMatrix, B::Diagonal, TS) = similar(A, TS) +matprod_dest(A::Diagonal, B::StructuredMatrix, TS) = similar(B, TS) +matprod_dest(A::Diagonal, B::Diagonal, TS) = similar(B, TS) + +# Special handling for adj/trans vec +matprod_dest(A::Diagonal, B::AdjOrTransAbsVec, TS) = similar(B, TS) + +# TODO: remove once not used anymore in SparseArrays.jl # some trait like this would be cool # onedefined(::Type{T}) where {T} = hasmethod(one, (T,)) # but we are actually asking for oneunit(T), that is, however, defined for generic T as @@ -617,9 +694,11 @@ function peakflops(n::Integer=4096; eltype::DataType=Float64, ntrials::Integer=3 end if parallel - let Distributed = Base.require(Base.PkgId( + let Distributed = Base.require_stdlib(Base.PkgId( Base.UUID((0x8ba89e20_285c_5b6f, 0x9357_94700520ee1b)), "Distributed")) - return sum(Distributed.pmap(peakflops, fill(n, Distributed.nworkers()))) + nworkers = @invokelatest Distributed.nworkers() + results = @invokelatest Distributed.pmap(peakflops, fill(n, nworkers)) + return sum(results) end else return 2*Float64(n)^3 / minimum(t) diff --git a/stdlib/LinearAlgebra/src/abstractq.jl b/stdlib/LinearAlgebra/src/abstractq.jl index 2aa333beef2b2..bf4064c907a2d 100644 --- a/stdlib/LinearAlgebra/src/abstractq.jl +++ b/stdlib/LinearAlgebra/src/abstractq.jl @@ -18,6 +18,10 @@ transpose(Q::AbstractQ{<:Real}) = AdjointQ(Q) transpose(Q::AbstractQ) = error("transpose not implemented for $(typeof(Q)). Consider using adjoint instead of transpose.") adjoint(adjQ::AdjointQ) = adjQ.Q +(^)(Q::AbstractQ, p::Integer) = p < 0 ? power_by_squaring(inv(Q), -p) : power_by_squaring(Q, p) +@inline Base.literal_pow(::typeof(^), Q::AbstractQ, ::Val{1}) = Q +@inline Base.literal_pow(::typeof(^), Q::AbstractQ, ::Val{-1}) = inv(Q) + # promotion with AbstractMatrix, at least for equal eltypes promote_rule(::Type{<:AbstractMatrix{T}}, ::Type{<:AbstractQ{T}}) where {T} = (@inline; Union{AbstractMatrix{T},AbstractQ{T}}) @@ -149,13 +153,16 @@ end # generically, treat AbstractQ like a matrix with its definite size qsize_check(Q::AbstractQ, B::AbstractVecOrMat) = size(Q, 2) == size(B, 1) || - throw(DimensionMismatch("second dimension of Q, $(size(Q,2)), must coincide with first dimension of B, $(size(B,1))")) + throw(DimensionMismatch(lazy"second dimension of Q, $(size(Q,2)), must coincide with first dimension of B, $(size(B,1))")) qsize_check(A::AbstractVecOrMat, Q::AbstractQ) = size(A, 2) == size(Q, 1) || - throw(DimensionMismatch("second dimension of A, $(size(A,2)), must coincide with first dimension of Q, $(size(Q,1))")) + throw(DimensionMismatch(lazy"second dimension of A, $(size(A,2)), must coincide with first dimension of Q, $(size(Q,1))")) qsize_check(Q::AbstractQ, P::AbstractQ) = size(Q, 2) == size(P, 1) || - throw(DimensionMismatch("second dimension of A, $(size(Q,2)), must coincide with first dimension of B, $(size(P,1))")) + throw(DimensionMismatch(lazy"second dimension of A, $(size(Q,2)), must coincide with first dimension of B, $(size(P,1))")) + +# mimic the AbstractArray fallback +*(Q::AbstractQ{<:Number}) = Q (*)(Q::AbstractQ, J::UniformScaling) = Q*J.λ function (*)(Q::AbstractQ, b::Number) @@ -314,7 +321,7 @@ function lmul!(A::QRPackedQ, B::AbstractVecOrMat) mA, nA = size(A.factors) mB, nB = size(B,1), size(B,2) if mA != mB - throw(DimensionMismatch("matrix A has dimensions ($mA,$nA) but B has dimensions ($mB, $nB)")) + throw(DimensionMismatch(lazy"matrix A has dimensions ($mA,$nA) but B has dimensions ($mB, $nB)")) end Afactors = A.factors @inbounds begin @@ -350,7 +357,7 @@ function lmul!(adjA::AdjointQ{<:Any,<:QRPackedQ}, B::AbstractVecOrMat) mA, nA = size(A.factors) mB, nB = size(B,1), size(B,2) if mA != mB - throw(DimensionMismatch("matrix A has dimensions ($mA,$nA) but B has dimensions ($mB, $nB)")) + throw(DimensionMismatch(lazy"matrix A has dimensions ($mA,$nA) but B has dimensions ($mB, $nB)")) end Afactors = A.factors @inbounds begin @@ -381,7 +388,7 @@ function rmul!(A::AbstractVecOrMat, Q::QRPackedQ) mQ, nQ = size(Q.factors) mA, nA = size(A,1), size(A,2) if nA != mQ - throw(DimensionMismatch("matrix A has dimensions ($mA,$nA) but matrix Q has dimensions ($mQ, $nQ)")) + throw(DimensionMismatch(lazy"matrix A has dimensions ($mA,$nA) but matrix Q has dimensions ($mQ, $nQ)")) end Qfactors = Q.factors @inbounds begin @@ -417,7 +424,7 @@ function rmul!(A::AbstractVecOrMat, adjQ::AdjointQ{<:Any,<:QRPackedQ}) mQ, nQ = size(Q.factors) mA, nA = size(A,1), size(A,2) if nA != mQ - throw(DimensionMismatch("matrix A has dimensions ($mA,$nA) but matrix Q has dimensions ($mQ, $nQ)")) + throw(DimensionMismatch(lazy"matrix A has dimensions ($mA,$nA) but matrix Q has dimensions ($mQ, $nQ)")) end Qfactors = Q.factors @inbounds begin @@ -518,10 +525,10 @@ rmul!(X::Adjoint{T,<:StridedVecOrMat{T}}, adjQ::AdjointQ{<:Any,<:HessenbergQ{T}} # flexible left-multiplication (and adjoint right-multiplication) qsize_check(Q::Union{QRPackedQ,QRCompactWYQ,HessenbergQ}, B::AbstractVecOrMat) = size(B, 1) in size(Q.factors) || - throw(DimensionMismatch("first dimension of B, $(size(B,1)), must equal one of the dimensions of Q, $(size(Q.factors))")) + throw(DimensionMismatch(lazy"first dimension of B, $(size(B,1)), must equal one of the dimensions of Q, $(size(Q.factors))")) qsize_check(A::AbstractVecOrMat, adjQ::AdjointQ{<:Any,<:Union{QRPackedQ,QRCompactWYQ,HessenbergQ}}) = (Q = adjQ.Q; size(A, 2) in size(Q.factors) || - throw(DimensionMismatch("second dimension of A, $(size(A,2)), must equal one of the dimensions of Q, $(size(Q.factors))"))) + throw(DimensionMismatch(lazy"second dimension of A, $(size(A,2)), must equal one of the dimensions of Q, $(size(Q.factors))"))) det(Q::HessenbergQ) = _det_tau(Q.τ) @@ -557,10 +564,10 @@ size(Q::LQPackedQ) = (n = size(Q.factors, 2); return n, n) qsize_check(adjQ::AdjointQ{<:Any,<:LQPackedQ}, B::AbstractVecOrMat) = size(B, 1) in size(adjQ.Q.factors) || - throw(DimensionMismatch("first dimension of B, $(size(B,1)), must equal one of the dimensions of Q, $(size(adjQ.Q.factors))")) + throw(DimensionMismatch(lazy"first dimension of B, $(size(B,1)), must equal one of the dimensions of Q, $(size(adjQ.Q.factors))")) qsize_check(A::AbstractVecOrMat, Q::LQPackedQ) = size(A, 2) in size(Q.factors) || - throw(DimensionMismatch("second dimension of A, $(size(A,2)), must equal one of the dimensions of Q, $(size(Q.factors))")) + throw(DimensionMismatch(lazy"second dimension of A, $(size(A,2)), must equal one of the dimensions of Q, $(size(Q.factors))")) # in-place right-application of LQPackedQs # these methods require that the applied-to matrix's (A's) number of columns diff --git a/stdlib/LinearAlgebra/src/adjtrans.jl b/stdlib/LinearAlgebra/src/adjtrans.jl index b3a06f8e7414b..f52460a870ca0 100644 --- a/stdlib/LinearAlgebra/src/adjtrans.jl +++ b/stdlib/LinearAlgebra/src/adjtrans.jl @@ -281,6 +281,11 @@ adjoint(A::Adjoint) = A.parent transpose(A::Transpose) = A.parent adjoint(A::Transpose{<:Real}) = A.parent transpose(A::Adjoint{<:Real}) = A.parent +adjoint(A::Transpose{<:Any,<:Adjoint}) = transpose(A.parent.parent) +transpose(A::Adjoint{<:Any,<:Transpose}) = adjoint(A.parent.parent) +# disambiguation +adjoint(A::Transpose{<:Real,<:Adjoint}) = transpose(A.parent.parent) +transpose(A::Adjoint{<:Real,<:Transpose}) = A.parent # printing function Base.showarg(io::IO, v::Adjoint, toplevel) @@ -319,7 +324,7 @@ axes(A::AdjOrTrans) = reverse(axes(A.parent)) length(A::AdjOrTrans) = length(A.parent) size(v::AdjOrTransAbsVec) = (1, length(v.parent)) size(A::AdjOrTransAbsMat) = reverse(size(A.parent)) -axes(v::AdjOrTransAbsVec) = (Base.OneTo(1), axes(v.parent)...) +axes(v::AdjOrTransAbsVec) = (axes(v.parent,2), axes(v.parent)...) axes(A::AdjOrTransAbsMat) = reverse(axes(A.parent)) IndexStyle(::Type{<:AdjOrTransAbsVec}) = IndexLinear() IndexStyle(::Type{<:AdjOrTransAbsMat}) = IndexCartesian() @@ -344,8 +349,8 @@ Base.strides(A::Transpose{<:Any, <:AbstractVector}) = (stride(A.parent, 2), stri Base.strides(A::Adjoint{<:Real, <:AbstractMatrix}) = reverse(strides(A.parent)) Base.strides(A::Transpose{<:Any, <:AbstractMatrix}) = reverse(strides(A.parent)) -Base.unsafe_convert(::Type{Ptr{T}}, A::Adjoint{<:Real, <:AbstractVecOrMat}) where {T} = Base.unsafe_convert(Ptr{T}, A.parent) -Base.unsafe_convert(::Type{Ptr{T}}, A::Transpose{<:Any, <:AbstractVecOrMat}) where {T} = Base.unsafe_convert(Ptr{T}, A.parent) +Base.cconvert(::Type{Ptr{T}}, A::Adjoint{<:Real, <:AbstractVecOrMat}) where {T} = Base.cconvert(Ptr{T}, A.parent) +Base.cconvert(::Type{Ptr{T}}, A::Transpose{<:Any, <:AbstractVecOrMat}) where {T} = Base.cconvert(Ptr{T}, A.parent) Base.elsize(::Type{<:Adjoint{<:Real, P}}) where {P<:AbstractVecOrMat} = Base.elsize(P) Base.elsize(::Type{<:Transpose{<:Any, P}}) where {P<:AbstractVecOrMat} = Base.elsize(P) @@ -391,15 +396,26 @@ hcat(tvs::Transpose{T,Vector{T}}...) where {T} = _transpose_hcat(tvs...) # # note that the caller's operation f operates in the domain of the wrapped vectors' entries. # hence the adjoint->f->adjoint shenanigans applied to the parent vectors' entries. -map(f, avs::AdjointAbsVec...) = adjoint(map((xs...) -> adjoint(f(adjoint.(xs)...)), parent.(avs)...)) -map(f, tvs::TransposeAbsVec...) = transpose(map((xs...) -> transpose(f(transpose.(xs)...)), parent.(tvs)...)) +function map(f, av::AdjointAbsVec, avs::AdjointAbsVec...) + s = (av, avs...) + adjoint(map((xs...) -> adjoint(f(adjoint.(xs)...)), parent.(s)...)) +end +function map(f, tv::TransposeAbsVec, tvs::TransposeAbsVec...) + s = (tv, tvs...) + transpose(map((xs...) -> transpose(f(transpose.(xs)...)), parent.(s)...)) +end quasiparentt(x) = parent(x); quasiparentt(x::Number) = x # to handle numbers in the defs below quasiparenta(x) = parent(x); quasiparenta(x::Number) = conj(x) # to handle numbers in the defs below +quasiparentc(x) = parent(parent(x)); quasiparentc(x::Number) = conj(x) # to handle numbers in the defs below broadcast(f, avs::Union{Number,AdjointAbsVec}...) = adjoint(broadcast((xs...) -> adjoint(f(adjoint.(xs)...)), quasiparenta.(avs)...)) broadcast(f, tvs::Union{Number,TransposeAbsVec}...) = transpose(broadcast((xs...) -> transpose(f(transpose.(xs)...)), quasiparentt.(tvs)...)) # Hack to preserve behavior after #32122; this needs to be done with a broadcast style instead to support dotted fusion Broadcast.broadcast_preserving_zero_d(f, avs::Union{Number,AdjointAbsVec}...) = adjoint(broadcast((xs...) -> adjoint(f(adjoint.(xs)...)), quasiparenta.(avs)...)) Broadcast.broadcast_preserving_zero_d(f, tvs::Union{Number,TransposeAbsVec}...) = transpose(broadcast((xs...) -> transpose(f(transpose.(xs)...)), quasiparentt.(tvs)...)) +Broadcast.broadcast_preserving_zero_d(f, tvs::Union{Number,Transpose{<:Any,<:AdjointAbsVec}}...) = + transpose(adjoint(broadcast((xs...) -> adjoint(transpose(f(conj.(xs)...))), quasiparentc.(tvs)...))) +Broadcast.broadcast_preserving_zero_d(f, tvs::Union{Number,Adjoint{<:Any,<:TransposeAbsVec}}...) = + adjoint(transpose(broadcast((xs...) -> transpose(adjoint(f(conj.(xs)...))), quasiparentc.(tvs)...))) # TODO unify and allow mixed combinations with a broadcast style @@ -449,7 +465,7 @@ tr(A::Transpose) = transpose(tr(parent(A))) function _dot_nonrecursive(u, v) lu = length(u) if lu != length(v) - throw(DimensionMismatch("first array has length $(lu) which does not match the length of the second, $(length(v)).")) + throw(DimensionMismatch(lazy"first array has length $(lu) which does not match the length of the second, $(length(v)).")) end if lu == 0 zero(eltype(u)) * zero(eltype(v)) @@ -466,10 +482,6 @@ end # vector * Adjoint/Transpose-vector *(u::AbstractVector, v::AdjOrTransAbsVec) = broadcast(*, u, v) -# Adjoint/Transpose-vector * Adjoint/Transpose-vector -# (necessary for disambiguation with fallback methods in linalg/matmul) -*(u::AdjointAbsVec, v::AdjointAbsVec) = throw(MethodError(*, (u, v))) -*(u::TransposeAbsVec, v::TransposeAbsVec) = throw(MethodError(*, (u, v))) # AdjOrTransAbsVec{<:Any,<:AdjOrTransAbsVec} is a lazy conj vectors # We need to expand the combinations to avoid ambiguities diff --git a/stdlib/LinearAlgebra/src/bidiag.jl b/stdlib/LinearAlgebra/src/bidiag.jl index f8cc3ceadcfad..145cce562991f 100644 --- a/stdlib/LinearAlgebra/src/bidiag.jl +++ b/stdlib/LinearAlgebra/src/bidiag.jl @@ -8,7 +8,7 @@ struct Bidiagonal{T,V<:AbstractVector{T}} <: AbstractMatrix{T} function Bidiagonal{T,V}(dv, ev, uplo::AbstractChar) where {T,V<:AbstractVector{T}} require_one_based_indexing(dv, ev) if length(ev) != max(length(dv)-1, 0) - throw(DimensionMismatch("length of diagonal vector is $(length(dv)), length of off-diagonal vector is $(length(ev))")) + throw(DimensionMismatch(lazy"length of diagonal vector is $(length(dv)), length of off-diagonal vector is $(length(ev))")) end (uplo != 'U' && uplo != 'L') && throw_uplo() new{T,V}(dv, ev, uplo) @@ -195,19 +195,14 @@ end #Converting from Bidiagonal to dense Matrix function Matrix{T}(A::Bidiagonal) where T - n = size(A, 1) - B = Matrix{T}(undef, n, n) - n == 0 && return B - n > 1 && fill!(B, zero(T)) - @inbounds for i = 1:n - 1 - B[i,i] = A.dv[i] - if A.uplo == 'U' - B[i,i+1] = A.ev[i] - else - B[i+1,i] = A.ev[i] - end + B = Matrix{T}(undef, size(A)) + if haszero(T) # optimized path for types with zero(T) defined + size(B,1) > 1 && fill!(B, zero(T)) + copyto!(view(B, diagind(B)), A.dv) + copyto!(view(B, diagind(B, A.uplo == 'U' ? 1 : -1)), A.ev) + else + copyto!(B, A) end - B[n,n] = A.dv[n] return B end Matrix(A::Bidiagonal{T}) where {T} = Matrix{promote_type(T, typeof(zero(T)))}(A) @@ -273,14 +268,13 @@ function show(io::IO, M::Bidiagonal) end size(M::Bidiagonal) = (n = length(M.dv); (n, n)) +axes(M::Bidiagonal) = (ax = axes(M.dv, 1); (ax, ax)) #Elementary operations for func in (:conj, :copy, :real, :imag) @eval ($func)(M::Bidiagonal) = Bidiagonal(($func)(M.dv), ($func)(M.ev), M.uplo) end -adjoint(B::Bidiagonal) = Adjoint(B) -transpose(B::Bidiagonal) = Transpose(B) adjoint(B::Bidiagonal{<:Number}) = Bidiagonal(conj(B.dv), conj(B.ev), B.uplo == 'U' ? :L : :U) transpose(B::Bidiagonal{<:Number}) = Bidiagonal(B.dv, B.ev, B.uplo == 'U' ? :L : :U) permutedims(B::Bidiagonal) = Bidiagonal(B.dv, B.ev, B.uplo == 'U' ? 'L' : 'U') @@ -426,23 +420,27 @@ end const BandedMatrix = Union{Bidiagonal,Diagonal,Tridiagonal,SymTridiagonal} # or BiDiTriSym const BiTriSym = Union{Bidiagonal,Tridiagonal,SymTridiagonal} +const TriSym = Union{Tridiagonal,SymTridiagonal} const BiTri = Union{Bidiagonal,Tridiagonal} -@inline mul!(C::AbstractVector, A::BandedMatrix, B::AbstractVector, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::BandedMatrix, B::AbstractVector, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::BandedMatrix, B::AbstractMatrix, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::AbstractMatrix, B::BandedMatrix, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::BandedMatrix, B::BandedMatrix, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) +@inline _mul!(C::AbstractVector, A::BandedMatrix, B::AbstractVector, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) +@inline _mul!(C::AbstractMatrix, A::BandedMatrix, B::AbstractVector, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) +@inline _mul!(C::AbstractMatrix, A::BandedMatrix, B::AbstractMatrix, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) +@inline _mul!(C::AbstractMatrix, A::AbstractMatrix, B::BandedMatrix, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) +@inline _mul!(C::AbstractMatrix, A::BandedMatrix, B::BandedMatrix, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) + +lmul!(A::Bidiagonal, B::AbstractVecOrMat) = @inline _mul!(B, A, B, MulAddMul()) +rmul!(B::AbstractMatrix, A::Bidiagonal) = @inline _mul!(B, B, A, MulAddMul()) function check_A_mul_B!_sizes(C, A, B) mA, nA = size(A) mB, nB = size(B) mC, nC = size(C) if mA != mC - throw(DimensionMismatch("first dimension of A, $mA, and first dimension of output C, $mC, must match")) + throw(DimensionMismatch(lazy"first dimension of A, $mA, and first dimension of output C, $mC, must match")) elseif nA != mB - throw(DimensionMismatch("second dimension of A, $nA, and first dimension of B, $mB, must match")) + throw(DimensionMismatch(lazy"second dimension of A, $nA, and first dimension of B, $mB, must match")) elseif nB != nC - throw(DimensionMismatch("second dimension of output C, $nC, and second dimension of B, $nB, must match")) + throw(DimensionMismatch(lazy"second dimension of output C, $nC, and second dimension of B, $nB, must match")) end end @@ -460,7 +458,11 @@ function _diag(A::Bidiagonal, k) end end -function _mul!(C::AbstractMatrix, A::BiTriSym, B::BiTriSym, _add::MulAddMul = MulAddMul()) +_mul!(C::AbstractMatrix, A::BiTriSym, B::TriSym, _add::MulAddMul) = + _bibimul!(C, A, B, _add) +_mul!(C::AbstractMatrix, A::BiTriSym, B::Bidiagonal, _add::MulAddMul) = + _bibimul!(C, A, B, _add) +function _bibimul!(C, A, B, _add) check_A_mul_B!_sizes(C, A, B) n = size(A,1) n <= 3 && return mul!(C, Array(A), Array(B), _add.alpha, _add.beta) @@ -517,7 +519,7 @@ function _mul!(C::AbstractMatrix, A::BiTriSym, B::BiTriSym, _add::MulAddMul = Mu C end -function _mul!(C::AbstractMatrix, A::BiTriSym, B::Diagonal, _add::MulAddMul = MulAddMul()) +function _mul!(C::AbstractMatrix, A::BiTriSym, B::Diagonal, _add::MulAddMul) require_one_based_indexing(C) check_A_mul_B!_sizes(C, A, B) n = size(A,1) @@ -553,15 +555,15 @@ function _mul!(C::AbstractMatrix, A::BiTriSym, B::Diagonal, _add::MulAddMul = Mu C end -function _mul!(C::AbstractVecOrMat, A::BiTriSym, B::AbstractVecOrMat, _add::MulAddMul = MulAddMul()) +function _mul!(C::AbstractVecOrMat, A::BiTriSym, B::AbstractVecOrMat, _add::MulAddMul) require_one_based_indexing(C, B) nA = size(A,1) nB = size(B,2) if !(size(C,1) == size(B,1) == nA) - throw(DimensionMismatch("A has first dimension $nA, B has $(size(B,1)), C has $(size(C,1)) but all must match")) + throw(DimensionMismatch(lazy"A has first dimension $nA, B has $(size(B,1)), C has $(size(C,1)) but all must match")) end if size(C,2) != nB - throw(DimensionMismatch("A has second dimension $nA, B has $(size(B,2)), C has $(size(C,2)) but all must match")) + throw(DimensionMismatch(lazy"A has second dimension $nA, B has $(size(B,2)), C has $(size(C,2)) but all must match")) end iszero(nA) && return C iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) @@ -583,7 +585,7 @@ function _mul!(C::AbstractVecOrMat, A::BiTriSym, B::AbstractVecOrMat, _add::MulA C end -function _mul!(C::AbstractMatrix, A::AbstractMatrix, B::BiTriSym, _add::MulAddMul = MulAddMul()) +function _mul!(C::AbstractMatrix, A::AbstractMatrix, B::TriSym, _add::MulAddMul) require_one_based_indexing(C, A) check_A_mul_B!_sizes(C, A, B) iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) @@ -618,7 +620,37 @@ function _mul!(C::AbstractMatrix, A::AbstractMatrix, B::BiTriSym, _add::MulAddMu C end -function _mul!(C::AbstractMatrix, A::Diagonal, B::BiTriSym, _add::MulAddMul = MulAddMul()) +function _mul!(C::AbstractMatrix, A::AbstractMatrix, B::Bidiagonal, _add::MulAddMul) + require_one_based_indexing(C, A) + check_A_mul_B!_sizes(C, A, B) + iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) + if size(A, 1) <= 3 || size(B, 2) <= 1 + return mul!(C, Array(A), Array(B), _add.alpha, _add.beta) + end + m, n = size(A) + @inbounds if B.uplo == 'U' + for i in 1:m + for j in n:-1:2 + _modify!(_add, A[i,j] * B.dv[j] + A[i,j-1] * B.ev[j-1], C, (i, j)) + end + _modify!(_add, A[i,1] * B.dv[1], C, (i, 1)) + end + else # uplo == 'L' + for i in 1:m + for j in 1:n-1 + _modify!(_add, A[i,j] * B.dv[j] + A[i,j+1] * B.ev[j], C, (i, j)) + end + _modify!(_add, A[i,n] * B.dv[n], C, (i, n)) + end + end + C +end + +_mul!(C::AbstractMatrix, A::Diagonal, B::Bidiagonal, _add::MulAddMul) = + _dibimul!(C, A, B, _add) +_mul!(C::AbstractMatrix, A::Diagonal, B::TriSym, _add::MulAddMul) = + _dibimul!(C, A, B, _add) +function _dibimul!(C, A, B, _add) require_one_based_indexing(C) check_A_mul_B!_sizes(C, A, B) n = size(A,1) @@ -729,11 +761,11 @@ function ldiv!(c::AbstractVecOrMat, A::Bidiagonal, b::AbstractVecOrMat) N = size(A, 2) mb, nb = size(b, 1), size(b, 2) if N != mb - throw(DimensionMismatch("second dimension of A, $N, does not match first dimension of b, $mb")) + throw(DimensionMismatch(lazy"second dimension of A, $N, does not match first dimension of b, $mb")) end mc, nc = size(c, 1), size(c, 2) if mc != mb || nc != nb - throw(DimensionMismatch("size of result, ($mc, $nc), does not match the size of b, ($mb, $nb)")) + throw(DimensionMismatch(lazy"size of result, ($mc, $nc), does not match the size of b, ($mb, $nb)")) end if N == 0 @@ -763,34 +795,35 @@ ldiv!(c::AbstractVecOrMat, A::AdjOrTrans{<:Any,<:Bidiagonal}, b::AbstractVecOrMa (t = wrapperop(A); _rdiv!(t(c), t(b), t(A)); return c) ### Generic promotion methods and fallbacks -\(A::Bidiagonal, B::AbstractVecOrMat) = ldiv!(_initarray(\, eltype(A), eltype(B), B), A, B) +\(A::Bidiagonal, B::AbstractVecOrMat) = + ldiv!(matprod_dest(A, B, promote_op(\, eltype(A), eltype(B))), A, B) \(xA::AdjOrTrans{<:Any,<:Bidiagonal}, B::AbstractVecOrMat) = copy(xA) \ B ### Triangular specializations for tri in (:UpperTriangular, :UnitUpperTriangular) @eval function \(B::Bidiagonal, U::$tri) - A = ldiv!(_initarray(\, eltype(B), eltype(U), U), B, U) + A = ldiv!(matprod_dest(B, U, promote_op(\, eltype(B), eltype(U))), B, U) return B.uplo == 'U' ? UpperTriangular(A) : A end @eval function \(U::$tri, B::Bidiagonal) - A = ldiv!(_initarray(\, eltype(U), eltype(B), U), U, B) + A = ldiv!(matprod_dest(U, B, promote_op(\, eltype(U), eltype(B))), U, B) return B.uplo == 'U' ? UpperTriangular(A) : A end end for tri in (:LowerTriangular, :UnitLowerTriangular) @eval function \(B::Bidiagonal, L::$tri) - A = ldiv!(_initarray(\, eltype(B), eltype(L), L), B, L) + A = ldiv!(matprod_dest(B, L, promote_op(\, eltype(B), eltype(L))), B, L) return B.uplo == 'L' ? LowerTriangular(A) : A end @eval function \(L::$tri, B::Bidiagonal) - A = ldiv!(_initarray(\, eltype(L), eltype(B), L), L, B) + A = ldiv!(matprod_dest(L, B, promote_op(\, eltype(L), eltype(B))), L, B) return B.uplo == 'L' ? LowerTriangular(A) : A end end ### Diagonal specialization function \(B::Bidiagonal, D::Diagonal) - A = ldiv!(_initarray(\, eltype(B), eltype(D), D), B, D) + A = ldiv!(similar(D, promote_op(\, eltype(B), eltype(D)), size(D)), B, D) return B.uplo == 'U' ? UpperTriangular(A) : LowerTriangular(A) end @@ -798,11 +831,11 @@ function _rdiv!(C::AbstractMatrix, A::AbstractMatrix, B::Bidiagonal) require_one_based_indexing(C, A, B) m, n = size(A) if size(B, 1) != n - throw(DimensionMismatch("right hand side B needs first dimension of size $n, has size $(size(B,1))")) + throw(DimensionMismatch(lazy"right hand side B needs first dimension of size $n, has size $(size(B,1))")) end mc, nc = size(C) if mc != m || nc != n - throw(DimensionMismatch("expect output to have size ($m, $n), but got ($mc, $nc)")) + throw(DimensionMismatch(lazy"expect output to have size ($m, $n), but got ($mc, $nc)")) end zi = findfirst(iszero, B.dv) @@ -840,33 +873,34 @@ rdiv!(A::AbstractMatrix, B::AdjOrTrans{<:Any,<:Bidiagonal}) = @inline _rdiv!(A, _rdiv!(C::AbstractMatrix, A::AbstractMatrix, B::AdjOrTrans{<:Any,<:Bidiagonal}) = (t = wrapperop(B); ldiv!(t(C), t(B), t(A)); return C) -/(A::AbstractMatrix, B::Bidiagonal) = _rdiv!(_initarray(/, eltype(A), eltype(B), A), A, B) +/(A::AbstractMatrix, B::Bidiagonal) = + _rdiv!(similar(A, promote_op(/, eltype(A), eltype(B)), size(A)), A, B) ### Triangular specializations for tri in (:UpperTriangular, :UnitUpperTriangular) @eval function /(U::$tri, B::Bidiagonal) - A = _rdiv!(_initarray(/, eltype(U), eltype(B), U), U, B) + A = _rdiv!(matprod_dest(U, B, promote_op(/, eltype(U), eltype(B))), U, B) return B.uplo == 'U' ? UpperTriangular(A) : A end @eval function /(B::Bidiagonal, U::$tri) - A = _rdiv!(_initarray(/, eltype(B), eltype(U), U), B, U) + A = _rdiv!(matprod_dest(B, U, promote_op(/, eltype(B), eltype(U))), B, U) return B.uplo == 'U' ? UpperTriangular(A) : A end end for tri in (:LowerTriangular, :UnitLowerTriangular) @eval function /(L::$tri, B::Bidiagonal) - A = _rdiv!(_initarray(/, eltype(L), eltype(B), L), L, B) + A = _rdiv!(matprod_dest(L, B, promote_op(/, eltype(L), eltype(B))), L, B) return B.uplo == 'L' ? LowerTriangular(A) : A end @eval function /(B::Bidiagonal, L::$tri) - A = _rdiv!(_initarray(/, eltype(B), eltype(L), L), B, L) + A = _rdiv!(matprod_dest(B, L, promote_op(/, eltype(B), eltype(L))), B, L) return B.uplo == 'L' ? LowerTriangular(A) : A end end ### Diagonal specialization function /(D::Diagonal, B::Bidiagonal) - A = _rdiv!(_initarray(/, eltype(D), eltype(B), D), D, B) + A = _rdiv!(similar(D, promote_op(/, eltype(D), eltype(B)), size(D)), D, B) return B.uplo == 'U' ? UpperTriangular(A) : LowerTriangular(A) end diff --git a/stdlib/LinearAlgebra/src/bitarray.jl b/stdlib/LinearAlgebra/src/bitarray.jl index d1857c3c38659..ccc9138d227a3 100644 --- a/stdlib/LinearAlgebra/src/bitarray.jl +++ b/stdlib/LinearAlgebra/src/bitarray.jl @@ -186,7 +186,7 @@ function istril(A::BitMatrix) end # fast 8x8 bit transpose from Henry S. Warrens's "Hacker's Delight" -# http://www.hackersdelight.org/hdcodetxt/transpose8.c.txt +# https://www.hackersdelight.org/hdcodetxt/transpose8.c.txt function transpose8x8(x::UInt64) y = x t = xor(y, y >>> 7) & 0x00aa00aa00aa00aa diff --git a/stdlib/LinearAlgebra/src/blas.jl b/stdlib/LinearAlgebra/src/blas.jl index 8da19baee5045..413b7866c5444 100644 --- a/stdlib/LinearAlgebra/src/blas.jl +++ b/stdlib/LinearAlgebra/src/blas.jl @@ -5,12 +5,11 @@ Interface to BLAS subroutines. """ module BLAS -import Base: copyto! using Base: require_one_based_indexing, USE_BLAS64 export # Note: `xFUNC_NAME` is a placeholder for not exported BLAS functions -# ref: http://www.netlib.org/blas/blasqr.pdf +# ref: https://www.netlib.org/blas/blasqr.pdf # Level 1 # xROTG # xROTMG @@ -52,6 +51,7 @@ export # xTBSV # xTPSV ger!, + geru!, # xGERU # xGERC her!, @@ -63,6 +63,8 @@ export # xSYR2 # xSPR2 # Level 3 + gemmt!, + gemmt, gemm!, gemm, symm!, @@ -82,7 +84,7 @@ export trsm!, trsm -using ..LinearAlgebra: libblastrampoline, BlasReal, BlasComplex, BlasFloat, BlasInt, DimensionMismatch, checksquare, stride1, chkstride1 +using ..LinearAlgebra: libblastrampoline, BlasReal, BlasComplex, BlasFloat, BlasInt, DimensionMismatch, checksquare, chkstride1 include("lbt.jl") @@ -157,16 +159,15 @@ function check() interface = USE_BLAS64 ? :ilp64 : :lp64 if !any(lib.interface == interface for lib in config.loaded_libs) interfacestr = uppercase(string(interface)) - @error("No loaded BLAS libraries were built with $(interfacestr) support") - println("Quitting.") - exit() + @error("No loaded BLAS libraries were built with $interfacestr support.") + exit(1) end end "Check that upper/lower (for special matrices) is correctly specified" function chkuplo(uplo::AbstractChar) if !(uplo == 'U' || uplo == 'L') - throw(ArgumentError(lazy"uplo argument must be 'U' (upper) or 'L' (lower), got $uplo")) + throw(ArgumentError(lazy"uplo argument must be 'U' (upper) or 'L' (lower), got '$uplo'")) end uplo end @@ -1062,7 +1063,7 @@ sbmv(uplo, k, A, x) Update vector `y` as `alpha*A*x + beta*y` where `A` is a symmetric band matrix of order `size(A,2)` with `k` super-diagonals stored in the argument `A`. The storage layout for `A` is described the reference BLAS module, level-2 BLAS at -. +. Only the [`uplo`](@ref stdlib-blas-uplo) triangle of `A` is used. Return the updated `y`. @@ -1415,6 +1416,41 @@ for (fname, elty) in ((:dger_,:Float64), end end +### geru + +""" + geru!(alpha, x, y, A) + +Rank-1 update of the matrix `A` with vectors `x` and `y` as `alpha*x*transpose(y) + A`. +""" +function geru! end + +for (fname, elty) in ((:zgeru_,:ComplexF64), (:cgeru_,:ComplexF32)) + @eval begin + function geru!(α::$elty, x::AbstractVector{$elty}, y::AbstractVector{$elty}, A::AbstractMatrix{$elty}) + require_one_based_indexing(A, x, y) + m, n = size(A) + if m != length(x) || n != length(y) + throw(DimensionMismatch(lazy"A has size ($m,$n), x has length $(length(x)), y has length $(length(y))")) + end + px, stx = vec_pointer_stride(x, ArgumentError("input vector with 0 stride is not allowed")) + py, sty = vec_pointer_stride(y, ArgumentError("input vector with 0 stride is not allowed")) + GC.@preserve x y ccall((@blasfunc($fname), libblastrampoline), Cvoid, + (Ref{BlasInt}, Ref{BlasInt}, Ref{$elty}, Ptr{$elty}, + Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, + Ref{BlasInt}), + m, n, α, px, stx, py, sty, A, max(1,stride(A,2))) + A + end + end +end +for elty in (:Float64, :Float32) + @eval begin + geru!(α::$elty, x::AbstractVector{$elty}, y::AbstractVector{$elty}, A::AbstractMatrix{$elty}) = + ger!(α, x, y, A) + end +end + ### syr """ @@ -1481,6 +1517,88 @@ end # Level 3 ## (GE) general matrix-matrix multiplication +""" + gemmt!(uplo, tA, tB, alpha, A, B, beta, C) + +Update the lower or upper triangular part specified by [`uplo`](@ref stdlib-blas-uplo) of `C` as +`alpha*A*B + beta*C` or the other variants according to [`tA`](@ref stdlib-blas-trans) and `tB`. +Return the updated `C`. + +!!! compat "Julia 1.11" + `gemmt!` requires at least Julia 1.11. +""" +function gemmt! end + +for (gemmt, elty) in + ((:dgemmt_,:Float64), + (:sgemmt_,:Float32), + (:zgemmt_,:ComplexF64), + (:cgemmt_,:ComplexF32)) + @eval begin + # SUBROUTINE DGEMMT(UPLO,TRANSA,TRANSB,N,K,ALPHA,A,LDA,B,LDB,BETA,C,LDC) + # * .. Scalar Arguments .. + # DOUBLE PRECISION ALPHA,BETA + # INTEGER K,LDA,LDB,LDC,N + # CHARACTER UPLO,TRANSA,TRANSB + # * .. Array Arguments .. + # DOUBLE PRECISION A(LDA,*),B(LDB,*),C(LDC,*) + function gemmt!(uplo::AbstractChar, transA::AbstractChar, transB::AbstractChar, + alpha::Union{($elty), Bool}, + A::AbstractVecOrMat{$elty}, B::AbstractVecOrMat{$elty}, + beta::Union{($elty), Bool}, + C::AbstractVecOrMat{$elty}) + chkuplo(uplo) + require_one_based_indexing(A, B, C) + m = size(A, transA == 'N' ? 1 : 2) + ka = size(A, transA == 'N' ? 2 : 1) + kb = size(B, transB == 'N' ? 1 : 2) + n = size(B, transB == 'N' ? 2 : 1) + if ka != kb || m != n || m != size(C,1) || n != size(C,2) + throw(DimensionMismatch(lazy"A has size ($m,$ka), B has size ($kb,$n), C has size $(size(C))")) + end + chkstride1(A) + chkstride1(B) + chkstride1(C) + ccall((@blasfunc($gemmt), libblastrampoline), Cvoid, + (Ref{UInt8}, Ref{UInt8}, Ref{UInt8}, Ref{BlasInt}, + Ref{BlasInt}, Ref{$elty}, Ptr{$elty}, Ref{BlasInt}, + Ptr{$elty}, Ref{BlasInt}, Ref{$elty}, Ptr{$elty}, + Ref{BlasInt}, Clong, Clong, Clong), + uplo, transA, transB, n, + ka, alpha, A, max(1,stride(A,2)), + B, max(1,stride(B,2)), beta, C, + max(1,stride(C,2)), 1, 1, 1) + C + end + function gemmt(uplo::AbstractChar, transA::AbstractChar, transB::AbstractChar, alpha::($elty), A::AbstractMatrix{$elty}, B::AbstractMatrix{$elty}) + gemmt!(uplo, transA, transB, alpha, A, B, zero($elty), similar(B, $elty, (size(A, transA == 'N' ? 1 : 2), size(B, transB == 'N' ? 2 : 1)))) + end + function gemmt(uplo::AbstractChar, transA::AbstractChar, transB::AbstractChar, A::AbstractMatrix{$elty}, B::AbstractMatrix{$elty}) + gemmt(uplo, transA, transB, one($elty), A, B) + end + end +end + +""" + gemmt(uplo, tA, tB, alpha, A, B) + +Return the lower or upper triangular part specified by [`uplo`](@ref stdlib-blas-uplo) of `A*B` or the other three variants according to [`tA`](@ref stdlib-blas-trans) and `tB`. + +!!! compat "Julia 1.11" + `gemmt` requires at least Julia 1.11. +""" +gemmt(uplo, tA, tB, alpha, A, B) + +""" + gemmt(uplo, tA, tB, A, B) + +Return the lower or upper triangular part specified by [`uplo`](@ref stdlib-blas-uplo) of `A*B` or the other three variants according to [`tA`](@ref stdlib-blas-trans) and `tB`. + +!!! compat "Julia 1.11" + `gemmt` requires at least Julia 1.11. +""" +gemmt(uplo, tA, tB, A, B) + """ gemm!(tA, tB, alpha, A, B, beta, C) diff --git a/stdlib/LinearAlgebra/src/bunchkaufman.jl b/stdlib/LinearAlgebra/src/bunchkaufman.jl index d1019a1a4ea5a..8d1ded9bf8111 100644 --- a/stdlib/LinearAlgebra/src/bunchkaufman.jl +++ b/stdlib/LinearAlgebra/src/bunchkaufman.jl @@ -4,6 +4,15 @@ ## LD for BunchKaufman, UL for CholeskyDense, LU for LUDense and ## define size methods for Factorization types using it. +##----------- Type utilities for generic Bunch-Kaufman implementation ------------ +# Generic real type. Any real number type should able to approximate +# real numbers, and thus be closed under arithmetic operations. +# Therefore so Int, Complex{Int}, etc. are excluded. +ClosedReal = T where T <: Union{AbstractFloat, Rational} +# Similarly, we also use a closed scalar type +ClosedScalar = Union{T, Complex{T}} where T <: ClosedReal +##-------------------------------------------------------------------------------- + """ BunchKaufman <: Factorization @@ -22,10 +31,10 @@ as appropriate given `S.uplo`, and `S.p`. # Examples ```jldoctest -julia> A = [1 2; 2 3] -2×2 Matrix{Int64}: - 1 2 - 2 3 +julia> A = Float64.([1 2; 2 3]) +2×2 Matrix{Float64}: + 1.0 2.0 + 2.0 3.0 julia> S = bunchkaufman(A) # A gets wrapped internally by Symmetric(A) BunchKaufman{Float64, Matrix{Float64}, Vector{Int64}} @@ -88,7 +97,7 @@ Base.iterate(S::BunchKaufman) = (S.D, Val(:UL)) Base.iterate(S::BunchKaufman, ::Val{:UL}) = (S.uplo == 'L' ? S.L : S.U, Val(:p)) Base.iterate(S::BunchKaufman, ::Val{:p}) = (S.p, Val(:done)) Base.iterate(S::BunchKaufman, ::Val{:done}) = nothing - +copy(S::BunchKaufman) = BunchKaufman(copy(S.LD), copy(S.ipiv), S.uplo, S.symmetric, S.rook, S.info) """ bunchkaufman!(A, rook::Bool=false; check = true) -> BunchKaufman @@ -141,14 +150,14 @@ The following functions are available for `BunchKaufman` objects: [`size`](@ref), `\\`, [`inv`](@ref), [`issymmetric`](@ref), [`ishermitian`](@ref), [`getindex`](@ref). -[^Bunch1977]: J R Bunch and L Kaufman, Some stable methods for calculating inertia and solving symmetric linear systems, Mathematics of Computation 31:137 (1977), 163-179. [url](http://www.ams.org/journals/mcom/1977-31-137/S0025-5718-1977-0428694-0/). +[^Bunch1977]: J R Bunch and L Kaufman, Some stable methods for calculating inertia and solving symmetric linear systems, Mathematics of Computation 31:137 (1977), 163-179. [url](https://www.ams.org/journals/mcom/1977-31-137/S0025-5718-1977-0428694-0/). # Examples ```jldoctest -julia> A = [1 2; 2 3] -2×2 Matrix{Int64}: - 1 2 - 2 3 +julia> A = Float64.([1 2; 2 3]) +2×2 Matrix{Float64}: + 1.0 2.0 + 2.0 3.0 julia> S = bunchkaufman(A) # A gets wrapped internally by Symmetric(A) BunchKaufman{Float64, Matrix{Float64}, Vector{Int64}} @@ -237,37 +246,47 @@ function _ipiv2perm_bk(v::AbstractVector{T}, maxi::Integer, uplo::AbstractChar, return p end -function getproperty(B::BunchKaufman{T,<:StridedMatrix}, d::Symbol) where {T<:BlasFloat} +function getproperty(B::BunchKaufman{TS}, + d::Symbol) where TS <: ClosedScalar{TR} where TR <: ClosedReal n = size(B, 1) if d === :p return _ipiv2perm_bk(getfield(B, :ipiv), n, getfield(B, :uplo), B.rook) elseif d === :P - return Matrix{T}(I, n, n)[:,invperm(B.p)] + return Matrix{TS}(I, n, n)[:,invperm(B.p)] elseif d === :L || d === :U || d === :D - if getfield(B, :rook) - LUD, od = LAPACK.syconvf_rook!(getfield(B, :uplo), 'C', copy(getfield(B, :LD)), getfield(B, :ipiv)) + if d === :D + _, od, md = generic_syconv(B, false) + elseif typeof(B) <: BunchKaufman{T,<:StridedMatrix} where {T<:BlasFloat} + # We use LAPACK whenever we can + if getfield(B, :rook) + LUD, _ = LAPACK.syconvf_rook!(getfield(B, :uplo), 'C', + copy(getfield(B, :LD)), getfield(B, :ipiv)) + else + LUD, _ = LAPACK.syconv!(getfield(B, :uplo), copy(getfield(B, :LD)), + getfield(B, :ipiv)) + end else - LUD, od = LAPACK.syconv!(getfield(B, :uplo), copy(getfield(B, :LD)), getfield(B, :ipiv)) + LUD, _ = generic_syconv(B) end if d === :D if getfield(B, :uplo) == 'L' odl = od[1:n - 1] - return Tridiagonal(odl, diag(LUD), getfield(B, :symmetric) ? odl : conj.(odl)) + return Tridiagonal(odl, md, getfield(B, :symmetric) ? odl : conj.(odl)) else # 'U' odu = od[2:n] - return Tridiagonal(getfield(B, :symmetric) ? odu : conj.(odu), diag(LUD), odu) + return Tridiagonal(getfield(B, :symmetric) ? odu : conj.(odu), md, odu) end elseif d === :L if getfield(B, :uplo) == 'L' return UnitLowerTriangular(LUD) else - throw(ArgumentError("factorization is U*D*transpose(U) but you requested L")) + throw(ArgumentError("factorization is U*D*U' but you requested L")) end else # :U if B.uplo == 'U' return UnitUpperTriangular(LUD) else - throw(ArgumentError("factorization is L*D*transpose(L) but you requested U")) + throw(ArgumentError("factorization is L*D*L' but you requested U")) end end else @@ -278,6 +297,27 @@ end Base.propertynames(B::BunchKaufman, private::Bool=false) = (:p, :P, :L, :U, :D, (private ? fieldnames(typeof(B)) : ())...) +function getproperties!(B::BunchKaufman{T,<:StridedMatrix}) where {T<:BlasFloat} + # NOTE: Unlike in the 'getproperty' function, in this function L/U and D are computed in place. + if B.rook + LUD, od = LAPACK.syconvf_rook!(B.uplo, 'C', B.LD, B.ipiv) + else + LUD, od = LAPACK.syconv!(B.uplo, B.LD, B.ipiv) + end + if B.uplo == 'U' + M = UnitUpperTriangular(LUD) + du = od[2:end] + # Avoid aliasing dl and du. + dl = B.symmetric ? du : conj.(du) + else + M = UnitLowerTriangular(LUD) + dl = od[1:end-1] + # Avoid aliasing dl and du. + du = B.symmetric ? dl : conj.(dl) + end + return (M, Tridiagonal(dl, diag(LUD), du), B.p) +end + issuccess(B::BunchKaufman) = B.info == 0 function adjoint(B::BunchKaufman) @@ -389,4 +429,1159 @@ end ## reconstruct the original matrix ## TODO: understand the procedure described at -## http://www.nag.com/numeric/FL/nagdoc_fl22/pdf/F07/f07mdf.pdf +## https://www.nag.com/numeric/FL/nagdoc_fl22/pdf/F07/f07mdf.pdf + + +##-------------------------------------------------------------------------- +##------------- Start of generic Bunch-Kaufman Implementation -------------- +##-------------------------------------------------------------------------- + +export inertia + +function arg_illegal(fun_name::AbstractString, + info::Integer, + waer::AbstractChar) + if waer == 'W' + @warn " ** On entry to '$(fun_name)' parameter number " * + "$(info) had an illegal value" + else + error(" ** On entry to '$(fun_name)' parameter number " * + "$(info) had an illegal value") + end +end + + +function cabs1(z::T) where T <: Complex + return abs(real(z)) + abs(imag(z)) +end + + +function cabsr(z::T) where T <: Complex + return abs(real(z)) +end + + +""" +generic_adr1!(uplo, alpha, x, y, A, syhe) -> nothing + +`generic_adr1!` performs the following adjoint (symmetric or Hermitian) +rank 1 operation + +`A[1:K,1:L] = alpha*x*y' + A[1:K,1:L]` + +in-place, where `alpha` is a scalar, `x` is a K element vector, `y` +is an L element vector and `A` is an `NxM` matrix. Note that `y'` can +denote either the transpose, i.e. `transpose(y)` or the conjugate +transpose , i.e. `adjoint(y)`. + +`uplo` is a character, either `'U'`, `'L'` or `'F'`, indicating whether +the matrix is stored in the upper triangular part (`uplo=='U'`), the +lower triangular part (`uplo=='L'`), or the full storage space is used +(`uplo=='F'`). If `uplo!='F'` then only the corresponding triangular +part is updated. The values `'U'` or `'L'` can only be used when A is +square (`N==M`). + +`syhe` is a character, either `'S'` or `'H'`, indicating whether the +symmetric adjoint (`syhe=='S'`, and `y'==transpose(y)`) or the hermitian +adjoint (`syhe=='H'`, and `y'==adjoint(y)`) must be used. +""" +function generic_adr1!(uplo::AbstractChar, + alpha::ClosedScalar{TR}, + x::AbstractVector{TS}, + y::AbstractVector{TS}, + A::AbstractMatrix{TS}, + syhe::AbstractChar + ) where TS <: ClosedScalar{TR} where TR <: ClosedReal + + # Inputs must be 1-indexed; bounds may not be checked. + Base.require_one_based_indexing(x, A) + + # Check argument validity + K = length(x) + L = length(y) + N, M = size(A) + info = 0::BlasInt + if (uplo != 'U' && uplo != 'L' && uplo != 'F') || (uplo != 'F' && N != M) + info = (-1)::BlasInt + elseif K > N + info = (-3)::BlasInt + elseif L > M + info = (-4)::BlasInt + elseif syhe != 'S' && syhe != 'H' + info = (-6)::BlasInt + end + if info < 0 + arg_illegal("generic_sadr1!", -info, 'E') + end + + # Load the requested adjoining operator + adj_op = syhe == 'S' ? identity : conj + + # Define loop range function according to the type of storage + # TODO: can we adjust the range without anonymous functions, + # but without having to write the same code thrice? + i_range = uplo == 'F' ? _ -> (1:K) : uplo == 'U' ? j -> (1:min(j,K)) : j -> (j:K) + + # Compute rank update of A + for j in 1:L; @inbounds begin + if y[j] != 0 + temp = alpha * adj_op(y[j]) + for i in i_range(j) + A[i,j] += x[i] * temp + end + end + end; end + return +end + + +""" +generic_mvpv!(trans, alpha, A, x, beta, y) -> nothing + +`generic_mvpv!` performs the following matrix-vector operation: + +`y[1:K] = alpha*A'*x[1:L] + beta*y[1:K]` + +in-place, where `alpha` and `beta` are scalars, `x` is a vector with at +least L elements, `y` is a vector with at least K elements, and `A` is +an `NxM` matrix. `A'` can denote the transpose, i.e. `transpose(A)` or +the conjugate transpose, i.e. `adjoint(A)`, and then `M==K && N==L`. +`A'` can also denote no adjoining at all, i.e. `A'==A`, and then +`N==K && M==L`. + +`trans` is a character, either `'T'`, `'C'` or `'N'`, indicating whether +`A'=transpose(A)`, `A'=adjoint(A)` or `A'=A`, respectively. +""" +function generic_mvpv!(trans::AbstractChar, + alpha::ClosedScalar{TR}, + A::AbstractMatrix{TS}, + x::AbstractVector{TS}, + beta::ClosedScalar{TR}, + y::AbstractVector{TS}, + ) where TS <: ClosedScalar{TR} where TR <: ClosedReal + + # Inputs must be 1-indexed; bounds may not be checked. + Base.require_one_based_indexing(A, x, y) + + # Check argument validity + M, N = size(A) + K = trans == 'N' ? M : N + L = trans == 'N' ? N : M + info = 0::BlasInt + if trans != 'T' && trans != 'C' && trans != 'N' + info = (-1)::BlasInt + elseif length(y) < K + info = (-3)::BlasInt + elseif length(x) < L + info = (-4)::BlasInt + end + if info < 0 + arg_illegal("generic_sadr1!", -info, 'E') + end + + # Quick return if possible. + if K == 0 || (alpha == 0 && beta == 1); return; end + + # Start the operations. In this version the elements of A are + # accessed sequentially with one pass through A. + # First form y := beta*y. + @inbounds begin + if beta != 1 + if beta == 0 + # Way less allocations and way faster for BigFloat. + # For Float64 there is some (acceptable IMO) performance loss. + y[1:K] .= 0 + else + for i in 1:K; y[i] *= beta; end + end + end + if alpha == 0 || L == 0; return; end + + if trans == 'N' + # Form y := alpha*A*x + y. + for j in 1:L + # Faster than a loop + axpy!(alpha*x[j], view(A, 1:K, j), view(y, 1:K)) + end + else + # Form y := alpha*A**T*x + y or y := alpha*A**H*x + y. + noconj = (trans == 'T') + for i = 1:K + temp = 0 + if noconj + for j = 1:L + temp = temp + A[j,i]*x[j] + end + else + for j = 1:L + temp = temp + conj(A[j,i])*x[j] + end + end + y[i] += alpha*temp + end + end + end + return +end + + +""" +bk_rowcol_swap!(A, k, kp, kstep, upper, herm) -> did_swap::Bool + +Performs the row and column interchange of the Bunch-Kaufman factorization. +If `upper==true` then the rows and columns `kp` of `A[1:k,1:k]` are +interchanged with either rows and columns `k` or `k-1` of `A[1:k,1:k]`, +depending on whether `kstep==1` or `kstep==2`, respectively. If +`upper==false` then the rows and columns `kp-k+1` of `A[k:N,k:N]` are +interchanged with either rows and columns `1` or `2` of `A[k:N,k:N]`, +depending on whether `kstep==1` or `kstep==2`, respectively. `herm=true` +then it is assumed that `A` is Hermitian, and conjugation is applied to +the appropriate entries of the interchanged rows and columns. If +`herm=false` no conjugation is performed. + +This is an internal helper function for the main Bunch-Kaufman +factorization function, `generic_bunchkaufman!`. As such, validity of the +input values is not verified. +""" +function bk_rowcol_swap!( + A::AbstractMatrix{TS}, + k::Integer, + kp::Integer, + kstep::Integer, + upper::Bool, + herm::Bool + ) where TS <: ClosedScalar{TR} where TR <: ClosedReal + kk = upper ? k - kstep + 1 : k + kstep - 1 + if kp != kk + if kp > 1 + thisview = upper ? view(A, 1:(kp-1), :) : view(A, (kp+1):size(A,1), :) + Base.swapcols!(thisview, kp, kk) + end + thisrange = upper ? ((kp+1):(kk-1)) : ((kk+1):(kp-1)) + if !herm + # Real/complex symmetric case + for j in thisrange + A[j,kk], A[kp,j] = A[kp,j], A[j,kk] + end + A[kk,kk], A[kp,kp] = A[kp,kp], A[kk,kk] + else + # Hermitian case + for j in thisrange + A[j,kk], A[kp,j] = conj(A[kp,j]), conj(A[j,kk]) + end + A[kp,kk] = conj(A[kp,kk]) + A[kk,kk], A[kp,kp] = real(A[kp,kp]), real(A[kk,kk]) + end + if kstep == 2 + if herm + # Force diagonal entry to be purely real + A[k,k] = real(A[k,k]) + end + if upper + A[k-1,k], A[kp,k] = A[kp,k], A[k-1,k] + else + A[k+1,k], A[kp,k] = A[kp,k], A[k+1,k] + end + end + return true + else + return false + end +end + + +""" +generic_bunchkaufman!(uplo, A, syhe, rook::Bool=false) -> +LD<:AbstractMatrix, ipiv<:AbstractVector{Integer}, info::BlasInt + +Computes the Bunch-Kaufman factorization of a symmetric or Hermitian +matrix `A` of size `NxN` as `P'*U*D*U'*P` or `P'*L*D*L'*P`, depending on +which triangle is stored in `A`. Note that if `A` is complex symmetric +then `U'` and `L'` denote the unconjugated transposes, i.e. +`transpose(U)` and `transpose(L)`. The resulting `U` or `L` and D are +stored in-place in `A`, LAPACK style. `LD` is just a reference to `A` +(that is, `LD===A`). `ipiv` stores the permutation information of the +algorithm in LAPACK format. `info` indicates whether the factorization +was successful and non-singular when `info==0`, or else `info` takes a +different value. The outputs `LD`, `ipiv`, `info` follow the format of +the LAPACK functions of the Bunch-Kaufman factorization (`dsytrf`, +`csytrf`, `chetrf`, etc.), so this function can (ideally) be used +interchangeably with its LAPACK counterparts `LAPACK.sytrf!`, +`LAPACK.sytrf_rook!`, etc. + +`uplo` is a character, either `'U'` or `'L'`, indicating whether the +matrix is stored in the upper triangular part (`uplo=='U'`) or in the +lower triangular part (`uplo=='L'`). + +`syhe` is a character, either `'S'` or `'H'`, indicating whether the +matrix is real/complex symmetric (`syhe=='S'`, and the symmetric +Bunch-Kaufman factorization is performed) or complex hermitian +(`syhe=='H'`, and the hermitian Bunch-Kaufman factorization is +performed). + +If `rook` is `true`, rook pivoting is used (also called bounded +Bunch-Kaufman factorization). If `rook` is `false`, rook pivoting is +not used (standard Bunch-Kaufman factorization). Rook pivoting can +require up to `~N^3/6` extra comparisons in addition to the `~N^3/3` +additions and `~N^3/3` multiplications of the standard Bunch-Kaufman +factorization. However, rook pivoting guarantees that the entries of +`U` or `L` are bounded. + +This function implements the factorization algorithm entirely in +native Julia, so it supports any number type representing real or +complex numbers. +""" +function generic_bunchkaufman!( + uplo::AbstractChar, + A::AbstractMatrix{TS}, + syhe::AbstractChar, + rook::Bool=false + ) where TS <: ClosedScalar{TR} where TR <: ClosedReal + + # Inputs must be 1-indexed; bounds may not be checked. + Base.require_one_based_indexing(A) + + # Initialize info integer as 0 + info = 0::BlasInt + # Get size of matrix + N, N2 = size(A) + # Initialize permutation vector + ipiv = Vector{BlasInt}(undef, N) + + # Check input correctness + if uplo != 'U' && uplo != 'L' + info = (-1)::BlasInt + elseif N != N2 + info = (-2)::BlasInt + elseif syhe != 'S' && syhe != 'H' + info = (-3)::BlasInt + end + if info < 0 + arg_illegal("generic_bunchkaufman!", -info, 'W') + return A, ipiv, info + end + # if rook + # error("Rook pivoting not implemented yet.") + # end + + # Initialize `alpha` for use in choosing pivot block size. + # The exact value is + # (1 + sqrt(17)) / 8 ~= 0.6404 + # For rational matrices we a the small denominator approximation: + # 16/25 = 0.64 ~= (1 + sqrt(17)) / 8 + # in order to not increase the denominator size too much in computations. + # The error of this approximation is ≤0.1%, and it still guarantees that a + # 2x2 block in the D factor has a positive-negative eigenvalue pair, as long + # as the approximation lies in (0,1). + alpha = TR <: AbstractFloat ? (1 + sqrt(TR(17))) / 8 : TR(16//25) + # Use complex 1-norm for pivot selection, as in LAPACK + abs1_fun = TS <: Real ? abs : cabs1 + + # Check if the matrix is symmetric of hermitian + if syhe == 'S' || (syhe == 'H' && TS <: Real) + # Use symmetric variant if matrix is real, regardless of 'syhe' value + syhe = 'S' + diag_abs_fun = abs1_fun + else + diag_abs_fun = cabsr + end + + # Compute machine safe minimum when working with floating point numbers. + # LAPACK doesn't use this for diagonal pivoting though... + if rook + if TR <: AbstractFloat + # eps(0) gives the smallest subnormal number, and eps(1) gives the floating + # point type epsilon. eps(0)/eps(1) gives the smallest normal number, plus + # possibly some rounding error. + sfmin = nextfloat(eps(TR(0)) / eps(TR(1)), 2) + small = 1 / prevfloat(typemax(TR), 2) + if small >= sfmin + # 1/sfmin may overflow, so use 'small' plus a bit as the safe minimum + sfmin = nextfloat(small * (1 + eps(TR(1))), 2) + end + else + # We're working with rationals in this case, so the all results are exact. + sfmin = TR(0) + end + end + + # Run factorization depending on where the data is stored + upper = (uplo == 'U') + herm = (syhe == 'H') + # TODO: Is this gonna inline properly? + @inline k_cond = upper ? k -> k >= 1 : k -> k <= N + @inline irange = upper ? j -> (j:-1:1) : j -> (j:N) + @inline conj_op = herm ? conj : identity + @inline diagreal_op = herm ? (j -> A[j,j] = TS(real(A[j,j]))) : _ -> () + k = upper ? N : 1 + # Main loop, comments refer to the upper triangular version of the factorization. + # The lower triangular version is analogous. + while k_cond(k); @inbounds begin + kstep = 1 + knext = upper ? k - 1 : k + 1 + p = k + # Determine rows and columns to be interchanged and whether + # a 1-by-1 or 2-by-2 pivot block will be used + absakk = diag_abs_fun(A[k,k]) + # IMAX is the row-index of the largest off-diagonal element in + # column K, and COLMAX is its absolute value. + # Determine both COLMAX and IMAX. + if upper && k > 1 + colmax, imax = findmax(abs1_fun, view(A, 1:(k-1), k)) + elseif (!upper) && k < N + colmax, imax = findmax(abs1_fun, view(A, (k+1):N, k)) + imax += k + else + colmax = 0 + end + if (max(absakk, colmax) == 0) || isnan(absakk) + # Column K is zero or underflow, or contains a NaN: + # set INFO and continue + if info == 0 + info = k::BlasInt + end + kp = k + if herm + # Force diagonal entry to be purely real + A[k,k] = real(A[k,k]) + end + else + if absakk >= alpha*colmax + # no interchange, use 1-by-1 pivot block + kp = k + elseif rook + # Loop until pivot found + while true + # Begin pivot search loop body + # JMAX is the column-index of the largest off-diagonal + # element in row IMAX, and ROWMAX is its absolute value. + # Determine both ROWMAX and JMAX. + if imax != k + thisview = upper ? view(A, imax, (imax+1):k) : + view(A, imax, k:(imax-1)) + rowmax, jmax = findmax(abs1_fun, thisview) + jmax += upper ? imax : k - 1 + else + # LAPACK makes rowmax=0 in this case, but I believe it's + # better to make rowmax=-1, so that we guarantee that jmax + # will be define in the next if-block. + # TODO: is this correct/safe? + rowmax = 0 + end + if (upper && imax > 1) || ((!upper) && imax < N) + # Remember that we only have the upper triangular part + # of the matrix. We inspect the part of the row in the + # lower triangular part by traversing the corresponding + # part of the transpose column. + if upper + stemp, itemp = findmax(abs1_fun, view(A, 1:(imax-1), imax)) + else + stemp, itemp = findmax(abs1_fun, view(A, (imax+1):N, imax)) + itemp += imax + end + if stemp > rowmax + rowmax = stemp + jmax = itemp + end + end + # Equivalent to testing for (used to handle NaN and Inf) + # CABS1( A( IMAX, IMAX ) ).GE.ALPHA*ROWMAX + if !(diag_abs_fun(A[imax,imax]) < alpha*rowmax) + # interchange rows and columns K and IMAX, + # use 1-by-1 pivot block + kp = imax + break + # Equivalent to testing for ROWMAX .EQ. COLMAX, + # used to handle NaN and Inf + elseif (p == jmax || rowmax <= colmax) + # interchange rows and columns K+1 and IMAX, + # use 2-by-2 pivot block + kp = imax + kstep = 2 + break + else + # Pivot NOT found, set variables and repeat + p = imax + colmax = rowmax + imax = jmax + end + # End pivot search loop body + end + else + # JMAX is the column-index of the largest off-diagonal + # element in row IMAX, and ROWMAX is its absolute value + # We don't really need JMAX, se we don't store it + thisview = upper ? view(A, imax, (imax+1):k) : view(A, imax, k:(imax-1)) + rowmax = findmax(abs1_fun, thisview)[1] + if (upper && imax > 1) || ((!upper) && imax < N) + # Remember that we only have the upper triangular part + # of the matrix. We inspect the part of the row in the + # lower triangular part by traversing the corresponding + # part of the transpose column. + thisview = upper ? view(A, 1:(imax-1), imax) : + view(A, (imax+1):N, imax) + rowmax = max(rowmax, findmax(abs1_fun, thisview)[1]) + end + if absakk >= alpha * colmax * (colmax/rowmax) + # no interchange, use 1-by-1 pivot block + kp = k + elseif diag_abs_fun(A[imax,imax]) >= alpha * rowmax + # interchange rows and columns K and IMAX, use 1-by-1 + # pivot block + kp = imax + else + # interchange rows and columns K-1 and IMAX, use 2-by-2 + # pivot block + kp = imax + p = imax + kstep = 2 + end + end + # Swap TWO rows and TWO columns + # First swap + # The first swap only needs to be done when using rook pivoting + if rook && kstep == 2 + # Interchange rows and column K and P in the leading + # submatrix A(1:k,1:k) if we have a 2-by-2 pivot + bk_rowcol_swap!(A, k, p, 1, upper, herm) + end + # Second swap + did_swap = bk_rowcol_swap!(A, k, kp, kstep, upper, herm) + if herm && (!did_swap) + # Force diagonal entries to be purely real + A[k,k] = real(A[k,k]) + if kstep == 2 + A[knext,knext] = real(A[knext,knext]) + end + end + if kstep == 1 + # 1-by-1 pivot block D(k): column k now holds + # W(k) = U(k)*D(k) + # where U(k) is the k-th column of U + # When rook=false, sfmin is not defined, but the short-circuit + # evaluation of the conditional avoids an error. + if (!rook) || absakk >= sfmin + # Perform a rank-1 update of A(1:k-1,1:k-1) as + # A := A - U(k)*D(k)*U(k)' = A - W(k)*1/D(k)*W(k)' + # Compute 1/D(k) + r1 = !herm ? 1 / A[k,k] : 1 / real(A[k,k]) + # Perform rank-1 update to store the Schur complement + # in a submatrix of A + x = upper ? view(A, 1:(k-1), k) : view(A, (k+1):N, k) + # if 'upper' this should assign by reference + thisview = upper ? A : view(A, (k+1):N, (k+1):N) + generic_adr1!(uplo, -r1, x, x, thisview, syhe) + # Store U(k) in column k + thisrange = upper ? (1:(k-1)) : ((k+1):N) + for i in thisrange + A[i,k] *= r1 + end + else + # Compute D(k) + r1 = !herm ? A[k,k] : real(A[k,k]) + # Store U(k) in column k + thisrange = upper ? (1:(k-1)) : ((k+1):N) + for i in thisrange + A[i,k] /= r1 + end + # Perform a rank-1 update of A(k+1:n,k+1:n) as + # A := A - U(k)*D(k)*U(k)**T + # = A - W(k)*(1/D(k))*W(k)**T + # = A - (W(k)/D(k))*(D(k))*(W(k)/D(K))**T + # Perform rank-1 update to store the Schur complement + # in a submatrix of A + x = upper ? view(A, 1:(k-1), k) : view(A, (k+1):N, k) + # if 'upper' this should assign by reference + thisview = upper ? A : view(A, (k+1):N, (k+1):N) + generic_adr1!(uplo, -r1, x, x, thisview, syhe) + end + elseif (upper && k > 2) || ((!upper) && k < N - 1) + # 2-by-2 pivot block D(k): columns k and k-1 now hold + # ( W(k-1) W(k) ) = ( U(k-1) U(k) )*D(k) + # where U(k) and U(k-1) are the k-th and (k-1)-th columns + # of U + # Perform a rank-2 update of A(1:k-2,1:k-2) as + # A := A - ( U(k-1) U(k) )*D(k)*( U(k-1) U(k) )' + # = A - ( W(k-1) W(k) )*inv(D(k))*( W(k-1) W(k) )' + thisrange = upper ? ((k-2):-1:1) : ((k+2):N) + if !herm + # Real/complex symmetric case + #TODO: is this way to compute the inverse backward stable? + # (it probably is as it comes from LAPACK) + dxk = A[knext,k] + dxx = A[knext,knext] / dxk + dkk = A[k,k] / dxk + t = 1 / (dkk * dxx - 1) + dxk = t / dxk + dkx = dxk + else + # Hermitian case + # TODO: is this way to compute the inverse backward stable? + # (it probably is as it is a small modification of LAPACK's + # method) + dxk = A[knext,k] + dxx = real(A[knext,knext]) / dxk + dkk = real(A[k,k]) / conj(dxk) + t = 1 / (real(dkk * dxx) - 1) + dkx = t / conj(dxk) + dxk = t / dxk + end + for j in thisrange + wknext = dxk * (dkk*A[j,knext] - A[j,k]) + wk = dkx * (dxx*A[j,k] - A[j,knext]) + for i in irange(j) + A[i,j] -= (A[i,k]*conj_op(wk) + A[i,knext]*conj_op(wknext)) + end + A[j,k] = wk + A[j,knext] = wknext + # Force diagonal entry to be purely real, but still of + # complex type TS (I don't know why in LAPACK this + # case, unlike the rest, enforces a complex type + # explicitly). + diagreal_op(j) + end + end + end + # Store details of the interchanges in IPIV + if kstep == 1 + ipiv[k] = kp + else + ipiv[k] = -p + ipiv[knext] = -kp + end + # Decrease K and return to the start of the main loop + # k -= upper ? kstep : -kstep + if upper; k -= kstep; else; k += kstep; end + end; end + return A, ipiv, info +end + + +""" +generic_syconv(F, gettri::Bool=true) -> +(TLU<:Union{AbstractMatrix,Nothing}, e<:AbstractVector, + d<:Union{AbstractVector,Nothing}) + +`generic_syconv` takes the Bunch-Kaufman object `F` and returns the +block-diagonal factor `D`, and the triangular factor `L` (or `U`) if +requested. If the `L` or `U` factor is requested then both `L` (or `U`) and +the main diagonal of `D` will be stored in `TLU`, following LAPACK format, +and `d` will be set to `nothing`. `e` contains the first subdiagonal of +`D`. If the triangular factor is not requested, then `TLU` will not be set +to `nothing`, and the main diagonal of `D` will be stored in `d`. + +`gettri` is a `Bool`, indicating whether the `L` (or `U`) triangular factor +should be computed (`gettri==true`) or not (`gettri==false`). If the +triangular factor is required, a copy of `A.LD` will be created, and the +triangular factor will be computed in-place in said copy. +""" +function generic_syconv( + F::BunchKaufman{TS}, + gettri::Bool=true + ) where TS <: ClosedScalar{TR} where TR <: ClosedReal + + # Inputs must be 1-indexed; bounds may not be checked. + Base.require_one_based_indexing(F.LD, F.ipiv) + + # Extract necessary variables + A, ipiv, rook = gettri ? deepcopy(F.LD) : F.LD, F.ipiv, F.rook + + # Get size of matrix + N = size(A)[1] + + # Initialize off-diagonal and diagonal vector + e = Vector{TS}(undef, N) + d = gettri ? nothing : diag(A, 0) + + # Quick return if possible + if N == 0; return gettri ? A : nothing, e, d; end + + # Main loops + upper = (F.uplo == 'U') + @inline icond_d = upper ? i -> i > 1 : i -> i < N + @inline icond_T = upper ? i -> i >= 1 : i -> i <= N + @inline inext = upper ? i -> i - 1 : i -> i + 1 + # Convert VALUE + i = upper ? N : 1 + e[N+1-i] = 0 + while icond_d(i); @inbounds begin + if ipiv[i] < 0 + ix = inext(i) + e[i] = A[ix,i] + e[ix] = 0 + if gettri; A[ix,i] = 0; end + if upper; i -= 1; else; i += 1; end + else + e[i] = 0 + end + if upper; i -= 1; else; i += 1; end + end; end + # Convert PERMUTATIONS + if gettri + i = upper ? N : 1 + while icond_T(i); @inbounds begin + thisview = upper ? view(A, :, (i+1):N) : view(A, :, 1:(i-1)) + ip = ipiv[i] + if ip > 0 || rook + Base.swaprows!(thisview, abs(ip), i) + end + if ip <= 0 + ix = inext(i) + Base.swaprows!(thisview, -ipiv[ix], ix) + if upper; i -= 1; else; i += 1; end + end + if upper; i -= 1; else; i += 1; end + end; end + end + return gettri ? A : nothing, e, d +end + + +""" +generic_bksolve!(F, B) -> X<:AbstractVecOrMat + +`generic_bksolve!` solves a system of linear equations `A*X = B` where +the Bunch-Kaufman factorization of `A` is provided by `F`. +""" +function generic_bksolve!( + F::BunchKaufman{TS}, + B0::AbstractVecOrMat{TS}, + ) where TS <: ClosedScalar{TR} where TR <: ClosedReal + + # Inputs must be 1-indexed; bounds may not be checked. + Base.require_one_based_indexing(F.LD, F.ipiv, B0) + + # Get size of matrices + N = size(F.LD)[1] + if typeof(B0) <: AbstractVector + N3 = size(B0)[1] + M = 1 + B = view(B0, :, :) + else + N3, M = size(B0) + B = B0 + end + + # Initialize info integer as 0 + info = 0::BlasInt + + # Check input correctness + if N3 != N + info = (-2)::BlasInt + end + if info < 0 + arg_illegal("generic_bksolve!", -info, 'E') + end + + # Quick return if possible + if N == 0 || M == 0; return B; end + + # Extract necessary variables + A, ipiv, symm, rook = F.LD, F.ipiv, issymmetric(F), F.rook + + # Load the requested adjoining operator + adj_op = symm ? identity : conj + + R1 = TR(1) + upper = (F.uplo == 'U') + @inline kcond1 = upper ? k -> k >= 1 : k -> k <= N + @inline kcond2 = upper ? k -> k <= N : k -> k >= 1 + @inline knext = upper ? k -> k - 1 : k -> k + 1 + @inline knext2 = upper ? k -> k + 1 : k -> k - 1 + k = upper ? N : 1 + while kcond1(k); @inbounds begin + kp = ipiv[k] + if kp > 0 + # 1 x 1 diagonal block + # Interchange rows K and IPIV(K). + Base.swaprows!(B, k, kp) + # Multiply by inv(U(K)), where U(K) is the transformation + # stored in column K of A. + Aview = upper ? view(A, 1:(k-1), k) : view(A, (k+1):N, k) + Bview = upper ? B : view(B, (k+1):N, :) + generic_adr1!('F', -R1, Aview, view(B, k, :), Bview, 'S') + # Multiply by the inverse of the diagonal block. + s = symm ? 1 / A[k,k] : 1 / real(A[k,k]) + for j in 1:M; B[k,j] *= s; end + if upper; k -= 1; else; k += 1; end + else + # 2 x 2 diagonal block + # Interchange rows K and -IPIV(K) THEN K-1 and -IPIV(K-1) + # The first interchange is only needed when rook pivoting is used + if rook; Base.swaprows!(B, k, -kp); end + kx = knext(k) + Base.swaprows!(B, kx, -ipiv[kx]) + # Multiply by inv(U(K)), where U(K) is the transformation + # stored in columns K-1 and K of A. + Aview = upper ? view(A, 1:(k-2), k) : view(A, (k+2):N, k) + Bview = upper ? B : view(B, (k+2):N, :) + generic_adr1!('F', -R1, Aview, view(B, k, :), Bview, 'S') + Aview = upper ? view(A, 1:(k-2), kx) : view(A, (k+2):N, kx) + generic_adr1!('F', -R1, Aview, view(B, kx, :), Bview, 'S') + # Multiply by the inverse of the diagonal block. + axk = A[kx,k] + axx = A[kx,kx] / axk + akk = A[k,k] / adj_op(axk) + denom = axx*akk - 1 + for j in 1:M + bx = B[kx,j] / axk + bk = B[k,j] / adj_op(axk) + B[kx,j] = (akk*bx - bk) / denom + B[k,j] = (axx*bk - bx) / denom + end + if upper; k -= 2; else; k += 2; end + end + end; end + # Next solve U'*X = B, overwriting B with X. + # K is the main loop index, increasing from 1 to N in steps of + # 1 or 2, depending on the size of the diagonal blocks. + k = upper ? 1 : N + while kcond2(k); @inbounds begin + Aview = upper ? view(A, 1:(k-1), k) : view(A, (k+1):N, k) + Bview = upper ? view(B, 1:(k-1), :) : view(B, (k+1):N, :) + B_row = view(B, k, :) + kp = ipiv[k] + if kp > 0 + # 1 x 1 diagonal block + # Multiply by inv(U**T(K)), where U(K) is the transformation + # stored in column K of A. + if symm + generic_mvpv!('T', -R1, Bview, Aview, R1, B_row) + else + conj!(B_row) + generic_mvpv!('C', -R1, Bview, Aview, R1, B_row) + conj!(B_row) + end + # Interchange rows K and IPIV(K). + Base.swaprows!(B, k, kp) + if upper; k += 1; else; k -= 1; end + else + # 2 x 2 diagonal block + # Multiply by inv(U**T(K+1)), where U(K+1) is the transformation + # stored in columns K and K+1 of A. + kx = knext2(k) + if symm + generic_mvpv!('T', -R1, Bview, Aview, R1, B_row) + Aview = upper ? view(A, 1:(k-1), kx) : view(A, (k+1):N, kx) + B_row = view(B, kx, :) + generic_mvpv!('T', -R1, Bview, Aview, R1, B_row) + elseif k > 1 + conj!(B_row) + generic_mvpv!('C', -R1, Bview, Aview, R1, B_row) + conj!(B_row) + Aview = upper ? view(A, 1:(k-1), kx) : view(A, (k+1):N, kx) + B_row = view(B, kx, :) + conj!(B_row) + generic_mvpv!('C', -R1, Bview, Aview, R1, B_row) + conj!(B_row) + end + # Interchange rows K and -IPIV(K) THEN K+1 and -IPIV(K+1). + # The second interchange is only needed when rook pivoting is used + Base.swaprows!(B, k, -kp) + if rook; Base.swaprows!(B, kx, -ipiv[kx]); end + if upper; k += 2; else; k -= 2; end + end + end; end + return B +end + + +""" +inertia(B::BunchKaufman; atol::Real=0, rtol::Real=atol>0 ? 0 : n*ϵ) -> + np::Union{Nothing,Integer}, nn::Union{Nothing,Integer}, nz::Integer + +`inertia` computes the numerical inertia (the number of positive, +negative and zero eigenvalues, given by `np`, `nn` and `nz`, +respectively) of a real symmetric of Hermitian matrix `B` that has been +factored using the Bunch-Kaufman algorithm. For complex symmetric +matrices the inertia is not defined. in that case `np` and `nn` are set +to `nothing`, but the function still returns the number of zero +eigenvalues. The inertia is computed by counting the eigenvalues signs +of `B.D`. The number of zero eigenvalues is computed as the number of +estimated eigenvalues with complex 1-norm (defined as `|re(.)|+|im(.)|`) +less or equal than `max(atol, rtol*s₁)`, where `s₁` is an upper bound of +the largest singular value of `B.D`, `σ₁` (more specifically, +`0.5*s₁ <= σ₁ <= s₁` for real matrices and `0.35*s₁ <= σ₁ <= s₁` for +complex matrices). `atol` and `rtol` are the absolute and relative +tolerances, respectively. The default relative tolerance is `n*ϵ`, where +`n` is the size of of `A`, and `ϵ` is the [`eps`](@ref) of the number +type of `A`, if this type is a subtype of `AbstractFloat`. In any other +case (if the number type of `A` is `Rational`, for example) `ϵ` is set +to `0`. + +!!! note + Numerical inertia can be a sensitive and imprecise characterization of + ill-conditioned matrices with eigenvalues that are close in magnitude to the + threshold tolerance `max(atol, rtol*s₁)`. In such cases, slight perturbations + to the Bunch-Kaufman computation or to the matrix can change the result of + `rank` by pushing one or more eigenvalues across the threshold. These + variations can even occur due to changes in floating-point errors between + different Julia versions, architectures, compilers, or operating systems. + In particular, the size of the entries of the tringular factor directly + influende the scale of the eigenvalues of the diagonal factor, so it is + strongly recommended to use rook pivoting is the inertia is going to be + computed. + On the other hand, if the matrix has rational entries, the inertia + computation is guaranteed is to be exact, as long as there is no + under/overflow in the underlying integer type (and in such cases Julia itself + throws an error), or a positive tolerance (absolute or relative) is + specified. +""" +function inertia(B::BunchKaufman{TS}; + atol::TR = TR(0), + rtol::TR = TR(0) + ) where TS <: ClosedScalar{TR} where TR <: ClosedReal + + # Check if matrix is complex symmetric + get_inertia = !(issymmetric(B) && TS <: Complex) + + # Initialize outputs + np, nn, nz = get_inertia ? (0, 0, 0) : (nothing, nothing, 0) + + # Compute matrix size + N = size(B, 1) + + # Quick return if possible + if N == 0; return np, nn, nz; end + + # Compute default relative tolerance + if rtol <= 0 && atol <= 0 + rtol = TR <: AbstractFloat ? (N * eps(TR)) : TR(0) + end + + # We use the complex 1-norm for complex matrices + real_matrix = (TS <: Real) + abs1_fun = real_matrix ? abs : cabs1 + real_fun = real_matrix ? identity : real + + # Check if we must track the largest singular value + get_s1 = (rtol > 0) + + # Constant for lower bound estimation of the smallest eigenvalue in 2x2 blocks. + # The best (largest) value for complex matrices is 1/sqrt(2), but for rational + # matrices we use the small denominator approximation 12/17, in order to not + # increase the denominator size too much in computations. The error of this + # approximation is ≤0.2%, and we still get a valid lower bound. + c = real_matrix ? TR(1) : (TR <: AbstractFloat ? 1/sqrt(TR(2)) : TR(12//17)) + + # First pass, estimate largest singular value and group together size-1 blocks + D = B.D + s1 = TR(0) + i = 1 + while i <= N; @inbounds begin + if i < N && D[i,i+1] != 0 + # 2x2 block + # The largest singular value of a 2x2 matrix is between [1, 2] times + # its complex max-norm, which is between [c, 1] times the largest + # complex 1-norm among the entries of the 2x2 matrix. See "Roger + # Horn and Charles Johnson. Matrix Analysis, 2nd Edition, 5.6.P23". + abs_Dii = abs1_fun(D[i,i]) + abs_Dxx = abs1_fun(D[i+1,i+1]) + s1_block = 2 * max(abs_Dii, abs1_fun(D[i,i+1]), abs_Dxx) + if get_s1; s1 = max(s1, s1_block); end + # Lower bound on the smallest eigenvalue complex 2-norm is + # abs(λ₂) ≥ abs(det(block)) / s1_block + # so the bound in terms of the complex 1-norm becomes + # abs1_fun(λ₂) ≥ c * abs1_fun(det(block)) / s1_block + # For rational matrices, if λ₂=0 then det(block)=0 and then the bound + # becomes zero too. If λ₁=0 too then the block has all zero entries + # and 's1_block'=0, but 'D[i,i+1]' != 0 and so 's1_block' > 0. However, we + # may still have that 'smin_block'≈0, then the value of 'smin_block' may not + # be accurate. In that case the counting routine will detect that both + # eigenvalues are zero without using 'smin_block', so it doesn't matter. + # TODO: is this the most numerically stable way to compute the determinant? + # TODO: is this the best way to avoid under/overflow? + if abs_Dii >= abs_Dxx + smin_block = c * abs1_fun((D[i,i]/s1_block)*D[i+1,i+1] - + (D[i,i+1]/s1_block)*D[i+1,i]) + else + smin_block = c * abs1_fun(D[i,i]*(D[i+1,i+1]/s1_block) - + (D[i,i+1]/s1_block)*D[i+1,i]) + end + # Store lower bound in-place in the lower off-diagonal and upper bound + # in-place in the upper off-diagonal. The trace is stored in the first + # diagonal entry block, but only if the full inertia is needed. + D[i,i+1] = s1_block + D[i+1,i] = smin_block + if get_inertia; D[i,i] += D[i+1,i+1]; end + i += 2 + else + # 1x1 block + if get_s1; s1 = max(s1, abs1_fun(D[i,i])); end + i += 1 + end + end; end + + # Second pass, count eigenvalue signs + tol = max(atol, rtol * s1) + i = 1 + while i <= N; @inbounds begin + if i < N && D[i,i+1] != 0 + # 2x2 block. For the counting of zero eigenvalues we use the lower bound on the + # eigenvalues' magnitude. This way, if an eigenvalue is deemed non-zero, then + # it is guaranteed that its magnitude is greater than the tolerance. + s1_block = real_fun(D[i,i+1]) + if (c / 2) * s1_block <= tol + # Lower bound of largest eigenvalue is smaller than the tolerance, + # we consider the both eigenvalues of this block to be zero. + nz += 2 + i += 2 + continue + end + # Reaching this part of the lopp implies that 's1_block' != 0. + smin_block = real_fun(D[i+1,i]) + trace_block = real_fun(D[i,i]) + if smin_block > tol || trace_block == 0 + # If first condition holds then the lower bound of the smallest eigenvalue + # is larger than the tolerance. If the second condition holds then the trace + # is exactly zero, so both eigenvalues have the same magnitude, and we + # already know that the largest one is non-zero. In any case we conclude + # that both eigenvalues are non-zero. + if get_inertia + # The eigenvalues of a 2x2 block are guaranteed to be a + # positive-negative pair. + np += 1 + nn += 1 + end + else + # The lower bound of smallest eigenvalue is smaller than the tolerance and + # the trace is non-zero, so we consider the smallest eigenvalues of this + # block to be zero. + nz += 1 + if get_inertia + # The trace is non-zero, and its sign is the same of the largest + # eigenvalue. + if trace_block >= 0 + np += 1 + else + nn += 1 + end + end + end + i += 2 + else + # 1x1 block + if get_inertia + eig = real_fun(D[i,i]) + if eig > tol + np += 1 + elseif eig < -tol + nn += 1 + else + nz += 1 + end + elseif abs1_fun(D[i,i]) <= tol + nz += 1 + end + i += 1 + end + end; end + + return np, nn, nz +end + + +""" + bunchkaufman_native!(A, rook::Bool=false; check = true) -> BunchKaufman + +`bunchkaufman_native!` is the same as [`bunchkaufman!`](@ref), but it performs +the factorization in native Julia code instead of calling LAPACK. +""" +function bunchkaufman_native!(A::AbstractMatrix{TS}, + rook::Bool = false; + check::Bool = true, + ) where TS <: ClosedScalar{TR} where TR <: ClosedReal + if A isa RealHermSymComplexSym{TR} + syhe = 'S' + elseif ishermitian(A) + syhe = 'H' + elseif issymmetric(A) + syhe = 'S' + else + throw(ArgumentError("Bunch-Kaufman decomposition is only valid for " * + "symmetric or Hermitian matrices")) + end + if A isa HermOrSym + Adata = A.data + uplo = A.uplo + else + Adata = A + uplo = 'U' + end + LD, ipiv, info = generic_bunchkaufman!(uplo, Adata, syhe, rook) + check && checknonsingular(info) + return BunchKaufman(LD, ipiv, uplo, syhe == 'S', rook, info) +end + + +""" +Overload 'bunchkaufman.jl' methods through multiple dispatch +""" + +function bunchkaufman!(A::AbstractMatrix{TS}, + rook::Bool = false; + check::Bool = true + ) where TS <: ClosedScalar{TR} where TR <: ClosedReal + return bunchkaufman_native!(A, rook; check) +end + +function bunchkaufman(A::AbstractMatrix{TS}, + rook::Bool = false; + check::Bool = true + ) where TS <: ClosedScalar{TR} where TR <: ClosedReal + return bunchkaufman!(eigencopy_oftype(A, TS), rook; check) +end + +function bunchkaufman(A::AbstractMatrix{TS}, + rook::Bool = false; + check::Bool = true + ) where TS <:Union{TI, Complex{TI}} where TI <: Integer + + # Identity whether matrix is symmetric or Hermitian or none + if A isa Symmetric + TA = Symmetric + elseif A isa Hermitian + TA = Hermitian + else + TA = Nothing + end + + # Create a rational copy of input integer matrix, as the Bunch-Kaufman + # algorithm is closed over the rationals but not over the integers. + # We promote input to BigInt to avoid overflow problems + if TA == Nothing + if TS <: Integer + M = Rational{BigInt}.(eigencopy_oftype(A, TS)) + else + M = Complex{Rational{BigInt}}.(eigencopy_oftype(A, TS)) + end + else + if TS <: Integer + M = TA(Rational{BigInt}.(eigencopy_oftype(A, TS)), Symbol(A.uplo)) + else + M = TA(Complex{Rational{BigInt}}.(eigencopy_oftype(A, TS)), + Symbol(A.uplo)) + end + end + + return bunchkaufman_native!(M, rook; check) +end + +function ldiv!(B::BunchKaufman{TS}, + R::AbstractVecOrMat{TS} + ) where TS <: ClosedScalar{TR} where TR <: ClosedReal + return generic_bksolve!(B, R) +end + +function inv(B::BunchKaufman{TS}) where TS <: ClosedScalar{TR} where TR <: ClosedReal + # I don't think there's value in implementing tha LAPACK in-place inverse + # functions `dsytri`, `chetri`, etc., unless of course an efficient + # in-place inverse function `inv!` is needed. + # TODO: reduce the operation count of the inverse by not computing the + # lower/upper triangular part. + if issymmetric(B) + return copytri!(B \ I, B.uplo) + else + return copytri!(B \ I, B.uplo, true) + end +end diff --git a/stdlib/LinearAlgebra/src/cholesky.jl b/stdlib/LinearAlgebra/src/cholesky.jl index 528eca5c3d8a3..c28613041e18a 100644 --- a/stdlib/LinearAlgebra/src/cholesky.jl +++ b/stdlib/LinearAlgebra/src/cholesky.jl @@ -427,7 +427,7 @@ The following functions are available for `CholeskyPivoted` objects: [`size`](@ref), [`\\`](@ref), [`inv`](@ref), [`det`](@ref), and [`rank`](@ref). The argument `tol` determines the tolerance for determining the rank. -For negative values, the tolerance is the machine precision. +For negative values, the tolerance is equal to `eps()*size(A,1)*maximum(diag(A))`. If you have a matrix `A` that is slightly non-Hermitian due to roundoff errors in its construction, wrap it in `Hermitian(A)` before passing it to `cholesky` in order to treat it as perfectly Hermitian. diff --git a/stdlib/LinearAlgebra/src/dense.jl b/stdlib/LinearAlgebra/src/dense.jl index 8d5a3e6ea8de2..47839fc6fd003 100644 --- a/stdlib/LinearAlgebra/src/dense.jl +++ b/stdlib/LinearAlgebra/src/dense.jl @@ -14,6 +14,7 @@ const NRM2_CUTOFF = 32 const ISONE_CUTOFF = 2^21 # 2M function isone(A::AbstractMatrix) + require_one_based_indexing(A) # multiplication not defined yet among offset matrices m, n = size(A) m != n && return false # only square matrices can satisfy x == one(x) if sizeof(A) < ISONE_CUTOFF @@ -106,6 +107,11 @@ norm1(x::Union{Array{T},StridedVector{T}}) where {T<:BlasReal} = norm2(x::Union{Array{T},StridedVector{T}}) where {T<:BlasFloat} = length(x) < NRM2_CUTOFF ? generic_norm2(x) : BLAS.nrm2(x) +# Conservative assessment of types that have zero(T) defined for themselves +haszero(::Type) = false +haszero(::Type{T}) where {T<:Number} = isconcretetype(T) +@propagate_inbounds _zero(M::AbstractArray{T}, i, j) where {T} = haszero(T) ? zero(T) : zero(M[i,j]) + """ triu!(M, k::Integer) @@ -136,7 +142,7 @@ function triu!(M::AbstractMatrix, k::Integer) m, n = size(M) for j in 1:min(n, m + k) for i in max(1, j - k + 1):m - M[i,j] = zero(M[i,j]) + @inbounds M[i,j] = _zero(M, i,j) end end M @@ -173,12 +179,13 @@ function tril!(M::AbstractMatrix, k::Integer) require_one_based_indexing(M) m, n = size(M) for j in max(1, k + 1):n - @inbounds for i in 1:min(j - k - 1, m) - M[i,j] = zero(M[i,j]) + for i in 1:min(j - k - 1, m) + @inbounds M[i,j] = _zero(M, i,j) end end M end + tril(M::Matrix, k::Integer) = tril!(copy(M), k) """ @@ -198,13 +205,27 @@ function fillband!(A::AbstractMatrix{T}, x, l, u) where T return A end -diagind(m::Integer, n::Integer, k::Integer=0) = +diagind(m::Integer, n::Integer, k::Integer=0) = diagind(IndexLinear(), m, n, k) +diagind(::IndexLinear, m::Integer, n::Integer, k::Integer=0) = k <= 0 ? range(1-k, step=m+1, length=min(m+k, n)) : range(k*m+1, step=m+1, length=min(m, n-k)) +function diagind(::IndexCartesian, m::Integer, n::Integer, k::Integer=0) + Cstart = CartesianIndex(1 + max(0,-k), 1 + max(0,k)) + Cstep = CartesianIndex(1, 1) + length = max(0, k <= 0 ? min(m+k, n) : min(m, n-k)) + StepRangeLen(Cstart, Cstep, length) +end + """ - diagind(M, k::Integer=0) + diagind(M::AbstractMatrix, k::Integer = 0, indstyle::IndexStyle = IndexLinear()) + diagind(M::AbstractMatrix, indstyle::IndexStyle = IndexLinear()) An `AbstractRange` giving the indices of the `k`th diagonal of the matrix `M`. +Optionally, an index style may be specified which determines the type of the range returned. +If `indstyle isa IndexLinear` (default), this returns an `AbstractRange{Integer}`. +On the other hand, if `indstyle isa IndexCartesian`, this returns an `AbstractRange{CartesianIndex{2}}`. + +If `k` is not provided, it is assumed to be `0` (corresponding to the main diagonal). See also: [`diag`](@ref), [`diagm`](@ref), [`Diagonal`](@ref). @@ -216,15 +237,23 @@ julia> A = [1 2 3; 4 5 6; 7 8 9] 4 5 6 7 8 9 -julia> diagind(A,-1) +julia> diagind(A, -1) 2:4:6 + +julia> diagind(A, IndexCartesian()) +StepRangeLen(CartesianIndex(1, 1), CartesianIndex(1, 1), 3) ``` + +!!! compat "Julia 1.11" + Specifying an `IndexStyle` requires at least Julia 1.11. """ -function diagind(A::AbstractMatrix, k::Integer=0) +function diagind(A::AbstractMatrix, k::Integer=0, indexstyle::IndexStyle = IndexLinear()) require_one_based_indexing(A) - diagind(size(A,1), size(A,2), k) + diagind(indexstyle, size(A,1), size(A,2), k) end +diagind(A::AbstractMatrix, indexstyle::IndexStyle) = diagind(A, 0, indexstyle) + """ diag(M, k::Integer=0) @@ -246,7 +275,7 @@ julia> diag(A,1) 6 ``` """ -diag(A::AbstractMatrix, k::Integer=0) = A[diagind(A,k)] +diag(A::AbstractMatrix, k::Integer=0) = A[diagind(A, k, IndexStyle(A))] """ diagm(kv::Pair{<:Integer,<:AbstractVector}...) @@ -308,7 +337,7 @@ function diagm_size(size::Tuple{Int,Int}, kv::Pair{<:Integer,<:AbstractVector}.. mmax = mapreduce(x -> length(x.second) - min(0,Int(x.first)), max, kv; init=0) nmax = mapreduce(x -> length(x.second) + max(0,Int(x.first)), max, kv; init=0) m, n = size - (m ≥ mmax && n ≥ nmax) || throw(DimensionMismatch("invalid size=$size")) + (m ≥ mmax && n ≥ nmax) || throw(DimensionMismatch(lazy"invalid size=$size")) return m, n end function diagm_container(size, kv::Pair{<:Integer,<:AbstractVector}...) @@ -463,8 +492,8 @@ julia> reshape(kron(v,w), (length(w), length(v))) ``` """ function kron(A::AbstractVecOrMat{T}, B::AbstractVecOrMat{S}) where {T,S} - R = Matrix{promote_op(*,T,S)}(undef, _kronsize(A, B)) - return kron!(R, A, B) + C = Matrix{promote_op(*,T,S)}(undef, _kronsize(A, B)) + return kron!(C, A, B) end function kron(a::AbstractVector{T}, b::AbstractVector{S}) where {T,S} c = Vector{promote_op(*,T,S)}(undef, length(a)*length(b)) @@ -490,7 +519,7 @@ end function schurpow(A::AbstractMatrix, p) if istriu(A) # Integer part - retmat = A ^ floor(p) + retmat = A ^ floor(Integer, p) # Real part if p - floor(p) == 0.5 # special case: A^0.5 === sqrt(A) @@ -501,7 +530,7 @@ function schurpow(A::AbstractMatrix, p) else S,Q,d = Schur{Complex}(schur(A)) # Integer part - R = S ^ floor(p) + R = S ^ floor(Integer, p) # Real part if p - floor(p) == 0.5 # special case: A^0.5 === sqrt(A) @@ -907,6 +936,54 @@ end sqrt(A::AdjointAbsMat) = adjoint(sqrt(parent(A))) sqrt(A::TransposeAbsMat) = transpose(sqrt(parent(A))) +""" + cbrt(A::AbstractMatrix{<:Real}) + +Computes the real-valued cube root of a real-valued matrix `A`. If `T = cbrt(A)`, then +we have `T*T*T ≈ A`, see example given below. + +If `A` is symmetric, i.e., of type `HermOrSym{<:Real}`, then ([`eigen`](@ref)) is used to +find the cube root. Otherwise, a specialized version of the p-th root algorithm [^S03] is +utilized, which exploits the real-valued Schur decomposition ([`schur`](@ref)) +to compute the cube root. + +[^S03]: + + Matthew I. Smith, "A Schur Algorithm for Computing Matrix pth Roots", + SIAM Journal on Matrix Analysis and Applications, vol. 24, 2003, pp. 971–989. + [doi:10.1137/S0895479801392697](https://doi.org/10.1137/s0895479801392697) + +# Examples +```jldoctest +julia> A = [0.927524 -0.15857; -1.3677 -1.01172] +2×2 Matrix{Float64}: + 0.927524 -0.15857 + -1.3677 -1.01172 + +julia> T = cbrt(A) +2×2 Matrix{Float64}: + 0.910077 -0.151019 + -1.30257 -0.936818 + +julia> T*T*T ≈ A +true +``` +""" +function cbrt(A::AbstractMatrix{<:Real}) + if checksquare(A) == 0 + return copy(A) + elseif issymmetric(A) + return cbrt(Symmetric(A, :U)) + else + S = schur(A) + return S.Z * _cbrt_quasi_triu!(S.T) * S.Z' + end +end + +# Cube roots of adjoint and transpose matrices +cbrt(A::AdjointAbsMat) = adjoint(cbrt(parent(A))) +cbrt(A::TransposeAbsMat) = transpose(cbrt(parent(A))) + function inv(A::StridedMatrix{T}) where T checksquare(A) if istriu(A) @@ -1569,7 +1646,7 @@ function cond(A::AbstractMatrix, p::Real=2) end end end - throw(ArgumentError("p-norm must be 1, 2 or Inf, got $p")) + throw(ArgumentError(lazy"p-norm must be 1, 2 or Inf, got $p")) end ## Lyapunov and Sylvester equation diff --git a/stdlib/LinearAlgebra/src/diagonal.jl b/stdlib/LinearAlgebra/src/diagonal.jl index c58f2f8d3b665..070edc39eab3a 100644 --- a/stdlib/LinearAlgebra/src/diagonal.jl +++ b/stdlib/LinearAlgebra/src/diagonal.jl @@ -10,7 +10,6 @@ struct Diagonal{T,V<:AbstractVector{T}} <: AbstractMatrix{T} new{T,V}(diag) end end -Diagonal{T,V}(d::Diagonal) where {T,V<:AbstractVector{T}} = Diagonal{T,V}(d.diag) Diagonal(v::AbstractVector{T}) where {T} = Diagonal{T,typeof(v)}(v) Diagonal{T}(v::AbstractVector) where {T} = Diagonal(convert(AbstractVector{T}, v)::AbstractVector{T}) @@ -102,6 +101,7 @@ julia> Diagonal(A) """ Diagonal(A::AbstractMatrix) = Diagonal(diag(A)) Diagonal{T}(A::AbstractMatrix) where T = Diagonal{T}(diag(A)) +Diagonal{T,V}(A::AbstractMatrix) where {T,V<:AbstractVector{T}} = Diagonal{T,V}(diag(A)) function convert(::Type{T}, A::AbstractMatrix) where T<:Diagonal checksquare(A) isdiag(A) ? T(A) : throw(InexactError(:convert, T, A)) @@ -116,11 +116,12 @@ AbstractMatrix{T}(D::Diagonal{T}) where {T} = copy(D) Matrix(D::Diagonal{T}) where {T} = Matrix{promote_type(T, typeof(zero(T)))}(D) Array(D::Diagonal{T}) where {T} = Matrix(D) function Matrix{T}(D::Diagonal) where {T} - n = size(D, 1) - B = Matrix{T}(undef, n, n) - n > 1 && fill!(B, zero(T)) - @inbounds for i in 1:n - B[i,i] = D.diag[i] + B = Matrix{T}(undef, size(D)) + if haszero(T) # optimized path for types with zero(T) defined + size(B,1) > 1 && fill!(B, zero(T)) + copyto!(view(B, diagind(B)), D.diag) + else + copyto!(B, D) end return B end @@ -161,6 +162,18 @@ end r end +function Base.minimum(D::Diagonal{T}) where T <: Number + mindiag = minimum(D.diag) + size(D, 1) > 1 && return (min(zero(T), mindiag)) + return mindiag +end + +function Base.maximum(D::Diagonal{T}) where T <: Number + maxdiag = Base.maximum(D.diag) + size(D, 1) > 1 && return (max(zero(T), maxdiag)) + return maxdiag +end + @inline function getindex(D::Diagonal, i::Int, j::Int) @boundscheck checkbounds(D, i, j) if i == j @@ -178,7 +191,7 @@ function setindex!(D::Diagonal, v, i::Int, j::Int) if i == j @inbounds D.diag[i] = v elseif !iszero(v) - throw(ArgumentError("cannot set off-diagonal entry ($i, $j) to a nonzero value ($v)")) + throw(ArgumentError(lazy"cannot set off-diagonal entry ($i, $j) to a nonzero value ($v)")) end return v end @@ -191,6 +204,8 @@ end parent(D::Diagonal) = D.diag +copy(D::Diagonal) = Diagonal(copy(D.diag)) + ishermitian(D::Diagonal{<:Real}) = true ishermitian(D::Diagonal{<:Number}) = isreal(D.diag) ishermitian(D::Diagonal) = all(ishermitian, D.diag) @@ -265,13 +280,13 @@ Base.literal_pow(::typeof(^), D::Diagonal, ::Val{-1}) = inv(D) # for disambiguat function _muldiag_size_check(A, B) nA = size(A, 2) mB = size(B, 1) - @noinline throw_dimerr(::AbstractMatrix, nA, mB) = throw(DimensionMismatch("second dimension of A, $nA, does not match first dimension of B, $mB")) - @noinline throw_dimerr(::AbstractVector, nA, mB) = throw(DimensionMismatch("second dimension of D, $nA, does not match length of V, $mB")) + @noinline throw_dimerr(::AbstractMatrix, nA, mB) = throw(DimensionMismatch(lazy"second dimension of A, $nA, does not match first dimension of B, $mB")) + @noinline throw_dimerr(::AbstractVector, nA, mB) = throw(DimensionMismatch(lazy"second dimension of D, $nA, does not match length of V, $mB")) nA == mB || throw_dimerr(B, nA, mB) return nothing end # the output matrix should have the same size as the non-diagonal input matrix or vector -@noinline throw_dimerr(szC, szA) = throw(DimensionMismatch("output matrix has size: $szC, but should have size $szA")) +@noinline throw_dimerr(szC, szA) = throw(DimensionMismatch(lazy"output matrix has size: $szC, but should have size $szA")) _size_check_out(C, ::Diagonal, A) = _size_check_out(C, A) _size_check_out(C, A, ::Diagonal) = _size_check_out(C, A) _size_check_out(C, A::Diagonal, ::Diagonal) = _size_check_out(C, A) @@ -296,27 +311,9 @@ function (*)(D::Diagonal, V::AbstractVector) return D.diag .* V end -(*)(A::AbstractMatrix, D::Diagonal) = - mul!(similar(A, promote_op(*, eltype(A), eltype(D.diag))), A, D) -(*)(A::HermOrSym, D::Diagonal) = - mul!(similar(A, promote_op(*, eltype(A), eltype(D.diag)), size(A)), A, D) -(*)(D::Diagonal, A::AbstractMatrix) = - mul!(similar(A, promote_op(*, eltype(D.diag), eltype(A))), D, A) -(*)(D::Diagonal, A::HermOrSym) = - mul!(similar(A, promote_op(*, eltype(A), eltype(D.diag)), size(A)), D, A) - rmul!(A::AbstractMatrix, D::Diagonal) = @inline mul!(A, A, D) lmul!(D::Diagonal, B::AbstractVecOrMat) = @inline mul!(B, D, B) -function (*)(A::AdjOrTransAbsMat, D::Diagonal) - Ac = copy_similar(A, promote_op(*, eltype(A), eltype(D.diag))) - rmul!(Ac, D) -end -function (*)(D::Diagonal, A::AdjOrTransAbsMat) - Ac = copy_similar(A, promote_op(*, eltype(A), eltype(D.diag))) - lmul!(D, Ac) -end - function __muldiag!(out, D::Diagonal, B, _add::MulAddMul{ais1,bis0}) where {ais1,bis0} require_one_based_indexing(out, B) alpha, beta = _add.alpha, _add.beta @@ -426,8 +423,7 @@ function (*)(Da::Diagonal, Db::Diagonal, Dc::Diagonal) return Diagonal(Da.diag .* Db.diag .* Dc.diag) end -/(A::AbstractVecOrMat, D::Diagonal) = _rdiv!(similar(A, _init_eltype(/, eltype(A), eltype(D))), A, D) -/(A::HermOrSym, D::Diagonal) = _rdiv!(similar(A, _init_eltype(/, eltype(A), eltype(D)), size(A)), A, D) +/(A::AbstractVecOrMat, D::Diagonal) = _rdiv!(matprod_dest(A, D, promote_op(/, eltype(A), eltype(D))), A, D) rdiv!(A::AbstractVecOrMat, D::Diagonal) = @inline _rdiv!(A, A, D) # avoid copy when possible via internal 3-arg backend @@ -436,7 +432,7 @@ function _rdiv!(B::AbstractVecOrMat, A::AbstractVecOrMat, D::Diagonal) dd = D.diag m, n = size(A, 1), size(A, 2) if (k = length(dd)) != n - throw(DimensionMismatch("left hand side has $n columns but D is $k by $k")) + throw(DimensionMismatch(lazy"left hand side has $n columns but D is $k by $k")) end @inbounds for j in 1:n ddj = dd[j] @@ -453,8 +449,7 @@ function \(D::Diagonal, B::AbstractVector) isnothing(j) || throw(SingularException(j)) return D.diag .\ B end -\(D::Diagonal, B::AbstractMatrix) = ldiv!(similar(B, _init_eltype(\, eltype(D), eltype(B))), D, B) -\(D::Diagonal, B::HermOrSym) = ldiv!(similar(B, _init_eltype(\, eltype(D), eltype(B)), size(B)), D, B) +\(D::Diagonal, B::AbstractMatrix) = ldiv!(matprod_dest(D, B, promote_op(\, eltype(D), eltype(B))), D, B) ldiv!(D::Diagonal, B::AbstractVecOrMat) = @inline ldiv!(B, D, B) function ldiv!(B::AbstractVecOrMat, D::Diagonal, A::AbstractVecOrMat) @@ -463,8 +458,8 @@ function ldiv!(B::AbstractVecOrMat, D::Diagonal, A::AbstractVecOrMat) d = length(dd) m, n = size(A, 1), size(A, 2) m′, n′ = size(B, 1), size(B, 2) - m == d || throw(DimensionMismatch("right hand side has $m rows but D is $d by $d")) - (m, n) == (m′, n′) || throw(DimensionMismatch("expect output to be $m by $n, but got $m′ by $n′")) + m == d || throw(DimensionMismatch(lazy"right hand side has $m rows but D is $d by $d")) + (m, n) == (m′, n′) || throw(DimensionMismatch(lazy"expect output to be $m by $n, but got $m′ by $n′")) j = findfirst(iszero, D.diag) isnothing(j) || throw(SingularException(j)) @inbounds for j = 1:n, i = 1:m @@ -473,12 +468,9 @@ function ldiv!(B::AbstractVecOrMat, D::Diagonal, A::AbstractVecOrMat) B end -# Optimizations for \, / between Diagonals -\(D::Diagonal, B::Diagonal) = ldiv!(similar(B, promote_op(\, eltype(D), eltype(B))), D, B) -/(A::Diagonal, D::Diagonal) = _rdiv!(similar(A, promote_op(/, eltype(A), eltype(D))), A, D) function _rdiv!(Dc::Diagonal, Db::Diagonal, Da::Diagonal) n, k = length(Db.diag), length(Da.diag) - n == k || throw(DimensionMismatch("left hand side has $n columns but D is $k by $k")) + n == k || throw(DimensionMismatch(lazy"left hand side has $n columns but D is $k by $k")) j = findfirst(iszero, Da.diag) isnothing(j) || throw(SingularException(j)) Dc.diag .= Db.diag ./ Da.diag @@ -506,10 +498,10 @@ function ldiv!(T::Tridiagonal, D::Diagonal, S::Union{SymTridiagonal,Tridiagonal} m = size(S, 1) dd = D.diag if (k = length(dd)) != m - throw(DimensionMismatch("diagonal matrix is $k by $k but right hand side has $m rows")) + throw(DimensionMismatch(lazy"diagonal matrix is $k by $k but right hand side has $m rows")) end if length(T.d) != m - throw(DimensionMismatch("target matrix size $(size(T)) does not match input matrix size $(size(S))")) + throw(DimensionMismatch(lazy"target matrix size $(size(T)) does not match input matrix size $(size(S))")) end m == 0 && return T j = findfirst(iszero, dd) @@ -538,15 +530,15 @@ function (/)(S::SymTridiagonal, D::Diagonal) dl = similar(S.ev, T, max(length(S.dv)-1, 0)) _rdiv!(Tridiagonal(dl, d, du), S, D) end -(/)(T::Tridiagonal, D::Diagonal) = _rdiv!(similar(T, promote_op(/, eltype(T), eltype(D))), T, D) +(/)(T::Tridiagonal, D::Diagonal) = _rdiv!(matprod_dest(T, D, promote_op(/, eltype(T), eltype(D))), T, D) function _rdiv!(T::Tridiagonal, S::Union{SymTridiagonal,Tridiagonal}, D::Diagonal) n = size(S, 2) dd = D.diag if (k = length(dd)) != n - throw(DimensionMismatch("left hand side has $n columns but D is $k by $k")) + throw(DimensionMismatch(lazy"left hand side has $n columns but D is $k by $k")) end if length(T.d) != n - throw(DimensionMismatch("target matrix size $(size(T)) does not match input matrix size $(size(S))")) + throw(DimensionMismatch(lazy"target matrix size $(size(T)) does not match input matrix size $(size(S))")) end n == 0 && return T j = findfirst(iszero, dd) @@ -616,7 +608,7 @@ end valB = B.diag; nB = length(valB) nC = checksquare(C) @boundscheck nC == nA*nB || - throw(DimensionMismatch("expect C to be a $(nA*nB)x$(nA*nB) matrix, got size $(nC)x$(nC)")) + throw(DimensionMismatch(lazy"expect C to be a $(nA*nB)x$(nA*nB) matrix, got size $(nC)x$(nC)")) isempty(A) || isempty(B) || fill!(C, zero(A[1,1] * B[1,1])) @inbounds for i = 1:nA, j = 1:nB idx = (i-1)*nB+j @@ -647,7 +639,7 @@ end (mB, nB) = size(B) (mC, nC) = size(C) @boundscheck (mC, nC) == (mA * mB, nA * nB) || - throw(DimensionMismatch("expect C to be a $(mA * mB)x$(nA * nB) matrix, got size $(mC)x$(nC)")) + throw(DimensionMismatch(lazy"expect C to be a $(mA * mB)x$(nA * nB) matrix, got size $(mC)x$(nC)")) isempty(A) || isempty(B) || fill!(C, zero(A[1,1] * B[1,1])) m = 1 @inbounds for j = 1:nA @@ -670,7 +662,7 @@ end (mB, nB) = size(B) (mC, nC) = size(C) @boundscheck (mC, nC) == (mA * mB, nA * nB) || - throw(DimensionMismatch("expect C to be a $(mA * mB)x$(nA * nB) matrix, got size $(mC)x$(nC)")) + throw(DimensionMismatch(lazy"expect C to be a $(mA * mB)x$(nA * nB) matrix, got size $(mC)x$(nC)")) isempty(A) || isempty(B) || fill!(C, zero(A[1,1] * B[1,1])) m = 1 @inbounds for j = 1:nA @@ -724,6 +716,9 @@ for f in (:exp, :cis, :log, :sqrt, @eval $f(D::Diagonal) = Diagonal($f.(D.diag)) end +# Cube root of a real-valued diagonal matrix +cbrt(A::Diagonal{<:Real}) = Diagonal(cbrt.(A.diag)) + function inv(D::Diagonal{T}) where T Di = similar(D.diag, typeof(inv(oneunit(T)))) for i = 1:length(D.diag) @@ -868,9 +863,6 @@ function svd(D::Diagonal{T}) where {T<:Number} return SVD(U, S, Vt) end -# disambiguation methods: * and / of Diagonal and Adj/Trans AbsVec -*(u::AdjointAbsVec, D::Diagonal) = (D'u')' -*(u::TransposeAbsVec, D::Diagonal) = transpose(transpose(D) * transpose(u)) *(x::AdjointAbsVec, D::Diagonal, y::AbstractVector) = _mapreduce_prod(*, x, D, y) *(x::TransposeAbsVec, D::Diagonal, y::AbstractVector) = _mapreduce_prod(*, x, D, y) /(u::AdjointAbsVec, D::Diagonal) = (D' \ u')' @@ -883,15 +875,15 @@ dot(x::AbstractVector, D::Diagonal, y::AbstractVector) = _mapreduce_prod(dot, x, dot(A::Diagonal, B::Diagonal) = dot(A.diag, B.diag) function dot(D::Diagonal, B::AbstractMatrix) - size(D) == size(B) || throw(DimensionMismatch("Matrix sizes $(size(D)) and $(size(B)) differ")) - return dot(D.diag, view(B, diagind(B))) + size(D) == size(B) || throw(DimensionMismatch(lazy"Matrix sizes $(size(D)) and $(size(B)) differ")) + return dot(D.diag, view(B, diagind(B, IndexStyle(B)))) end dot(A::AbstractMatrix, B::Diagonal) = conj(dot(B, A)) function _mapreduce_prod(f, x, D::Diagonal, y) if !(length(x) == length(D.diag) == length(y)) - throw(DimensionMismatch("x has length $(length(x)), D has size $(size(D)), and y has $(length(y))")) + throw(DimensionMismatch(lazy"x has length $(length(x)), D has size $(size(D)), and y has $(length(y))")) end if isempty(x) && isempty(D) && isempty(y) return zero(promote_op(f, eltype(x), eltype(D), eltype(y))) diff --git a/stdlib/LinearAlgebra/src/eigen.jl b/stdlib/LinearAlgebra/src/eigen.jl index 489bfa4665c7a..d1d2a156b1ed4 100644 --- a/stdlib/LinearAlgebra/src/eigen.jl +++ b/stdlib/LinearAlgebra/src/eigen.jl @@ -173,7 +173,8 @@ function eigen!(A::StridedMatrix{T}; permute::Bool=true, scale::Bool=true, sortb n = size(A, 2) n == 0 && return Eigen(zeros(T, 0), zeros(T, 0, 0)) ishermitian(A) && return eigen!(Hermitian(A), sortby=sortby) - eval, evec = LAPACK.geevx!(permute ? (scale ? 'B' : 'P') : (scale ? 'S' : 'N'), 'N', 'V', 'N', A)[[2,4]] + E = LAPACK.geevx!(permute ? (scale ? 'B' : 'P') : (scale ? 'S' : 'N'), 'N', 'V', 'N', A) + eval, evec = E[2], E[4] return Eigen(sorteig!(eval, evec, sortby)...) end @@ -235,22 +236,23 @@ true ``` """ function eigen(A::AbstractMatrix{T}; permute::Bool=true, scale::Bool=true, sortby::Union{Function,Nothing}=eigsortby) where T - isdiag(A) && return eigen(Diagonal{eigtype(T)}(diag(A)); sortby) - ishermitian(A) && return eigen!(eigencopy_oftype(Hermitian(A), eigtype(T)); sortby) - AA = eigencopy_oftype(A, eigtype(T)) - return eigen!(AA; permute, scale, sortby) + _eigen(A; permute, scale, sortby) end function eigen(A::AbstractMatrix{T}; permute::Bool=true, scale::Bool=true, sortby::Union{Function,Nothing}=eigsortby) where {T <: Union{Float16,Complex{Float16}}} + E = _eigen(A; permute, scale, sortby) + values = convert(AbstractVector{isreal(E.values) ? Float16 : Complex{Float16}}, E.values) + vectors = convert(AbstractMatrix{isreal(E.vectors) ? Float16 : Complex{Float16}}, E.vectors) + return Eigen(values, vectors) +end +function _eigen(A::AbstractMatrix{T}; permute=true, scale=true, sortby=eigsortby) where {T} isdiag(A) && return eigen(Diagonal{eigtype(T)}(diag(A)); sortby) - E = if ishermitian(A) + if ishermitian(A) eigen!(eigencopy_oftype(Hermitian(A), eigtype(T)); sortby) else eigen!(eigencopy_oftype(A, eigtype(T)); permute, scale, sortby) end - values = convert(AbstractVector{isreal(E.values) ? Float16 : Complex{Float16}}, E.values) - vectors = convert(AbstractMatrix{isreal(E.vectors) ? Float16 : Complex{Float16}}, E.vectors) - return Eigen(values, vectors) end + eigen(x::Number) = Eigen([x], fill(one(x), 1, 1)) """ @@ -344,7 +346,7 @@ eigvals(A::AbstractMatrix{T}; kws...) where T = """ For a scalar input, `eigvals` will return a scalar. -# Example +# Examples ```jldoctest julia> eigvals(-2) -2 diff --git a/stdlib/LinearAlgebra/src/exceptions.jl b/stdlib/LinearAlgebra/src/exceptions.jl index a8d81aad3e067..7791b1ddef416 100644 --- a/stdlib/LinearAlgebra/src/exceptions.jl +++ b/stdlib/LinearAlgebra/src/exceptions.jl @@ -6,6 +6,13 @@ export LAPACKException, RankDeficientException, ZeroPivotException +""" + LAPACKException + +Generic LAPACK exception thrown either during direct calls to the [LAPACK functions](@ref man-linalg-lapack-functions) +or during calls to other functions that use the LAPACK functions internally but lack specialized error handling. The `info` field +contains additional information on the underlying error and depends on the LAPACK function that was invoked. +""" struct LAPACKException <: Exception info::BlasInt end @@ -38,9 +45,16 @@ function Base.showerror(io::IO, ex::PosDefException) else print(io, "positive definite") end - print(io, "; Cholesky factorization failed.") + print(io, "; Factorization failed.") end +""" + RankDeficientException + +Exception thrown when the input matrix is [rank deficient](https://en.wikipedia.org/wiki/Rank_(linear_algebra)). Some +linear algebra functions, such as the Cholesky decomposition, are only applicable to matrices that are not rank +deficient. The `info` field indicates the computed rank of the matrix. +""" struct RankDeficientException <: Exception info::BlasInt end diff --git a/stdlib/LinearAlgebra/src/factorization.jl b/stdlib/LinearAlgebra/src/factorization.jl index 6f5a631cf9164..4cefc661741be 100644 --- a/stdlib/LinearAlgebra/src/factorization.jl +++ b/stdlib/LinearAlgebra/src/factorization.jl @@ -64,11 +64,10 @@ transpose(F::AdjointFactorization{<:Real}) = F.parent conj(A::TransposeFactorization) = adjoint(A.parent) conj(A::AdjointFactorization) = transpose(A.parent) +# These functions expect a non-zero info to be positive, indicating the position where a problem was detected checkpositivedefinite(info) = info == 0 || throw(PosDefException(info)) -checknonsingular(info, ::RowMaximum) = info == 0 || throw(SingularException(info)) -checknonsingular(info, ::RowNonZero) = info == 0 || throw(SingularException(info)) -checknonsingular(info, ::NoPivot) = info == 0 || throw(ZeroPivotException(info)) -checknonsingular(info) = checknonsingular(info, RowMaximum()) +checknonsingular(info) = info == 0 || throw(SingularException(info)) +checknozeropivot(info) = info == 0 || throw(ZeroPivotException(info)) """ issuccess(F::Factorization) @@ -78,16 +77,13 @@ Test that a factorization of a matrix succeeded. !!! compat "Julia 1.6" `issuccess(::CholeskyPivoted)` requires Julia 1.6 or later. +# Examples + ```jldoctest julia> F = cholesky([1 0; 0 1]); julia> issuccess(F) true - -julia> F = lu([1 0; 0 0]; check = false); - -julia> issuccess(F) -false ``` """ issuccess(F::Factorization) diff --git a/stdlib/LinearAlgebra/src/generic.jl b/stdlib/LinearAlgebra/src/generic.jl index 8fb79354c5656..35014cd520630 100644 --- a/stdlib/LinearAlgebra/src/generic.jl +++ b/stdlib/LinearAlgebra/src/generic.jl @@ -110,7 +110,7 @@ end function generic_mul!(C::AbstractArray, X::AbstractArray, s::Number, _add::MulAddMul) if length(C) != length(X) - throw(DimensionMismatch("first array has length $(length(C)) which does not match the length of the second, $(length(X)).")) + throw(DimensionMismatch(lazy"first array has length $(length(C)) which does not match the length of the second, $(length(X)).")) end for (IC, IX) in zip(eachindex(C), eachindex(X)) @inbounds _modify!(_add, X[IX] * s, C, IC) @@ -120,7 +120,7 @@ end function generic_mul!(C::AbstractArray, s::Number, X::AbstractArray, _add::MulAddMul) if length(C) != length(X) - throw(DimensionMismatch("first array has length $(length(C)) which does not + throw(DimensionMismatch(lazy"first array has length $(length(C)) which does not match the length of the second, $(length(X)).")) end for (IC, IX) in zip(eachindex(C), eachindex(X)) @@ -129,7 +129,10 @@ match the length of the second, $(length(X)).")) C end -@inline function mul!(C::AbstractArray, s::Number, X::AbstractArray, alpha::Number, beta::Number) +@inline mul!(C::AbstractArray, s::Number, X::AbstractArray, alpha::Number, beta::Number) = + _lscale_add!(C, s, X, alpha, beta) + +@inline function _lscale_add!(C::AbstractArray, s::Number, X::AbstractArray, alpha::Number, beta::Number) if axes(C) == axes(X) C .= (s .* X) .*ₛ alpha .+ C .*ₛ beta else @@ -137,7 +140,10 @@ end end return C end -@inline function mul!(C::AbstractArray, X::AbstractArray, s::Number, alpha::Number, beta::Number) +@inline mul!(C::AbstractArray, X::AbstractArray, s::Number, alpha::Number, beta::Number) = + _rscale_add!(C, X, s, alpha, beta) + +@inline function _rscale_add!(C::AbstractArray, X::AbstractArray, s::Number, alpha::Number, beta::Number) if axes(C) == axes(X) C .= (X .* s) .*ₛ alpha .+ C .*ₛ beta else @@ -338,7 +344,7 @@ julia> triu(a) 0.0 0.0 0.0 1.0 ``` """ -triu(M::AbstractMatrix) = triu!(copy(M)) +triu(M::AbstractMatrix) = triu!(copymutable(M)) """ tril(M) @@ -362,7 +368,7 @@ julia> tril(a) 1.0 1.0 1.0 1.0 ``` """ -tril(M::AbstractMatrix) = tril!(copy(M)) +tril(M::AbstractMatrix) = tril!(copymutable(M)) """ triu(M, k::Integer) @@ -393,7 +399,7 @@ julia> triu(a,-3) 1.0 1.0 1.0 1.0 ``` """ -triu(M::AbstractMatrix,k::Integer) = triu!(copy(M),k) +triu(M::AbstractMatrix,k::Integer) = triu!(copymutable(M),k) """ tril(M, k::Integer) @@ -424,7 +430,7 @@ julia> tril(a,-3) 1.0 0.0 0.0 0.0 ``` """ -tril(M::AbstractMatrix,k::Integer) = tril!(copy(M),k) +tril(M::AbstractMatrix,k::Integer) = tril!(copymutable(M),k) """ triu!(M) @@ -742,7 +748,7 @@ function opnorm(A::AbstractMatrix, p::Real=2) elseif p == Inf return opnormInf(A) else - throw(ArgumentError("invalid p-norm p=$p. Valid: 1, 2, Inf")) + throw(ArgumentError(lazy"invalid p-norm p=$p. Valid: 1, 2, Inf")) end end @@ -755,8 +761,8 @@ This is equivalent to [`norm`](@ref). @inline opnorm(x::Number, p::Real=2) = norm(x, p) """ - opnorm(A::Adjoint{<:Any,<:AbstracVector}, q::Real=2) - opnorm(A::Transpose{<:Any,<:AbstracVector}, q::Real=2) + opnorm(A::Adjoint{<:Any,<:AbstractVector}, q::Real=2) + opnorm(A::Transpose{<:Any,<:AbstractVector}, q::Real=2) For Adjoint/Transpose-wrapped vectors, return the operator ``q``-norm of `A`, which is equivalent to the `p`-norm with value `p = q/(q-1)`. They coincide at `p = q = 2`. @@ -858,6 +864,8 @@ function dot(x, y) # arbitrary iterables end (vx, xs) = ix (vy, ys) = iy + typeof(vx) == typeof(x) && typeof(vy) == typeof(y) && throw(ArgumentError( + "cannot evaluate dot recursively if the type of an element is identical to that of the container")) s = dot(vx, vy) while true ix = iterate(x, xs) @@ -878,7 +886,7 @@ dot(x::Number, y::Number) = conj(x) * y function dot(x::AbstractArray, y::AbstractArray) lx = length(x) if lx != length(y) - throw(DimensionMismatch("first array has length $(lx) which does not match the length of the second, $(length(y)).")) + throw(DimensionMismatch(lazy"first array has length $(lx) which does not match the length of the second, $(length(y)).")) end if lx == 0 return dot(zero(eltype(x)), zero(eltype(y))) @@ -1456,7 +1464,7 @@ julia> axpy!(2, x, y) function axpy!(α, x::AbstractArray, y::AbstractArray) n = length(x) if n != length(y) - throw(DimensionMismatch("x has length $n, but y has length $(length(y))")) + throw(DimensionMismatch(lazy"x has length $n, but y has length $(length(y))")) end iszero(α) && return y for (IY, IX) in zip(eachindex(y), eachindex(x)) @@ -1467,7 +1475,7 @@ end function axpy!(α, x::AbstractArray, rx::AbstractArray{<:Integer}, y::AbstractArray, ry::AbstractArray{<:Integer}) if length(rx) != length(ry) - throw(DimensionMismatch("rx has length $(length(rx)), but ry has length $(length(ry))")) + throw(DimensionMismatch(lazy"rx has length $(length(rx)), but ry has length $(length(ry))")) elseif !checkindex(Bool, eachindex(IndexLinear(), x), rx) throw(BoundsError(x, rx)) elseif !checkindex(Bool, eachindex(IndexLinear(), y), ry) @@ -1501,7 +1509,7 @@ julia> axpby!(2, x, 2, y) """ function axpby!(α, x::AbstractArray, β, y::AbstractArray) if length(x) != length(y) - throw(DimensionMismatch("x has length $(length(x)), but y has length $(length(y))")) + throw(DimensionMismatch(lazy"x has length $(length(x)), but y has length $(length(y))")) end iszero(α) && isone(β) && return y for (IX, IY) in zip(eachindex(x), eachindex(y)) @@ -1541,7 +1549,7 @@ function rotate!(x::AbstractVector, y::AbstractVector, c, s) require_one_based_indexing(x, y) n = length(x) if n != length(y) - throw(DimensionMismatch("x has length $(length(x)), but y has length $(length(y))")) + throw(DimensionMismatch(lazy"x has length $(length(x)), but y has length $(length(y))")) end @inbounds for i = 1:n xi, yi = x[i], y[i] @@ -1564,7 +1572,7 @@ function reflect!(x::AbstractVector, y::AbstractVector, c, s) require_one_based_indexing(x, y) n = length(x) if n != length(y) - throw(DimensionMismatch("x has length $(length(x)), but y has length $(length(y))")) + throw(DimensionMismatch(lazy"x has length $(length(x)), but y has length $(length(y))")) end @inbounds for i = 1:n xi, yi = x[i], y[i] @@ -1605,7 +1613,7 @@ Multiplies `A` in-place by a Householder reflection on the left. It is equivalen require_one_based_indexing(x) m, n = size(A, 1), size(A, 2) if length(x) != m - throw(DimensionMismatch("reflector has length $(length(x)), which must match the first dimension of matrix A, $m")) + throw(DimensionMismatch(lazy"reflector has length $(length(x)), which must match the first dimension of matrix A, $m")) end m == 0 && return A @inbounds for j = 1:n @@ -1634,6 +1642,15 @@ julia> M = [1 0; 2 2] julia> det(M) 2.0 ``` +Note that, in general, `det` computes a floating-point approximation of the +determinant, even for integer matrices, typically via Gaussian elimination. +Julia includes an exact algorithm for integer determinants (the Bareiss algorithm), +but only uses it by default for `BigInt` matrices (since determinants quickly +overflow any fixed integer precision): +```jldoctest +julia> det(BigInt[1 0; 2 2]) # exact integer determinant +2 +``` """ function det(A::AbstractMatrix{T}) where {T} if istriu(A) || istril(A) @@ -1760,7 +1777,7 @@ Calculates the determinant of a matrix using the [Bareiss Algorithm](https://en.wikipedia.org/wiki/Bareiss_algorithm). Also refer to [`det_bareiss!`](@ref). """ -det_bareiss(M) = det_bareiss!(copy(M)) +det_bareiss(M) = det_bareiss!(copymutable(M)) @@ -1897,3 +1914,48 @@ end normalize(x) = x / norm(x) normalize(x, p::Real) = x / norm(x, p) + +""" + copytrito!(B, A, uplo) -> B + +Copies a triangular part of a matrix `A` to another matrix `B`. +`uplo` specifies the part of the matrix `A` to be copied to `B`. +Set `uplo = 'L'` for the lower triangular part or `uplo = 'U'` +for the upper triangular part. + +!!! compat "Julia 1.11" + `copytrito!` requires at least Julia 1.11. + +# Examples +```jldoctest +julia> A = [1 2 ; 3 4]; + +julia> B = [0 0 ; 0 0]; + +julia> copytrito!(B, A, 'L') +2×2 Matrix{Int64}: + 1 0 + 3 4 +``` +""" +function copytrito!(B::AbstractMatrix, A::AbstractMatrix, uplo::AbstractChar) + require_one_based_indexing(A, B) + BLAS.chkuplo(uplo) + m,n = size(A) + m1,n1 = size(B) + (m1 < m || n1 < n) && throw(DimensionMismatch(lazy"B of size ($m1,$n1) should have at least the same number of rows and columns than A of size ($m,$n)")) + if uplo == 'U' + for j=1:n + for i=1:min(j,m) + @inbounds B[i,j] = A[i,j] + end + end + else # uplo == 'L' + for j=1:n + for i=j:m + @inbounds B[i,j] = A[i,j] + end + end + end + return B +end diff --git a/stdlib/LinearAlgebra/src/hessenberg.jl b/stdlib/LinearAlgebra/src/hessenberg.jl index e0264ee5a8a60..28c23be520775 100644 --- a/stdlib/LinearAlgebra/src/hessenberg.jl +++ b/stdlib/LinearAlgebra/src/hessenberg.jl @@ -56,6 +56,7 @@ UpperHessenberg(A::AbstractMatrix) = UpperHessenberg{eltype(A),typeof(A)}(A) Matrix(H::UpperHessenberg{T}) where {T} = Matrix{T}(H) Array(H::UpperHessenberg) = Matrix(H) size(H::UpperHessenberg) = size(H.data) +axes(H::UpperHessenberg) = axes(H.data) parent(H::UpperHessenberg) = H.data # similar behaves like UpperTriangular @@ -65,6 +66,9 @@ similar(H::UpperHessenberg, ::Type{T}, dims::Dims{N}) where {T,N} = similar(H.da AbstractMatrix{T}(H::UpperHessenberg) where {T} = UpperHessenberg{T}(H) AbstractMatrix{T}(H::UpperHessenberg{T}) where {T} = copy(H) +Base.dataids(A::UpperHessenberg) = Base.dataids(parent(A)) +Base.unaliascopy(A::UpperHessenberg) = UpperHessenberg(Base.unaliascopy(parent(A))) + copy(H::UpperHessenberg) = UpperHessenberg(copy(H.data)) real(H::UpperHessenberg{<:Real}) = H real(H::UpperHessenberg{<:Complex}) = UpperHessenberg(triu!(real(H.data),-1)) @@ -83,13 +87,13 @@ end Base.isassigned(H::UpperHessenberg, i::Int, j::Int) = i <= j+1 ? isassigned(H.data, i, j) : true -getindex(H::UpperHessenberg{T}, i::Integer, j::Integer) where {T} = +Base.@propagate_inbounds getindex(H::UpperHessenberg{T}, i::Integer, j::Integer) where {T} = i <= j+1 ? convert(T, H.data[i,j]) : zero(T) -function setindex!(A::UpperHessenberg, x, i::Integer, j::Integer) +Base.@propagate_inbounds function setindex!(A::UpperHessenberg, x, i::Integer, j::Integer) if i > j+1 x == 0 || throw(ArgumentError("cannot set index in the lower triangular part " * - "($i, $j) of an UpperHessenberg matrix to a nonzero value ($x)")) + lazy"($i, $j) of an UpperHessenberg matrix to a nonzero value ($x)")) else A.data[i,j] = x end @@ -132,29 +136,29 @@ for T = (:Number, :UniformScaling, :Diagonal) end function *(H::UpperHessenberg, U::UpperOrUnitUpperTriangular) - HH = mul!(_initarray(*, eltype(H), eltype(U), H), H, U) + HH = mul!(matprod_dest(H, U, promote_op(matprod, eltype(H), eltype(U))), H, U) UpperHessenberg(HH) end function *(U::UpperOrUnitUpperTriangular, H::UpperHessenberg) - HH = mul!(_initarray(*, eltype(U), eltype(H), H), U, H) + HH = mul!(matprod_dest(U, H, promote_op(matprod, eltype(U), eltype(H))), U, H) UpperHessenberg(HH) end function /(H::UpperHessenberg, U::UpperTriangular) - HH = _rdiv!(_initarray(/, eltype(H), eltype(U), H), H, U) + HH = _rdiv!(matprod_dest(H, U, promote_op(/, eltype(H), eltype(U))), H, U) UpperHessenberg(HH) end function /(H::UpperHessenberg, U::UnitUpperTriangular) - HH = _rdiv!(_initarray(/, eltype(H), eltype(U), H), H, U) + HH = _rdiv!(matprod_dest(H, U, promote_op(/, eltype(H), eltype(U))), H, U) UpperHessenberg(HH) end function \(U::UpperTriangular, H::UpperHessenberg) - HH = ldiv!(_initarray(\, eltype(U), eltype(H), H), U, H) + HH = ldiv!(matprod_dest(U, H, promote_op(\, eltype(U), eltype(H))), U, H) UpperHessenberg(HH) end function \(U::UnitUpperTriangular, H::UpperHessenberg) - HH = ldiv!(_initarray(\, eltype(U), eltype(H), H), U, H) + HH = ldiv!(matprod_dest(U, H, promote_op(\, eltype(U), eltype(H))), U, H) UpperHessenberg(HH) end @@ -179,7 +183,7 @@ end function ldiv!(F::UpperHessenberg, B::AbstractVecOrMat; shift::Number=false) checksquare(F) m = size(F,1) - m != size(B,1) && throw(DimensionMismatch("wrong right-hand-side # rows != $m")) + m != size(B,1) && throw(DimensionMismatch(lazy"wrong right-hand-side # rows != $m")) require_one_based_indexing(B) n = size(B,2) H = F.data @@ -229,7 +233,7 @@ end function rdiv!(B::AbstractMatrix, F::UpperHessenberg; shift::Number=false) checksquare(F) m = size(F,1) - m != size(B,2) && throw(DimensionMismatch("wrong right-hand-side # cols != $m")) + m != size(B,2) && throw(DimensionMismatch(lazy"wrong right-hand-side # cols != $m")) require_one_based_indexing(B) n = size(B,1) H = F.data diff --git a/stdlib/LinearAlgebra/src/lapack.jl b/stdlib/LinearAlgebra/src/lapack.jl index 7bb26c2b6589a..cf9a47073abd0 100644 --- a/stdlib/LinearAlgebra/src/lapack.jl +++ b/stdlib/LinearAlgebra/src/lapack.jl @@ -26,21 +26,23 @@ Handle only negative LAPACK error codes """ function chkargsok(ret::BlasInt) if ret < 0 - throw(ArgumentError("invalid argument #$(-ret) to LAPACK call")) + throw(ArgumentError(lazy"invalid argument #$(-ret) to LAPACK call")) end end "Handle all nonzero info codes" -function chklapackerror(ret::BlasInt) +function chklapackerror(ret::BlasInt, f...) if ret == 0 return elseif ret < 0 - throw(ArgumentError("invalid argument #$(-ret) to LAPACK call")) + throw(ArgumentError(lazy"invalid argument #$(-ret) to LAPACK call")) else # ret > 0 - throw(LAPACKException(ret)) + chklapackerror_positive(ret, f...) end end +chklapackerror_positive(ret, f...) = throw(LAPACKException(ret)) + function chknonsingular(ret::BlasInt) if ret > 0 throw(SingularException(ret)) @@ -53,10 +55,23 @@ function chkposdef(ret::BlasInt) end end +# Generic fallback function to assert that parameters are valid +# In specific cases, the following functions may be more useful +macro chkvalidparam(position::Int, param, validvalues) + :(chkvalidparam($position, $(string(param)), $(esc(param)), $validvalues)) +end +function chkvalidparam(position::Int, var::String, val, validvals) + if val ∉ validvals + throw(ArgumentError( + lazy"argument #$position: $var must be one of $validvals, but $(repr(val)) was passed")) + end + return val +end + "Check that {c}transpose is correctly specified" function chktrans(trans::AbstractChar) if !(trans == 'N' || trans == 'C' || trans == 'T') - throw(ArgumentError("trans argument must be 'N' (no transpose), 'T' (transpose), or 'C' (conjugate transpose), got $trans")) + throw(ArgumentError(lazy"trans argument must be 'N' (no transpose), 'T' (transpose), or 'C' (conjugate transpose), got '$trans'")) end trans end @@ -64,7 +79,7 @@ end "Check that left/right hand side multiply is correctly specified" function chkside(side::AbstractChar) if !(side == 'L' || side == 'R') - throw(ArgumentError("side argument must be 'L' (left hand multiply) or 'R' (right hand multiply), got $side")) + throw(ArgumentError(lazy"side argument must be 'L' (left hand multiply) or 'R' (right hand multiply), got '$side'")) end side end @@ -72,7 +87,7 @@ end "Check that unit diagonal flag is correctly specified" function chkdiag(diag::AbstractChar) if !(diag == 'U' || diag =='N') - throw(ArgumentError("diag argument must be 'U' (unit diagonal) or 'N' (non-unit diagonal), got $diag")) + throw(ArgumentError(lazy"diag argument must be 'U' (unit diagonal) or 'N' (non-unit diagonal), got '$diag'")) end diag end @@ -91,6 +106,7 @@ end function chkuplofinite(A::AbstractMatrix, uplo::AbstractChar) require_one_based_indexing(A) + chkuplo(uplo) m, n = size(A) if uplo == 'U' @inbounds for j in 1:n, i in 1:j @@ -162,7 +178,7 @@ for (gbtrf, gbtrs, elty) in info = Ref{BlasInt}() n = size(AB,2) if m != n || m != size(B,1) - throw(DimensionMismatch("matrix AB has dimensions $(size(AB)), but right hand side matrix B has dimensions $(size(B))")) + throw(DimensionMismatch(lazy"matrix AB has dimensions $(size(AB)), but right hand side matrix B has dimensions $(size(B))")) end ccall((@blasfunc($gbtrs), libblastrampoline), Cvoid, (Ref{UInt8}, Ref{BlasInt}, Ref{BlasInt}, Ref{BlasInt}, Ref{BlasInt}, @@ -211,7 +227,9 @@ for (gebal, gebak, elty, relty) in # .. Array Arguments .. # DOUBLE PRECISION A( LDA, * ), SCALE( * ) function gebal!(job::AbstractChar, A::AbstractMatrix{$elty}) + require_one_based_indexing(A) chkstride1(A) + @chkvalidparam 1 job ('N', 'P', 'S', 'B') n = checksquare(A) chkfinite(A) # balancing routines don't support NaNs and Infs ihi = Ref{BlasInt}() @@ -236,6 +254,7 @@ for (gebal, gebak, elty, relty) in ilo::BlasInt, ihi::BlasInt, scale::AbstractVector{$relty}, V::AbstractMatrix{$elty}) require_one_based_indexing(scale, V) + @chkvalidparam 1 job ('N', 'P', 'S', 'B') chkstride1(scale, V) chkside(side) chkfinite(V) # balancing routines don't support NaNs and Infs @@ -336,7 +355,7 @@ for (gebrd, gelqf, geqlf, geqrf, geqp3, geqrt, geqrt3, gerqf, getrf, elty, relty n = BlasInt(size(A, 2)) lda = BlasInt(max(1,stride(A, 2))) if length(tau) != min(m,n) - throw(DimensionMismatch("tau has length $(length(tau)), but needs length $(min(m,n))")) + throw(DimensionMismatch(lazy"tau has length $(length(tau)), but needs length $(min(m,n))")) end lwork = BlasInt(-1) work = Vector{$elty}(undef, 1) @@ -367,7 +386,7 @@ for (gebrd, gelqf, geqlf, geqrf, geqp3, geqrt, geqrt3, gerqf, getrf, elty, relty n = BlasInt(size(A, 2)) lda = BlasInt(max(1,stride(A, 2))) if length(tau) != min(m,n) - throw(DimensionMismatch("tau has length $(length(tau)), but needs length $(min(m,n))")) + throw(DimensionMismatch(lazy"tau has length $(length(tau)), but needs length $(min(m,n))")) end lwork = BlasInt(-1) work = Vector{$elty}(undef, 1) @@ -397,10 +416,10 @@ for (gebrd, gelqf, geqlf, geqrf, geqp3, geqrt, geqrt3, gerqf, getrf, elty, relty chkstride1(A,jpvt,tau) m,n = size(A) if length(tau) != min(m,n) - throw(DimensionMismatch("tau has length $(length(tau)), but needs length $(min(m,n))")) + throw(DimensionMismatch(lazy"tau has length $(length(tau)), but needs length $(min(m,n))")) end if length(jpvt) != n - throw(DimensionMismatch("jpvt has length $(length(jpvt)), but needs length $n")) + throw(DimensionMismatch(lazy"jpvt has length $(length(jpvt)), but needs length $n")) end lda = stride(A,2) if lda == 0 @@ -447,11 +466,11 @@ for (gebrd, gelqf, geqlf, geqrf, geqp3, geqrt, geqrt3, gerqf, getrf, elty, relty minmn = min(m, n) nb = size(T, 1) if nb > minmn - throw(ArgumentError("block size $nb > $minmn too large")) + throw(ArgumentError(lazy"block size $nb > $minmn too large")) end lda = max(1, stride(A,2)) work = Vector{$elty}(undef, nb*n) - if n > 0 + if minmn > 0 info = Ref{BlasInt}() ccall((@blasfunc($geqrt), libblastrampoline), Cvoid, (Ref{BlasInt}, Ref{BlasInt}, Ref{BlasInt}, Ptr{$elty}, @@ -472,12 +491,12 @@ for (gebrd, gelqf, geqlf, geqrf, geqp3, geqrt, geqrt3, gerqf, getrf, elty, relty m, n = size(A) p, q = size(T) if m < n - throw(DimensionMismatch("input matrix A has dimensions ($m,$n), but should have more rows than columns")) + throw(DimensionMismatch(lazy"input matrix A has dimensions ($m,$n), but should have more rows than columns")) end if p != n || q != n - throw(DimensionMismatch("block reflector T has dimensions ($p,$q), but should have dimensions ($n,$n)")) + throw(DimensionMismatch(lazy"block reflector T has dimensions ($p,$q), but should have dimensions ($n,$n)")) end - if n > 0 + if n > 0 # this implies `m > 0` because of `m >= n` info = Ref{BlasInt}() ccall((@blasfunc($geqrt3), libblastrampoline), Cvoid, (Ref{BlasInt}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, @@ -500,7 +519,7 @@ for (gebrd, gelqf, geqlf, geqrf, geqp3, geqrt, geqrt3, gerqf, getrf, elty, relty chkstride1(A,tau) m, n = size(A) if length(tau) != min(m,n) - throw(DimensionMismatch("tau has length $(length(tau)), but needs length $(min(m,n))")) + throw(DimensionMismatch(lazy"tau has length $(length(tau)), but needs length $(min(m,n))")) end work = Vector{$elty}(undef, 1) lwork = BlasInt(-1) @@ -529,7 +548,7 @@ for (gebrd, gelqf, geqlf, geqrf, geqp3, geqrt, geqrt3, gerqf, getrf, elty, relty chkstride1(A,tau) m, n = size(A) if length(tau) != min(m,n) - throw(DimensionMismatch("tau has length $(length(tau)), but needs length $(min(m,n))")) + throw(DimensionMismatch(lazy"tau has length $(length(tau)), but needs length $(min(m,n))")) end lwork = BlasInt(-1) work = Vector{$elty}(undef, 1) @@ -613,18 +632,22 @@ Compute the pivoted `QR` factorization of `A`, `AP = QR` using BLAS level 3. reflectors. The arguments `jpvt` and `tau` are optional and allow for passing preallocated arrays. When passed, `jpvt` must have length greater than or equal to `n` if `A` is an `(m x n)` matrix and `tau` must have length -greater than or equal to the smallest dimension of `A`. +greater than or equal to the smallest dimension of `A`. On entry, if `jpvt[j]` +does not equal zero then the `j`th column of `A` is permuted to the front of +`AP`. `A`, `jpvt`, and `tau` are modified in-place. """ geqp3!(A::AbstractMatrix, jpvt::AbstractVector{BlasInt}, tau::AbstractVector) function geqp3!(A::AbstractMatrix{<:BlasFloat}, jpvt::AbstractVector{BlasInt}) + require_one_based_indexing(A, jpvt) m, n = size(A) geqp3!(A, jpvt, similar(A, min(m, n))) end function geqp3!(A::AbstractMatrix{<:BlasFloat}) + require_one_based_indexing(A) m, n = size(A) geqp3!(A, zeros(BlasInt, n), similar(A, min(m, n))) end @@ -773,6 +796,7 @@ for (larfg, elty) in # .. Array Arguments .. # DOUBLE PRECISION x( * ) function larfg!(x::AbstractVector{$elty}) + require_one_based_indexing(x) N = BlasInt(length(x)) α = Ref{$elty}(x[1]) incx = BlasInt(1) @@ -801,6 +825,7 @@ for (larf, elty) in # DOUBLE PRECISION c( ldc, * ), v( * ), work( * ) function larf!(side::AbstractChar, v::AbstractVector{$elty}, τ::$elty, C::AbstractMatrix{$elty}, work::AbstractVector{$elty}) + require_one_based_indexing(v, C, work) m, n = size(C) chkside(side) ldc = max(1, stride(C, 2)) @@ -816,6 +841,7 @@ for (larf, elty) in function larf!(side::AbstractChar, v::AbstractVector{$elty}, τ::$elty, C::AbstractMatrix{$elty}) + require_one_based_indexing(v, C) m, n = size(C) chkside(side) lwork = side == 'L' ? n : m @@ -843,7 +869,7 @@ for (tzrzf, ormrz, elty) in chkstride1(A) m, n = size(A) if n < m - throw(DimensionMismatch("input matrix A has dimensions ($m,$n), but cannot have fewer columns than rows")) + throw(DimensionMismatch(lazy"input matrix A has dimensions ($m,$n), but cannot have fewer columns than rows")) end lda = max(1, stride(A,2)) tau = similar(A, $elty, m) @@ -948,7 +974,7 @@ for (gels, gesv, getrs, getri, elty) in btrn = trans == 'T' m, n = size(A) if size(B,1) != (btrn ? n : m) - throw(DimensionMismatch("matrix A has dimensions ($m,$n), transposed: $btrn, but leading dimension of B is $(size(B,1))")) + throw(DimensionMismatch(lazy"matrix A has dimensions ($m,$n), transposed: $btrn, but leading dimension of B is $(size(B,1))")) end info = Ref{BlasInt}() work = Vector{$elty}(undef, 1) @@ -991,7 +1017,7 @@ for (gels, gesv, getrs, getri, elty) in chkstride1(A, B) n = checksquare(A) if size(B,1) != n - throw(DimensionMismatch("B has leading dimension $(size(B,1)), but needs $n")) + throw(DimensionMismatch(lazy"B has leading dimension $(size(B,1)), but needs $n")) end ipiv = similar(A, BlasInt, n) info = Ref{BlasInt}() @@ -1016,10 +1042,10 @@ for (gels, gesv, getrs, getri, elty) in chkstride1(A, B, ipiv) n = checksquare(A) if n != size(B, 1) - throw(DimensionMismatch("B has leading dimension $(size(B,1)), but needs $n")) + throw(DimensionMismatch(lazy"B has leading dimension $(size(B,1)), but needs $n")) end if n != length(ipiv) - throw(DimensionMismatch("ipiv has length $(length(ipiv)), but needs to be $n")) + throw(DimensionMismatch(lazy"ipiv has length $(length(ipiv)), but needs to be $n")) end nrhs = size(B, 2) info = Ref{BlasInt}() @@ -1042,7 +1068,7 @@ for (gels, gesv, getrs, getri, elty) in chkstride1(A, ipiv) n = checksquare(A) if n != length(ipiv) - throw(DimensionMismatch("ipiv has length $(length(ipiv)), but needs $n")) + throw(DimensionMismatch(lazy"ipiv has length $(length(ipiv)), but needs $n")) end lda = max(1,stride(A, 2)) lwork = BlasInt(-1) @@ -1130,6 +1156,7 @@ for (gesvx, elty) in AF::AbstractMatrix{$elty}, ipiv::AbstractVector{BlasInt}, equed::AbstractChar, R::AbstractVector{$elty}, C::AbstractVector{$elty}, B::AbstractVecOrMat{$elty}) require_one_based_indexing(A, AF, ipiv, R, C, B) + @chkvalidparam 1 fact ('F', 'N', 'E') chktrans(trans) chkstride1(ipiv, R, C, B) n = checksquare(A) @@ -1164,6 +1191,7 @@ for (gesvx, elty) in end function gesvx!(A::AbstractMatrix{$elty}, B::AbstractVecOrMat{$elty}) + require_one_based_indexing(A, B) n = size(A,1) X, equed, R, C, B, rcond, ferr, berr, rpgf = gesvx!('N', 'N', A, @@ -1200,6 +1228,7 @@ for (gesvx, elty, relty) in AF::AbstractMatrix{$elty}, ipiv::AbstractVector{BlasInt}, equed::AbstractChar, R::AbstractVector{$relty}, C::AbstractVector{$relty}, B::AbstractVecOrMat{$elty}) require_one_based_indexing(A, AF, ipiv, R, C, B) + @chkvalidparam 1 fact ('F', 'N', 'E') chktrans(trans) chkstride1(A, AF, ipiv, R, C, B) n = checksquare(A) @@ -1235,6 +1264,7 @@ for (gesvx, elty, relty) in #Wrapper for the no-equilibration, no-transpose calculation function gesvx!(A::AbstractMatrix{$elty}, B::AbstractVecOrMat{$elty}) + require_one_based_indexing(A, B) n = size(A,1) X, equed, R, C, B, rcond, ferr, berr, rpgf = gesvx!('N', 'N', A, @@ -1301,7 +1331,7 @@ for (gelsd, gelsy, elty) in chkstride1(A, B) m, n = size(A) if size(B, 1) != m - throw(DimensionMismatch("B has leading dimension $(size(B,1)) but needs $m")) + throw(DimensionMismatch(lazy"B has leading dimension $(size(B,1)) but needs $m")) end newB = [B; zeros($elty, max(0, n - size(B, 1)), size(B, 2))] s = similar(A, $elty, min(m, n)) @@ -1346,7 +1376,7 @@ for (gelsd, gelsy, elty) in n = size(A, 2) nrhs = size(B, 2) if size(B, 1) != m - throw(DimensionMismatch("B has leading dimension $(size(B,1)) but needs $m")) + throw(DimensionMismatch(lazy"B has leading dimension $(size(B,1)) but needs $m")) end newB = [B; zeros($elty, max(0, n - size(B, 1)), size(B, 2))] lda = max(1, stride(A,2)) @@ -1396,7 +1426,7 @@ for (gelsd, gelsy, elty, relty) in chkstride1(A, B) m, n = size(A) if size(B, 1) != m - throw(DimensionMismatch("B has leading dimension $(size(B,1)) but needs $m")) + throw(DimensionMismatch(lazy"B has leading dimension $(size(B,1)) but needs $m")) end newB = [B; zeros($elty, max(0, n - size(B, 1)), size(B, 2))] s = similar(A, $relty, min(m, n)) @@ -1443,7 +1473,7 @@ for (gelsd, gelsy, elty, relty) in m, n = size(A) nrhs = size(B, 2) if size(B, 1) != m - throw(DimensionMismatch("B has leading dimension $(size(B,1)) but needs $m")) + throw(DimensionMismatch(lazy"B has leading dimension $(size(B,1)) but needs $m")) end newB = [B; zeros($elty, max(0, n - size(B, 1)), size(B, 2))] lda = max(1, m) @@ -1517,13 +1547,13 @@ for (gglse, elty) in ((:dgglse_, :Float64), m, n = size(A) p = size(B, 1) if size(B, 2) != n - throw(DimensionMismatch("B has second dimension $(size(B,2)), needs $n")) + throw(DimensionMismatch(lazy"B has second dimension $(size(B,2)), needs $n")) end if length(c) != m - throw(DimensionMismatch("c has length $(length(c)), needs $m")) + throw(DimensionMismatch(lazy"c has length $(length(c)), needs $m")) end if length(d) != p - throw(DimensionMismatch("d has length $(length(d)), needs $p")) + throw(DimensionMismatch(lazy"d has length $(length(d)), needs $p")) end X = zeros($elty, n) info = Ref{BlasInt}() @@ -1573,8 +1603,11 @@ for (geev, gesvd, gesdd, ggsvd, elty, relty) in # DOUBLE PRECISION A( LDA, * ), VL( LDVL, * ), VR( LDVR, * ), # $ WI( * ), WORK( * ), WR( * ) function geev!(jobvl::AbstractChar, jobvr::AbstractChar, A::AbstractMatrix{$elty}) + require_one_based_indexing(A) chkstride1(A) n = checksquare(A) + @chkvalidparam 1 jobvl ('N', 'V') + @chkvalidparam 2 jobvr ('N', 'V') chkfinite(A) # balancing routines don't support NaNs and Infs lvecs = jobvl == 'V' rvecs = jobvr == 'V' @@ -1631,6 +1664,7 @@ for (geev, gesvd, gesdd, ggsvd, elty, relty) in function gesdd!(job::AbstractChar, A::AbstractMatrix{$elty}) require_one_based_indexing(A) chkstride1(A) + @chkvalidparam 1 job ('A', 'S', 'O', 'N') m, n = size(A) minmn = min(m, n) if job == 'A' @@ -1712,6 +1746,9 @@ for (geev, gesvd, gesdd, ggsvd, elty, relty) in function gesvd!(jobu::AbstractChar, jobvt::AbstractChar, A::AbstractMatrix{$elty}) require_one_based_indexing(A) chkstride1(A) + @chkvalidparam 1 jobu ('A', 'S', 'O', 'N') + @chkvalidparam 2 jobvt ('A', 'S', 'O', 'N') + (jobu == jobvt == 'O') && throw(ArgumentError("jobu and jobvt cannot both be O")) m, n = size(A) minmn = min(m, n) S = similar(A, $relty, minmn) @@ -1781,9 +1818,12 @@ for (geev, gesvd, gesdd, ggsvd, elty, relty) in function ggsvd!(jobu::AbstractChar, jobv::AbstractChar, jobq::AbstractChar, A::AbstractMatrix{$elty}, B::AbstractMatrix{$elty}) require_one_based_indexing(A, B) chkstride1(A, B) + @chkvalidparam 1 jobu ('U', 'N') + @chkvalidparam 2 jobv ('V', 'N') + @chkvalidparam 3 jobq ('Q', 'N') m, n = size(A) if size(B, 2) != n - throw(DimensionMismatch("B has second dimension $(size(B,2)) but needs $n")) + throw(DimensionMismatch(lazy"B has second dimension $(size(B,2)) but needs $n")) end p = size(B, 1) k = Vector{BlasInt}(undef, 1) @@ -1908,9 +1948,12 @@ for (f, elty) in ((:dggsvd3_, :Float64), function ggsvd3!(jobu::AbstractChar, jobv::AbstractChar, jobq::AbstractChar, A::AbstractMatrix{$elty}, B::AbstractMatrix{$elty}) require_one_based_indexing(A, B) chkstride1(A, B) + @chkvalidparam 1 jobu ('U', 'N') + @chkvalidparam 2 jobv ('V', 'N') + @chkvalidparam 3 jobq ('Q', 'N') m, n = size(A) if size(B, 2) != n - throw(DimensionMismatch("B has second dimension $(size(B,2)) but needs $n")) + throw(DimensionMismatch(lazy"B has second dimension $(size(B,2)) but needs $n")) end p = size(B, 1) k = Ref{BlasInt}() @@ -1967,9 +2010,12 @@ for (f, elty, relty) in ((:zggsvd3_, :ComplexF64, :Float64), function ggsvd3!(jobu::AbstractChar, jobv::AbstractChar, jobq::AbstractChar, A::AbstractMatrix{$elty}, B::AbstractMatrix{$elty}) require_one_based_indexing(A, B) chkstride1(A, B) + @chkvalidparam 1 jobu ('U', 'N') + @chkvalidparam 2 jobv ('V', 'N') + @chkvalidparam 3 jobq ('Q', 'N') m, n = size(A) if size(B, 2) != n - throw(DimensionMismatch("B has second dimension $(size(B,2)) but needs $n")) + throw(DimensionMismatch(lazy"B has second dimension $(size(B,2)) but needs $n")) end p = size(B, 1) k = Vector{BlasInt}(undef, 1) @@ -2053,31 +2099,34 @@ for (geevx, ggev, ggev3, elty) in # $ SCALE( * ), VL( LDVL, * ), VR( LDVR, * ), # $ WI( * ), WORK( * ), WR( * ) function geevx!(balanc::AbstractChar, jobvl::AbstractChar, jobvr::AbstractChar, sense::AbstractChar, A::AbstractMatrix{$elty}) - n = checksquare(A) - chkfinite(A) # balancing routines don't support NaNs and Infs - lda = max(1,stride(A,2)) - wr = similar(A, $elty, n) - wi = similar(A, $elty, n) - if balanc ∉ ['N', 'P', 'S', 'B'] - throw(ArgumentError("balanc must be 'N', 'P', 'S', or 'B', but $balanc was passed")) + require_one_based_indexing(A) + @chkvalidparam 1 balanc ('N', 'P', 'S', 'B') + @chkvalidparam 4 sense ('N', 'E', 'V', 'B') + if sense ∈ ('E', 'B') && !(jobvl == jobvr == 'V') + throw(ArgumentError(lazy"sense = '$sense' requires jobvl = 'V' and jobvr = 'V'")) end + n = checksquare(A) ldvl = 0 if jobvl == 'V' ldvl = n elseif jobvl == 'N' ldvl = 0 else - throw(ArgumentError("jobvl must be 'V' or 'N', but $jobvl was passed")) + throw(ArgumentError(lazy"jobvl must be 'V' or 'N', but $jobvl was passed")) end - VL = similar(A, $elty, ldvl, n) ldvr = 0 if jobvr == 'V' ldvr = n elseif jobvr == 'N' ldvr = 0 else - throw(ArgumentError("jobvr must be 'V' or 'N', but $jobvr was passed")) + throw(ArgumentError(lazy"jobvr must be 'V' or 'N', but $jobvr was passed")) end + chkfinite(A) # balancing routines don't support NaNs and Infs + lda = max(1,stride(A,2)) + wr = similar(A, $elty, n) + wi = similar(A, $elty, n) + VL = similar(A, $elty, ldvl, n) VR = similar(A, $elty, ldvr, n) ilo = Ref{BlasInt}() ihi = Ref{BlasInt}() @@ -2093,7 +2142,7 @@ for (geevx, ggev, ggev3, elty) in elseif sense == 'V' || sense == 'B' iworksize = 2*n - 2 else - throw(ArgumentError("sense must be 'N', 'E', 'V' or 'B', but $sense was passed")) + throw(ArgumentError(lazy"sense must be 'N', 'E', 'V' or 'B', but $sense was passed")) end iwork = Vector{BlasInt}(undef, iworksize) info = Ref{BlasInt}() @@ -2137,30 +2186,30 @@ for (geevx, ggev, ggev3, elty) in chkstride1(A,B) n, m = checksquare(A,B) if n != m - throw(DimensionMismatch("A has dimensions $(size(A)), and B has dimensions $(size(B)), but A and B must have the same size")) + throw(DimensionMismatch(lazy"A has dimensions $(size(A)), and B has dimensions $(size(B)), but A and B must have the same size")) end - lda = max(1, stride(A, 2)) - ldb = max(1, stride(B, 2)) - alphar = similar(A, $elty, n) - alphai = similar(A, $elty, n) - beta = similar(A, $elty, n) ldvl = 0 if jobvl == 'V' ldvl = n elseif jobvl == 'N' ldvl = 1 else - throw(ArgumentError("jobvl must be 'V' or 'N', but $jobvl was passed")) + throw(ArgumentError(lazy"jobvl must be 'V' or 'N', but $jobvl was passed")) end - vl = similar(A, $elty, ldvl, n) ldvr = 0 if jobvr == 'V' ldvr = n elseif jobvr == 'N' ldvr = 1 else - throw(ArgumentError("jobvr must be 'V' or 'N', but $jobvr was passed")) + throw(ArgumentError(lazy"jobvr must be 'V' or 'N', but $jobvr was passed")) end + lda = max(1, stride(A, 2)) + ldb = max(1, stride(B, 2)) + alphar = similar(A, $elty, n) + alphai = similar(A, $elty, n) + beta = similar(A, $elty, n) + vl = similar(A, $elty, ldvl, n) vr = similar(A, $elty, ldvr, n) work = Vector{$elty}(undef, 1) lwork = BlasInt(-1) @@ -2201,30 +2250,30 @@ for (geevx, ggev, ggev3, elty) in chkstride1(A,B) n, m = checksquare(A,B) if n != m - throw(DimensionMismatch("A has dimensions $(size(A)), and B has dimensions $(size(B)), but A and B must have the same size")) + throw(DimensionMismatch(lazy"A has dimensions $(size(A)), and B has dimensions $(size(B)), but A and B must have the same size")) end - lda = max(1, stride(A, 2)) - ldb = max(1, stride(B, 2)) - alphar = similar(A, $elty, n) - alphai = similar(A, $elty, n) - beta = similar(A, $elty, n) ldvl = 0 if jobvl == 'V' ldvl = n elseif jobvl == 'N' ldvl = 1 else - throw(ArgumentError("jobvl must be 'V' or 'N', but $jobvl was passed")) + throw(ArgumentError(lazy"jobvl must be 'V' or 'N', but $jobvl was passed")) end - vl = similar(A, $elty, ldvl, n) ldvr = 0 if jobvr == 'V' ldvr = n elseif jobvr == 'N' ldvr = 1 else - throw(ArgumentError("jobvr must be 'V' or 'N', but $jobvr was passed")) + throw(ArgumentError(lazy"jobvr must be 'V' or 'N', but $jobvr was passed")) end + lda = max(1, stride(A, 2)) + ldb = max(1, stride(B, 2)) + alphar = similar(A, $elty, n) + alphai = similar(A, $elty, n) + beta = similar(A, $elty, n) + vl = similar(A, $elty, ldvl, n) vr = similar(A, $elty, ldvr, n) work = Vector{$elty}(undef, 1) lwork = BlasInt(-1) @@ -2271,33 +2320,37 @@ for (geevx, ggev, ggev3, elty, relty) in # COMPLEX*16 A( LDA, * ), VL( LDVL, * ), VR( LDVR, * ), # $ W( * ), WORK( * ) function geevx!(balanc::AbstractChar, jobvl::AbstractChar, jobvr::AbstractChar, sense::AbstractChar, A::AbstractMatrix{$elty}) - n = checksquare(A) - chkfinite(A) # balancing routines don't support NaNs and Infs - lda = max(1,stride(A,2)) - w = similar(A, $elty, n) - if balanc ∉ ['N', 'P', 'S', 'B'] - throw(ArgumentError("balanc must be 'N', 'P', 'S', or 'B', but $balanc was passed")) + require_one_based_indexing(A) + if balanc ∉ ('N', 'P', 'S', 'B') + throw(ArgumentError(lazy"balanc must be 'N', 'P', 'S', or 'B', but $balanc was passed")) + end + if sense ∉ ('N','E','V','B') + throw(ArgumentError(lazy"sense must be 'N', 'E', 'V' or 'B', but $sense was passed")) + end + if sense ∈ ('E', 'B') && !(jobvl == jobvr == 'V') + throw(ArgumentError(lazy"sense = '$sense' requires jobvl = 'V' and jobvr = 'V'")) end + n = checksquare(A) ldvl = 0 if jobvl == 'V' ldvl = n elseif jobvl == 'N' ldvl = 0 else - throw(ArgumentError("jobvl must be 'V' or 'N', but $jobvl was passed")) + throw(ArgumentError(lazy"jobvl must be 'V' or 'N', but $jobvl was passed")) end - VL = similar(A, $elty, ldvl, n) ldvr = 0 if jobvr == 'V' ldvr = n elseif jobvr == 'N' ldvr = 0 else - throw(ArgumentError("jobvr must be 'V' or 'N', but $jobvr was passed")) - end - if sense ∉ ['N','E','V','B'] - throw(ArgumentError("sense must be 'N', 'E', 'V' or 'B', but $sense was passed")) + throw(ArgumentError(lazy"jobvr must be 'V' or 'N', but $jobvr was passed")) end + chkfinite(A) # balancing routines don't support NaNs and Infs + lda = max(1,stride(A,2)) + w = similar(A, $elty, n) + VL = similar(A, $elty, ldvl, n) VR = similar(A, $elty, ldvr, n) ilo = Ref{BlasInt}() ihi = Ref{BlasInt}() @@ -2348,29 +2401,29 @@ for (geevx, ggev, ggev3, elty, relty) in chkstride1(A, B) n, m = checksquare(A, B) if n != m - throw(DimensionMismatch("A has dimensions $(size(A)), and B has dimensions $(size(B)), but A and B must have the same size")) + throw(DimensionMismatch(lazy"A has dimensions $(size(A)), and B has dimensions $(size(B)), but A and B must have the same size")) end - lda = max(1, stride(A, 2)) - ldb = max(1, stride(B, 2)) - alpha = similar(A, $elty, n) - beta = similar(A, $elty, n) ldvl = 0 if jobvl == 'V' ldvl = n elseif jobvl == 'N' ldvl = 1 else - throw(ArgumentError("jobvl must be 'V' or 'N', but $jobvl was passed")) + throw(ArgumentError(lazy"jobvl must be 'V' or 'N', but $jobvl was passed")) end - vl = similar(A, $elty, ldvl, n) ldvr = 0 if jobvr == 'V' ldvr = n elseif jobvr == 'N' ldvr = 1 else - throw(ArgumentError("jobvr must be 'V' or 'N', but $jobvr was passed")) + throw(ArgumentError(lazy"jobvr must be 'V' or 'N', but $jobvr was passed")) end + lda = max(1, stride(A, 2)) + ldb = max(1, stride(B, 2)) + alpha = similar(A, $elty, n) + beta = similar(A, $elty, n) + vl = similar(A, $elty, ldvl, n) vr = similar(A, $elty, ldvr, n) work = Vector{$elty}(undef, 1) lwork = BlasInt(-1) @@ -2413,29 +2466,29 @@ for (geevx, ggev, ggev3, elty, relty) in chkstride1(A, B) n, m = checksquare(A, B) if n != m - throw(DimensionMismatch("A has dimensions $(size(A)), and B has dimensions $(size(B)), but A and B must have the same size")) + throw(DimensionMismatch(lazy"A has dimensions $(size(A)), and B has dimensions $(size(B)), but A and B must have the same size")) end - lda = max(1, stride(A, 2)) - ldb = max(1, stride(B, 2)) - alpha = similar(A, $elty, n) - beta = similar(A, $elty, n) ldvl = 0 if jobvl == 'V' ldvl = n elseif jobvl == 'N' ldvl = 1 else - throw(ArgumentError("jobvl must be 'V' or 'N', but $jobvl was passed")) + throw(ArgumentError(lazy"jobvl must be 'V' or 'N', but $jobvl was passed")) end - vl = similar(A, $elty, ldvl, n) ldvr = 0 if jobvr == 'V' ldvr = n elseif jobvr == 'N' ldvr = 1 else - throw(ArgumentError("jobvr must be 'V' or 'N', but $jobvr was passed")) + throw(ArgumentError(lazy"jobvr must be 'V' or 'N', but $jobvr was passed")) end + lda = max(1, stride(A, 2)) + ldb = max(1, stride(B, 2)) + alpha = similar(A, $elty, n) + beta = similar(A, $elty, n) + vl = similar(A, $elty, ldvl, n) vr = similar(A, $elty, ldvr, n) work = Vector{$elty}(undef, 1) lwork = BlasInt(-1) @@ -2520,21 +2573,22 @@ for (laic1, elty) in function laic1!(job::Integer, x::AbstractVector{$elty}, sest::$elty, w::AbstractVector{$elty}, gamma::$elty) require_one_based_indexing(x, w) + @chkvalidparam 1 job (1,2) j = length(x) if j != length(w) - throw(DimensionMismatch("vectors must have same length, but length of x is $j and length of w is $(length(w))")) + throw(DimensionMismatch(lazy"vectors must have same length, but length of x is $j and length of w is $(length(w))")) end - sestpr = Vector{$elty}(undef, 1) - s = Vector{$elty}(undef, 1) - c = Vector{$elty}(undef, 1) + sestpr = Ref{$elty}() + s = Ref{$elty}() + c = Ref{$elty}() ccall((@blasfunc($laic1), libblastrampoline), Cvoid, (Ref{BlasInt}, Ref{BlasInt}, Ptr{$elty}, Ref{$elty}, - Ptr{$elty}, Ref{$elty}, Ptr{$elty}, Ptr{$elty}, - Ptr{$elty}), + Ptr{$elty}, Ref{$elty}, Ref{$elty}, Ref{$elty}, + Ref{$elty}), job, j, x, sest, w, gamma, sestpr, s, c) - sestpr[1], s[1], c[1] + sestpr[], s[], c[] end end end @@ -2554,21 +2608,22 @@ for (laic1, elty, relty) in function laic1!(job::Integer, x::AbstractVector{$elty}, sest::$relty, w::AbstractVector{$elty}, gamma::$elty) require_one_based_indexing(x, w) + @chkvalidparam 1 job (1,2) j = length(x) if j != length(w) - throw(DimensionMismatch("vectors must have same length, but length of x is $j and length of w is $(length(w))")) + throw(DimensionMismatch(lazy"vectors must have same length, but length of x is $j and length of w is $(length(w))")) end - sestpr = Vector{$relty}(undef, 1) - s = Vector{$elty}(undef, 1) - c = Vector{$elty}(undef, 1) + sestpr = Ref{$relty}() + s = Ref{$elty}() + c = Ref{$elty}() ccall((@blasfunc($laic1), libblastrampoline), Cvoid, (Ref{BlasInt}, Ref{BlasInt}, Ptr{$elty}, Ref{$relty}, - Ptr{$elty}, Ref{$elty}, Ptr{$relty}, Ptr{$elty}, - Ptr{$elty}), + Ptr{$elty}, Ref{$elty}, Ref{$relty}, Ref{$elty}, + Ref{$elty}), job, j, x, sest, w, gamma, sestpr, s, c) - sestpr[1], s[1], c[1] + sestpr[], s[], c[] end end end @@ -2591,13 +2646,13 @@ for (gtsv, gttrf, gttrs, elty) in chkstride1(B, dl, d, du) n = length(d) if !(n >= length(dl) >= n - 1) - throw(DimensionMismatch("subdiagonal has length $(length(dl)), but should be $n or $(n - 1)")) + throw(DimensionMismatch(lazy"subdiagonal has length $(length(dl)), but should be $n or $(n - 1)")) end if !(n >= length(du) >= n - 1) - throw(DimensionMismatch("superdiagonal has length $(length(du)), but should be $n or $(n - 1)")) + throw(DimensionMismatch(lazy"superdiagonal has length $(length(du)), but should be $n or $(n - 1)")) end if n != size(B,1) - throw(DimensionMismatch("B has leading dimension $(size(B,1)), but should have $n")) + throw(DimensionMismatch(lazy"B has leading dimension $(size(B,1)), but should have $n")) end if n == 0 return B # Early exit if possible @@ -2622,10 +2677,10 @@ for (gtsv, gttrf, gttrs, elty) in chkstride1(dl,d,du) n = length(d) if length(dl) != n - 1 - throw(DimensionMismatch("subdiagonal has length $(length(dl)), but should be $(n - 1)")) + throw(DimensionMismatch(lazy"subdiagonal has length $(length(dl)), but should be $(n - 1)")) end if length(du) != n - 1 - throw(DimensionMismatch("superdiagonal has length $(length(du)), but should be $(n - 1)")) + throw(DimensionMismatch(lazy"superdiagonal has length $(length(du)), but should be $(n - 1)")) end du2 = similar(d, $elty, n-2) ipiv = similar(d, BlasInt, n) @@ -2653,13 +2708,13 @@ for (gtsv, gttrf, gttrs, elty) in chkstride1(B, ipiv, dl, d, du, du2) n = length(d) if length(dl) != n - 1 - throw(DimensionMismatch("subdiagonal has length $(length(dl)), but should be $(n - 1)")) + throw(DimensionMismatch(lazy"subdiagonal has length $(length(dl)), but should be $(n - 1)")) end if length(du) != n - 1 - throw(DimensionMismatch("superdiagonal has length $(length(du)), but should be $(n - 1)")) + throw(DimensionMismatch(lazy"superdiagonal has length $(length(du)), but should be $(n - 1)")) end if n != size(B,1) - throw(DimensionMismatch("B has leading dimension $(size(B,1)), but should have $n")) + throw(DimensionMismatch(lazy"B has leading dimension $(size(B,1)), but should have $n")) end info = Ref{BlasInt}() ccall((@blasfunc($gttrs), libblastrampoline), Cvoid, @@ -2723,7 +2778,7 @@ for (orglq, orgqr, orgql, orgrq, ormlq, ormqr, ormql, ormrq, gemqrt, elty) in n = size(A, 2) m = min(n, size(A, 1)) if k > m - throw(DimensionMismatch("invalid number of reflectors: k = $k should be <= m = $m")) + throw(DimensionMismatch(lazy"invalid number of reflectors: k = $k should be <= m = $m")) end work = Vector{$elty}(undef, 1) lwork = BlasInt(-1) @@ -2757,7 +2812,7 @@ for (orglq, orgqr, orgql, orgrq, ormlq, ormqr, ormql, ormrq, gemqrt, elty) in m = size(A, 1) n = min(m, size(A, 2)) if k > n - throw(DimensionMismatch("invalid number of reflectors: k = $k should be <= n = $n")) + throw(DimensionMismatch(lazy"invalid number of reflectors: k = $k should be <= n = $n")) end work = Vector{$elty}(undef, 1) lwork = BlasInt(-1) @@ -2793,7 +2848,7 @@ for (orglq, orgqr, orgql, orgrq, ormlq, ormqr, ormql, ormrq, gemqrt, elty) in m = size(A, 1) n = min(m, size(A, 2)) if k > n - throw(DimensionMismatch("invalid number of reflectors: k = $k should be <= n = $n")) + throw(DimensionMismatch(lazy"invalid number of reflectors: k = $k should be <= n = $n")) end work = Vector{$elty}(undef, 1) lwork = BlasInt(-1) @@ -2828,10 +2883,10 @@ for (orglq, orgqr, orgql, orgrq, ormlq, ormqr, ormql, ormrq, gemqrt, elty) in chkstride1(A,tau) m, n = size(A) if n < m - throw(DimensionMismatch("input matrix A has dimensions ($m,$n), but cannot have fewer columns than rows")) + throw(DimensionMismatch(lazy"input matrix A has dimensions ($m,$n), but cannot have fewer columns than rows")) end if k > n - throw(DimensionMismatch("invalid number of reflectors: k = $k should be <= n = $n")) + throw(DimensionMismatch(lazy"invalid number of reflectors: k = $k should be <= n = $n")) end work = Vector{$elty}(undef, 1) lwork = BlasInt(-1) @@ -2869,16 +2924,16 @@ for (orglq, orgqr, orgql, orgrq, ormlq, ormqr, ormql, ormrq, gemqrt, elty) in nA = size(A, 2) k = length(tau) if side == 'L' && m != nA - throw(DimensionMismatch("for a left-sided multiplication, the first dimension of C, $m, must equal the second dimension of A, $nA")) + throw(DimensionMismatch(lazy"for a left-sided multiplication, the first dimension of C, $m, must equal the second dimension of A, $nA")) end if side == 'R' && n != nA - throw(DimensionMismatch("for a right-sided multiplication, the second dimension of C, $n, must equal the second dimension of A, $nA")) + throw(DimensionMismatch(lazy"for a right-sided multiplication, the second dimension of C, $n, must equal the second dimension of A, $nA")) end if side == 'L' && k > m - throw(DimensionMismatch("invalid number of reflectors: k = $k should be <= m = $m")) + throw(DimensionMismatch(lazy"invalid number of reflectors: k = $k should be <= m = $m")) end if side == 'R' && k > n - throw(DimensionMismatch("invalid number of reflectors: k = $k should be <= n = $n")) + throw(DimensionMismatch(lazy"invalid number of reflectors: k = $k should be <= n = $n")) end work = Vector{$elty}(undef, 1) lwork = BlasInt(-1) @@ -2916,16 +2971,16 @@ for (orglq, orgqr, orgql, orgrq, ormlq, ormqr, ormql, ormrq, gemqrt, elty) in mA = size(A, 1) k = length(tau) if side == 'L' && m != mA - throw(DimensionMismatch("for a left-sided multiplication, the first dimension of C, $m, must equal the second dimension of A, $mA")) + throw(DimensionMismatch(lazy"for a left-sided multiplication, the first dimension of C, $m, must equal the second dimension of A, $mA")) end if side == 'R' && n != mA - throw(DimensionMismatch("for a right-sided multiplication, the second dimension of C, $m, must equal the second dimension of A, $mA")) + throw(DimensionMismatch(lazy"for a right-sided multiplication, the second dimension of C, $m, must equal the second dimension of A, $mA")) end if side == 'L' && k > m - throw(DimensionMismatch("invalid number of reflectors: k = $k should be <= m = $m")) + throw(DimensionMismatch(lazy"invalid number of reflectors: k = $k should be <= m = $m")) end if side == 'R' && k > n - throw(DimensionMismatch("invalid number of reflectors: k = $k should be <= n = $n")) + throw(DimensionMismatch(lazy"invalid number of reflectors: k = $k should be <= n = $n")) end work = Vector{$elty}(undef, 1) lwork = BlasInt(-1) @@ -2966,16 +3021,16 @@ for (orglq, orgqr, orgql, orgrq, ormlq, ormqr, ormql, ormrq, gemqrt, elty) in mA = size(A, 1) k = length(tau) if side == 'L' && m != mA - throw(DimensionMismatch("for a left-sided multiplication, the first dimension of C, $m, must equal the second dimension of A, $mA")) + throw(DimensionMismatch(lazy"for a left-sided multiplication, the first dimension of C, $m, must equal the second dimension of A, $mA")) end if side == 'R' && n != mA - throw(DimensionMismatch("for a right-sided multiplication, the second dimension of C, $m, must equal the second dimension of A, $mA")) + throw(DimensionMismatch(lazy"for a right-sided multiplication, the second dimension of C, $m, must equal the second dimension of A, $mA")) end if side == 'L' && k > m - throw(DimensionMismatch("invalid number of reflectors: k = $k should be <= m = $m")) + throw(DimensionMismatch(lazy"invalid number of reflectors: k = $k should be <= m = $m")) end if side == 'R' && k > n - throw(DimensionMismatch("invalid number of reflectors: k = $k should be <= n = $n")) + throw(DimensionMismatch(lazy"invalid number of reflectors: k = $k should be <= n = $n")) end work = Vector{$elty}(undef, 1) lwork = BlasInt(-1) @@ -3016,16 +3071,16 @@ for (orglq, orgqr, orgql, orgrq, ormlq, ormqr, ormql, ormrq, gemqrt, elty) in nA = size(A, 2) k = length(tau) if side == 'L' && m != nA - throw(DimensionMismatch("for a left-sided multiplication, the first dimension of C, $m, must equal the second dimension of A, $nA")) + throw(DimensionMismatch(lazy"for a left-sided multiplication, the first dimension of C, $m, must equal the second dimension of A, $nA")) end if side == 'R' && n != nA - throw(DimensionMismatch("for a right-sided multiplication, the second dimension of C, $m, must equal the second dimension of A, $nA")) + throw(DimensionMismatch(lazy"for a right-sided multiplication, the second dimension of C, $m, must equal the second dimension of A, $nA")) end if side == 'L' && k > m - throw(DimensionMismatch("invalid number of reflectors: k = $k should be <= m = $m")) + throw(DimensionMismatch(lazy"invalid number of reflectors: k = $k should be <= m = $m")) end if side == 'R' && k > n - throw(DimensionMismatch("invalid number of reflectors: k = $k should be <= n = $n")) + throw(DimensionMismatch(lazy"invalid number of reflectors: k = $k should be <= n = $n")) end work = Vector{$elty}(undef, 1) lwork = BlasInt(-1) @@ -3058,31 +3113,31 @@ for (orglq, orgqr, orgql, orgrq, ormlq, ormqr, ormql, ormrq, gemqrt, elty) in end if side == 'L' if !(0 <= k <= m) - throw(DimensionMismatch("wrong value for k = $k: must be between 0 and $m")) + throw(DimensionMismatch(lazy"wrong value for k = $k: must be between 0 and $m")) end if m != size(V,1) - throw(DimensionMismatch("first dimensions of C, $m, and V, $(size(V,1)) must match")) + throw(DimensionMismatch(lazy"first dimensions of C, $m, and V, $(size(V,1)) must match")) end ldv = stride(V,2) if ldv < max(1, m) - throw(DimensionMismatch("Q and C don't fit! The stride of V, $ldv, is too small")) + throw(DimensionMismatch(lazy"Q and C don't fit! The stride of V, $ldv, is too small")) end wss = n*k elseif side == 'R' if !(0 <= k <= n) - throw(DimensionMismatch("wrong value for k = $k: must be between 0 and $n")) + throw(DimensionMismatch(lazy"wrong value for k = $k: must be between 0 and $n")) end if n != size(V,1) - throw(DimensionMismatch("second dimension of C, $n, and first dimension of V, $(size(V,1)) must match")) + throw(DimensionMismatch(lazy"second dimension of C, $n, and first dimension of V, $(size(V,1)) must match")) end ldv = stride(V,2) if ldv < max(1, n) - throw(DimensionMismatch("Q and C don't fit! The stride of V, $ldv, is too small")) + throw(DimensionMismatch(lazy"Q and C don't fit! The stride of V, $ldv, is too small")) end wss = m*k end if !(1 <= nb <= k) - throw(DimensionMismatch("wrong value for nb = $nb, which must be between 1 and $k")) + throw(DimensionMismatch(lazy"wrong value for nb = $nb, which must be between 1 and $k")) end ldc = stride(C, 2) work = Vector{$elty}(undef, wss) @@ -3203,7 +3258,7 @@ for (posv, potrf, potri, potrs, pstrf, elty, rtyp) in n = checksquare(A) chkuplo(uplo) if size(B,1) != n - throw(DimensionMismatch("first dimension of B, $(size(B,1)), and size of A, ($n,$n), must match!")) + throw(DimensionMismatch(lazy"first dimension of B, $(size(B,1)), and size of A, ($n,$n), must match!")) end info = Ref{BlasInt}() ccall((@blasfunc($posv), libblastrampoline), Cvoid, @@ -3273,7 +3328,7 @@ for (posv, potrf, potri, potrs, pstrf, elty, rtyp) in chkuplo(uplo) nrhs = size(B,2) if size(B,1) != n - throw(DimensionMismatch("first dimension of B, $(size(B,1)), and size of A, ($n,$n), must match!")) + throw(DimensionMismatch(lazy"first dimension of B, $(size(B,1)), and size of A, ($n,$n), must match!")) end lda = max(1,stride(A,2)) if lda == 0 || nrhs == 0 @@ -3299,6 +3354,7 @@ for (posv, potrf, potri, potrs, pstrf, elty, rtyp) in # DOUBLE PRECISION A( LDA, * ), WORK( 2*N ) # INTEGER PIV( N ) function pstrf!(uplo::AbstractChar, A::AbstractMatrix{$elty}, tol::Real) + require_one_based_indexing(A) chkstride1(A) n = checksquare(A) chkuplo(uplo) @@ -3389,10 +3445,10 @@ for (ptsv, pttrf, elty, relty) in chkstride1(B, D, E) n = length(D) if length(E) != n - 1 - throw(DimensionMismatch("E has length $(length(E)), but needs $(n - 1)")) + throw(DimensionMismatch(lazy"E has length $(length(E)), but needs $(n - 1)")) end if n != size(B,1) - throw(DimensionMismatch("B has first dimension $(size(B,1)) but needs $n")) + throw(DimensionMismatch(lazy"B has first dimension $(size(B,1)) but needs $n")) end info = Ref{BlasInt}() ccall((@blasfunc($ptsv), libblastrampoline), Cvoid, @@ -3413,7 +3469,7 @@ for (ptsv, pttrf, elty, relty) in chkstride1(D, E) n = length(D) if length(E) != n - 1 - throw(DimensionMismatch("E has length $(length(E)), but needs $(n - 1)")) + throw(DimensionMismatch(lazy"E has length $(length(E)), but needs $(n - 1)")) end info = Ref{BlasInt}() ccall((@blasfunc($pttrf), libblastrampoline), Cvoid, @@ -3457,10 +3513,10 @@ for (pttrs, elty, relty) in chkstride1(B, D, E) n = length(D) if length(E) != n - 1 - throw(DimensionMismatch("E has length $(length(E)), but needs $(n - 1)")) + throw(DimensionMismatch(lazy"E has length $(length(E)), but needs $(n - 1)")) end if n != size(B,1) - throw(DimensionMismatch("B has first dimension $(size(B,1)) but needs $n")) + throw(DimensionMismatch(lazy"B has first dimension $(size(B,1)) but needs $n")) end info = Ref{BlasInt}() ccall((@blasfunc($pttrs), libblastrampoline), Cvoid, @@ -3491,10 +3547,10 @@ for (pttrs, elty, relty) in chkuplo(uplo) n = length(D) if length(E) != n - 1 - throw(DimensionMismatch("E has length $(length(E)), but needs $(n - 1)")) + throw(DimensionMismatch(lazy"E has length $(length(E)), but needs $(n - 1)")) end if n != size(B,1) - throw(DimensionMismatch("B has first dimension $(size(B,1)) but needs $n")) + throw(DimensionMismatch(lazy"B has first dimension $(size(B,1)) but needs $n")) end info = Ref{BlasInt}() ccall((@blasfunc($pttrs), libblastrampoline), Cvoid, @@ -3530,6 +3586,7 @@ for (trtri, trtrs, elty) in # .. Array Arguments .. # DOUBLE PRECISION A( LDA, * ) function trtri!(uplo::AbstractChar, diag::AbstractChar, A::AbstractMatrix{$elty}) + require_one_based_indexing(A) chkstride1(A) n = checksquare(A) chkuplo(uplo) @@ -3559,7 +3616,7 @@ for (trtri, trtrs, elty) in n = checksquare(A) chkuplo(uplo) if n != size(B,1) - throw(DimensionMismatch("B has first dimension $(size(B,1)) but needs $n")) + throw(DimensionMismatch(lazy"B has first dimension $(size(B,1)) but needs $n")) end info = Ref{BlasInt}() ccall((@blasfunc($trtrs), libblastrampoline), Cvoid, @@ -3569,11 +3626,12 @@ for (trtri, trtrs, elty) in uplo, trans, diag, n, size(B,2), A, max(1,stride(A,2)), B, max(1,stride(B,2)), info, 1, 1, 1) - chklapackerror(info[]) + chklapackerror(info[], trtrs!) B end end end +chklapackerror_positive(ret, ::typeof(trtrs!)) = chknonsingular(ret) """ trtri!(uplo, diag, A) @@ -3611,10 +3669,12 @@ for (trcon, trevc, trrfs, elty) in # INTEGER IWORK( * ) # DOUBLE PRECISION A( LDA, * ), WORK( * ) function trcon!(norm::AbstractChar, uplo::AbstractChar, diag::AbstractChar, A::AbstractMatrix{$elty}) + require_one_based_indexing(A) chkstride1(A) chkdiag(diag) n = checksquare(A) chkuplo(uplo) + @chkvalidparam 1 norm ('O', '1', 'I') rcond = Ref{$elty}() work = Vector{$elty}(undef, 3n) iwork = Vector{BlasInt}(undef, n) @@ -3646,9 +3706,10 @@ for (trcon, trevc, trrfs, elty) in VR::AbstractMatrix{$elty} = similar(T)) require_one_based_indexing(select, T, VL, VR) # Extract - if side ∉ ['L','R','B'] - throw(ArgumentError("side argument must be 'L' (left eigenvectors), 'R' (right eigenvectors), or 'B' (both), got $side")) + if side ∉ ('L','R','B') + throw(ArgumentError(lazy"side argument must be 'L' (left eigenvectors), 'R' (right eigenvectors), or 'B' (both), got $side")) end + @chkvalidparam 2 howmny ('A', 'B', 'S') n, mm = checksquare(T), size(VL, 2) ldt, ldvl, ldvr = stride(T, 2), stride(VL, 2), stride(VR, 2) @@ -3712,7 +3773,7 @@ for (trcon, trevc, trrfs, elty) in n = size(A,2) nrhs = size(B,2) if nrhs != size(X,2) - throw(DimensionMismatch("second dimensions of B, $nrhs, and X, $(size(X,2)), must match")) + throw(DimensionMismatch(lazy"second dimensions of B, $nrhs, and X, $(size(X,2)), must match")) end work = Vector{$elty}(undef, 3n) iwork = Vector{BlasInt}(undef, n) @@ -3744,8 +3805,10 @@ for (trcon, trevc, trrfs, elty, relty) in # DOUBLE PRECISION RWORK( * ) # COMPLEX*16 A( LDA, * ), WORK( * ) function trcon!(norm::AbstractChar, uplo::AbstractChar, diag::AbstractChar, A::AbstractMatrix{$elty}) + require_one_based_indexing(A) chkstride1(A) n = checksquare(A) + @chkvalidparam 1 norm ('O', '1', 'I') chkuplo(uplo) chkdiag(diag) rcond = Ref{$relty}(1) @@ -3785,9 +3848,10 @@ for (trcon, trevc, trrfs, elty, relty) in # Check chkstride1(T, select, VL, VR) - if side ∉ ['L','R','B'] - throw(ArgumentError("side argument must be 'L' (left eigenvectors), 'R' (right eigenvectors), or 'B' (both), got $side")) + if side ∉ ('L','R','B') + throw(ArgumentError(lazy"side argument must be 'L' (left eigenvectors), 'R' (right eigenvectors), or 'B' (both), got $side")) end + @chkvalidparam 2 howmny ('A', 'B', 'S') # Allocate m = Ref{BlasInt}() @@ -3846,7 +3910,7 @@ for (trcon, trevc, trrfs, elty, relty) in n = size(A,2) nrhs = size(B,2) if nrhs != size(X,2) - throw(DimensionMismatch("second dimensions of B, $nrhs, and X, $(size(X,2)), must match")) + throw(DimensionMismatch(lazy"second dimensions of B, $nrhs, and X, $(size(X,2)), must match")) end work = Vector{$elty}(undef, 2n) rwork = Vector{$relty}(undef, n) @@ -3914,10 +3978,11 @@ for (stev, stebz, stegr, stein, elty) in @eval begin function stev!(job::AbstractChar, dv::AbstractVector{$elty}, ev::AbstractVector{$elty}) require_one_based_indexing(dv, ev) + @chkvalidparam 1 job ('N', 'V') chkstride1(dv, ev) n = length(dv) if length(ev) != n - 1 && length(ev) != n - throw(DimensionMismatch("ev has length $(length(ev)) but needs one less than or equal to dv's length, $n)")) + throw(DimensionMismatch(lazy"ev has length $(length(ev)) but needs one less than or equal to dv's length, $n)")) end Zmat = similar(dv, $elty, (n, job != 'N' ? n : 0)) work = Vector{$elty}(undef, max(1, 2n-2)) @@ -3936,10 +4001,12 @@ for (stev, stebz, stegr, stein, elty) in #* eigenvalues. function stebz!(range::AbstractChar, order::AbstractChar, vl::$elty, vu::$elty, il::Integer, iu::Integer, abstol::Real, dv::AbstractVector{$elty}, ev::AbstractVector{$elty}) require_one_based_indexing(dv, ev) + @chkvalidparam 1 range ('A', 'V', 'I') + @chkvalidparam 2 order ('B', 'E') chkstride1(dv, ev) n = length(dv) if length(ev) != n - 1 - throw(DimensionMismatch("ev has length $(length(ev)) but needs one less than dv's length, $n)")) + throw(DimensionMismatch(lazy"ev has length $(length(ev)) but needs one less than dv's length, $n)")) end m = Ref{BlasInt}() nsplit = Vector{BlasInt}(undef, 1) @@ -3967,6 +4034,8 @@ for (stev, stebz, stegr, stein, elty) in function stegr!(jobz::AbstractChar, range::AbstractChar, dv::AbstractVector{$elty}, ev::AbstractVector{$elty}, vl::Real, vu::Real, il::Integer, iu::Integer) require_one_based_indexing(dv, ev) + @chkvalidparam 1 jobz ('N', 'V') + @chkvalidparam 2 range ('A', 'V', 'I') chkstride1(dv, ev) n = length(dv) ne = length(ev) @@ -3976,7 +4045,7 @@ for (stev, stebz, stegr, stein, elty) in eev = copy(ev) eev[n] = zero($elty) else - throw(DimensionMismatch("ev has length $ne but needs one less than or equal to dv's length, $n)")) + throw(DimensionMismatch(lazy"ev has length $ne but needs one less than or equal to dv's length, $n)")) end abstol = Vector{$elty}(undef, 1) @@ -4026,12 +4095,12 @@ for (stev, stebz, stegr, stein, elty) in ev = copy(ev_in) ev[n] = zero($elty) else - throw(DimensionMismatch("ev_in has length $ne but needs one less than or equal to dv's length, $n)")) + throw(DimensionMismatch(lazy"ev_in has length $ne but needs one less than or equal to dv's length, $n)")) end ldz = n #Leading dimension #Number of eigenvalues to find if !(1 <= length(w_in) <= n) - throw(DimensionMismatch("w_in has length $(length(w_in)), but needs to be between 1 and $n")) + throw(DimensionMismatch(lazy"w_in has length $(length(w_in)), but needs to be between 1 and $n")) end m = length(w_in) #If iblock and isplit are invalid input, assume worst-case block partitioning, @@ -4139,6 +4208,7 @@ for (syconv, sysv, sytrf, sytri, sytrs, elty) in # INTEGER IPIV( * ) # DOUBLE PRECISION A( LDA, * ), WORK( * ) function syconv!(uplo::AbstractChar, A::AbstractMatrix{$elty}, ipiv::AbstractVector{BlasInt}) + require_one_based_indexing(A, ipiv) chkstride1(A, ipiv) n = checksquare(A) chkuplo(uplo) @@ -4166,7 +4236,7 @@ for (syconv, sysv, sytrf, sytri, sytrs, elty) in n = checksquare(A) chkuplo(uplo) if n != size(B,1) - throw(DimensionMismatch("B has first dimension $(size(B,1)), but needs $n")) + throw(DimensionMismatch(lazy"B has first dimension $(size(B,1)), but needs $n")) end ipiv = similar(A, BlasInt, n) work = Vector{$elty}(undef, 1) @@ -4196,6 +4266,7 @@ for (syconv, sysv, sytrf, sytri, sytrs, elty) in # INTEGER IPIV( * ) # DOUBLE PRECISION A( LDA, * ), WORK( * ) function sytrf!(uplo::AbstractChar, A::AbstractMatrix{$elty}, ipiv::AbstractVector{BlasInt}) + require_one_based_indexing(A) chkstride1(A) n = checksquare(A) chkuplo(uplo) @@ -4220,6 +4291,8 @@ for (syconv, sysv, sytrf, sytri, sytrs, elty) in end function sytrf!(uplo::AbstractChar, A::AbstractMatrix{$elty}) + require_one_based_indexing(A) + chkuplo(uplo) n = checksquare(A) ipiv = similar(A, BlasInt, n) sytrf!(uplo, A, ipiv) @@ -4262,6 +4335,7 @@ for (syconv, sysv, sytrf, sytri, sytrs, elty) in # INTEGER IPIV( * ) # DOUBLE PRECISION A( LDA, * ), WORK( * ) function sytri!(uplo::AbstractChar, A::AbstractMatrix{$elty}, ipiv::AbstractVector{BlasInt}) + require_one_based_indexing(A, ipiv) chkstride1(A, ipiv) n = checksquare(A) chkuplo(uplo) @@ -4291,7 +4365,7 @@ for (syconv, sysv, sytrf, sytri, sytrs, elty) in n = checksquare(A) chkuplo(uplo) if n != size(B,1) - throw(DimensionMismatch("B has first dimension $(size(B,1)), but needs $n")) + throw(DimensionMismatch(lazy"B has first dimension $(size(B,1)), but needs $n")) end info = Ref{BlasInt}() ccall((@blasfunc($sytrs), libblastrampoline), Cvoid, @@ -4323,7 +4397,7 @@ for (sysv, sytrf, sytri, sytrs, syconvf, elty) in n = checksquare(A) chkuplo(uplo) if n != size(B,1) - throw(DimensionMismatch("B has first dimension $(size(B,1)), but needs $n")) + throw(DimensionMismatch(lazy"B has first dimension $(size(B,1)), but needs $n")) end ipiv = similar(A, BlasInt, n) work = Vector{$elty}(undef, 1) @@ -4353,6 +4427,7 @@ for (sysv, sytrf, sytri, sytrs, syconvf, elty) in # INTEGER IPIV( * ) # DOUBLE PRECISION A( LDA, * ), WORK( * ) function sytrf_rook!(uplo::AbstractChar, A::AbstractMatrix{$elty}) + require_one_based_indexing(A) chkstride1(A) n = checksquare(A) chkuplo(uplo) @@ -4385,6 +4460,7 @@ for (sysv, sytrf, sytri, sytrs, syconvf, elty) in # INTEGER IPIV( * ) # DOUBLE PRECISION A( LDA, * ), WORK( * ) function sytri_rook!(uplo::AbstractChar, A::AbstractMatrix{$elty}, ipiv::AbstractVector{BlasInt}) + require_one_based_indexing(A, ipiv) chkstride1(A, ipiv) n = checksquare(A) chkuplo(uplo) @@ -4414,7 +4490,7 @@ for (sysv, sytrf, sytri, sytrs, syconvf, elty) in n = checksquare(A) chkuplo(uplo) if n != size(B,1) - throw(DimensionMismatch("B has first dimension $(size(B,1)), but needs $n")) + throw(DimensionMismatch(lazy"B has first dimension $(size(B,1)), but needs $n")) end info = Ref{BlasInt}() ccall((@blasfunc($sytrs), libblastrampoline), Cvoid, @@ -4448,10 +4524,10 @@ for (sysv, sytrf, sytri, sytrs, syconvf, elty) in throw(ArgumentError("way must be C or R")) end if length(ipiv) != n - throw(ArgumentError("length of pivot vector was $(length(ipiv)) but should have been $n")) + throw(ArgumentError(lazy"length of pivot vector was $(length(ipiv)) but should have been $n")) end if length(e) != n - throw(ArgumentError("length of e vector was $(length(e)) but should have been $n")) + throw(ArgumentError(lazy"length of e vector was $(length(e)) but should have been $n")) end # allocate @@ -4487,6 +4563,7 @@ for (syconv, hesv, hetrf, hetri, hetrs, elty, relty) in # INTEGER IPIV( * ) # COMPLEX*16 A( LDA, * ), WORK( * ) function syconv!(uplo::AbstractChar, A::AbstractMatrix{$elty}, ipiv::AbstractVector{BlasInt}) + require_one_based_indexing(A,ipiv) chkstride1(A,ipiv) n = checksquare(A) chkuplo(uplo) @@ -4514,7 +4591,7 @@ for (syconv, hesv, hetrf, hetri, hetrs, elty, relty) in n = checksquare(A) chkuplo(uplo) if n != size(B,1) - throw(DimensionMismatch("B has first dimension $(size(B,1)), but needs $n")) + throw(DimensionMismatch(lazy"B has first dimension $(size(B,1)), but needs $n")) end ipiv = similar(A, BlasInt, n) work = Vector{$elty}(undef, 1) @@ -4544,6 +4621,7 @@ for (syconv, hesv, hetrf, hetri, hetrs, elty, relty) in # INTEGER IPIV( * ) # COMPLEX*16 A( LDA, * ), WORK( * ) function hetrf!(uplo::AbstractChar, A::AbstractMatrix{$elty}, ipiv::AbstractVector{BlasInt}) + require_one_based_indexing(A) chkstride1(A) n = checksquare(A) chkuplo(uplo) @@ -4565,6 +4643,8 @@ for (syconv, hesv, hetrf, hetri, hetrs, elty, relty) in end function hetrf!(uplo::AbstractChar, A::AbstractMatrix{$elty}) + require_one_based_indexing(A) + chkuplo(uplo) n = checksquare(A) ipiv = similar(A, BlasInt, n) hetrf!(uplo, A, ipiv) @@ -4609,6 +4689,7 @@ for (syconv, hesv, hetrf, hetri, hetrs, elty, relty) in # INTEGER IPIV( * ) # COMPLEX*16 A( LDA, * ), WORK( * ) function hetri!(uplo::AbstractChar, A::AbstractMatrix{$elty}, ipiv::AbstractVector{BlasInt}) + require_one_based_indexing(A, ipiv) chkstride1(A, ipiv) n = checksquare(A) chkuplo(uplo) @@ -4633,10 +4714,11 @@ for (syconv, hesv, hetrf, hetri, hetrs, elty, relty) in function hetrs!(uplo::AbstractChar, A::AbstractMatrix{$elty}, ipiv::AbstractVector{BlasInt}, B::AbstractVecOrMat{$elty}) require_one_based_indexing(A, ipiv, B) + chkuplo(uplo) chkstride1(A,B,ipiv) n = checksquare(A) if n != size(B,1) - throw(DimensionMismatch("B has first dimension $(size(B,1)), but needs $n")) + throw(DimensionMismatch(lazy"B has first dimension $(size(B,1)), but needs $n")) end info = Ref{BlasInt}() ccall((@blasfunc($hetrs), libblastrampoline), Cvoid, @@ -4667,7 +4749,7 @@ for (hesv, hetrf, hetri, hetrs, elty, relty) in n = checksquare(A) chkuplo(uplo) if n != size(B,1) - throw(DimensionMismatch("B has first dimension $(size(B,1)), but needs $n")) + throw(DimensionMismatch(lazy"B has first dimension $(size(B,1)), but needs $n")) end ipiv = similar(A, BlasInt, n) work = Vector{$elty}(undef, 1) @@ -4697,6 +4779,7 @@ for (hesv, hetrf, hetri, hetrs, elty, relty) in # INTEGER IPIV( * ) # COMPLEX*16 A( LDA, * ), WORK( * ) function hetrf_rook!(uplo::AbstractChar, A::AbstractMatrix{$elty}) + require_one_based_indexing(A) chkstride1(A) n = checksquare(A) chkuplo(uplo) @@ -4727,6 +4810,7 @@ for (hesv, hetrf, hetri, hetrs, elty, relty) in # INTEGER IPIV( * ) # COMPLEX*16 A( LDA, * ), WORK( * ) function hetri_rook!(uplo::AbstractChar, A::AbstractMatrix{$elty}, ipiv::AbstractVector{BlasInt}) + require_one_based_indexing(A,ipiv) chkstride1(A,ipiv) n = checksquare(A) chkuplo(uplo) @@ -4752,9 +4836,10 @@ for (hesv, hetrf, hetri, hetrs, elty, relty) in ipiv::AbstractVector{BlasInt}, B::AbstractVecOrMat{$elty}) require_one_based_indexing(A, ipiv, B) chkstride1(A,B,ipiv) + chkuplo(uplo) n = checksquare(A) if n != size(B,1) - throw(DimensionMismatch("B has first dimension $(size(B,1)), but needs $n")) + throw(DimensionMismatch(lazy"B has first dimension $(size(B,1)), but needs $n")) end info = Ref{BlasInt}() ccall((@blasfunc($hetrs), libblastrampoline), Cvoid, @@ -4786,7 +4871,7 @@ for (sysv, sytrf, sytri, sytrs, elty, relty) in n = checksquare(A) chkuplo(uplo) if n != size(B,1) - throw(DimensionMismatch("B has first dimension $(size(B,1)), but needs $n")) + throw(DimensionMismatch(lazy"B has first dimension $(size(B,1)), but needs $n")) end ipiv = similar(A, BlasInt, n) work = Vector{$elty}(undef, 1) @@ -4817,6 +4902,7 @@ for (sysv, sytrf, sytri, sytrs, elty, relty) in # INTEGER IPIV( * ) # COMPLEX*16 A( LDA, * ), WORK( * ) function sytrf!(uplo::AbstractChar, A::AbstractMatrix{$elty}, ipiv::AbstractVector{BlasInt}) + require_one_based_indexing(A) chkstride1(A) n = checksquare(A) chkuplo(uplo) @@ -4841,6 +4927,8 @@ for (sysv, sytrf, sytri, sytrs, elty, relty) in end function sytrf!(uplo::AbstractChar, A::AbstractMatrix{$elty}) + require_one_based_indexing(A) + chkuplo(uplo) n = checksquare(A) ipiv = similar(A, BlasInt, n) sytrf!(uplo, A, ipiv) @@ -4884,6 +4972,7 @@ for (sysv, sytrf, sytri, sytrs, elty, relty) in # INTEGER IPIV( * ) # COMPLEX*16 A( LDA, * ), WORK( * ) function sytri!(uplo::AbstractChar, A::AbstractMatrix{$elty}, ipiv::AbstractVector{BlasInt}) + require_one_based_indexing(A, ipiv) chkstride1(A, ipiv) n = checksquare(A) chkuplo(uplo) @@ -4912,7 +5001,7 @@ for (sysv, sytrf, sytri, sytrs, elty, relty) in n = checksquare(A) chkuplo(uplo) if n != size(B,1) - throw(DimensionMismatch("B has first dimension $(size(B,1)), but needs $n")) + throw(DimensionMismatch(lazy"B has first dimension $(size(B,1)), but needs $n")) end info = Ref{BlasInt}() ccall((@blasfunc($sytrs), libblastrampoline), Cvoid, @@ -4944,7 +5033,7 @@ for (sysv, sytrf, sytri, sytrs, syconvf, elty, relty) in n = checksquare(A) chkuplo(uplo) if n != size(B,1) - throw(DimensionMismatch("B has first dimension $(size(B,1)), but needs $n")) + throw(DimensionMismatch(lazy"B has first dimension $(size(B,1)), but needs $n")) end ipiv = similar(A, BlasInt, n) work = Vector{$elty}(undef, 1) @@ -4975,6 +5064,7 @@ for (sysv, sytrf, sytri, sytrs, syconvf, elty, relty) in # INTEGER IPIV( * ) # COMPLEX*16 A( LDA, * ), WORK( * ) function sytrf_rook!(uplo::AbstractChar, A::AbstractMatrix{$elty}) + require_one_based_indexing(A) chkstride1(A) n = checksquare(A) chkuplo(uplo) @@ -5008,6 +5098,7 @@ for (sysv, sytrf, sytri, sytrs, syconvf, elty, relty) in # INTEGER IPIV( * ) # COMPLEX*16 A( LDA, * ), WORK( * ) function sytri_rook!(uplo::AbstractChar, A::AbstractMatrix{$elty}, ipiv::AbstractVector{BlasInt}) + require_one_based_indexing(A, ipiv) chkstride1(A, ipiv) n = checksquare(A) chkuplo(uplo) @@ -5036,7 +5127,7 @@ for (sysv, sytrf, sytri, sytrs, syconvf, elty, relty) in n = checksquare(A) chkuplo(uplo) if n != size(B,1) - throw(DimensionMismatch("B has first dimension $(size(B,1)), but needs $n")) + throw(DimensionMismatch(lazy"B has first dimension $(size(B,1)), but needs $n")) end info = Ref{BlasInt}() ccall((@blasfunc($sytrs), libblastrampoline), Cvoid, @@ -5069,13 +5160,13 @@ for (sysv, sytrf, sytri, sytrs, syconvf, elty, relty) in # check chkuplo(uplo) if way != 'C' && way != 'R' - throw(ArgumentError("way must be 'C' or 'R'")) + throw(ArgumentError(lazy"way must be 'C' or 'R'")) end if length(ipiv) != n - throw(ArgumentError("length of pivot vector was $(length(ipiv)) but should have been $n")) + throw(ArgumentError(lazy"length of pivot vector was $(length(ipiv)) but should have been $n")) end if length(e) != n - throw(ArgumentError("length of e vector was $(length(e)) but should have been $n")) + throw(ArgumentError(lazy"length of e vector was $(length(e)) but should have been $n")) end # allocate @@ -5235,6 +5326,9 @@ for (syev, syevr, syevd, sygvd, elty) in # * .. Array Arguments .. # DOUBLE PRECISION A( LDA, * ), W( * ), WORK( * ) function syev!(jobz::AbstractChar, uplo::AbstractChar, A::AbstractMatrix{$elty}) + require_one_based_indexing(A) + @chkvalidparam 1 jobz ('N', 'V') + chkuplo(uplo) chkstride1(A) n = checksquare(A) W = similar(A, $elty, n) @@ -5268,15 +5362,18 @@ for (syev, syevr, syevd, sygvd, elty) in # DOUBLE PRECISION A( LDA, * ), W( * ), WORK( * ), Z( LDZ, * ) function syevr!(jobz::AbstractChar, range::AbstractChar, uplo::AbstractChar, A::AbstractMatrix{$elty}, vl::AbstractFloat, vu::AbstractFloat, il::Integer, iu::Integer, abstol::AbstractFloat) + require_one_based_indexing(A) + @chkvalidparam 1 jobz ('N', 'V') + @chkvalidparam 2 range ('A', 'V', 'I') chkstride1(A) n = checksquare(A) - chkuplofinite(A, uplo) if range == 'I' && !(1 <= il <= iu <= n) - throw(ArgumentError("illegal choice of eigenvalue indices (il = $il, iu = $iu), which must be between 1 and n = $n")) + throw(ArgumentError(lazy"illegal choice of eigenvalue indices (il = $il, iu = $iu), which must be between 1 and n = $n")) end if range == 'V' && vl >= vu - throw(ArgumentError("lower boundary, $vl, must be less than upper boundary, $vu")) + throw(ArgumentError(lazy"lower boundary, $vl, must be less than upper boundary, $vu")) end + chkuplofinite(A, uplo) lda = stride(A,2) m = Ref{BlasInt}() W = similar(A, $elty, n) @@ -5329,6 +5426,8 @@ for (syev, syevr, syevd, sygvd, elty) in # INTEGER IWORK( * ) # DOUBLE PRECISION A( LDA, * ), W( * ), WORK( * ) function syevd!(jobz::AbstractChar, uplo::AbstractChar, A::AbstractMatrix{$elty}) + require_one_based_indexing(A) + @chkvalidparam 1 jobz ('N', 'V') chkstride1(A) n = checksquare(A) chkuplofinite(A, uplo) @@ -5370,10 +5469,14 @@ for (syev, syevr, syevd, sygvd, elty) in # INTEGER IWORK( * ) # DOUBLE PRECISION A( LDA, * ), B( LDB, * ), W( * ), WORK( * ) function sygvd!(itype::Integer, jobz::AbstractChar, uplo::AbstractChar, A::AbstractMatrix{$elty}, B::AbstractMatrix{$elty}) + require_one_based_indexing(A, B) + @chkvalidparam 1 itype 1:3 + @chkvalidparam 2 jobz ('N', 'V') + chkuplo(uplo) chkstride1(A, B) n, m = checksquare(A, B) if n != m - throw(DimensionMismatch("dimensions of A, ($n,$n), and B, ($m,$m), must match")) + throw(DimensionMismatch(lazy"dimensions of A, ($n,$n), and B, ($m,$m), must match")) end lda = max(1, stride(A, 2)) ldb = max(1, stride(B, 2)) @@ -5420,6 +5523,8 @@ for (syev, syevr, syevd, sygvd, elty, relty) in # DOUBLE PRECISION RWORK( * ), W( * ) # COMPLEX*16 A( LDA, * ), WORK( * ) function syev!(jobz::AbstractChar, uplo::AbstractChar, A::AbstractMatrix{$elty}) + require_one_based_indexing(A) + @chkvalidparam 1 jobz ('N', 'V') chkstride1(A) chkuplofinite(A, uplo) n = checksquare(A) @@ -5459,14 +5564,17 @@ for (syev, syevr, syevd, sygvd, elty, relty) in # COMPLEX*16 A( LDA, * ), WORK( * ), Z( LDZ, * ) function syevr!(jobz::AbstractChar, range::AbstractChar, uplo::AbstractChar, A::AbstractMatrix{$elty}, vl::AbstractFloat, vu::AbstractFloat, il::Integer, iu::Integer, abstol::AbstractFloat) + require_one_based_indexing(A) + @chkvalidparam 1 jobz ('N', 'V') + @chkvalidparam 2 range ('A', 'V', 'I') chkstride1(A) chkuplofinite(A, uplo) n = checksquare(A) if range == 'I' && !(1 <= il <= iu <= n) - throw(ArgumentError("illegal choice of eigenvalue indices (il = $il, iu=$iu), which must be between 1 and n = $n")) + throw(ArgumentError(lazy"illegal choice of eigenvalue indices (il = $il, iu=$iu), which must be between 1 and n = $n")) end if range == 'V' && vl >= vu - throw(ArgumentError("lower boundary, $vl, must be less than upper boundary, $vu")) + throw(ArgumentError(lazy"lower boundary, $vl, must be less than upper boundary, $vu")) end lda = max(1,stride(A,2)) m = Ref{BlasInt}() @@ -5528,6 +5636,8 @@ for (syev, syevr, syevd, sygvd, elty, relty) in # DOUBLE PRECISION RWORK( * ) # COMPLEX*16 A( LDA, * ), WORK( * ) function syevd!(jobz::AbstractChar, uplo::AbstractChar, A::AbstractMatrix{$elty}) + require_one_based_indexing(A) + @chkvalidparam 1 jobz ('N', 'V') chkstride1(A) chkuplofinite(A, uplo) n = checksquare(A) @@ -5573,12 +5683,15 @@ for (syev, syevr, syevd, sygvd, elty, relty) in # DOUBLE PRECISION RWORK( * ), W( * ) # COMPLEX*16 A( LDA, * ), B( LDB, * ), WORK( * ) function sygvd!(itype::Integer, jobz::AbstractChar, uplo::AbstractChar, A::AbstractMatrix{$elty}, B::AbstractMatrix{$elty}) + require_one_based_indexing(A, B) + @chkvalidparam 1 itype 1:3 + @chkvalidparam 2 jobz ('N', 'V') chkstride1(A, B) chkuplofinite(A, uplo) chkuplofinite(B, uplo) n, m = checksquare(A, B) if n != m - throw(DimensionMismatch("dimensions of A, ($n,$n), and B, ($m,$m), must match")) + throw(DimensionMismatch(lazy"dimensions of A, ($n,$n), and B, ($m,$m), must match")) end lda = max(1, stride(A, 2)) ldb = max(1, stride(B, 2)) @@ -5689,19 +5802,19 @@ for (bdsqr, relty, elty) in # Do checks chkuplo(uplo) if length(e_) != n - 1 - throw(DimensionMismatch("off-diagonal has length $(length(e_)) but should have length $(n - 1)")) + throw(DimensionMismatch(lazy"off-diagonal has length $(length(e_)) but should have length $(n - 1)")) end if ncvt > 0 && ldvt < n - throw(DimensionMismatch("leading dimension of Vt, $ldvt, must be at least $n")) + throw(DimensionMismatch(lazy"leading dimension of Vt, $ldvt, must be at least $n")) end if ldu < nru - throw(DimensionMismatch("leading dimension of U, $ldu, must be at least $nru")) + throw(DimensionMismatch(lazy"leading dimension of U, $ldu, must be at least $nru")) end if size(U, 2) != n - throw(DimensionMismatch("U must have $n columns but has $(size(U, 2))")) + throw(DimensionMismatch(lazy"U must have $n columns but has $(size(U, 2))")) end if ncc > 0 && ldc < n - throw(DimensionMismatch("leading dimension of C, $ldc, must be at least $n")) + throw(DimensionMismatch(lazy"leading dimension of C, $ldc, must be at least $n")) end # Allocate work = Vector{$relty}(undef, 4n) @@ -5768,7 +5881,7 @@ for (bdsdc, elty) in ldvt=ldu=max(1, n) lwork=3*n^2 + 4*n else - throw(ArgumentError("COMPQ argument must be 'N', 'P' or 'I', got $(repr(compq))")) + throw(ArgumentError(lazy"COMPQ argument must be 'N', 'P' or 'I', got $(repr(compq))")) end u = similar(d, $elty, (ldu, n)) vt = similar(d, $elty, (ldvt, n)) @@ -5822,6 +5935,8 @@ for (gecon, elty) in # INTEGER IWORK( * ) # DOUBLE PRECISION A( LDA, * ), WORK( * ) function gecon!(normtype::AbstractChar, A::AbstractMatrix{$elty}, anorm::$elty) + require_one_based_indexing(A) + @chkvalidparam 1 normtype ('0', '1', 'I') chkstride1(A) n = checksquare(A) lda = max(1, stride(A, 2)) @@ -5856,6 +5971,8 @@ for (gecon, elty, relty) in # DOUBLE PRECISION RWORK( * ) # COMPLEX*16 A( LDA, * ), WORK( * ) function gecon!(normtype::AbstractChar, A::AbstractMatrix{$elty}, anorm::$relty) + require_one_based_indexing(A) + @chkvalidparam 1 normtype ('0', '1', 'I') chkstride1(A) n = checksquare(A) lda = max(1, stride(A, 2)) @@ -5898,6 +6015,7 @@ for (gehrd, elty) in # * .. Array Arguments .. # DOUBLE PRECISION A( LDA, * ), TAU( * ), WORK( * ) function gehrd!(ilo::Integer, ihi::Integer, A::AbstractMatrix{$elty}) + require_one_based_indexing(A) chkstride1(A) n = checksquare(A) chkfinite(A) # balancing routines don't support NaNs and Infs @@ -5951,7 +6069,7 @@ for (orghr, elty) in chkstride1(A, tau) n = checksquare(A) if n - length(tau) != 1 - throw(DimensionMismatch("tau has length $(length(tau)), needs $(n - 1)")) + throw(DimensionMismatch(lazy"tau has length $(length(tau)), needs $(n - 1)")) end work = Vector{$elty}(undef, 1) lwork = BlasInt(-1) @@ -6000,11 +6118,13 @@ for (ormhr, elty) in require_one_based_indexing(A, tau, C) chkstride1(A, tau, C) + chkside(side) + chktrans(trans) n = checksquare(A) mC, nC = size(C, 1), size(C, 2) if n - length(tau) != 1 - throw(DimensionMismatch("tau has length $(length(tau)), needs $(n - 1)")) + throw(DimensionMismatch(lazy"tau has length $(length(tau)), needs $(n - 1)")) end if (side == 'L' && mC != n) || (side == 'R' && nC != n) throw(DimensionMismatch("A and C matrices are not conformable")) @@ -6047,6 +6167,8 @@ for (hseqr, elty) in function hseqr!(job::AbstractChar, compz::AbstractChar, ilo::Integer, ihi::Integer, H::AbstractMatrix{$elty}, Z::AbstractMatrix{$elty}) require_one_based_indexing(H, Z) + @chkvalidparam 1 job ('E', 'S') + @chkvalidparam 2 compz ('N', 'I', 'V') chkstride1(H) n = checksquare(H) checksquare(Z) == n || throw(DimensionMismatch()) @@ -6089,6 +6211,8 @@ for (hseqr, elty) in function hseqr!(job::AbstractChar, compz::AbstractChar, ilo::Integer, ihi::Integer, H::AbstractMatrix{$elty}, Z::AbstractMatrix{$elty}) require_one_based_indexing(H, Z) + @chkvalidparam 1 job ('E', 'S') + @chkvalidparam 2 compz ('N', 'I', 'V') chkstride1(H) n = checksquare(H) checksquare(Z) == n || throw(DimensionMismatch()) @@ -6147,6 +6271,7 @@ for (hetrd, elty) in # * .. Array Arguments .. # DOUBLE PRECISION A( LDA, * ), D( * ), E( * ), TAU( * ), WORK( * ) function hetrd!(uplo::AbstractChar, A::AbstractMatrix{$elty}) + require_one_based_indexing(A) chkstride1(A) n = checksquare(A) chkuplo(uplo) @@ -6201,7 +6326,7 @@ for (orgtr, elty) in chkstride1(A, tau) n = checksquare(A) if n - length(tau) != 1 - throw(DimensionMismatch("tau has length $(length(tau)), needs $(n - 1)")) + throw(DimensionMismatch(lazy"tau has length $(length(tau)), needs $(n - 1)")) end chkuplo(uplo) work = Vector{$elty}(undef, 1) @@ -6252,14 +6377,16 @@ for (ormtr, elty) in require_one_based_indexing(A, tau, C) chkstride1(A, tau, C) n = checksquare(A) + chkside(side) chkuplo(uplo) + chktrans(trans) mC, nC = size(C, 1), size(C, 2) if n - length(tau) != 1 - throw(DimensionMismatch("tau has length $(length(tau)), needs $(n - 1)")) + throw(DimensionMismatch(lazy"tau has length $(length(tau)), needs $(n - 1)")) end if (side == 'L' && mC != n) || (side == 'R' && nC != n) - throw(DimensionMismatch("A and C matrices are not conformable")) + throw(DimensionMismatch(lazy"A and C matrices are not conformable")) end work = Vector{$elty}(undef, 1) @@ -6300,6 +6427,7 @@ for (gees, gges, gges3, elty) in # $ WR( * ) function gees!(jobvs::AbstractChar, A::AbstractMatrix{$elty}) require_one_based_indexing(A) + @chkvalidparam 1 jobvs ('N', 'V') chkstride1(A) n = checksquare(A) sdim = Vector{BlasInt}(undef, 1) @@ -6339,10 +6467,13 @@ for (gees, gges, gges3, elty) in # $ B( LDB, * ), BETA( * ), VSL( LDVSL, * ), # $ VSR( LDVSR, * ), WORK( * ) function gges!(jobvsl::AbstractChar, jobvsr::AbstractChar, A::AbstractMatrix{$elty}, B::AbstractMatrix{$elty}) + require_one_based_indexing(A, B) + @chkvalidparam 1 jobvsl ('N', 'V') + @chkvalidparam 2 jobvsr ('N', 'V') chkstride1(A, B) n, m = checksquare(A, B) if n != m - throw(DimensionMismatch("dimensions of A, ($n,$n), and B, ($m,$m), must match")) + throw(DimensionMismatch(lazy"dimensions of A, ($n,$n), and B, ($m,$m), must match")) end sdim = BlasInt(0) alphar = similar(A, $elty, n) @@ -6388,10 +6519,13 @@ for (gees, gges, gges3, elty) in # $ B( LDB, * ), BETA( * ), VSL( LDVSL, * ), # $ VSR( LDVSR, * ), WORK( * ) function gges3!(jobvsl::AbstractChar, jobvsr::AbstractChar, A::AbstractMatrix{$elty}, B::AbstractMatrix{$elty}) + require_one_based_indexing(A, B) + @chkvalidparam 1 jobvsl ('N', 'V') + @chkvalidparam 2 jobvsr ('N', 'V') chkstride1(A, B) n, m = checksquare(A, B) if n != m - throw(DimensionMismatch("dimensions of A, ($n,$n), and B, ($m,$m), must match")) + throw(DimensionMismatch(lazy"dimensions of A, ($n,$n), and B, ($m,$m), must match")) end sdim = BlasInt(0) alphar = similar(A, $elty, n) @@ -6443,6 +6577,7 @@ for (gees, gges, gges3, elty, relty) in # COMPLEX*16 A( LDA, * ), VS( LDVS, * ), W( * ), WORK( * ) function gees!(jobvs::AbstractChar, A::AbstractMatrix{$elty}) require_one_based_indexing(A) + @chkvalidparam 1 jobvs ('N', 'V') chkstride1(A) n = checksquare(A) sort = 'N' @@ -6484,10 +6619,13 @@ for (gees, gges, gges3, elty, relty) in # $ BETA( * ), VSL( LDVSL, * ), VSR( LDVSR, * ), # $ WORK( * ) function gges!(jobvsl::AbstractChar, jobvsr::AbstractChar, A::AbstractMatrix{$elty}, B::AbstractMatrix{$elty}) + require_one_based_indexing(A, B) + @chkvalidparam 1 jobvsl ('N', 'V') + @chkvalidparam 2 jobvsr ('N', 'V') chkstride1(A, B) n, m = checksquare(A, B) if n != m - throw(DimensionMismatch("dimensions of A, ($n,$n), and B, ($m,$m), must match")) + throw(DimensionMismatch(lazy"dimensions of A, ($n,$n), and B, ($m,$m), must match")) end sdim = BlasInt(0) alpha = similar(A, $elty, n) @@ -6534,10 +6672,13 @@ for (gees, gges, gges3, elty, relty) in # $ BETA( * ), VSL( LDVSL, * ), VSR( LDVSR, * ), # $ WORK( * ) function gges3!(jobvsl::AbstractChar, jobvsr::AbstractChar, A::AbstractMatrix{$elty}, B::AbstractMatrix{$elty}) + require_one_based_indexing(A, B) + @chkvalidparam 1 jobvsl ('N', 'V') + @chkvalidparam 2 jobvsr ('N', 'V') chkstride1(A, B) n, m = checksquare(A, B) if n != m - throw(DimensionMismatch("dimensions of A, ($n,$n), and B, ($m,$m), must match")) + throw(DimensionMismatch(lazy"dimensions of A, ($n,$n), and B, ($m,$m), must match")) end sdim = BlasInt(0) alpha = similar(A, $elty, n) @@ -6622,6 +6763,8 @@ for (trexc, trsen, tgsen, elty) in # * .. Array Arguments .. # DOUBLE PRECISION Q( LDQ, * ), T( LDT, * ), WORK( * ) function trexc!(compq::AbstractChar, ifst::BlasInt, ilst::BlasInt, T::AbstractMatrix{$elty}, Q::AbstractMatrix{$elty}) + require_one_based_indexing(T, Q) + @chkvalidparam 1 compq ('V', 'N') chkstride1(T, Q) n = checksquare(T) ldt = max(1, stride(T, 2)) @@ -6654,6 +6797,9 @@ for (trexc, trsen, tgsen, elty) in # DOUBLE PRECISION Q( LDQ, * ), T( LDT, * ), WI( * ), WORK( * ), WR( * ) function trsen!(job::AbstractChar, compq::AbstractChar, select::AbstractVector{BlasInt}, T::AbstractMatrix{$elty}, Q::AbstractMatrix{$elty}) + require_one_based_indexing(T, Q, select) + @chkvalidparam 1 job ('N', 'E', 'V', 'B') + @chkvalidparam 2 compq ('V', 'N') chkstride1(T, Q, select) n = checksquare(T) ldt = max(1, stride(T, 2)) @@ -6709,16 +6855,17 @@ for (trexc, trsen, tgsen, elty) in # .. function tgsen!(select::AbstractVector{BlasInt}, S::AbstractMatrix{$elty}, T::AbstractMatrix{$elty}, Q::AbstractMatrix{$elty}, Z::AbstractMatrix{$elty}) + require_one_based_indexing(select, S, T, Q, Z) chkstride1(select, S, T, Q, Z) n, nt, nq, nz = checksquare(S, T, Q, Z) if n != nt - throw(DimensionMismatch("dimensions of S, ($n,$n), and T, ($nt,$nt), must match")) + throw(DimensionMismatch(lazy"dimensions of S, ($n,$n), and T, ($nt,$nt), must match")) end if n != nq - throw(DimensionMismatch("dimensions of S, ($n,$n), and Q, ($nq,$nq), must match")) + throw(DimensionMismatch(lazy"dimensions of S, ($n,$n), and Q, ($nq,$nq), must match")) end if n != nz - throw(DimensionMismatch("dimensions of S, ($n,$n), and Z, ($nz,$nz), must match")) + throw(DimensionMismatch(lazy"dimensions of S, ($n,$n), and Z, ($nz,$nz), must match")) end lds = max(1, stride(S, 2)) ldt = max(1, stride(T, 2)) @@ -6774,6 +6921,8 @@ for (trexc, trsen, tgsen, elty, relty) in # .. Array Arguments .. # DOUBLE PRECISION Q( LDQ, * ), T( LDT, * ), WORK( * ) function trexc!(compq::AbstractChar, ifst::BlasInt, ilst::BlasInt, T::AbstractMatrix{$elty}, Q::AbstractMatrix{$elty}) + require_one_based_indexing(T, Q) + @chkvalidparam 1 compq ('V', 'N') chkstride1(T, Q) n = checksquare(T) ldt = max(1, stride(T, 2)) @@ -6804,6 +6953,9 @@ for (trexc, trsen, tgsen, elty, relty) in # COMPLEX Q( LDQ, * ), T( LDT, * ), W( * ), WORK( * ) function trsen!(job::AbstractChar, compq::AbstractChar, select::AbstractVector{BlasInt}, T::AbstractMatrix{$elty}, Q::AbstractMatrix{$elty}) + require_one_based_indexing(select, T, Q) + @chkvalidparam 1 job ('N', 'E', 'V', 'B') + @chkvalidparam 2 compq ('N', 'V') chkstride1(select, T, Q) n = checksquare(T) ldt = max(1, stride(T, 2)) @@ -6854,16 +7006,17 @@ for (trexc, trsen, tgsen, elty, relty) in # .. function tgsen!(select::AbstractVector{BlasInt}, S::AbstractMatrix{$elty}, T::AbstractMatrix{$elty}, Q::AbstractMatrix{$elty}, Z::AbstractMatrix{$elty}) + require_one_based_indexing(select, S, T, Q, Z) chkstride1(select, S, T, Q, Z) n, nt, nq, nz = checksquare(S, T, Q, Z) if n != nt - throw(DimensionMismatch("dimensions of S, ($n,$n), and T, ($nt,$nt), must match")) + throw(DimensionMismatch(lazy"dimensions of S, ($n,$n), and T, ($nt,$nt), must match")) end if n != nq - throw(DimensionMismatch("dimensions of S, ($n,$n), and Q, ($nq,$nq), must match")) + throw(DimensionMismatch(lazy"dimensions of S, ($n,$n), and Q, ($nq,$nq), must match")) end if n != nz - throw(DimensionMismatch("dimensions of S, ($n,$n), and Z, ($nz,$nz), must match")) + throw(DimensionMismatch(lazy"dimensions of S, ($n,$n), and Z, ($nz,$nz), must match")) end lds = max(1, stride(S, 2)) ldt = max(1, stride(T, 2)) @@ -6954,13 +7107,15 @@ for (fn, elty, relty) in ((:dtrsyl_, :Float64, :Float64), function trsyl!(transa::AbstractChar, transb::AbstractChar, A::AbstractMatrix{$elty}, B::AbstractMatrix{$elty}, C::AbstractMatrix{$elty}, isgn::Int=1) require_one_based_indexing(A, B, C) + chktrans(transa) + chktrans(transb) chkstride1(A, B, C) m, n = checksquare(A), checksquare(B) lda = max(1, stride(A, 2)) ldb = max(1, stride(B, 2)) m1, n1 = size(C) if m != m1 || n != n1 - throw(DimensionMismatch("dimensions of A, ($m,$n), and C, ($m1,$n1), must match")) + throw(DimensionMismatch(lazy"dimensions of A, ($m,$n), and C, ($m1,$n1), must match")) end ldc = max(1, stride(C, 2)) scale = Ref{$relty}() @@ -6992,4 +7147,57 @@ Returns `X` (overwriting `C`) and `scale`. """ trsyl!(transa::AbstractChar, transb::AbstractChar, A::AbstractMatrix, B::AbstractMatrix, C::AbstractMatrix, isgn::Int=1) +for (fn, elty) in ((:dlacpy_, :Float64), + (:slacpy_, :Float32), + (:zlacpy_, :ComplexF64), + (:clacpy_, :ComplexF32)) + @eval begin + # SUBROUTINE DLACPY( UPLO, M, N, A, LDA, B, LDB ) + # .. Scalar Arguments .. + # CHARACTER UPLO + # INTEGER LDA, LDB, M, N + # .. + # .. Array Arguments .. + # DOUBLE PRECISION A( LDA, * ), B( LDB, * ) + # .. + function lacpy!(B::AbstractMatrix{$elty}, A::AbstractMatrix{$elty}, uplo::AbstractChar) + require_one_based_indexing(A, B) + chkstride1(A, B) + m,n = size(A) + m1,n1 = size(B) + (m1 < m || n1 < n) && throw(DimensionMismatch(lazy"B of size ($m1,$n1) should have at least the same number of rows and columns than A of size ($m,$n)")) + lda = max(1, stride(A, 2)) + ldb = max(1, stride(B, 2)) + ccall((@blasfunc($fn), libblastrampoline), Cvoid, + (Ref{UInt8}, Ref{BlasInt}, Ref{BlasInt}, Ptr{$elty}, + Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Clong), + uplo, m, n, A, lda, B, ldb, 1) + B + end + end +end + +""" + lacpy!(B, A, uplo) -> B + +Copies all or part of a matrix `A` to another matrix `B`. +uplo specifies the part of the matrix `A` to be copied to `B`. +Set `uplo = 'L'` for the lower triangular part, `uplo = 'U'` +for the upper triangular part, any other character for all +the matrix `A`. + +# Examples +```jldoctest +julia> A = [1. 2. ; 3. 4.]; + +julia> B = [0. 0. ; 0. 0.]; + +julia> LAPACK.lacpy!(B, A, 'U') +2×2 Matrix{Float64}: + 1.0 2.0 + 0.0 4.0 +``` +""" +lacpy!(B::AbstractMatrix, A::AbstractMatrix, uplo::AbstractChar) + end # module diff --git a/stdlib/LinearAlgebra/src/lbt.jl b/stdlib/LinearAlgebra/src/lbt.jl index b133741611adc..aadcb45d606a3 100644 --- a/stdlib/LinearAlgebra/src/lbt.jl +++ b/stdlib/LinearAlgebra/src/lbt.jl @@ -247,11 +247,11 @@ If the given `symbol_name` is not contained within the list of exported symbols, function lbt_find_backing_library(symbol_name, interface::Symbol; config::LBTConfig = lbt_get_config()) if interface ∉ (:ilp64, :lp64) - throw(ArgumentError("Invalid interface specification: '$(interface)'")) + throw(ArgumentError(lazy"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)'")) + throw(ArgumentError(lazy"Invalid exported symbol name '$(symbol_name)'")) end # Convert to zero-indexed symbol_idx -= 1 diff --git a/stdlib/LinearAlgebra/src/ldlt.jl b/stdlib/LinearAlgebra/src/ldlt.jl index d3d6234961c44..89e57d0dd27eb 100644 --- a/stdlib/LinearAlgebra/src/ldlt.jl +++ b/stdlib/LinearAlgebra/src/ldlt.jl @@ -175,7 +175,7 @@ function ldiv!(S::LDLt{<:Any,<:SymTridiagonal}, B::AbstractVecOrMat) require_one_based_indexing(B) n, nrhs = size(B, 1), size(B, 2) if size(S,1) != n - throw(DimensionMismatch("Matrix has dimensions $(size(S)) but right hand side has first dimension $n")) + throw(DimensionMismatch(lazy"Matrix has dimensions $(size(S)) but right hand side has first dimension $n")) end d = S.data.dv l = S.data.ev diff --git a/stdlib/LinearAlgebra/src/lu.jl b/stdlib/LinearAlgebra/src/lu.jl index 109def9fddb2c..72755e0eb6799 100644 --- a/stdlib/LinearAlgebra/src/lu.jl +++ b/stdlib/LinearAlgebra/src/lu.jl @@ -21,6 +21,7 @@ The individual components of the factorization `F::LU` can be accessed via [`get Iterating the factorization produces the components `F.L`, `F.U`, and `F.p`. # Examples + ```jldoctest julia> A = [4 3; 6 3] 2×2 Matrix{Int64}: @@ -50,7 +51,7 @@ true struct LU{T,S<:AbstractMatrix{T},P<:AbstractVector{<:Integer}} <: Factorization{T} factors::S ipiv::P - info::BlasInt + info::BlasInt # Can be negative to indicate failed unpivoted factorization function LU{T,S,P}(factors, ipiv, info) where {T, S<:AbstractMatrix{T}, P<:AbstractVector{<:Integer}} require_one_based_indexing(factors) @@ -76,16 +77,25 @@ Base.iterate(S::LU, ::Val{:done}) = nothing adjoint(F::LU{<:Real}) = TransposeFactorization(F) transpose(F::LU{<:Real}) = TransposeFactorization(F) -# the following method is meant to catch calls to lu!(A::LAPACKArray) without a pivoting stategy -lu!(A::StridedMatrix{<:BlasFloat}; check::Bool = true) = lu!(A, RowMaximum(); check=check) -function lu!(A::StridedMatrix{T}, ::RowMaximum; check::Bool = true) where {T<:BlasFloat} +function _check_lu_success(info, allowsingular) + if info < 0 # zero pivot error from unpivoted LU + checknozeropivot(-info) + else + allowsingular || checknonsingular(info) + end +end + +# the following method is meant to catch calls to lu!(A::LAPACKArray) without a pivoting strategy +lu!(A::StridedMatrix{<:BlasFloat}; check::Bool = true, allowsingular::Bool = false) = lu!(A, RowMaximum(); check, allowsingular) +function lu!(A::StridedMatrix{T}, ::RowMaximum; check::Bool = true, allowsingular::Bool = false) where {T<:BlasFloat} lpt = LAPACK.getrf!(A; check) - check && checknonsingular(lpt[3]) + check && _check_lu_success(lpt[3], allowsingular) return LU{T,typeof(lpt[1]),typeof(lpt[2])}(lpt[1], lpt[2], lpt[3]) end -function lu!(A::HermOrSym{T}, pivot::Union{RowMaximum,NoPivot,RowNonZero} = lupivottype(T); check::Bool = true) where {T} +function lu!(A::HermOrSym{T}, pivot::Union{RowMaximum,NoPivot,RowNonZero} = lupivottype(T); + check::Bool = true, allowsingular::Bool = false) where {T} copytri!(A.data, A.uplo, isa(A, Hermitian)) - lu!(A.data, pivot; check = check) + lu!(A.data, pivot; check, allowsingular) end # for backward compatibility # TODO: remove towards Julia v2 @@ -93,13 +103,16 @@ end @deprecate lu!(A::Union{StridedMatrix,HermOrSym,Tridiagonal}, ::Val{false}; check::Bool = true) lu!(A, NoPivot(); check=check) """ - lu!(A, pivot = RowMaximum(); check = true) -> LU + lu!(A, pivot = RowMaximum(); check = true, allowsingular = false) -> LU `lu!` is the same as [`lu`](@ref), but saves space by overwriting the input `A`, instead of creating a copy. An [`InexactError`](@ref) exception is thrown if the factorization produces a number not representable by the element type of `A`, e.g. for integer types. +!!! compat "Julia 1.11" + The `allowsingular` keyword argument was added in Julia 1.11. + # Examples ```jldoctest julia> A = [4. 3.; 6. 3.] @@ -129,10 +142,10 @@ Stacktrace: [...] ``` """ -lu!(A::AbstractMatrix, pivot::Union{RowMaximum,NoPivot,RowNonZero} = lupivottype(eltype(A)); check::Bool = true) = - generic_lufact!(A, pivot; check = check) +lu!(A::AbstractMatrix, pivot::Union{RowMaximum,NoPivot,RowNonZero} = lupivottype(eltype(A)); + check::Bool = true, allowsingular::Bool = false) = generic_lufact!(A, pivot; check, allowsingular) function generic_lufact!(A::AbstractMatrix{T}, pivot::Union{RowMaximum,NoPivot,RowNonZero} = lupivottype(T); - check::Bool = true) where {T} + check::Bool = true, allowsingular::Bool = false) where {T} check && LAPACK.chkfinite(A) # Extract values m, n = size(A) @@ -188,7 +201,12 @@ function generic_lufact!(A::AbstractMatrix{T}, pivot::Union{RowMaximum,NoPivot,R end end end - check && checknonsingular(info, pivot) + if pivot === NoPivot() + # Use a negative value to distinguish a failed factorization (zero in pivot + # position during unpivoted LU) from a valid but rank-deficient factorization + info = -info + end + check && _check_lu_success(info, allowsingular) return LU{T,typeof(A),typeof(ipiv)}(A, ipiv, convert(BlasInt, info)) end @@ -215,7 +233,7 @@ lupivottype(::Type{T}) where {T} = RowMaximum() # for all other types we must promote to a type which is stable under division """ - lu(A, pivot = RowMaximum(); check = true) -> F::LU + lu(A, pivot = RowMaximum(); check = true, allowsingular = false) -> F::LU Compute the LU factorization of `A`. @@ -223,6 +241,10 @@ When `check = true`, an error is thrown if the decomposition fails. When `check = false`, responsibility for checking the decomposition's validity (via [`issuccess`](@ref)) lies with the user. +By default, with `check = true`, an error is also thrown when the decomposition +produces valid factors, but the upper-triangular factor `U` is rank-deficient. This may be changed by +passing `allowsingular = true`. + In most cases, if `A` is a subtype `S` of `AbstractMatrix{T}` with an element type `T` supporting `+`, `-`, `*` and `/`, the return type is `LU{T,S{T}}`. @@ -240,7 +262,8 @@ One of the following pivoting strategies can be selected via the optional `pivot to be factorized rows. (This corresponds to the typical choice in hand calculations, and is also useful for more general algebraic number types that support [`iszero`](@ref) but not `abs` or `<`.) -* `NoPivot()`: pivoting turned off (may fail if a zero entry is encountered). +* `NoPivot()`: pivoting turned off (will fail if a zero entry is encountered in + a pivot position, even when `allowsingular = true`). The individual components of the factorization `F` can be accessed via [`getproperty`](@ref): @@ -269,6 +292,9 @@ The relationship between `F` and `A` is | [`logabsdet`](@ref) | ✓ | ✓ | | [`size`](@ref) | ✓ | ✓ | +!!! compat "Julia 1.11" + The `allowsingular` keyword argument was added in Julia 1.11. + # Examples ```jldoctest julia> A = [4 3; 6 3] @@ -294,6 +320,17 @@ julia> l, u, p = lu(A); # destructuring via iteration julia> l == F.L && u == F.U && p == F.p true + +julia> lu([1 2; 1 2], allowsingular = true) +LU{Float64, Matrix{Float64}, Vector{Int64}} +L factor: +2×2 Matrix{Float64}: + 1.0 0.0 + 1.0 1.0 +U factor (rank-deficient): +2×2 Matrix{Float64}: + 1.0 2.0 + 0.0 0.0 ``` """ lu(A::AbstractMatrix{T}, args...; kwargs...) where {T} = @@ -309,9 +346,9 @@ _lucopy(A::HermOrSym, T) = copymutable_oftype(A, T) _lucopy(A::Tridiagonal, T) = copymutable_oftype(A, T) lu(S::LU) = S -function lu(x::Number; check::Bool=true) +function lu(x::Number; check::Bool=true, allowsingular::Bool=false) info = x == 0 ? one(BlasInt) : zero(BlasInt) - check && checknonsingular(info) + check && _check_lu_success(info, allowsingular) return LU(fill(x, 1, 1), BlasInt[1], info) end @@ -357,17 +394,47 @@ end Base.propertynames(F::LU, private::Bool=false) = (:L, :U, :p, :P, (private ? fieldnames(typeof(F)) : ())...) -issuccess(F::LU) = F.info == 0 + +""" + issuccess(F::LU; allowsingular = false) + +Test that the LU factorization of a matrix succeeded. By default a +factorization that produces a valid but rank-deficient U factor is considered a +failure. This can be changed by passing `allowsingular = true`. + +!!! compat "Julia 1.11" + The `allowsingular` keyword argument was added in Julia 1.11. + +# Examples + +```jldoctest +julia> F = lu([1 2; 1 2], check = false); + +julia> issuccess(F) +false + +julia> issuccess(F, allowsingular = true) +true +``` +""" +function issuccess(F::LU; allowsingular::Bool=false) + # A negative info is always a failure, a positive info indicates a valid but rank-deficient U factor + F.info == 0 || (allowsingular && F.info > 0) +end function show(io::IO, mime::MIME{Symbol("text/plain")}, F::LU) - if issuccess(F) + if F.info < 0 + print(io, "Failed factorization of type $(typeof(F))") + else summary(io, F); println(io) println(io, "L factor:") show(io, mime, F.L) - println(io, "\nU factor:") + if F.info > 0 + println(io, "\nU factor (rank-deficient):") + else + println(io, "\nU factor:") + end show(io, mime, F.U) - else - print(io, "Failed factorization of type $(typeof(F))") end end @@ -494,7 +561,8 @@ inv!(A::LU{T,<:StridedMatrix}) where {T} = inv(A::LU{<:BlasFloat,<:StridedMatrix}) = inv!(copy(A)) # Tridiagonal -function lu!(A::Tridiagonal{T,V}, pivot::Union{RowMaximum,NoPivot} = RowMaximum(); check::Bool = true) where {T,V} +function lu!(A::Tridiagonal{T,V}, pivot::Union{RowMaximum,NoPivot} = RowMaximum(); + check::Bool = true, allowsingular::Bool = false) where {T,V} n = size(A, 1) has_du2_defined = isdefined(A, :du2) && length(A.du2) == max(0, n-2) if has_du2_defined @@ -502,16 +570,17 @@ function lu!(A::Tridiagonal{T,V}, pivot::Union{RowMaximum,NoPivot} = RowMaximum( else du2 = similar(A.d, max(0, n-2))::V end - _lu_tridiag!(A.dl, A.d, A.du, du2, Vector{BlasInt}(undef, n), pivot, check) + _lu_tridiag!(A.dl, A.d, A.du, du2, Vector{BlasInt}(undef, n), pivot, check, allowsingular) end -function lu!(F::LU{<:Any,<:Tridiagonal}, A::Tridiagonal, pivot::Union{RowMaximum,NoPivot} = RowMaximum(); check::Bool = true) +function lu!(F::LU{<:Any,<:Tridiagonal}, A::Tridiagonal, pivot::Union{RowMaximum,NoPivot} = RowMaximum(); + check::Bool = true, allowsingular::Bool = false) B = F.factors size(B) == size(A) || throw(DimensionMismatch()) copyto!(B, A) - _lu_tridiag!(B.dl, B.d, B.du, B.du2, F.ipiv, pivot, check) + _lu_tridiag!(B.dl, B.d, B.du, B.du2, F.ipiv, pivot, check, allowsingular) end # See dgttrf.f -@inline function _lu_tridiag!(dl, d, du, du2, ipiv, pivot, check) +@inline function _lu_tridiag!(dl, d, du, du2, ipiv, pivot, check, allowsingular) T = eltype(d) V = typeof(d) @@ -520,9 +589,6 @@ end # Initialize variables info = 0 - if dl === du - throw(ArgumentError("off-diagonals must not alias")) - end fill!(du2, 0) @inbounds begin @@ -578,7 +644,7 @@ end end end end - check && checknonsingular(info, pivot) + check && _check_lu_success(info, allowsingular) return LU{T,Tridiagonal{T,V},typeof(ipiv)}(Tridiagonal{T,V}(dl, d, du, du2), ipiv, convert(BlasInt, info)) end @@ -614,7 +680,7 @@ function ldiv!(A::LU{T,Tridiagonal{T,V}}, B::AbstractVecOrMat) where {T,V} require_one_based_indexing(B) n = size(A,1) if n != size(B,1) - throw(DimensionMismatch("matrix has dimensions ($n,$n) but right hand side has $(size(B,1)) rows")) + throw(DimensionMismatch(lazy"matrix has dimensions ($n,$n) but right hand side has $(size(B,1)) rows")) end nrhs = size(B,2) dl = A.factors.dl @@ -647,7 +713,7 @@ function ldiv!(transA::TransposeFactorization{<:Any,<:LU{T,Tridiagonal{T,V}}}, B A = transA.parent n = size(A,1) if n != size(B,1) - throw(DimensionMismatch("matrix has dimensions ($n,$n) but right hand side has $(size(B,1)) rows")) + throw(DimensionMismatch(lazy"matrix has dimensions ($n,$n) but right hand side has $(size(B,1)) rows")) end nrhs = size(B,2) dl = A.factors.dl @@ -684,7 +750,7 @@ function ldiv!(adjA::AdjointFactorization{<:Any,<:LU{T,Tridiagonal{T,V}}}, B::Ab A = adjA.parent n = size(A,1) if n != size(B,1) - throw(DimensionMismatch("matrix has dimensions ($n,$n) but right hand side has $(size(B,1)) rows")) + throw(DimensionMismatch(lazy"matrix has dimensions ($n,$n) but right hand side has $(size(B,1)) rows")) end nrhs = size(B,2) dl = A.factors.dl diff --git a/stdlib/LinearAlgebra/src/matmul.jl b/stdlib/LinearAlgebra/src/matmul.jl index 018ad20e538c8..95a24b0d798ea 100644 --- a/stdlib/LinearAlgebra/src/matmul.jl +++ b/stdlib/LinearAlgebra/src/matmul.jl @@ -2,6 +2,9 @@ # matmul.jl: Everything to do with dense matrix multiplication +# unused internal constant, here for legacy reasons +const tilebufsize = 10800 # Approximately 32k/3 + # Matrix-matrix multiplication AdjOrTransStridedMat{T} = Union{Adjoint{<:Any, <:StridedMatrix{T}}, Transpose{<:Any, <:StridedMatrix{T}}} @@ -62,9 +65,14 @@ end (*)(a::AbstractVector, adjB::AdjointAbsMat) = reshape(a, length(a), 1) * adjB (*)(a::AbstractVector, B::AbstractMatrix) = reshape(a, length(a), 1) * B +# Add a level of indirection and specialize _mul! to avoid ambiguities in mul! @inline mul!(y::AbstractVector, A::AbstractVecOrMat, x::AbstractVector, + alpha::Number, beta::Number) = _mul!(y, A, x, alpha, beta) + +@inline _mul!(y::AbstractVector, A::AbstractVecOrMat, x::AbstractVector, alpha::Number, beta::Number) = generic_matvecmul!(y, wrapper_char(A), _unwrap(A), x, MulAddMul(alpha, beta)) + # BLAS cases # equal eltypes @inline generic_matvecmul!(y::StridedVector{T}, tA, A::StridedVecOrMat{T}, x::StridedVector{T}, @@ -103,8 +111,11 @@ julia> [1 1; 0 1] * [1 0; 1 1] """ function (*)(A::AbstractMatrix, B::AbstractMatrix) TS = promote_op(matprod, eltype(A), eltype(B)) - mul!(similar(B, TS, (size(A, 1), size(B, 2))), A, B) + mul!(matprod_dest(A, B, TS), A, B) end + +matprod_dest(A, B, TS) = similar(B, TS, (size(A, 1), size(B, 2))) + # optimization for dispatching to BLAS, e.g. *(::Matrix{Float32}, ::Matrix{Float64}) # but avoiding the case *(::Matrix{<:BlasComplex}, ::Matrix{<:BlasReal}) # which is better handled by reinterpreting rather than promotion @@ -173,7 +184,7 @@ function Base.muladd(A::AbstractMatrix, y::AbstractVecOrMat, z::Union{Number, Ab end for d in ndims(Ay)+1:ndims(z) # Similar error to what Ay + z would give, to match (Any,Any,Any) method: - size(z,d) > 1 && throw(DimensionMismatch(string("dimensions must match: z has dims ", + size(z,d) > 1 && throw(DimensionMismatch(string("z has dims ", axes(z), ", must have singleton at dim ", d))) end Ay .+ z @@ -186,7 +197,7 @@ function Base.muladd(u::AbstractVector, v::AdjOrTransAbsVec, z::Union{Number, Ab end for d in 3:ndims(z) # Similar error to (u*v) + z: - size(z,d) > 1 && throw(DimensionMismatch(string("dimensions must match: z has dims ", + size(z,d) > 1 && throw(DimensionMismatch(string("z has dims ", axes(z), ", must have singleton at dim ", d))) end (u .* v) .+ z @@ -214,18 +225,24 @@ end """ mul!(Y, A, B) -> Y -Calculates the matrix-matrix or matrix-vector product ``AB`` and stores the result in `Y`, +Calculates the matrix-matrix or matrix-vector product ``A B`` and stores the result in `Y`, overwriting the existing value of `Y`. Note that `Y` must not be aliased with either `A` or `B`. # Examples ```jldoctest -julia> A=[1.0 2.0; 3.0 4.0]; B=[1.0 1.0; 1.0 1.0]; Y = similar(B); mul!(Y, A, B); +julia> A = [1.0 2.0; 3.0 4.0]; B = [1.0 1.0; 1.0 1.0]; Y = similar(B); + +julia> mul!(Y, A, B) === Y +true julia> Y 2×2 Matrix{Float64}: 3.0 3.0 7.0 7.0 + +julia> Y == A * B +true ``` # Implementation @@ -233,9 +250,7 @@ For custom matrix and vector types, it is recommended to implement 5-argument `mul!` rather than implementing 3-argument `mul!` directly if possible. """ -@inline function mul!(C, A, B) - return mul!(C, A, B, true, false) -end +mul!(C, A, B) = mul!(C, A, B, true, false) """ mul!(C, A, B, α, β) -> C @@ -249,18 +264,27 @@ aliased with either `A` or `B`. # Examples ```jldoctest -julia> A=[1.0 2.0; 3.0 4.0]; B=[1.0 1.0; 1.0 1.0]; C=[1.0 2.0; 3.0 4.0]; +julia> A = [1.0 2.0; 3.0 4.0]; B = [1.0 1.0; 1.0 1.0]; C = [1.0 2.0; 3.0 4.0]; + +julia> α, β = 100.0, 10.0; -julia> mul!(C, A, B, 100.0, 10.0) === C +julia> mul!(C, A, B, α, β) === C true julia> C 2×2 Matrix{Float64}: 310.0 320.0 730.0 740.0 + +julia> C_original = [1.0 2.0; 3.0 4.0]; # A copy of the original value of C + +julia> C == A * B * α + C_original * β +true ``` """ -@inline mul!(C::AbstractMatrix, A::AbstractVecOrMat, B::AbstractVecOrMat, α::Number, β::Number) = +@inline mul!(C::AbstractMatrix, A::AbstractVecOrMat, B::AbstractVecOrMat, α::Number, β::Number) = _mul!(C, A, B, α, β) +# Add a level of indirection and specialize _mul! to avoid ambiguities in mul! +@inline _mul!(C::AbstractMatrix, A::AbstractVecOrMat, B::AbstractVecOrMat, α::Number, β::Number) = generic_matmatmul!( C, wrapper_char(A), @@ -337,7 +361,8 @@ julia> lmul!(F.Q, B) lmul!(A, B) # THE one big BLAS dispatch -@inline function generic_matmatmul!(C::StridedMatrix{T}, tA, tB, A::StridedVecOrMat{T}, B::StridedVecOrMat{T}, +# aggressive constant propagation makes mul!(C, A, B) invoke gemm_wrapper! directly +Base.@constprop :aggressive function generic_matmatmul!(C::StridedMatrix{T}, tA, tB, A::StridedVecOrMat{T}, B::StridedVecOrMat{T}, _add::MulAddMul=MulAddMul()) where {T<:BlasFloat} if all(in(('N', 'T', 'C')), (tA, tB)) if tA == 'T' && tB == 'N' && A === B @@ -364,16 +389,16 @@ lmul!(A, B) return BLAS.hemm!('R', tB == 'H' ? 'U' : 'L', alpha, B, A, beta, C) end end - return _generic_matmatmul!(C, 'N', 'N', wrap(A, tA), wrap(B, tB), _add) + return _generic_matmatmul!(C, wrap(A, tA), wrap(B, tB), _add) end # Complex matrix times (transposed) real matrix. Reinterpret the first matrix to real for efficiency. -@inline function generic_matmatmul!(C::StridedVecOrMat{Complex{T}}, tA, tB, A::StridedVecOrMat{Complex{T}}, B::StridedVecOrMat{T}, +Base.@constprop :aggressive function generic_matmatmul!(C::StridedVecOrMat{Complex{T}}, tA, tB, A::StridedVecOrMat{Complex{T}}, B::StridedVecOrMat{T}, _add::MulAddMul=MulAddMul()) where {T<:BlasReal} if all(in(('N', 'T', 'C')), (tA, tB)) gemm_wrapper!(C, tA, tB, A, B, _add) else - _generic_matmatmul!(C, 'N', 'N', wrap(A, tA), wrap(B, tB), _add) + _generic_matmatmul!(C, wrap(A, tA), wrap(B, tB), _add) end end @@ -398,7 +423,8 @@ end A end -function gemv!(y::StridedVector{T}, tA::AbstractChar, A::StridedVecOrMat{T}, x::StridedVector{T}, +Base.@constprop :aggressive function gemv!(y::StridedVector{T}, tA::AbstractChar, + A::StridedVecOrMat{T}, x::StridedVector{T}, α::Number=true, β::Number=false) where {T<:BlasFloat} mA, nA = lapack_size(tA, A) nA != length(x) && @@ -428,7 +454,7 @@ function gemv!(y::StridedVector{T}, tA::AbstractChar, A::StridedVecOrMat{T}, x:: end end -function gemv!(y::StridedVector{Complex{T}}, tA::AbstractChar, A::StridedVecOrMat{Complex{T}}, x::StridedVector{T}, +Base.@constprop :aggressive function gemv!(y::StridedVector{Complex{T}}, tA::AbstractChar, A::StridedVecOrMat{Complex{T}}, x::StridedVector{T}, α::Number = true, β::Number = false) where {T<:BlasReal} mA, nA = lapack_size(tA, A) nA != length(x) && @@ -450,8 +476,9 @@ function gemv!(y::StridedVector{Complex{T}}, tA::AbstractChar, A::StridedVecOrMa end end -function gemv!(y::StridedVector{Complex{T}}, tA::AbstractChar, A::StridedVecOrMat{T}, x::StridedVector{Complex{T}}, - α::Number = true, β::Number = false) where {T<:BlasFloat} +Base.@constprop :aggressive function gemv!(y::StridedVector{Complex{T}}, tA::AbstractChar, + A::StridedVecOrMat{T}, x::StridedVector{Complex{T}}, + α::Number = true, β::Number = false) where {T<:BlasReal} mA, nA = lapack_size(tA, A) nA != length(x) && throw(DimensionMismatch(lazy"second dimension of A, $nA, does not match length of x, $(length(x))")) @@ -563,7 +590,7 @@ function gemm_wrapper(tA::AbstractChar, tB::AbstractChar, if all(in(('N', 'T', 'C')), (tA, tB)) gemm_wrapper!(C, tA, tB, A, B) else - _generic_matmatmul!(C, 'N', 'N', wrap(A, tA), wrap(B, tB), _add) + _generic_matmatmul!(C, wrap(A, tA), wrap(B, tB), _add) end end @@ -604,7 +631,7 @@ function gemm_wrapper!(C::StridedVecOrMat{T}, tA::AbstractChar, tB::AbstractChar stride(C, 2) >= size(C, 1)) return BLAS.gemm!(tA, tB, alpha, A, B, beta, C) end - _generic_matmatmul!(C, tA, tB, A, B, _add) + _generic_matmatmul!(C, wrap(A, tA), wrap(B, tB), _add) end function gemm_wrapper!(C::StridedVecOrMat{Complex{T}}, tA::AbstractChar, tB::AbstractChar, @@ -647,7 +674,7 @@ function gemm_wrapper!(C::StridedVecOrMat{Complex{T}}, tA::AbstractChar, tB::Abs BLAS.gemm!(tA, tB, alpha, reinterpret(T, A), B, beta, reinterpret(T, C)) return C end - _generic_matmatmul!(C, tA, tB, A, B, _add) + _generic_matmatmul!(C, wrap(A, tA), wrap(B, tB), _add) end # blas.jl defines matmul for floats; other integer and mixed precision @@ -655,19 +682,60 @@ end lapack_size(t::AbstractChar, M::AbstractVecOrMat) = (size(M, t=='N' ? 1 : 2), size(M, t=='N' ? 2 : 1)) +""" + copyto!(B::AbstractMatrix, ir_dest::AbstractUnitRange, jr_dest::AbstractUnitRange, + tM::AbstractChar, + M::AbstractVecOrMat, ir_src::AbstractUnitRange, jr_src::AbstractUnitRange) -> B + +Efficiently copy elements of matrix `M` to `B` conditioned on the character +parameter `tM` as follows: + +| `tM` | Destination | Source | +| --- | :--- | :--- | +| `'N'` | `B[ir_dest, jr_dest]` | `M[ir_src, jr_src]` | +| `'T'` | `B[ir_dest, jr_dest]` | `transpose(M)[ir_src, jr_src]` | +| `'C'` | `B[ir_dest, jr_dest]` | `adjoint(M)[ir_src, jr_src]` | + +The elements `B[ir_dest, jr_dest]` are overwritten. Furthermore, the index range +parameters must satisfy `length(ir_dest) == length(ir_src)` and +`length(jr_dest) == length(jr_src)`. + +See also [`copy_transpose!`](@ref) and [`copy_adjoint!`](@ref). +""" function copyto!(B::AbstractVecOrMat, ir_dest::AbstractUnitRange{Int}, jr_dest::AbstractUnitRange{Int}, tM::AbstractChar, M::AbstractVecOrMat, ir_src::AbstractUnitRange{Int}, jr_src::AbstractUnitRange{Int}) if tM == 'N' copyto!(B, ir_dest, jr_dest, M, ir_src, jr_src) + elseif tM == 'T' + copy_transpose!(B, ir_dest, jr_dest, M, jr_src, ir_src) else - LinearAlgebra.copy_transpose!(B, ir_dest, jr_dest, M, jr_src, ir_src) - tM == 'C' && conj!(@view B[ir_dest, jr_dest]) + copy_adjoint!(B, ir_dest, jr_dest, M, jr_src, ir_src) end B end +""" + copy_transpose!(B::AbstractMatrix, ir_dest::AbstractUnitRange, jr_dest::AbstractUnitRange, + tM::AbstractChar, + M::AbstractVecOrMat, ir_src::AbstractUnitRange, jr_src::AbstractUnitRange) -> B + +Efficiently copy elements of matrix `M` to `B` conditioned on the character +parameter `tM` as follows: + +| `tM` | Destination | Source | +| --- | :--- | :--- | +| `'N'` | `B[ir_dest, jr_dest]` | `transpose(M)[jr_src, ir_src]` | +| `'T'` | `B[ir_dest, jr_dest]` | `M[jr_src, ir_src]` | +| `'C'` | `B[ir_dest, jr_dest]` | `conj(M)[jr_src, ir_src]` | + +The elements `B[ir_dest, jr_dest]` are overwritten. Furthermore, the index +range parameters must satisfy `length(ir_dest) == length(jr_src)` and +`length(jr_dest) == length(ir_src)`. + +See also [`copyto!`](@ref) and [`copy_adjoint!`](@ref). +""" function copy_transpose!(B::AbstractMatrix, ir_dest::AbstractUnitRange{Int}, jr_dest::AbstractUnitRange{Int}, tM::AbstractChar, M::AbstractVecOrMat, ir_src::AbstractUnitRange{Int}, jr_src::AbstractUnitRange{Int}) if tM == 'N' - LinearAlgebra.copy_transpose!(B, ir_dest, jr_dest, M, ir_src, jr_src) + copy_transpose!(B, ir_dest, jr_dest, M, ir_src, jr_src) else copyto!(B, ir_dest, jr_dest, M, jr_src, ir_src) tM == 'C' && conj!(@view B[ir_dest, jr_dest]) @@ -762,199 +830,66 @@ function generic_matmatmul(tA, tB, A::AbstractVecOrMat{T}, B::AbstractMatrix{S}) generic_matmatmul!(C, tA, tB, A, B) end -const tilebufsize = 10800 # Approximately 32k/3 - -function generic_matmatmul!(C::AbstractVecOrMat, tA, tB, A::AbstractVecOrMat, B::AbstractVecOrMat, _add::MulAddMul) - mA, nA = lapack_size(tA, A) - mB, nB = lapack_size(tB, B) - mC, nC = size(C) - - if iszero(_add.alpha) - return _rmul_or_fill!(C, _add.beta) - end - if mA == nA == mB == nB == mC == nC == 2 - return matmul2x2!(C, tA, tB, A, B, _add) - end - if mA == nA == mB == nB == mC == nC == 3 - return matmul3x3!(C, tA, tB, A, B, _add) - end - A, tA = tA in ('H', 'h', 'S', 's') ? (wrap(A, tA), 'N') : (A, tA) - B, tB = tB in ('H', 'h', 'S', 's') ? (wrap(B, tB), 'N') : (B, tB) - _generic_matmatmul!(C, tA, tB, A, B, _add) -end +# aggressive const prop makes mixed eltype mul!(C, A, B) invoke _generic_matmatmul! directly +Base.@constprop :aggressive generic_matmatmul!(C::AbstractVecOrMat, tA, tB, A::AbstractVecOrMat, B::AbstractVecOrMat, _add::MulAddMul) = + _generic_matmatmul!(C, wrap(A, tA), wrap(B, tB), _add) -function _generic_matmatmul!(C::AbstractVecOrMat{R}, tA, tB, A::AbstractVecOrMat{T}, B::AbstractVecOrMat{S}, +@noinline function _generic_matmatmul!(C::AbstractVecOrMat{R}, A::AbstractVecOrMat{T}, B::AbstractVecOrMat{S}, _add::MulAddMul) where {T,S,R} - @assert tA in ('N', 'T', 'C') && tB in ('N', 'T', 'C') - require_one_based_indexing(C, A, B) - - mA, nA = lapack_size(tA, A) - mB, nB = lapack_size(tB, B) - if mB != nA - throw(DimensionMismatch(lazy"matrix A has dimensions ($mA,$nA), matrix B has dimensions ($mB,$nB)")) - end - if size(C,1) != mA || size(C,2) != nB - throw(DimensionMismatch(lazy"result C has dimensions $(size(C)), needs ($mA,$nB)")) - end - - if iszero(_add.alpha) || isempty(A) || isempty(B) - return _rmul_or_fill!(C, _add.beta) - end - - tile_size = 0 - if isbitstype(R) && isbitstype(T) && isbitstype(S) && (tA == 'N' || tB != 'N') - tile_size = floor(Int, sqrt(tilebufsize / max(sizeof(R), sizeof(S), sizeof(T), 1))) - end - @inbounds begin - if tile_size > 0 - sz = (tile_size, tile_size) - Atile = Array{T}(undef, sz) - Btile = Array{S}(undef, sz) - - z1 = zero(A[1, 1]*B[1, 1] + A[1, 1]*B[1, 1]) - z = convert(promote_type(typeof(z1), R), z1) - - if mA < tile_size && nA < tile_size && nB < tile_size - copy_transpose!(Atile, 1:nA, 1:mA, tA, A, 1:mA, 1:nA) - copyto!(Btile, 1:mB, 1:nB, tB, B, 1:mB, 1:nB) - for j = 1:nB - boff = (j-1)*tile_size - for i = 1:mA - aoff = (i-1)*tile_size - s = z - for k = 1:nA - s += Atile[aoff+k] * Btile[boff+k] - end - _modify!(_add, s, C, (i,j)) - end - end - else - Ctile = Array{R}(undef, sz) - for jb = 1:tile_size:nB - jlim = min(jb+tile_size-1,nB) - jlen = jlim-jb+1 - for ib = 1:tile_size:mA - ilim = min(ib+tile_size-1,mA) - ilen = ilim-ib+1 - fill!(Ctile, z) - for kb = 1:tile_size:nA - klim = min(kb+tile_size-1,mB) - klen = klim-kb+1 - copy_transpose!(Atile, 1:klen, 1:ilen, tA, A, ib:ilim, kb:klim) - copyto!(Btile, 1:klen, 1:jlen, tB, B, kb:klim, jb:jlim) - for j=1:jlen - bcoff = (j-1)*tile_size - for i = 1:ilen - aoff = (i-1)*tile_size - s = z - for k = 1:klen - s += Atile[aoff+k] * Btile[bcoff+k] - end - Ctile[bcoff+i] += s - end - end - end - if isone(_add.alpha) && iszero(_add.beta) - copyto!(C, ib:ilim, jb:jlim, Ctile, 1:ilen, 1:jlen) - else - C[ib:ilim, jb:jlim] .= @views _add.(Ctile[1:ilen, 1:jlen], C[ib:ilim, jb:jlim]) - end - end + AxM = axes(A, 1) + AxK = axes(A, 2) # we use two `axes` calls in case of `AbstractVector` + BxK = axes(B, 1) + BxN = axes(B, 2) + CxM = axes(C, 1) + CxN = axes(C, 2) + if AxM != CxM + throw(DimensionMismatch(lazy"matrix A has axes ($AxM,$AxK), matrix C has axes ($CxM,$CxN)")) + end + if AxK != BxK + throw(DimensionMismatch(lazy"matrix A has axes ($AxM,$AxK), matrix B has axes ($BxK,$CxN)")) + end + if BxN != CxN + throw(DimensionMismatch(lazy"matrix B has axes ($BxK,$BxN), matrix C has axes ($CxM,$CxN)")) + end + if isbitstype(R) && sizeof(R) ≤ 16 && !(A isa Adjoint || A isa Transpose) + _rmul_or_fill!(C, _add.beta) + (iszero(_add.alpha) || isempty(A) || isempty(B)) && return C + @inbounds for n in BxN, k in BxK + Balpha = B[k,n]*_add.alpha + @simd for m in AxM + C[m,n] = muladd(A[m,k], Balpha, C[m,n]) end end + elseif isbitstype(R) && sizeof(R) ≤ 16 && ((A isa Adjoint && B isa Adjoint) || (A isa Transpose && B isa Transpose)) + _rmul_or_fill!(C, _add.beta) + (iszero(_add.alpha) || isempty(A) || isempty(B)) && return C + t = wrapperop(A) + pB = parent(B) + pA = parent(A) + tmp = similar(C, CxN) + ci = first(CxM) + ta = t(_add.alpha) + for i in AxM + mul!(tmp, pB, view(pA, :, i)) + C[ci,:] .+= t.(ta .* tmp) + ci += 1 + end else - # Multiplication for non-plain-data uses the naive algorithm - if tA == 'N' - if tB == 'N' - for i = 1:mA, j = 1:nB - z2 = zero(A[i, 1]*B[1, j] + A[i, 1]*B[1, j]) - Ctmp = convert(promote_type(R, typeof(z2)), z2) - for k = 1:nA - Ctmp += A[i, k]*B[k, j] - end - _modify!(_add, Ctmp, C, (i,j)) - end - elseif tB == 'T' - for i = 1:mA, j = 1:nB - z2 = zero(A[i, 1]*transpose(B[j, 1]) + A[i, 1]*transpose(B[j, 1])) - Ctmp = convert(promote_type(R, typeof(z2)), z2) - for k = 1:nA - Ctmp += A[i, k] * transpose(B[j, k]) - end - _modify!(_add, Ctmp, C, (i,j)) - end - else - for i = 1:mA, j = 1:nB - z2 = zero(A[i, 1]*B[j, 1]' + A[i, 1]*B[j, 1]') - Ctmp = convert(promote_type(R, typeof(z2)), z2) - for k = 1:nA - Ctmp += A[i, k]*B[j, k]' - end - _modify!(_add, Ctmp, C, (i,j)) - end - end - elseif tA == 'T' - if tB == 'N' - for i = 1:mA, j = 1:nB - z2 = zero(transpose(A[1, i])*B[1, j] + transpose(A[1, i])*B[1, j]) - Ctmp = convert(promote_type(R, typeof(z2)), z2) - for k = 1:nA - Ctmp += transpose(A[k, i]) * B[k, j] - end - _modify!(_add, Ctmp, C, (i,j)) - end - elseif tB == 'T' - for i = 1:mA, j = 1:nB - z2 = zero(transpose(A[1, i])*transpose(B[j, 1]) + transpose(A[1, i])*transpose(B[j, 1])) - Ctmp = convert(promote_type(R, typeof(z2)), z2) - for k = 1:nA - Ctmp += transpose(A[k, i]) * transpose(B[j, k]) - end - _modify!(_add, Ctmp, C, (i,j)) - end - else - for i = 1:mA, j = 1:nB - z2 = zero(transpose(A[1, i])*B[j, 1]' + transpose(A[1, i])*B[j, 1]') - Ctmp = convert(promote_type(R, typeof(z2)), z2) - for k = 1:nA - Ctmp += transpose(A[k, i]) * adjoint(B[j, k]) - end - _modify!(_add, Ctmp, C, (i,j)) - end - end - else - if tB == 'N' - for i = 1:mA, j = 1:nB - z2 = zero(A[1, i]'*B[1, j] + A[1, i]'*B[1, j]) - Ctmp = convert(promote_type(R, typeof(z2)), z2) - for k = 1:nA - Ctmp += A[k, i]'B[k, j] - end - _modify!(_add, Ctmp, C, (i,j)) - end - elseif tB == 'T' - for i = 1:mA, j = 1:nB - z2 = zero(A[1, i]'*transpose(B[j, 1]) + A[1, i]'*transpose(B[j, 1])) - Ctmp = convert(promote_type(R, typeof(z2)), z2) - for k = 1:nA - Ctmp += adjoint(A[k, i]) * transpose(B[j, k]) - end - _modify!(_add, Ctmp, C, (i,j)) - end - else - for i = 1:mA, j = 1:nB - z2 = zero(A[1, i]'*B[j, 1]' + A[1, i]'*B[j, 1]') - Ctmp = convert(promote_type(R, typeof(z2)), z2) - for k = 1:nA - Ctmp += A[k, i]'B[j, k]' - end - _modify!(_add, Ctmp, C, (i,j)) - end + if iszero(_add.alpha) || isempty(A) || isempty(B) + return _rmul_or_fill!(C, _add.beta) + end + a1 = first(AxK) + b1 = first(BxK) + @inbounds for i in AxM, j in BxN + z2 = zero(A[i, a1]*B[b1, j] + A[i, a1]*B[b1, j]) + Ctmp = convert(promote_type(R, typeof(z2)), z2) + @simd for k in AxK + Ctmp = muladd(A[i, k], B[k, j], Ctmp) end + _modify!(_add, Ctmp, C, (i,j)) end end - end # @inbounds - C + return C end diff --git a/stdlib/LinearAlgebra/src/qr.jl b/stdlib/LinearAlgebra/src/qr.jl index 782e4778c56c9..06c2fba2932f5 100644 --- a/stdlib/LinearAlgebra/src/qr.jl +++ b/stdlib/LinearAlgebra/src/qr.jl @@ -541,7 +541,7 @@ function ldiv!(A::QRPivoted{T,<:StridedMatrix}, B::AbstractMatrix{T}, rcond::Rea m, n = size(A) if m > size(B, 1) || n > size(B, 1) - throw(DimensionMismatch("B has leading dimension $(size(B, 1)) but needs at least $(max(m, n))")) + throw(DimensionMismatch(lazy"B has leading dimension $(size(B, 1)) but needs at least $(max(m, n))")) end if length(A.factors) == 0 || length(B) == 0 @@ -734,7 +734,7 @@ _ret_size(A::Factorization, B::AbstractMatrix) = (max(size(A, 2), size(B, 1)), s function (\)(A::Union{QR{T},QRCompactWY{T},QRPivoted{T}}, BIn::VecOrMat{Complex{T}}) where T<:BlasReal require_one_based_indexing(BIn) m, n = size(A) - m == size(BIn, 1) || throw(DimensionMismatch("left hand side has $m rows, but right hand side has $(size(BIn,1)) rows")) + m == size(BIn, 1) || throw(DimensionMismatch(lazy"left hand side has $m rows, but right hand side has $(size(BIn,1)) rows")) # |z1|z3| reinterpret |x1|x2|x3|x4| transpose |x1|y1| reshape |x1|y1|x3|y3| # |z2|z4| -> |y1|y2|y3|y4| -> |x2|y2| -> |x2|y2|x4|y4| diff --git a/stdlib/LinearAlgebra/src/special.jl b/stdlib/LinearAlgebra/src/special.jl index d028fe43e6338..1363708fb515f 100644 --- a/stdlib/LinearAlgebra/src/special.jl +++ b/stdlib/LinearAlgebra/src/special.jl @@ -108,11 +108,9 @@ for op in (:+, :-) end # disambiguation between triangular and banded matrices, banded ones "dominate" -mul!(C::AbstractMatrix, A::AbstractTriangular, B::BandedMatrix) = _mul!(C, A, B, MulAddMul()) -mul!(C::AbstractMatrix, A::BandedMatrix, B::AbstractTriangular) = _mul!(C, A, B, MulAddMul()) -mul!(C::AbstractMatrix, A::AbstractTriangular, B::BandedMatrix, alpha::Number, beta::Number) = +_mul!(C::AbstractMatrix, A::AbstractTriangular, B::BandedMatrix, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) -mul!(C::AbstractMatrix, A::BandedMatrix, B::AbstractTriangular, alpha::Number, beta::Number) = +_mul!(C::AbstractMatrix, A::BandedMatrix, B::AbstractTriangular, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) function *(H::UpperHessenberg, B::Bidiagonal) @@ -289,7 +287,7 @@ _small_enough(A::SymTridiagonal) = size(A, 1) <= 2 function fill!(A::Union{Diagonal,Bidiagonal,Tridiagonal,SymTridiagonal}, x) xT = convert(eltype(A), x) (iszero(xT) || _small_enough(A)) && return fillstored!(A, xT) - throw(ArgumentError("array of type $(typeof(A)) and size $(size(A)) can + throw(ArgumentError(lazy"array of type $(typeof(A)) and size $(size(A)) can not be filled with $x, since some of its entries are constrained.")) end diff --git a/stdlib/LinearAlgebra/src/structuredbroadcast.jl b/stdlib/LinearAlgebra/src/structuredbroadcast.jl index 02e39b199679b..f2c35c8edcce4 100644 --- a/stdlib/LinearAlgebra/src/structuredbroadcast.jl +++ b/stdlib/LinearAlgebra/src/structuredbroadcast.jl @@ -2,14 +2,14 @@ ## Broadcast styles import Base.Broadcast -using Base.Broadcast: DefaultArrayStyle, Broadcasted, tail +using Base.Broadcast: DefaultArrayStyle, Broadcasted struct StructuredMatrixStyle{T} <: Broadcast.AbstractArrayStyle{2} end StructuredMatrixStyle{T}(::Val{2}) where {T} = StructuredMatrixStyle{T}() StructuredMatrixStyle{T}(::Val{N}) where {T,N} = Broadcast.DefaultArrayStyle{N}() -const StructuredMatrix = Union{Diagonal,Bidiagonal,SymTridiagonal,Tridiagonal,LowerTriangular,UnitLowerTriangular,UpperTriangular,UnitUpperTriangular} -for ST in Base.uniontypes(StructuredMatrix) +const StructuredMatrix{T} = Union{Diagonal{T},Bidiagonal{T},SymTridiagonal{T},Tridiagonal{T},LowerTriangular{T},UnitLowerTriangular{T},UpperTriangular{T},UnitUpperTriangular{T}} +for ST in (Diagonal,Bidiagonal,SymTridiagonal,Tridiagonal,LowerTriangular,UnitLowerTriangular,UpperTriangular,UnitUpperTriangular) @eval Broadcast.BroadcastStyle(::Type{<:$ST}) = $(StructuredMatrixStyle{ST}()) end @@ -133,6 +133,7 @@ fails as `zero(::Tuple{Int})` is not defined. However, iszerodefined(::Type) = false iszerodefined(::Type{<:Number}) = true iszerodefined(::Type{<:AbstractArray{T}}) where T = iszerodefined(T) +iszerodefined(::Type{<:UniformScaling{T}}) where T = iszerodefined(T) fzeropreserving(bc) = (v = fzero(bc); !ismissing(v) && (iszerodefined(typeof(v)) ? iszero(v) : v == 0)) # Like sparse matrices, we assume that the zero-preservation property of a broadcasted @@ -144,6 +145,7 @@ fzero(::Type{T}) where T = T fzero(r::Ref) = r[] fzero(t::Tuple{Any}) = t[1] fzero(S::StructuredMatrix) = zero(eltype(S)) +fzero(::StructuredMatrix{<:AbstractMatrix{T}}) where {T<:Number} = haszero(T) ? zero(T)*I : missing fzero(x) = missing function fzero(bc::Broadcast.Broadcasted) args = map(fzero, bc.args) @@ -152,8 +154,13 @@ end function Base.similar(bc::Broadcasted{StructuredMatrixStyle{T}}, ::Type{ElType}) where {T,ElType} inds = axes(bc) - if isstructurepreserving(bc) || (fzeropreserving(bc) && !(T <: Union{SymTridiagonal,UnitLowerTriangular,UnitUpperTriangular})) + fzerobc = fzeropreserving(bc) + if isstructurepreserving(bc) || (fzerobc && !(T <: Union{SymTridiagonal,UnitLowerTriangular,UnitUpperTriangular})) return structured_broadcast_alloc(bc, T, ElType, length(inds[1])) + elseif fzerobc && T <: UnitLowerTriangular + return similar(convert(Broadcasted{StructuredMatrixStyle{LowerTriangular}}, bc), ElType) + elseif fzerobc && T <: UnitUpperTriangular + return similar(convert(Broadcasted{StructuredMatrixStyle{UpperTriangular}}, bc), ElType) end return similar(convert(Broadcasted{DefaultArrayStyle{ndims(bc)}}, bc), ElType) end @@ -171,7 +178,7 @@ function copyto!(dest::Diagonal, bc::Broadcasted{<:StructuredMatrixStyle}) axs = axes(dest) axes(bc) == axs || Broadcast.throwdm(axes(bc), axs) for i in axs[1] - dest.diag[i] = @inbounds Broadcast._broadcast_getindex(bc, CartesianIndex(i, i)) + dest.diag[i] = @inbounds bc[CartesianIndex(i, i)] end return dest end @@ -181,15 +188,15 @@ function copyto!(dest::Bidiagonal, bc::Broadcasted{<:StructuredMatrixStyle}) axs = axes(dest) axes(bc) == axs || Broadcast.throwdm(axes(bc), axs) for i in axs[1] - dest.dv[i] = @inbounds Broadcast._broadcast_getindex(bc, CartesianIndex(i, i)) + dest.dv[i] = @inbounds bc[CartesianIndex(i, i)] end if dest.uplo == 'U' for i = 1:size(dest, 1)-1 - dest.ev[i] = @inbounds Broadcast._broadcast_getindex(bc, CartesianIndex(i, i+1)) + dest.ev[i] = @inbounds bc[CartesianIndex(i, i+1)] end else for i = 1:size(dest, 1)-1 - dest.ev[i] = @inbounds Broadcast._broadcast_getindex(bc, CartesianIndex(i+1, i)) + dest.ev[i] = @inbounds bc[CartesianIndex(i+1, i)] end end return dest @@ -200,11 +207,11 @@ function copyto!(dest::SymTridiagonal, bc::Broadcasted{<:StructuredMatrixStyle}) axs = axes(dest) axes(bc) == axs || Broadcast.throwdm(axes(bc), axs) for i in axs[1] - dest.dv[i] = @inbounds Broadcast._broadcast_getindex(bc, CartesianIndex(i, i)) + dest.dv[i] = @inbounds bc[CartesianIndex(i, i)] end for i = 1:size(dest, 1)-1 - v = @inbounds Broadcast._broadcast_getindex(bc, CartesianIndex(i, i+1)) - v == (@inbounds Broadcast._broadcast_getindex(bc, CartesianIndex(i+1, i))) || throw(ArgumentError("broadcasted assignment breaks symmetry between locations ($i, $(i+1)) and ($(i+1), $i)")) + v = @inbounds bc[CartesianIndex(i, i+1)] + v == (@inbounds bc[CartesianIndex(i+1, i)]) || throw(ArgumentError(lazy"broadcasted assignment breaks symmetry between locations ($i, $(i+1)) and ($(i+1), $i)")) dest.ev[i] = v end return dest @@ -215,11 +222,11 @@ function copyto!(dest::Tridiagonal, bc::Broadcasted{<:StructuredMatrixStyle}) axs = axes(dest) axes(bc) == axs || Broadcast.throwdm(axes(bc), axs) for i in axs[1] - dest.d[i] = @inbounds Broadcast._broadcast_getindex(bc, CartesianIndex(i, i)) + dest.d[i] = @inbounds bc[CartesianIndex(i, i)] end for i = 1:size(dest, 1)-1 - dest.du[i] = @inbounds Broadcast._broadcast_getindex(bc, CartesianIndex(i, i+1)) - dest.dl[i] = @inbounds Broadcast._broadcast_getindex(bc, CartesianIndex(i+1, i)) + dest.du[i] = @inbounds bc[CartesianIndex(i, i+1)] + dest.dl[i] = @inbounds bc[CartesianIndex(i+1, i)] end return dest end @@ -230,7 +237,7 @@ function copyto!(dest::LowerTriangular, bc::Broadcasted{<:StructuredMatrixStyle} axes(bc) == axs || Broadcast.throwdm(axes(bc), axs) for j in axs[2] for i in j:axs[1][end] - @inbounds dest.data[i,j] = Broadcast._broadcast_getindex(bc, CartesianIndex(i, j)) + @inbounds dest.data[i,j] = bc[CartesianIndex(i, j)] end end return dest @@ -242,7 +249,7 @@ function copyto!(dest::UpperTriangular, bc::Broadcasted{<:StructuredMatrixStyle} axes(bc) == axs || Broadcast.throwdm(axes(bc), axs) for j in axs[2] for i in 1:j - @inbounds dest.data[i,j] = Broadcast._broadcast_getindex(bc, CartesianIndex(i, j)) + @inbounds dest.data[i,j] = bc[CartesianIndex(i, j)] end end return dest @@ -251,6 +258,8 @@ end # We can also implement `map` and its promotion in terms of broadcast with a stricter dimension check function map(f, A::StructuredMatrix, Bs::StructuredMatrix...) sz = size(A) - all(map(B->size(B)==sz, Bs)) || throw(DimensionMismatch("dimensions must match")) + for B in Bs + size(B) == sz || Base.throw_promote_shape_mismatch(sz, size(B)) + end return f.(A, Bs...) end diff --git a/stdlib/LinearAlgebra/src/svd.jl b/stdlib/LinearAlgebra/src/svd.jl index c1b886f616f02..dc8d717932be3 100644 --- a/stdlib/LinearAlgebra/src/svd.jl +++ b/stdlib/LinearAlgebra/src/svd.jl @@ -302,8 +302,8 @@ Iterating the decomposition produces the components `U`, `V`, `Q`, `D1`, `D2`, a The entries of `F.D1` and `F.D2` are related, as explained in the LAPACK documentation for the -[generalized SVD](http://www.netlib.org/lapack/lug/node36.html) and the -[xGGSVD3](http://www.netlib.org/lapack/explore-html/d6/db3/dggsvd3_8f.html) +[generalized SVD](https://www.netlib.org/lapack/lug/node36.html) and the +[xGGSVD3](https://www.netlib.org/lapack/explore-html/d6/db3/dggsvd3_8f.html) routine which is called underneath (in LAPACK 3.6.0 and newer). # Examples diff --git a/stdlib/LinearAlgebra/src/symmetric.jl b/stdlib/LinearAlgebra/src/symmetric.jl index 0c19e26e3dd4a..410140bf7e8be 100644 --- a/stdlib/LinearAlgebra/src/symmetric.jl +++ b/stdlib/LinearAlgebra/src/symmetric.jl @@ -73,8 +73,8 @@ If a symmetric view of a matrix is to be constructed of which the elements are n matrices nor numbers, an appropriate method of `symmetric` has to be implemented. In that case, `symmetric_type` has to be implemented, too. """ -symmetric(A::AbstractMatrix, uplo::Symbol) = Symmetric(A, uplo) -symmetric(A::Number, ::Symbol) = A +symmetric(A::AbstractMatrix, uplo::Symbol=:U) = Symmetric(A, uplo) +symmetric(A::Number, ::Symbol=:U) = A """ symmetric_type(T::Type) @@ -164,8 +164,8 @@ If a hermitian view of a matrix is to be constructed of which the elements are n matrices nor numbers, an appropriate method of `hermitian` has to be implemented. In that case, `hermitian_type` has to be implemented, too. """ -hermitian(A::AbstractMatrix, uplo::Symbol) = Hermitian(A, uplo) -hermitian(A::Number, ::Symbol) = convert(typeof(A), real(A)) +hermitian(A::AbstractMatrix, uplo::Symbol=:U) = Hermitian(A, uplo) +hermitian(A::Number, ::Symbol=:U) = convert(typeof(A), real(A)) """ hermitian_type(T::Type) @@ -202,7 +202,7 @@ for (S, H) in ((:Symmetric, :Hermitian), (:Hermitian, :Symmetric)) function $S(A::$H, uplo::Symbol) if A.uplo == char_uplo(uplo) if $H === Hermitian && !(eltype(A) <: Real) && - any(!isreal, A.data[i] for i in diagind(A.data)) + any(!isreal, A.data[i] for i in diagind(A.data, IndexStyle(A.data))) throw(ArgumentError("Cannot construct $($S)($($H))); diagonal contains complex values")) end @@ -223,6 +223,7 @@ const RealHermSymComplexHerm{T<:Real,S} = Union{Hermitian{T,S}, Symmetric{T,S}, const RealHermSymComplexSym{T<:Real,S} = Union{Hermitian{T,S}, Symmetric{T,S}, Symmetric{Complex{T},S}} size(A::HermOrSym) = size(A.data) +axes(A::HermOrSym) = axes(A.data) @inline function Base.isassigned(A::HermOrSym, i::Int, j::Int) @boundscheck checkbounds(Bool, A, i, j) || return false @inbounds if i == j || ((A.uplo == 'U') == (i < j)) @@ -253,12 +254,12 @@ end end end -function setindex!(A::Symmetric, v, i::Integer, j::Integer) +@propagate_inbounds function setindex!(A::Symmetric, v, i::Integer, j::Integer) i == j || throw(ArgumentError("Cannot set a non-diagonal index in a symmetric matrix")) setindex!(A.data, v, i, j) end -function setindex!(A::Hermitian, v, i::Integer, j::Integer) +@propagate_inbounds function setindex!(A::Hermitian, v, i::Integer, j::Integer) if i != j throw(ArgumentError("Cannot set a non-diagonal index in a Hermitian matrix")) elseif !isreal(v) @@ -268,10 +269,38 @@ function setindex!(A::Hermitian, v, i::Integer, j::Integer) end end +Base.dataids(A::HermOrSym) = Base.dataids(parent(A)) +Base.unaliascopy(A::Hermitian) = Hermitian(Base.unaliascopy(parent(A)), sym_uplo(A.uplo)) +Base.unaliascopy(A::Symmetric) = Symmetric(Base.unaliascopy(parent(A)), sym_uplo(A.uplo)) + +_conjugation(::Symmetric) = transpose +_conjugation(::Hermitian) = adjoint + diag(A::Symmetric) = symmetric.(diag(parent(A)), sym_uplo(A.uplo)) diag(A::Hermitian) = hermitian.(diag(parent(A)), sym_uplo(A.uplo)) -isdiag(A::HermOrSym) = isdiag(A.uplo == 'U' ? UpperTriangular(A.data) : LowerTriangular(A.data)) +function applytri(f, A::HermOrSym) + if A.uplo == 'U' + f(UpperTriangular(A.data)) + else + f(LowerTriangular(A.data)) + end +end + +function applytri(f, A::HermOrSym, B::HermOrSym) + if A.uplo == B.uplo == 'U' + f(UpperTriangular(A.data), UpperTriangular(B.data)) + elseif A.uplo == B.uplo == 'L' + f(LowerTriangular(A.data), LowerTriangular(B.data)) + elseif A.uplo == 'U' + f(UpperTriangular(A.data), UpperTriangular(_conjugation(B)(B.data))) + else # A.uplo == 'L' + f(UpperTriangular(_conjugation(A)(A.data)), UpperTriangular(B.data)) + end +end +parentof_applytri(f, args...) = applytri(parent ∘ f, args...) + +isdiag(A::HermOrSym) = applytri(isdiag, A) # For A<:Union{Symmetric,Hermitian}, similar(A[, neweltype]) should yield a matrix with the same # symmetry type, uplo flag, and underlying storage type as A. The following methods cover these cases. @@ -288,21 +317,20 @@ end similar(A::Union{Symmetric,Hermitian}, ::Type{T}, dims::Dims{N}) where {T,N} = similar(parent(A), T, dims) # Conversion -function Matrix(A::Symmetric) - B = copytri!(convert(Matrix, copy(A.data)), A.uplo) +function Matrix{T}(A::Symmetric) where {T} + B = copytri!(convert(Matrix{T}, copy(A.data)), A.uplo) for i = 1:size(A, 1) B[i,i] = symmetric(A[i,i], sym_uplo(A.uplo))::symmetric_type(eltype(A.data)) end return B end -function Matrix(A::Hermitian) - B = copytri!(convert(Matrix, copy(A.data)), A.uplo, true) +function Matrix{T}(A::Hermitian) where {T} + B = copytri!(convert(Matrix{T}, copy(A.data)), A.uplo, true) for i = 1:size(A, 1) B[i,i] = hermitian(A[i,i], sym_uplo(A.uplo))::hermitian_type(eltype(A.data)) end return B end -Array(A::Union{Symmetric,Hermitian}) = convert(Matrix, A) parent(A::HermOrSym) = A.data Symmetric{T,S}(A::Symmetric{T,S}) where {T,S<:AbstractMatrix{T}} = A @@ -314,14 +342,14 @@ Hermitian{T,S}(A::Hermitian) where {T,S<:AbstractMatrix{T}} = Hermitian{T,S}(con AbstractMatrix{T}(A::Hermitian) where {T} = Hermitian(convert(AbstractMatrix{T}, A.data), sym_uplo(A.uplo)) AbstractMatrix{T}(A::Hermitian{T}) where {T} = copy(A) -copy(A::Symmetric{T,S}) where {T,S} = (B = copy(A.data); Symmetric{T,typeof(B)}(B,A.uplo)) -copy(A::Hermitian{T,S}) where {T,S} = (B = copy(A.data); Hermitian{T,typeof(B)}(B,A.uplo)) +copy(A::Symmetric) = (Symmetric(parentof_applytri(copy, A), sym_uplo(A.uplo))) +copy(A::Hermitian) = (Hermitian(parentof_applytri(copy, A), sym_uplo(A.uplo))) function copyto!(dest::Symmetric, src::Symmetric) if src.uplo == dest.uplo copyto!(dest.data, src.data) else - transpose!(dest.data, src.data) + transpose!(dest.data, Base.unalias(dest.data, src.data)) end return dest end @@ -330,7 +358,7 @@ function copyto!(dest::Hermitian, src::Hermitian) if src.uplo == dest.uplo copyto!(dest.data, src.data) else - adjoint!(dest.data, src.data) + adjoint!(dest.data, Base.unalias(dest.data, src.data)) end return dest end @@ -389,9 +417,9 @@ transpose(A::Hermitian) = Transpose(A) real(A::Symmetric{<:Real}) = A real(A::Hermitian{<:Real}) = A -real(A::Symmetric) = Symmetric(real(A.data), sym_uplo(A.uplo)) -real(A::Hermitian) = Hermitian(real(A.data), sym_uplo(A.uplo)) -imag(A::Symmetric) = Symmetric(imag(A.data), sym_uplo(A.uplo)) +real(A::Symmetric) = Symmetric(parentof_applytri(real, A), sym_uplo(A.uplo)) +real(A::Hermitian) = Hermitian(parentof_applytri(real, A), sym_uplo(A.uplo)) +imag(A::Symmetric) = Symmetric(parentof_applytri(imag, A), sym_uplo(A.uplo)) Base.copy(A::Adjoint{<:Any,<:Symmetric}) = Symmetric(copy(adjoint(A.parent.data)), ifelse(A.parent.uplo == 'U', :L, :U)) @@ -401,8 +429,9 @@ Base.copy(A::Transpose{<:Any,<:Hermitian}) = tr(A::Symmetric) = tr(A.data) # to avoid AbstractMatrix fallback (incl. allocations) tr(A::Hermitian) = real(tr(A.data)) -Base.conj(A::HermOrSym) = typeof(A)(conj(A.data), A.uplo) -Base.conj!(A::HermOrSym) = typeof(A)(conj!(A.data), A.uplo) +Base.conj(A::Symmetric) = Symmetric(parentof_applytri(conj, A), sym_uplo(A.uplo)) +Base.conj(A::Hermitian) = Hermitian(parentof_applytri(conj, A), sym_uplo(A.uplo)) +Base.conj!(A::HermOrSym) = typeof(A)(parentof_applytri(conj!, A), A.uplo) # tril/triu function tril(A::Hermitian, k::Integer=0) @@ -453,25 +482,25 @@ function triu(A::Symmetric, k::Integer=0) end end -for (T, trans, real) in [(:Symmetric, :transpose, :identity), (:Hermitian, :adjoint, :real)] +for (T, trans, real) in [(:Symmetric, :transpose, :identity), (:(Hermitian{<:Union{Real,Complex}}), :adjoint, :real)] @eval begin function dot(A::$T, B::$T) n = size(A, 2) if n != size(B, 2) - throw(DimensionMismatch("A has dimensions $(size(A)) but B has dimensions $(size(B))")) + throw(DimensionMismatch(lazy"A has dimensions $(size(A)) but B has dimensions $(size(B))")) end - dotprod = zero(dot(first(A), first(B))) + dotprod = $real(zero(dot(first(A), first(B)))) @inbounds if A.uplo == 'U' && B.uplo == 'U' for j in 1:n for i in 1:(j - 1) dotprod += 2 * $real(dot(A.data[i, j], B.data[i, j])) end - dotprod += dot(A[j, j], B[j, j]) + dotprod += $real(dot(A[j, j], B[j, j])) end elseif A.uplo == 'L' && B.uplo == 'L' for j in 1:n - dotprod += dot(A[j, j], B[j, j]) + dotprod += $real(dot(A[j, j], B[j, j])) for i in (j + 1):n dotprod += 2 * $real(dot(A.data[i, j], B.data[i, j])) end @@ -481,11 +510,11 @@ for (T, trans, real) in [(:Symmetric, :transpose, :identity), (:Hermitian, :adjo for i in 1:(j - 1) dotprod += 2 * $real(dot(A.data[i, j], $trans(B.data[j, i]))) end - dotprod += dot(A[j, j], B[j, j]) + dotprod += $real(dot(A[j, j], B[j, j])) end else for j in 1:n - dotprod += dot(A[j, j], B[j, j]) + dotprod += $real(dot(A[j, j], B[j, j])) for i in (j + 1):n dotprod += 2 * $real(dot(A.data[i, j], $trans(B.data[j, i]))) end @@ -496,24 +525,141 @@ for (T, trans, real) in [(:Symmetric, :transpose, :identity), (:Hermitian, :adjo end end -(-)(A::Symmetric) = Symmetric(-A.data, sym_uplo(A.uplo)) -(-)(A::Hermitian) = Hermitian(-A.data, sym_uplo(A.uplo)) +function kron(A::Hermitian{T}, B::Hermitian{S}) where {T<:Union{Real,Complex},S<:Union{Real,Complex}} + resultuplo = A.uplo == 'U' || B.uplo == 'U' ? :U : :L + C = Hermitian(Matrix{promote_op(*, T, S)}(undef, _kronsize(A, B)), resultuplo) + return kron!(C, A, B) +end -## Addition/subtraction -for f ∈ (:+, :-), (Wrapper, conjugation) ∈ ((:Hermitian, :adjoint), (:Symmetric, :transpose)) - @eval begin - function $f(A::$Wrapper, B::$Wrapper) - if A.uplo == B.uplo - return $Wrapper($f(parent(A), parent(B)), sym_uplo(A.uplo)) - elseif A.uplo == 'U' - return $Wrapper($f(parent(A), $conjugation(parent(B))), :U) - else - return $Wrapper($f($conjugation(parent(A)), parent(B)), :U) +function kron(A::Symmetric{T}, B::Symmetric{S}) where {T<:Number,S<:Number} + resultuplo = A.uplo == 'U' || B.uplo == 'U' ? :U : :L + C = Symmetric(Matrix{promote_op(*, T, S)}(undef, _kronsize(A, B)), resultuplo) + return kron!(C, A, B) +end + +function kron!(C::Hermitian{<:Union{Real,Complex}}, A::Hermitian{<:Union{Real,Complex}}, B::Hermitian{<:Union{Real,Complex}}) + size(C) == _kronsize(A, B) || throw(DimensionMismatch("kron!")) + if ((A.uplo == 'U' || B.uplo == 'U') && C.uplo != 'U') || ((A.uplo == 'L' && B.uplo == 'L') && C.uplo != 'L') + throw(ArgumentError("C.uplo must match A.uplo and B.uplo, got $(C.uplo) $(A.uplo) $(B.uplo)")) + end + _hermkron!(C.data, A.data, B.data, conj, real, A.uplo, B.uplo) + return C +end + +function kron!(C::Symmetric{<:Number}, A::Symmetric{<:Number}, B::Symmetric{<:Number}) + size(C) == _kronsize(A, B) || throw(DimensionMismatch("kron!")) + if ((A.uplo == 'U' || B.uplo == 'U') && C.uplo != 'U') || ((A.uplo == 'L' && B.uplo == 'L') && C.uplo != 'L') + throw(ArgumentError("C.uplo must match A.uplo and B.uplo, got $(C.uplo) $(A.uplo) $(B.uplo)")) + end + _hermkron!(C.data, A.data, B.data, identity, identity, A.uplo, B.uplo) + return C +end + +function _hermkron!(C, A, B, conj::TC, real::TR, Auplo, Buplo) where {TC,TR} + n_A = size(A, 1) + n_B = size(B, 1) + @inbounds if Auplo == 'U' && Buplo == 'U' + for j = 1:n_A + jnB = (j - 1) * n_B + for i = 1:(j-1) + Aij = A[i, j] + inB = (i - 1) * n_B + for l = 1:n_B + for k = 1:(l-1) + C[inB+k, jnB+l] = Aij * B[k, l] + C[inB+l, jnB+k] = Aij * conj(B[k, l]) + end + C[inB+l, jnB+l] = Aij * real(B[l, l]) + end + end + Ajj = real(A[j, j]) + for l = 1:n_B + for k = 1:(l-1) + C[jnB+k, jnB+l] = Ajj * B[k, l] + end + C[jnB+l, jnB+l] = Ajj * real(B[l, l]) + end + end + elseif Auplo == 'U' && Buplo == 'L' + for j = 1:n_A + jnB = (j - 1) * n_B + for i = 1:(j-1) + Aij = A[i, j] + inB = (i - 1) * n_B + for l = 1:n_B + C[inB+l, jnB+l] = Aij * real(B[l, l]) + for k = (l+1):n_B + C[inB+l, jnB+k] = Aij * conj(B[k, l]) + C[inB+k, jnB+l] = Aij * B[k, l] + end + end + end + Ajj = real(A[j, j]) + for l = 1:n_B + C[jnB+l, jnB+l] = Ajj * real(B[l, l]) + for k = (l+1):n_B + C[jnB+l, jnB+k] = Ajj * conj(B[k, l]) + end + end + end + elseif Auplo == 'L' && Buplo == 'U' + for j = 1:n_A + jnB = (j - 1) * n_B + Ajj = real(A[j, j]) + for l = 1:n_B + for k = 1:(l-1) + C[jnB+k, jnB+l] = Ajj * B[k, l] + end + C[jnB+l, jnB+l] = Ajj * real(B[l, l]) + end + for i = (j+1):n_A + conjAij = conj(A[i, j]) + inB = (i - 1) * n_B + for l = 1:n_B + for k = 1:(l-1) + C[jnB+k, inB+l] = conjAij * B[k, l] + C[jnB+l, inB+k] = conjAij * conj(B[k, l]) + end + C[jnB+l, inB+l] = conjAij * real(B[l, l]) + end + end + end + else #if Auplo == 'L' && Buplo == 'L' + for j = 1:n_A + jnB = (j - 1) * n_B + Ajj = real(A[j, j]) + for l = 1:n_B + C[jnB+l, jnB+l] = Ajj * real(B[l, l]) + for k = (l+1):n_B + C[jnB+k, jnB+l] = Ajj * B[k, l] + end + end + for i = (j+1):n_A + Aij = A[i, j] + inB = (i - 1) * n_B + for l = 1:n_B + C[inB+l, jnB+l] = Aij * real(B[l, l]) + for k = (l+1):n_B + C[inB+k, jnB+l] = Aij * B[k, l] + C[inB+l, jnB+k] = Aij * conj(B[k, l]) + end + end end end end end +(-)(A::Symmetric) = Symmetric(parentof_applytri(-, A), sym_uplo(A.uplo)) +(-)(A::Hermitian) = Hermitian(parentof_applytri(-, A), sym_uplo(A.uplo)) + +## Addition/subtraction +for f ∈ (:+, :-), Wrapper ∈ (:Hermitian, :Symmetric) + @eval function $f(A::$Wrapper, B::$Wrapper) + uplo = A.uplo == B.uplo ? sym_uplo(A.uplo) : (:U) + $Wrapper(parentof_applytri($f, A, B), uplo) + end +end + for f in (:+, :-) @eval begin $f(A::Hermitian, B::Symmetric{<:Real}) = $f(A, Hermitian(parent(B), sym_uplo(B.uplo))) @@ -555,12 +701,12 @@ function dot(x::AbstractVector, A::RealHermSymComplexHerm, y::AbstractVector) end # Scaling with Number -*(A::Symmetric, x::Number) = Symmetric(A.data*x, sym_uplo(A.uplo)) -*(x::Number, A::Symmetric) = Symmetric(x*A.data, sym_uplo(A.uplo)) -*(A::Hermitian, x::Real) = Hermitian(A.data*x, sym_uplo(A.uplo)) -*(x::Real, A::Hermitian) = Hermitian(x*A.data, sym_uplo(A.uplo)) -/(A::Symmetric, x::Number) = Symmetric(A.data/x, sym_uplo(A.uplo)) -/(A::Hermitian, x::Real) = Hermitian(A.data/x, sym_uplo(A.uplo)) +*(A::Symmetric, x::Number) = Symmetric(parentof_applytri(y -> y * x, A), sym_uplo(A.uplo)) +*(x::Number, A::Symmetric) = Symmetric(parentof_applytri(y -> x * y, A), sym_uplo(A.uplo)) +*(A::Hermitian, x::Real) = Hermitian(parentof_applytri(y -> y * x, A), sym_uplo(A.uplo)) +*(x::Real, A::Hermitian) = Hermitian(parentof_applytri(y -> x * y, A), sym_uplo(A.uplo)) +/(A::Symmetric, x::Number) = Symmetric(parentof_applytri(y -> y/x, A), sym_uplo(A.uplo)) +/(A::Hermitian, x::Real) = Hermitian(parentof_applytri(y -> y/x, A), sym_uplo(A.uplo)) factorize(A::HermOrSym) = _factorize(A) function _factorize(A::HermOrSym{T}; check::Bool=true) where T @@ -574,6 +720,12 @@ function _factorize(A::HermOrSym{T}; check::Bool=true) where T end end +logabsdet(A::RealHermSymComplexHerm) = ((l, s) = logabsdet(_factorize(A; check=false)); return real(l), s) +logabsdet(A::Symmetric{<:Real}) = logabsdet(_factorize(A; check=false)) +logabsdet(A::Symmetric) = logabsdet(_factorize(A; check=false)) +logdet(A::RealHermSymComplexHerm) = real(logdet(_factorize(A; check=false))) +logdet(A::Symmetric{<:Real}) = logdet(_factorize(A; check=false)) +logdet(A::Symmetric) = logdet(_factorize(A; check=false)) det(A::RealHermSymComplexHerm) = real(det(_factorize(A; check=false))) det(A::Symmetric{<:Real}) = det(_factorize(A; check=false)) det(A::Symmetric) = det(_factorize(A; check=false)) @@ -812,6 +964,13 @@ for func in (:log, :sqrt) end end +# Cube root of a real-valued symmetric matrix +function cbrt(A::HermOrSym{<:Real}) + F = eigen(A) + A = F.vectors * Diagonal(cbrt.(F.values)) * F.vectors' + return A +end + """ hermitianpart(A, uplo=:U) -> Hermitian diff --git a/stdlib/LinearAlgebra/src/symmetriceigen.jl b/stdlib/LinearAlgebra/src/symmetriceigen.jl index 279577c31d664..0c86383685807 100644 --- a/stdlib/LinearAlgebra/src/symmetriceigen.jl +++ b/stdlib/LinearAlgebra/src/symmetriceigen.jl @@ -184,6 +184,49 @@ function eigen!(A::AbstractMatrix, C::Cholesky; sortby::Union{Function,Nothing}= GeneralizedEigen(sorteig!(vals, vecs, sortby)...) end +# Bunch-Kaufmann (LDLT) based solution for generalized eigenvalues and eigenvectors +function eigen(A::StridedMatrix{T}, B::BunchKaufman{T,<:AbstractMatrix}; sortby::Union{Function,Nothing}=nothing) where {T<:BlasFloat} + eigen!(copy(A), copy(B); sortby) +end +function eigen!(A::StridedMatrix{T}, B::BunchKaufman{T,<:StridedMatrix}; sortby::Union{Function,Nothing}=nothing) where {T<:BlasFloat} + M, TD, p = getproperties!(B) + # Compute generalized eigenvalues of equivalent matrix: + # A' = inv(Tridiagonal(dl,d,du))*inv(M)*P*A*P'*inv(M') + # See: https://github.com/JuliaLang/julia/pull/50471#issuecomment-1627836781 + permutecols!(A, p) + permuterows!(A, p) + ldiv!(M, A) + rdiv!(A, M') + ldiv!(TD, A) + vals, vecs = eigen!(A; sortby) + # Compute generalized eigenvectors from 'vecs': + # vecs = P'*inv(M')*vecs + # See: https://github.com/JuliaLang/julia/pull/50471#issuecomment-1627836781 + M = B.uplo == 'U' ? UnitUpperTriangular{eltype(vecs)}(M) : UnitLowerTriangular{eltype(vecs)}(M) ; + ldiv!(M', vecs) + invpermuterows!(vecs, p) + GeneralizedEigen(sorteig!(vals, vecs, sortby)...) +end + +# LU based solution for generalized eigenvalues and eigenvectors +function eigen(A::StridedMatrix{T}, F::LU{T,<:StridedMatrix}; sortby::Union{Function,Nothing}=nothing) where {T} + return eigen!(copy(A), copy(F); sortby) +end +function eigen!(A::StridedMatrix{T}, F::LU{T,<:StridedMatrix}; sortby::Union{Function,Nothing}=nothing) where {T} + L = UnitLowerTriangular(F.L) + U = UpperTriangular(F.U) + permuterows!(A, F.p) + ldiv!(L, A) + rdiv!(A, U) + vals, vecs = eigen!(A; sortby) + # Compute generalized eigenvectors from 'vecs': + # vecs = P'*inv(M')*vecs + # See: https://github.com/JuliaLang/julia/pull/50471#issuecomment-1627836781 + U = UpperTriangular{eltype(vecs)}(U) + ldiv!(U, vecs) + GeneralizedEigen(sorteig!(vals, vecs, sortby)...) +end + # Perform U' \ A / U in-place, where U::Union{UpperTriangular,Diagonal} UtiAUi!(A, U) = _UtiAUi!(A, U) UtiAUi!(A::Symmetric, U) = Symmetric(_UtiAUi!(copytri!(parent(A), A.uplo), U), sym_uplo(A.uplo)) @@ -218,3 +261,67 @@ function eigvals!(A::AbstractMatrix{T}, C::Cholesky{T, <:AbstractMatrix}; sortby # Cholesky decomposition based eigenvalues return eigvals!(UtiAUi!(A, C.U); sortby) end + +# Bunch-Kaufmann (LDLT) based solution for generalized eigenvalues +function eigvals(A::StridedMatrix{T}, B::BunchKaufman{T,<:AbstractMatrix}; sortby::Union{Function,Nothing}=nothing) where {T<:BlasFloat} + eigvals!(copy(A), copy(B); sortby) +end +function eigvals!(A::StridedMatrix{T}, B::BunchKaufman{T,<:StridedMatrix}; sortby::Union{Function,Nothing}=nothing) where {T<:BlasFloat} + M, TD, p = getproperties!(B) + # Compute generalized eigenvalues of equivalent matrix: + # A' = inv(Tridiagonal(dl,d,du))*inv(M)*P*A*P'*inv(M') + # See: https://github.com/JuliaLang/julia/pull/50471#issuecomment-1627836781 + permutecols!(A, p) + permuterows!(A, p) + ldiv!(M, A) + rdiv!(A, M') + ldiv!(TD, A) + return eigvals!(A; sortby) +end + +# LU based solution for generalized eigenvalues +function eigvals(A::StridedMatrix{T}, F::LU{T,<:StridedMatrix}; sortby::Union{Function,Nothing}=nothing) where {T} + return eigvals!(copy(A), copy(F); sortby) +end +function eigvals!(A::StridedMatrix{T}, F::LU{T,<:StridedMatrix}; sortby::Union{Function,Nothing}=nothing) where {T} + L = UnitLowerTriangular(F.L) + U = UpperTriangular(F.U) + # Compute generalized eigenvalues of equivalent matrix: + # A' = inv(L)*(P*A)*inv(U) + # See: https://github.com/JuliaLang/julia/pull/50471#issuecomment-1627836781 + permuterows!(A, F.p) + ldiv!(L, A) + rdiv!(A, U) + return eigvals!(A; sortby) +end + + +function eigen(A::Hermitian{Complex{T}, <:Tridiagonal}; kwargs...) where {T} + (; dl, d, du) = parent(A) + N = length(d) + if N <= 1 + eigen(parent(A); kwargs...) + else + if A.uplo == 'U' + E = du' + Er = abs.(du) + else + E = dl + Er = abs.(E) + end + S = Vector{eigtype(eltype(A))}(undef, N) + S[1] = 1 + for i ∈ 1:N-1 + S[i+1] = iszero(Er[i]) ? oneunit(eltype(S)) : S[i] * sign(E[i]) + end + B = SymTridiagonal(float.(real.(d)), Er) + Λ, Φ = eigen(B; kwargs...) + return Eigen(Λ, Diagonal(S) * Φ) + end +end + +function eigvals(A::Hermitian{Complex{T}, <:Tridiagonal}; kwargs...) where {T} + (; dl, d, du) = parent(A) + Er = A.uplo == 'U' ? abs.(du) : abs.(dl) + eigvals(SymTridiagonal(float.(real.(d)), Er); kwargs...) +end diff --git a/stdlib/LinearAlgebra/src/transpose.jl b/stdlib/LinearAlgebra/src/transpose.jl index cd9123615d4bb..8aa04f7d34b48 100644 --- a/stdlib/LinearAlgebra/src/transpose.jl +++ b/stdlib/LinearAlgebra/src/transpose.jl @@ -178,8 +178,41 @@ copy(::Union{Transpose,Adjoint}) Base.copy(A::TransposeAbsMat) = transpose!(similar(A.parent, reverse(axes(A.parent))), A.parent) Base.copy(A::AdjointAbsMat) = adjoint!(similar(A.parent, reverse(axes(A.parent))), A.parent) -function copy_transpose!(B::AbstractVecOrMat, ir_dest::AbstractRange{Int}, jr_dest::AbstractRange{Int}, - A::AbstractVecOrMat, ir_src::AbstractRange{Int}, jr_src::AbstractRange{Int}) +""" + copy_transpose!(B::AbstractVecOrMat, ir_dest::AbstractRange{Int}, jr_dest::AbstractRange{Int}, + A::AbstractVecOrMat, ir_src::AbstractRange{Int}, jr_src::AbstractRange{Int}) -> B + +Efficiently copy elements of matrix `A` to `B` with transposition as follows: + + B[ir_dest, jr_dest] = transpose(A)[jr_src, ir_src] + +The elements `B[ir_dest, jr_dest]` are overwritten. Furthermore, +the index range parameters must satisfy `length(ir_dest) == length(jr_src)` and +`length(jr_dest) == length(ir_src)`. +""" +copy_transpose!(B::AbstractVecOrMat, ir_dest::AbstractRange{Int}, jr_dest::AbstractRange{Int}, + A::AbstractVecOrMat, ir_src::AbstractRange{Int}, jr_src::AbstractRange{Int}) = + _copy_adjtrans!(B, ir_dest, jr_dest, A, ir_src, jr_src, transpose) + +""" + copy_adjoint!(B::AbstractVecOrMat, ir_dest::AbstractRange{Int}, jr_dest::AbstractRange{Int}, + A::AbstractVecOrMat, ir_src::AbstractRange{Int}, jr_src::AbstractRange{Int}) -> B + +Efficiently copy elements of matrix `A` to `B` with adjunction as follows: + + B[ir_dest, jr_dest] = adjoint(A)[jr_src, ir_src] + +The elements `B[ir_dest, jr_dest]` are overwritten. Furthermore, +the index range parameters must satisfy `length(ir_dest) == length(jr_src)` and +`length(jr_dest) == length(ir_src)`. +""" +copy_adjoint!(B::AbstractVecOrMat, ir_dest::AbstractRange{Int}, jr_dest::AbstractRange{Int}, + A::AbstractVecOrMat, ir_src::AbstractRange{Int}, jr_src::AbstractRange{Int}) = + _copy_adjtrans!(B, ir_dest, jr_dest, A, ir_src, jr_src, adjoint) + +function _copy_adjtrans!(B::AbstractVecOrMat, ir_dest::AbstractRange{Int}, jr_dest::AbstractRange{Int}, + A::AbstractVecOrMat, ir_src::AbstractRange{Int}, jr_src::AbstractRange{Int}, + tfun::T) where {T} if length(ir_dest) != length(jr_src) throw(ArgumentError(LazyString("source and destination must have same size (got ", length(jr_src)," and ",length(ir_dest),")"))) @@ -194,7 +227,7 @@ function copy_transpose!(B::AbstractVecOrMat, ir_dest::AbstractRange{Int}, jr_de for jsrc in jr_src jdest = first(jr_dest) for isrc in ir_src - B[idest,jdest] = A[isrc,jsrc] + B[idest,jdest] = tfun(A[isrc,jsrc]) jdest += step(jr_dest) end idest += step(ir_dest) @@ -202,13 +235,10 @@ function copy_transpose!(B::AbstractVecOrMat, ir_dest::AbstractRange{Int}, jr_de return B end -function copy_similar(A::AdjointAbsMat, ::Type{T}) where {T} - C = similar(A, T, size(A)) - adjoint!(C, parent(A)) -end -function copy_similar(A::TransposeAbsMat, ::Type{T}) where {T} - C = similar(A, T, size(A)) - transpose!(C, parent(A)) +function copy_similar(A::AdjOrTransAbsMat, ::Type{T}) where {T} + Ap = parent(A) + f! = inplace_adj_or_trans(A) + return f!(similar(Ap, T, reverse(axes(Ap))), Ap) end function Base.copyto_unaliased!(deststyle::IndexStyle, dest::AbstractMatrix, srcstyle::IndexCartesian, src::AdjOrTransAbsMat) diff --git a/stdlib/LinearAlgebra/src/triangular.jl b/stdlib/LinearAlgebra/src/triangular.jl index 798c748380cba..bc6c2a64a6d7d 100644 --- a/stdlib/LinearAlgebra/src/triangular.jl +++ b/stdlib/LinearAlgebra/src/triangular.jl @@ -3,6 +3,12 @@ ## Triangular # could be renamed to Triangular when that name has been fully deprecated +""" + AbstractTriangular + +Supertype of triangular matrix types such as [`LowerTriangular`](@ref), [`UpperTriangular`](@ref), +[`UnitLowerTriangular`](@ref) and [`UnitUpperTriangular`](@ref). +""" abstract type AbstractTriangular{T} <: AbstractMatrix{T} end # First loop through all methods that don't need special care for upper/lower and unit diagonal @@ -23,12 +29,11 @@ for t in (:LowerTriangular, :UnitLowerTriangular, :UpperTriangular, :UnitUpperTr $t{T}(A::AbstractMatrix) where {T} = $t(convert(AbstractMatrix{T}, A)) $t{T}(A::$t) where {T} = $t(convert(AbstractMatrix{T}, A.data)) - Matrix(A::$t{T}) where {T} = Matrix{T}(A) - AbstractMatrix{T}(A::$t) where {T} = $t{T}(A) AbstractMatrix{T}(A::$t{T}) where {T} = copy(A) size(A::$t) = size(A.data) + axes(A::$t) = axes(A.data) # For A<:AbstractTriangular, similar(A[, neweltype]) should yield a matrix with the same # triangular type and underlying storage type as A. The following method covers these cases. @@ -38,9 +43,11 @@ for t in (:LowerTriangular, :UnitLowerTriangular, :UpperTriangular, :UnitUpperTr similar(A::$t, ::Type{T}, dims::Dims{N}) where {T,N} = similar(parent(A), T, dims) copy(A::$t) = $t(copy(A.data)) + Base.unaliascopy(A::$t) = $t(Base.unaliascopy(A.data)) real(A::$t{<:Real}) = A real(A::$t{<:Complex}) = (B = real(A.data); $t(B)) + real(A::$t{<:Complex, <:StridedMaybeAdjOrTransMat}) = $t(real.(A)) end end @@ -147,14 +154,37 @@ const UpperOrUnitUpperTriangular{T,S} = Union{UpperTriangular{T,S}, UnitUpperTri const LowerOrUnitLowerTriangular{T,S} = Union{LowerTriangular{T,S}, UnitLowerTriangular{T,S}} const UpperOrLowerTriangular{T,S} = Union{UpperOrUnitUpperTriangular{T,S}, LowerOrUnitLowerTriangular{T,S}} +Base.dataids(A::UpperOrLowerTriangular) = Base.dataids(A.data) + imag(A::UpperTriangular) = UpperTriangular(imag(A.data)) imag(A::LowerTriangular) = LowerTriangular(imag(A.data)) -imag(A::UnitLowerTriangular) = LowerTriangular(tril!(imag(A.data),-1)) -imag(A::UnitUpperTriangular) = UpperTriangular(triu!(imag(A.data),1)) +imag(A::UpperTriangular{<:Any,<:StridedMaybeAdjOrTransMat}) = imag.(A) +imag(A::LowerTriangular{<:Any,<:StridedMaybeAdjOrTransMat}) = imag.(A) +function imag(A::UnitLowerTriangular) + L = LowerTriangular(A.data) + Lim = similar(L) # must be mutable to set diagonals to zero + Lim .= imag.(L) + for i in 1:size(Lim,1) + Lim[i,i] = zero(Lim[i,i]) + end + return Lim +end +function imag(A::UnitUpperTriangular) + U = UpperTriangular(A.data) + Uim = similar(U) # must be mutable to set diagonals to zero + Uim .= imag.(U) + for i in 1:size(Uim,1) + Uim[i,i] = zero(Uim[i,i]) + end + return Uim +end Array(A::AbstractTriangular) = Matrix(A) parent(A::UpperOrLowerTriangular) = A.data +# For strided matrices, we may only loop over the filled triangle +copy(A::UpperOrLowerTriangular{<:Any, <:StridedMaybeAdjOrTransMat}) = copyto!(similar(A), A) + # then handle all methods that requires specific handling of upper/lower and unit diagonal function Matrix{T}(A::LowerTriangular) where T @@ -233,61 +263,77 @@ Base.isstored(A::UnitUpperTriangular, i::Int, j::Int) = Base.isstored(A::UpperTriangular, i::Int, j::Int) = i <= j ? Base.isstored(A.data, i, j) : false -getindex(A::UnitLowerTriangular{T}, i::Integer, j::Integer) where {T} = +@propagate_inbounds getindex(A::UnitLowerTriangular{T}, i::Integer, j::Integer) where {T} = i > j ? A.data[i,j] : ifelse(i == j, oneunit(T), zero(T)) -getindex(A::LowerTriangular, i::Integer, j::Integer) = - i >= j ? A.data[i,j] : zero(A.data[j,i]) -getindex(A::UnitUpperTriangular{T}, i::Integer, j::Integer) where {T} = +@propagate_inbounds getindex(A::LowerTriangular, i::Integer, j::Integer) = + i >= j ? A.data[i,j] : _zero(A.data,j,i) +@propagate_inbounds getindex(A::UnitUpperTriangular{T}, i::Integer, j::Integer) where {T} = i < j ? A.data[i,j] : ifelse(i == j, oneunit(T), zero(T)) -getindex(A::UpperTriangular, i::Integer, j::Integer) = - i <= j ? A.data[i,j] : zero(A.data[j,i]) +@propagate_inbounds getindex(A::UpperTriangular, i::Integer, j::Integer) = + i <= j ? A.data[i,j] : _zero(A.data,j,i) -function setindex!(A::UpperTriangular, x, i::Integer, j::Integer) +@propagate_inbounds function setindex!(A::UpperTriangular, x, i::Integer, j::Integer) if i > j iszero(x) || throw(ArgumentError("cannot set index in the lower triangular part " * - "($i, $j) of an UpperTriangular matrix to a nonzero value ($x)")) + lazy"($i, $j) of an UpperTriangular matrix to a nonzero value ($x)")) else A.data[i,j] = x end return A end -function setindex!(A::UnitUpperTriangular, x, i::Integer, j::Integer) +@propagate_inbounds function setindex!(A::UnitUpperTriangular, x, i::Integer, j::Integer) if i > j iszero(x) || throw(ArgumentError("cannot set index in the lower triangular part " * - "($i, $j) of a UnitUpperTriangular matrix to a nonzero value ($x)")) + lazy"($i, $j) of a UnitUpperTriangular matrix to a nonzero value ($x)")) elseif i == j - x == oneunit(x) || throw(ArgumentError("cannot set index on the diagonal ($i, $j) " * - "of a UnitUpperTriangular matrix to a non-unit value ($x)")) + x == oneunit(x) || throw(ArgumentError(lazy"cannot set index on the diagonal ($i, $j) " * + lazy"of a UnitUpperTriangular matrix to a non-unit value ($x)")) else A.data[i,j] = x end return A end -function setindex!(A::LowerTriangular, x, i::Integer, j::Integer) +@propagate_inbounds function setindex!(A::LowerTriangular, x, i::Integer, j::Integer) if i < j iszero(x) || throw(ArgumentError("cannot set index in the upper triangular part " * - "($i, $j) of a LowerTriangular matrix to a nonzero value ($x)")) + lazy"($i, $j) of a LowerTriangular matrix to a nonzero value ($x)")) else A.data[i,j] = x end return A end -function setindex!(A::UnitLowerTriangular, x, i::Integer, j::Integer) +@propagate_inbounds function setindex!(A::UnitLowerTriangular, x, i::Integer, j::Integer) if i < j iszero(x) || throw(ArgumentError("cannot set index in the upper triangular part " * - "($i, $j) of a UnitLowerTriangular matrix to a nonzero value ($x)")) + lazy"($i, $j) of a UnitLowerTriangular matrix to a nonzero value ($x)")) elseif i == j - x == oneunit(x) || throw(ArgumentError("cannot set index on the diagonal ($i, $j) " * - "of a UnitLowerTriangular matrix to a non-unit value ($x)")) + x == oneunit(x) || throw(ArgumentError(lazy"cannot set index on the diagonal ($i, $j) " * + lazy"of a UnitLowerTriangular matrix to a non-unit value ($x)")) else A.data[i,j] = x end return A end +@inline function fill!(A::UpperTriangular, x) + iszero(x) || throw(ArgumentError("cannot set indices in the lower triangular part " * + lazy"of an UpperTriangular matrix to a nonzero value ($x)")) + for col in axes(A,2), row in firstindex(A,1):col + @inbounds A.data[row, col] = x + end + A +end +@inline function fill!(A::LowerTriangular, x) + iszero(x) || throw(ArgumentError("cannot set indices in the upper triangular part " * + lazy"of a LowerTriangular matrix to a nonzero value ($x)")) + for col in axes(A,2), row in col:lastindex(A,1) + @inbounds A.data[row, col] = x + end + A +end ## structured matrix methods ## function Base.replace_in_print_matrix(A::Union{UpperTriangular,UnitUpperTriangular}, @@ -326,7 +372,15 @@ function tril!(A::UpperTriangular{T}, k::Integer=0) where {T} return UpperTriangular(tril!(A.data,k)) end end -triu!(A::UpperTriangular, k::Integer=0) = UpperTriangular(triu!(A.data, k)) +function triu!(A::UpperTriangular, k::Integer=0) + n = size(A,1) + if k > 0 + for j in 1:n, i in max(1,j-k+1):j + A.data[i,j] = zero(eltype(A)) + end + end + return A +end function tril!(A::UnitUpperTriangular{T}, k::Integer=0) where {T} n = size(A,1) @@ -369,7 +423,15 @@ function triu!(A::LowerTriangular{T}, k::Integer=0) where {T} end end -tril!(A::LowerTriangular, k::Integer=0) = LowerTriangular(tril!(A.data, k)) +function tril!(A::LowerTriangular, k::Integer=0) + n = size(A,1) + if k < 0 + for j in 1:n, i in j:min(j-k-1,n) + A.data[i, j] = zero(eltype(A)) + end + end + A +end function triu!(A::UnitLowerTriangular{T}, k::Integer=0) where T n = size(A,1) @@ -407,13 +469,13 @@ transpose(A::UnitLowerTriangular) = UnitUpperTriangular(transpose(A.data)) transpose(A::UnitUpperTriangular) = UnitLowerTriangular(transpose(A.data)) transpose!(A::LowerTriangular) = UpperTriangular(copytri!(A.data, 'L', false, true)) -transpose!(A::UnitLowerTriangular) = UnitUpperTriangular(copytri!(A.data, 'L', false, true)) +transpose!(A::UnitLowerTriangular) = UnitUpperTriangular(copytri!(A.data, 'L', false, false)) transpose!(A::UpperTriangular) = LowerTriangular(copytri!(A.data, 'U', false, true)) -transpose!(A::UnitUpperTriangular) = UnitLowerTriangular(copytri!(A.data, 'U', false, true)) +transpose!(A::UnitUpperTriangular) = UnitLowerTriangular(copytri!(A.data, 'U', false, false)) adjoint!(A::LowerTriangular) = UpperTriangular(copytri!(A.data, 'L' , true, true)) -adjoint!(A::UnitLowerTriangular) = UnitUpperTriangular(copytri!(A.data, 'L' , true, true)) +adjoint!(A::UnitLowerTriangular) = UnitUpperTriangular(copytri!(A.data, 'L' , true, false)) adjoint!(A::UpperTriangular) = LowerTriangular(copytri!(A.data, 'U' , true, true)) -adjoint!(A::UnitUpperTriangular) = UnitLowerTriangular(copytri!(A.data, 'U' , true, true)) +adjoint!(A::UnitUpperTriangular) = UnitLowerTriangular(copytri!(A.data, 'U' , true, false)) diag(A::LowerTriangular) = diag(A.data) diag(A::UnitLowerTriangular) = fill(oneunit(eltype(A)), size(A,1)) @@ -424,20 +486,29 @@ diag(A::UnitUpperTriangular) = fill(oneunit(eltype(A)), size(A,1)) -(A::LowerTriangular) = LowerTriangular(-A.data) -(A::UpperTriangular) = UpperTriangular(-A.data) function -(A::UnitLowerTriangular) - Anew = -A.data + Adata = A.data + Anew = similar(Adata) # must be mutable, even if Adata is not + @. Anew = -Adata for i = 1:size(A, 1) Anew[i, i] = -A[i, i] end LowerTriangular(Anew) end function -(A::UnitUpperTriangular) - Anew = -A.data + Adata = A.data + Anew = similar(Adata) # must be mutable, even if Adata is not + @. Anew = -Adata for i = 1:size(A, 1) Anew[i, i] = -A[i, i] end UpperTriangular(Anew) end +# use broadcasting if the parents are strided, where we loop only over the triangular part +for TM in (:LowerTriangular, :UpperTriangular) + @eval -(A::$TM{<:Any, <:StridedMaybeAdjOrTransMat}) = broadcast(-, A) +end + tr(A::LowerTriangular) = tr(A.data) tr(A::UnitLowerTriangular) = size(A, 1) * oneunit(eltype(A)) tr(A::UpperTriangular) = tr(A.data) @@ -445,34 +516,42 @@ tr(A::UnitUpperTriangular) = size(A, 1) * oneunit(eltype(A)) # copy and scale function copyto!(A::T, B::T) where {T<:Union{UpperTriangular,UnitUpperTriangular}} + @boundscheck checkbounds(A, axes(B)...) n = size(B,1) + B2 = Base.unalias(A, B) for j = 1:n for i = 1:(isa(B, UnitUpperTriangular) ? j-1 : j) - @inbounds A[i,j] = B[i,j] + @inbounds A[i,j] = B2[i,j] end end return A end function copyto!(A::T, B::T) where {T<:Union{LowerTriangular,UnitLowerTriangular}} + @boundscheck checkbounds(A, axes(B)...) n = size(B,1) + B2 = Base.unalias(A, B) for j = 1:n for i = (isa(B, UnitLowerTriangular) ? j+1 : j):n - @inbounds A[i,j] = B[i,j] + @inbounds A[i,j] = B2[i,j] end end return A end -# Define `mul!` for (Unit){Upper,Lower}Triangular matrices times a number. -# be permissive here and require compatibility later in _triscale! -@inline mul!(A::AbstractTriangular, B::AbstractTriangular, C::Number, alpha::Number, beta::Number) = +@inline _rscale_add!(A::AbstractTriangular, B::AbstractTriangular, C::Number, alpha::Number, beta::Number) = _triscale!(A, B, C, MulAddMul(alpha, beta)) -@inline mul!(A::AbstractTriangular, B::Number, C::AbstractTriangular, alpha::Number, beta::Number) = +@inline _lscale_add!(A::AbstractTriangular, B::Number, C::AbstractTriangular, alpha::Number, beta::Number) = _triscale!(A, B, C, MulAddMul(alpha, beta)) +function checksize1(A, B) + szA, szB = size(A), size(B) + szA == szB || throw(DimensionMismatch(lazy"size of A, $szA, does not match size of B, $szB")) + checksquare(B) +end + function _triscale!(A::UpperTriangular, B::UpperTriangular, c::Number, _add) - n = checksquare(B) - iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) + n = checksize1(A, B) + iszero(_add.alpha) && return _rmul_or_fill!(A, _add.beta) for j = 1:n for i = 1:j @inbounds _modify!(_add, B.data[i,j] * c, A.data, (i,j)) @@ -481,8 +560,8 @@ function _triscale!(A::UpperTriangular, B::UpperTriangular, c::Number, _add) return A end function _triscale!(A::UpperTriangular, c::Number, B::UpperTriangular, _add) - n = checksquare(B) - iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) + n = checksize1(A, B) + iszero(_add.alpha) && return _rmul_or_fill!(A, _add.beta) for j = 1:n for i = 1:j @inbounds _modify!(_add, c * B.data[i,j], A.data, (i,j)) @@ -491,8 +570,8 @@ function _triscale!(A::UpperTriangular, c::Number, B::UpperTriangular, _add) return A end function _triscale!(A::UpperOrUnitUpperTriangular, B::UnitUpperTriangular, c::Number, _add) - n = checksquare(B) - iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) + n = checksize1(A, B) + iszero(_add.alpha) && return _rmul_or_fill!(A, _add.beta) for j = 1:n @inbounds _modify!(_add, c, A, (j,j)) for i = 1:(j - 1) @@ -502,8 +581,8 @@ function _triscale!(A::UpperOrUnitUpperTriangular, B::UnitUpperTriangular, c::Nu return A end function _triscale!(A::UpperOrUnitUpperTriangular, c::Number, B::UnitUpperTriangular, _add) - n = checksquare(B) - iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) + n = checksize1(A, B) + iszero(_add.alpha) && return _rmul_or_fill!(A, _add.beta) for j = 1:n @inbounds _modify!(_add, c, A, (j,j)) for i = 1:(j - 1) @@ -513,8 +592,8 @@ function _triscale!(A::UpperOrUnitUpperTriangular, c::Number, B::UnitUpperTriang return A end function _triscale!(A::LowerTriangular, B::LowerTriangular, c::Number, _add) - n = checksquare(B) - iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) + n = checksize1(A, B) + iszero(_add.alpha) && return _rmul_or_fill!(A, _add.beta) for j = 1:n for i = j:n @inbounds _modify!(_add, B.data[i,j] * c, A.data, (i,j)) @@ -523,8 +602,8 @@ function _triscale!(A::LowerTriangular, B::LowerTriangular, c::Number, _add) return A end function _triscale!(A::LowerTriangular, c::Number, B::LowerTriangular, _add) - n = checksquare(B) - iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) + n = checksize1(A, B) + iszero(_add.alpha) && return _rmul_or_fill!(A, _add.beta) for j = 1:n for i = j:n @inbounds _modify!(_add, c * B.data[i,j], A.data, (i,j)) @@ -533,8 +612,8 @@ function _triscale!(A::LowerTriangular, c::Number, B::LowerTriangular, _add) return A end function _triscale!(A::LowerOrUnitLowerTriangular, B::UnitLowerTriangular, c::Number, _add) - n = checksquare(B) - iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) + n = checksize1(A, B) + iszero(_add.alpha) && return _rmul_or_fill!(A, _add.beta) for j = 1:n @inbounds _modify!(_add, c, A, (j,j)) for i = (j + 1):n @@ -544,8 +623,8 @@ function _triscale!(A::LowerOrUnitLowerTriangular, B::UnitLowerTriangular, c::Nu return A end function _triscale!(A::LowerOrUnitLowerTriangular, c::Number, B::UnitLowerTriangular, _add) - n = checksquare(B) - iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) + n = checksize1(A, B) + iszero(_add.alpha) && return _rmul_or_fill!(A, _add.beta) for j = 1:n @inbounds _modify!(_add, c, A, (j,j)) for i = (j + 1):n @@ -668,6 +747,90 @@ fillstored!(A::UnitUpperTriangular, x) = (fillband!(A.data, x, 1, size(A,2)-1); -(A::UnitLowerTriangular, B::UnitLowerTriangular) = LowerTriangular(tril(A.data, -1) - tril(B.data, -1)) -(A::AbstractTriangular, B::AbstractTriangular) = copyto!(similar(parent(A)), A) - copyto!(similar(parent(B)), B) +# use broadcasting if the parents are strided, where we loop only over the triangular part +for op in (:+, :-) + for TM1 in (:LowerTriangular, :UnitLowerTriangular), TM2 in (:LowerTriangular, :UnitLowerTriangular) + @eval $op(A::$TM1{<:Any, <:StridedMaybeAdjOrTransMat}, B::$TM2{<:Any, <:StridedMaybeAdjOrTransMat}) = broadcast($op, A, B) + end + for TM1 in (:UpperTriangular, :UnitUpperTriangular), TM2 in (:UpperTriangular, :UnitUpperTriangular) + @eval $op(A::$TM1{<:Any, <:StridedMaybeAdjOrTransMat}, B::$TM2{<:Any, <:StridedMaybeAdjOrTransMat}) = broadcast($op, A, B) + end +end + +function kron(A::UpperTriangular{T}, B::UpperTriangular{S}) where {T<:Number,S<:Number} + C = UpperTriangular(Matrix{promote_op(*, T, S)}(undef, _kronsize(A, B))) + return kron!(C, A, B) +end + +function kron(A::LowerTriangular{T}, B::LowerTriangular{S}) where {T<:Number,S<:Number} + C = LowerTriangular(Matrix{promote_op(*, T, S)}(undef, _kronsize(A, B))) + return kron!(C, A, B) +end + +function kron!(C::UpperTriangular{<:Number}, A::UpperTriangular{<:Number}, B::UpperTriangular{<:Number}) + size(C) == _kronsize(A, B) || throw(DimensionMismatch("kron!")) + _triukron!(C.data, A.data, B.data) + return C +end + +function kron!(C::LowerTriangular{<:Number}, A::LowerTriangular{<:Number}, B::LowerTriangular{<:Number}) + size(C) == _kronsize(A, B) || throw(DimensionMismatch("kron!")) + _trilkron!(C.data, A.data, B.data) + return C +end + +function _triukron!(C, A, B) + n_A = size(A, 1) + n_B = size(B, 1) + @inbounds for j = 1:n_A + jnB = (j - 1) * n_B + for i = 1:(j-1) + Aij = A[i, j] + inB = (i - 1) * n_B + for l = 1:n_B + for k = 1:l + C[inB+k, jnB+l] = Aij * B[k, l] + end + for k = 1:(l-1) + C[inB+l, jnB+k] = zero(eltype(C)) + end + end + end + Ajj = A[j, j] + for l = 1:n_B + for k = 1:l + C[jnB+k, jnB+l] = Ajj * B[k, l] + end + end + end +end + +function _trilkron!(C, A, B) + n_A = size(A, 1) + n_B = size(B, 1) + @inbounds for j = 1:n_A + jnB = (j - 1) * n_B + Ajj = A[j, j] + for l = 1:n_B + for k = l:n_B + C[jnB+k, jnB+l] = Ajj * B[k, l] + end + end + for i = (j+1):n_A + Aij = A[i, j] + inB = (i - 1) * n_B + for l = 1:n_B + for k = l:n_B + C[inB+k, jnB+l] = Aij * B[k, l] + end + for k = (l+1):n_B + C[inB+l, jnB+k] = zero(eltype(C)) + end + end + end + end +end + ###################### # BlasFloat routines # ###################### @@ -688,18 +851,14 @@ isunit_char(::LowerTriangular) = 'N' isunit_char(::UnitLowerTriangular) = 'U' lmul!(A::Tridiagonal, B::AbstractTriangular) = A*full!(B) -mul!(C::AbstractVecOrMat, A::AbstractTriangular, B::AbstractVector) = _trimul!(C, A, B) -mul!(C::AbstractMatrix, A::AbstractTriangular, B::AbstractMatrix) = _trimul!(C, A, B) -mul!(C::AbstractMatrix, A::AbstractMatrix, B::AbstractTriangular) = _trimul!(C, A, B) -mul!(C::AbstractMatrix, A::AbstractTriangular, B::AbstractTriangular) = _trimul!(C, A, B) # generic fallback for AbstractTriangular matrices outside of the four subtypes provided here _trimul!(C::AbstractVecOrMat, A::AbstractTriangular, B::AbstractVector) = lmul!(A, copyto!(C, B)) _trimul!(C::AbstractMatrix, A::AbstractTriangular, B::AbstractMatrix) = - lmul!(A, inplace_adj_or_trans(B)(C, _unwrap_at(B))) + lmul!(A, copyto!(C, B)) _trimul!(C::AbstractMatrix, A::AbstractMatrix, B::AbstractTriangular) = - rmul!(inplace_adj_or_trans(A)(C, _unwrap_at(A)), B) + rmul!(copyto!(C, A), B) _trimul!(C::AbstractMatrix, A::AbstractTriangular, B::AbstractTriangular) = lmul!(A, copyto!(C, B)) # redirect for UpperOrLowerTriangular @@ -722,9 +881,9 @@ rmul!(A::AbstractMatrix, B::AbstractTriangular) = @inline _trimul!(A, A, B) for TC in (:AbstractVector, :AbstractMatrix) - @eval @inline function mul!(C::$TC, A::AbstractTriangular, B::AbstractVector, alpha::Number, beta::Number) + @eval @inline function _mul!(C::$TC, A::AbstractTriangular, B::AbstractVector, alpha::Number, beta::Number) if isone(alpha) && iszero(beta) - return mul!(C, A, B) + return _trimul!(C, A, B) else return generic_matvecmul!(C, 'N', A, B, MulAddMul(alpha, beta)) end @@ -734,9 +893,9 @@ for (TA, TB) in ((:AbstractTriangular, :AbstractMatrix), (:AbstractMatrix, :AbstractTriangular), (:AbstractTriangular, :AbstractTriangular) ) - @eval @inline function mul!(C::AbstractMatrix, A::$TA, B::$TB, alpha::Number, beta::Number) + @eval @inline function _mul!(C::AbstractMatrix, A::$TA, B::$TB, alpha::Number, beta::Number) if isone(alpha) && iszero(beta) - return mul!(C, A, B) + return _trimul!(C, A, B) else return generic_matmatmul!(C, 'N', 'N', A, B, MulAddMul(alpha, beta)) end @@ -746,9 +905,9 @@ end ldiv!(C::AbstractVecOrMat, A::AbstractTriangular, B::AbstractVecOrMat) = _ldiv!(C, A, B) # generic fallback for AbstractTriangular, directs to 2-arg [l/r]div! _ldiv!(C::AbstractVecOrMat, A::AbstractTriangular, B::AbstractVecOrMat) = - ldiv!(A, inplace_adj_or_trans(B)(C, _unwrap_at(B))) + ldiv!(A, copyto!(C, B)) _rdiv!(C::AbstractMatrix, A::AbstractMatrix, B::AbstractTriangular) = - rdiv!(inplace_adj_or_trans(A)(C, _unwrap_at(A)), B) + rdiv!(copyto!(C, A), B) # redirect for UpperOrLowerTriangular to generic_*div! _ldiv!(C::AbstractVecOrMat, A::UpperOrLowerTriangular, B::AbstractVecOrMat) = generic_trimatdiv!(C, uplo_char(A), isunit_char(A), wrapperop(parent(A)), _unwrap_at(parent(A)), B) @@ -867,47 +1026,52 @@ end for (t, unitt) in ((UpperTriangular, UnitUpperTriangular), (LowerTriangular, UnitLowerTriangular)) + tstrided = t{<:Any, <:StridedMaybeAdjOrTransMat} @eval begin (*)(A::$t, x::Number) = $t(A.data*x) + (*)(A::$tstrided, x::Number) = A .* x function (*)(A::$unitt, x::Number) - B = A.data*x + B = $t(A.data)*x for i = 1:size(A, 1) - B[i,i] = x + B.data[i,i] = x end - $t(B) + return B end (*)(x::Number, A::$t) = $t(x*A.data) + (*)(x::Number, A::$tstrided) = x .* A function (*)(x::Number, A::$unitt) - B = x*A.data + B = x*$t(A.data) for i = 1:size(A, 1) - B[i,i] = x + B.data[i,i] = x end - $t(B) + return B end (/)(A::$t, x::Number) = $t(A.data/x) + (/)(A::$tstrided, x::Number) = A ./ x function (/)(A::$unitt, x::Number) - B = A.data/x + B = $t(A.data)/x invx = inv(x) for i = 1:size(A, 1) - B[i,i] = invx + B.data[i,i] = invx end - $t(B) + return B end (\)(x::Number, A::$t) = $t(x\A.data) + (\)(x::Number, A::$tstrided) = x .\ A function (\)(x::Number, A::$unitt) - B = x\A.data + B = x\$t(A.data) invx = inv(x) for i = 1:size(A, 1) - B[i,i] = invx + B.data[i,i] = invx end - $t(B) + return B end end end @@ -918,11 +1082,11 @@ function generic_trimatmul!(C::AbstractVecOrMat, uploc, isunitc, tfun::Function, m, n = size(B, 1), size(B, 2) N = size(A, 1) if m != N - throw(DimensionMismatch("right hand side B needs first dimension of size $(size(A,1)), has size $m")) + throw(DimensionMismatch(lazy"right hand side B needs first dimension of size $(size(A,1)), has size $m")) end mc, nc = size(C, 1), size(C, 2) if mc != N || nc != n - throw(DimensionMismatch("output has dimensions ($mc,$nc), should have ($N,$n)")) + throw(DimensionMismatch(lazy"output has dimensions ($mc,$nc), should have ($N,$n)")) end oA = oneunit(eltype(A)) unit = isunitc == 'U' @@ -980,11 +1144,11 @@ function generic_trimatmul!(C::AbstractVecOrMat, uploc, isunitc, ::Function, xA: m, n = size(B, 1), size(B, 2) N = size(A, 1) if m != N - throw(DimensionMismatch("right hand side B needs first dimension of size $(size(A,1)), has size $m")) + throw(DimensionMismatch(lazy"right hand side B needs first dimension of size $(size(A,1)), has size $m")) end mc, nc = size(C, 1), size(C, 2) if mc != N || nc != n - throw(DimensionMismatch("output has dimensions ($mc,$nc), should have ($N,$n)")) + throw(DimensionMismatch(lazy"output has dimensions ($mc,$nc), should have ($N,$n)")) end oA = oneunit(eltype(A)) unit = isunitc == 'U' @@ -1017,11 +1181,11 @@ function generic_mattrimul!(C::AbstractMatrix, uploc, isunitc, tfun::Function, A m, n = size(A, 1), size(A, 2) N = size(B, 1) if n != N - throw(DimensionMismatch("right hand side B needs first dimension of size $n, has size $N")) + throw(DimensionMismatch(lazy"right hand side B needs first dimension of size $n, has size $N")) end mc, nc = size(C, 1), size(C, 2) if mc != m || nc != N - throw(DimensionMismatch("output has dimensions ($mc,$nc), should have ($m,$N)")) + throw(DimensionMismatch(lazy"output has dimensions ($mc,$nc), should have ($m,$N)")) end oB = oneunit(eltype(B)) unit = isunitc == 'U' @@ -1079,11 +1243,11 @@ function generic_mattrimul!(C::AbstractMatrix, uploc, isunitc, ::Function, A::Ab m, n = size(A, 1), size(A, 2) N = size(B, 1) if n != N - throw(DimensionMismatch("right hand side B needs first dimension of size $n, has size $N")) + throw(DimensionMismatch(lazy"right hand side B needs first dimension of size $n, has size $N")) end mc, nc = size(C, 1), size(C, 2) if mc != m || nc != N - throw(DimensionMismatch("output has dimensions ($mc,$nc), should have ($m,$N)")) + throw(DimensionMismatch(lazy"output has dimensions ($mc,$nc), should have ($m,$N)")) end oB = oneunit(eltype(B)) unit = isunitc == 'U' @@ -1127,10 +1291,10 @@ function generic_trimatdiv!(C::AbstractVecOrMat, uploc, isunitc, tfun::Function, mA, nA = size(A) m, n = size(B, 1), size(B,2) if nA != m - throw(DimensionMismatch("second dimension of left hand side A, $nA, and first dimension of right hand side B, $m, must be equal")) + throw(DimensionMismatch(lazy"second dimension of left hand side A, $nA, and first dimension of right hand side B, $m, must be equal")) end if size(C) != size(B) - throw(DimensionMismatch("size of output, $(size(C)), does not match size of right hand side, $(size(B))")) + throw(DimensionMismatch(lazy"size of output, $(size(C)), does not match size of right hand side, $(size(B))")) end oA = oneunit(eltype(A)) @inbounds if uploc == 'U' @@ -1263,10 +1427,10 @@ function generic_trimatdiv!(C::AbstractVecOrMat, uploc, isunitc, ::Function, xA: mA, nA = size(A) m, n = size(B, 1), size(B,2) if nA != m - throw(DimensionMismatch("second dimension of left hand side A, $nA, and first dimension of right hand side B, $m, must be equal")) + throw(DimensionMismatch(lazy"second dimension of left hand side A, $nA, and first dimension of right hand side B, $m, must be equal")) end if size(C) != size(B) - throw(DimensionMismatch("size of output, $(size(C)), does not match size of right hand side, $(size(B))")) + throw(DimensionMismatch(lazy"size of output, $(size(C)), does not match size of right hand side, $(size(B))")) end oA = oneunit(eltype(A)) @inbounds if uploc == 'U' @@ -1345,10 +1509,10 @@ function generic_mattridiv!(C::AbstractMatrix, uploc, isunitc, tfun::Function, A require_one_based_indexing(C, A, B) m, n = size(A) if size(B, 1) != n - throw(DimensionMismatch("right hand side B needs first dimension of size $n, has size $(size(B,1))")) + throw(DimensionMismatch(lazy"right hand side B needs first dimension of size $n, has size $(size(B,1))")) end if size(C) != size(A) - throw(DimensionMismatch("size of output, $(size(C)), does not match size of left hand side, $(size(A))")) + throw(DimensionMismatch(lazy"size of output, $(size(C)), does not match size of left hand side, $(size(A))")) end oB = oneunit(eltype(B)) unit = isunitc == 'U' @@ -1408,10 +1572,10 @@ function generic_mattridiv!(C::AbstractMatrix, uploc, isunitc, ::Function, A::Ab require_one_based_indexing(C, A, B) m, n = size(A) if size(B, 1) != n - throw(DimensionMismatch("right hand side B needs first dimension of size $n, has size $(size(B,1))")) + throw(DimensionMismatch(lazy"right hand side B needs first dimension of size $n, has size $(size(B,1))")) end if size(C) != size(A) - throw(DimensionMismatch("size of output, $(size(C)), does not match size of left hand side, $(size(A))")) + throw(DimensionMismatch(lazy"size of output, $(size(C)), does not match size of left hand side, $(size(A))")) end oB = oneunit(eltype(B)) unit = isunitc == 'U' @@ -1455,22 +1619,11 @@ rmul!(A::LowerTriangular, B::UnitLowerTriangular) = LowerTriangular(rmul!(tril!( ## necessary in the general triangular solve problem. _inner_type_promotion(op, ::Type{TA}, ::Type{TB}) where {TA<:Integer,TB<:Integer} = - _init_eltype(*, TA, TB) + promote_op(matprod, TA, TB) _inner_type_promotion(op, ::Type{TA}, ::Type{TB}) where {TA,TB} = - _init_eltype(op, TA, TB) + promote_op(op, TA, TB) ## The general promotion methods -function *(A::AbstractTriangular, B::AbstractTriangular) - TAB = _init_eltype(*, eltype(A), eltype(B)) - mul!(similar(B, TAB, size(B)), A, B) -end - for mat in (:AbstractVector, :AbstractMatrix) - ### Multiplication with triangle to the left and hence rhs cannot be transposed. - @eval function *(A::AbstractTriangular, B::$mat) - require_one_based_indexing(B) - TAB = _init_eltype(*, eltype(A), eltype(B)) - mul!(similar(B, TAB, size(B)), A, B) - end ### Left division with triangle to the left hence rhs cannot be transposed. No quotients. @eval function \(A::Union{UnitUpperTriangular,UnitLowerTriangular}, B::$mat) require_one_based_indexing(B) @@ -1480,7 +1633,7 @@ for mat in (:AbstractVector, :AbstractMatrix) ### Left division with triangle to the left hence rhs cannot be transposed. Quotients. @eval function \(A::Union{UpperTriangular,LowerTriangular}, B::$mat) require_one_based_indexing(B) - TAB = _init_eltype(\, eltype(A), eltype(B)) + TAB = promote_op(\, eltype(A), eltype(B)) ldiv!(similar(B, TAB, size(B)), A, B) end ### Right division with triangle to the right hence lhs cannot be transposed. No quotients. @@ -1492,20 +1645,10 @@ for mat in (:AbstractVector, :AbstractMatrix) ### Right division with triangle to the right hence lhs cannot be transposed. Quotients. @eval function /(A::$mat, B::Union{UpperTriangular,LowerTriangular}) require_one_based_indexing(A) - TAB = _init_eltype(/, eltype(A), eltype(B)) + TAB = promote_op(/, eltype(A), eltype(B)) _rdiv!(similar(A, TAB, size(A)), A, B) end end -### Multiplication with triangle to the right and hence lhs cannot be transposed. -# Only for AbstractMatrix, hence outside the above loop. -function *(A::AbstractMatrix, B::AbstractTriangular) - require_one_based_indexing(A) - TAB = _init_eltype(*, eltype(A), eltype(B)) - mul!(similar(A, TAB, size(A)), A, B) -end -# ambiguity resolution with definitions in matmul.jl -*(v::AdjointAbsVec, A::AbstractTriangular) = adjoint(adjoint(A) * v.parent) -*(v::TransposeAbsVec, A::AbstractTriangular) = transpose(transpose(A) * v.parent) ## Some Triangular-Triangular cases. We might want to write tailored methods ## for these cases, but I'm not sure it is worth it. @@ -1552,9 +1695,9 @@ end # Higham and Lin, "An improved Schur-Padé algorithm for fractional powers of # a matrix and their Fréchet derivatives", SIAM. J. Matrix Anal. & Appl., # 34(3), (2013) 1341–1360. -function powm!(A0::UpperTriangular{<:BlasFloat}, p::Real) +function powm!(A0::UpperTriangular, p::Real) if abs(p) >= 1 - throw(ArgumentError("p must be a real number in (-1,1), got $p")) + throw(ArgumentError(lazy"p must be a real number in (-1,1), got $p")) end normA0 = opnorm(A0, 1) @@ -1584,7 +1727,7 @@ function powm!(A0::UpperTriangular{<:BlasFloat}, p::Real) end copyto!(Stmp, S) mul!(S, A, c) - ldiv!(Stmp, S.data) + ldiv!(Stmp, S) c = (p - j) / (j4 - 2) for i = 1:n @@ -1592,14 +1735,14 @@ function powm!(A0::UpperTriangular{<:BlasFloat}, p::Real) end copyto!(Stmp, S) mul!(S, A, c) - ldiv!(Stmp, S.data) + ldiv!(Stmp, S) end for i = 1:n S[i, i] = S[i, i] + 1 end copyto!(Stmp, S) mul!(S, A, -p) - ldiv!(Stmp, S.data) + ldiv!(Stmp, S) for i = 1:n @inbounds S[i, i] = S[i, i] + 1 end @@ -2259,7 +2402,8 @@ function _sqrt_quasitriu_diag_block!(R, A) R[i, i] = sqrt(ta(A[i, i])) i += 1 else - # this branch is never reached when A is complex triangular + # This branch is never reached when A is complex triangular + @assert eltype(A) <: Real @views _sqrt_real_2x2!(R[i:(i + 1), i:(i + 1)], A[i:(i + 1), i:(i + 1)]) i += 2 end @@ -2483,7 +2627,7 @@ function eigvecs(A::AbstractTriangular{T}) where T if TT <: BlasFloat return eigvecs(convert(AbstractMatrix{TT}, A)) else - throw(ArgumentError("eigvecs type $(typeof(A)) not supported. Please submit a pull request.")) + throw(ArgumentError(lazy"eigvecs type $(typeof(A)) not supported. Please submit a pull request.")) end end det(A::UnitUpperTriangular{T}) where {T} = one(T) @@ -2530,3 +2674,94 @@ for (tritype, comptritype) in ((:LowerTriangular, :UpperTriangular), @eval /(u::TransposeAbsVec, A::$tritype{<:Any,<:Adjoint}) = transpose($comptritype(conj(parent(parent(A)))) \ u.parent) @eval /(u::TransposeAbsVec, A::$tritype{<:Any,<:Transpose}) = transpose(transpose(A) \ u.parent) end + +# Cube root of a 2x2 real-valued matrix with complex conjugate eigenvalues and equal diagonal values. +# Reference [1]: Smith, M. I. (2003). A Schur Algorithm for Computing Matrix pth Roots. +# SIAM Journal on Matrix Analysis and Applications (Vol. 24, Issue 4, pp. 971–989). +# https://doi.org/10.1137/s0895479801392697 +function _cbrt_2x2!(A::AbstractMatrix{T}) where {T<:Real} + @assert checksquare(A) == 2 + @inbounds begin + (A[1,1] == A[2,2]) || throw(ArgumentError("_cbrt_2x2!: Matrix A must have equal diagonal values.")) + (A[1,2]*A[2,1] < 0) || throw(ArgumentError("_cbrt_2x2!: Matrix A must have complex conjugate eigenvalues.")) + μ = sqrt(-A[1,2]*A[2,1]) + r = cbrt(hypot(A[1,1], μ)) + θ = atan(μ, A[1,1]) + s, c = sincos(θ/3) + α, β′ = r*c, r*s/µ + A[1,1] = α + A[2,2] = α + A[1,2] = β′*A[1,2] + A[2,1] = β′*A[2,1] + end + return A +end + +# Cube root of a quasi upper triangular matrix (output of Schur decomposition) +# Reference [1]: Smith, M. I. (2003). A Schur Algorithm for Computing Matrix pth Roots. +# SIAM Journal on Matrix Analysis and Applications (Vol. 24, Issue 4, pp. 971–989). +# https://doi.org/10.1137/s0895479801392697 +@views function _cbrt_quasi_triu!(A::AbstractMatrix{T}) where {T<:Real} + m, n = size(A) + (m == n) || throw(ArgumentError("_cbrt_quasi_triu!: Matrix A must be square.")) + # Cube roots of 1x1 and 2x2 diagonal blocks + i = 1 + sizes = ones(Int,n) + S = zeros(T,2,n) + while i < n + if !iszero(A[i+1,i]) + _cbrt_2x2!(A[i:i+1,i:i+1]) + mul!(S[1:2,i:i+1], A[i:i+1,i:i+1], A[i:i+1,i:i+1]) + sizes[i] = 2 + sizes[i+1] = 0 + i += 2 + else + A[i,i] = cbrt(A[i,i]) + S[1,i] = A[i,i]*A[i,i] + i += 1 + end + end + if i == n + A[n,n] = cbrt(A[n,n]) + S[1,n] = A[n,n]*A[n,n] + end + # Algorithm 4.3 in Reference [1] + Δ = I(4) + M_L₀ = zeros(T,4,4) + M_L₁ = zeros(T,4,4) + M_Bᵢⱼ⁽⁰⁾ = zeros(T,2,2) + M_Bᵢⱼ⁽¹⁾ = zeros(T,2,2) + for k = 1:n-1 + for i = 1:n-k + if sizes[i] == 0 || sizes[i+k] == 0 continue end + k₁, k₂ = i+1+(sizes[i+1]==0), i+k-1 + i₁, i₂, j₁, j₂, s₁, s₂ = i, i+sizes[i]-1, i+k, i+k+sizes[i+k]-1, sizes[i], sizes[i+k] + L₀ = M_L₀[1:s₁*s₂,1:s₁*s₂] + L₁ = M_L₁[1:s₁*s₂,1:s₁*s₂] + Bᵢⱼ⁽⁰⁾ = M_Bᵢⱼ⁽⁰⁾[1:s₁, 1:s₂] + Bᵢⱼ⁽¹⁾ = M_Bᵢⱼ⁽¹⁾[1:s₁, 1:s₂] + # Compute Bᵢⱼ⁽⁰⁾ and Bᵢⱼ⁽¹⁾ + mul!(Bᵢⱼ⁽⁰⁾, A[i₁:i₂,k₁:k₂], A[k₁:k₂,j₁:j₂]) + # Retrieve Rᵢ,ᵢ₊ₖ as A[i+k,i]' + mul!(Bᵢⱼ⁽¹⁾, A[i₁:i₂,k₁:k₂], A[j₁:j₂,k₁:k₂]') + # Solve Uᵢ,ᵢ₊ₖ using Reference [1, (4.10)] + kron!(L₀, Δ[1:s₂,1:s₂], S[1:s₁,i₁:i₂]) + L₀ .+= kron!(L₁, A[j₁:j₂,j₁:j₂]', A[i₁:i₂,i₁:i₂]) + L₀ .+= kron!(L₁, S[1:s₂,j₁:j₂]', Δ[1:s₁,1:s₁]) + mul!(A[i₁:i₂,j₁:j₂], A[i₁:i₂,i₁:i₂], Bᵢⱼ⁽⁰⁾, -1.0, 1.0) + A[i₁:i₂,j₁:j₂] .-= Bᵢⱼ⁽¹⁾ + ldiv!(lu!(L₀), A[i₁:i₂,j₁:j₂][:]) + # Compute and store Rᵢ,ᵢ₊ₖ' in A[i+k,i] + mul!(Bᵢⱼ⁽⁰⁾, A[i₁:i₂,i₁:i₂], A[i₁:i₂,j₁:j₂], 1.0, 1.0) + mul!(Bᵢⱼ⁽⁰⁾, A[i₁:i₂,j₁:j₂], A[j₁:j₂,j₁:j₂], 1.0, 1.0) + A[j₁:j₂,i₁:i₂] .= Bᵢⱼ⁽⁰⁾' + end + end + # Make quasi triangular + for j=1:n for i=j+1+(sizes[j]==2):n A[i,j] = 0 end end + return A +end + +# Cube roots of real-valued triangular matrices +cbrt(A::UpperTriangular{T}) where {T<:Real} = UpperTriangular(_cbrt_quasi_triu!(Matrix{T}(A))) +cbrt(A::LowerTriangular{T}) where {T<:Real} = LowerTriangular(_cbrt_quasi_triu!(Matrix{T}(A'))') diff --git a/stdlib/LinearAlgebra/src/tridiag.jl b/stdlib/LinearAlgebra/src/tridiag.jl index d71519cfa5365..9403b44538b11 100644 --- a/stdlib/LinearAlgebra/src/tridiag.jl +++ b/stdlib/LinearAlgebra/src/tridiag.jl @@ -9,7 +9,7 @@ struct SymTridiagonal{T, V<:AbstractVector{T}} <: AbstractMatrix{T} function SymTridiagonal{T, V}(dv, ev) where {T, V<:AbstractVector{T}} require_one_based_indexing(dv, ev) if !(length(dv) - 1 <= length(ev) <= length(dv)) - throw(DimensionMismatch("subdiagonal has wrong length. Has length $(length(ev)), but should be either $(length(dv) - 1) or $(length(dv)).")) + throw(DimensionMismatch(lazy"subdiagonal has wrong length. Has length $(length(ev)), but should be either $(length(dv) - 1) or $(length(dv)).")) end new{T, V}(dv, ev) end @@ -70,9 +70,14 @@ julia> A[2,1] SymTridiagonal(dv::V, ev::V) where {T,V<:AbstractVector{T}} = SymTridiagonal{T}(dv, ev) SymTridiagonal{T}(dv::V, ev::V) where {T,V<:AbstractVector{T}} = SymTridiagonal{T,V}(dv, ev) function SymTridiagonal{T}(dv::AbstractVector, ev::AbstractVector) where {T} - SymTridiagonal(convert(AbstractVector{T}, dv)::AbstractVector{T}, - convert(AbstractVector{T}, ev)::AbstractVector{T}) + d = convert(AbstractVector{T}, dv)::AbstractVector{T} + e = convert(AbstractVector{T}, ev)::AbstractVector{T} + typeof(d) == typeof(e) ? + SymTridiagonal{T}(d, e) : + throw(ArgumentError("diagonal vectors needed to be convertible to same type")) end +SymTridiagonal(d::AbstractVector{T}, e::AbstractVector{S}) where {T,S} = + SymTridiagonal{promote_type(T, S)}(d, e) """ SymTridiagonal(A::AbstractMatrix) @@ -130,19 +135,24 @@ function Matrix{T}(M::SymTridiagonal) where T n = size(M, 1) Mf = Matrix{T}(undef, n, n) n == 0 && return Mf - n > 2 && fill!(Mf, zero(T)) - @inbounds for i = 1:n-1 - Mf[i,i] = symmetric(M.dv[i], :U) - Mf[i+1,i] = transpose(M.ev[i]) - Mf[i,i+1] = M.ev[i] + if haszero(T) # optimized path for types with zero(T) defined + n > 2 && fill!(Mf, zero(T)) + @inbounds for i = 1:n-1 + Mf[i,i] = symmetric(M.dv[i], :U) + Mf[i+1,i] = transpose(M.ev[i]) + Mf[i,i+1] = M.ev[i] + end + Mf[n,n] = symmetric(M.dv[n], :U) + else + copyto!(Mf, M) end - Mf[n,n] = symmetric(M.dv[n], :U) return Mf end Matrix(M::SymTridiagonal{T}) where {T} = Matrix{promote_type(T, typeof(zero(T)))}(M) Array(M::SymTridiagonal) = Matrix(M) size(A::SymTridiagonal) = (n = length(A.dv); (n, n)) +axes(M::SymTridiagonal) = (ax = axes(M.dv, 1); (ax, ax)) similar(S::SymTridiagonal, ::Type{T}) where {T} = SymTridiagonal(similar(S.dv, T), similar(S.ev, T)) similar(S::SymTridiagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = similar(S.dv, T, dims) @@ -157,7 +167,6 @@ end transpose(S::SymTridiagonal) = S adjoint(S::SymTridiagonal{<:Real}) = S -adjoint(S::SymTridiagonal) = Adjoint(S) permutedims(S::SymTridiagonal) = S function permutedims(S::SymTridiagonal, perm) Base.checkdims_perm(S, S, perm) @@ -181,8 +190,8 @@ function diag(M::SymTridiagonal{T}, n::Integer=0) where T<:Number elseif absn <= size(M,1) return fill!(similar(M.dv, size(M,1)-absn), zero(T)) else - throw(ArgumentError(string("requested diagonal, $n, must be at least $(-size(M, 1)) ", - "and at most $(size(M, 2)) for an $(size(M, 1))-by-$(size(M, 2)) matrix"))) + throw(ArgumentError(string(lazy"requested diagonal, $n, must be at least $(-size(M, 1)) ", + lazy"and at most $(size(M, 2)) for an $(size(M, 1))-by-$(size(M, 2)) matrix"))) end end function diag(M::SymTridiagonal, n::Integer=0) @@ -197,8 +206,8 @@ function diag(M::SymTridiagonal, n::Integer=0) elseif n <= size(M,1) throw(ArgumentError("requested diagonal contains undefined zeros of an array type")) else - throw(ArgumentError(string("requested diagonal, $n, must be at least $(-size(M, 1)) ", - "and at most $(size(M, 2)) for an $(size(M, 1))-by-$(size(M, 2)) matrix"))) + throw(ArgumentError(string(lazy"requested diagonal, $n, must be at least $(-size(M, 1)) ", + lazy"and at most $(size(M, 2)) for an $(size(M, 1))-by-$(size(M, 2)) matrix"))) end end @@ -340,8 +349,8 @@ isdiag(M::SymTridiagonal) = iszero(_evview(M)) function tril!(M::SymTridiagonal{T}, k::Integer=0) where T n = length(M.dv) if !(-n - 1 <= k <= n - 1) - throw(ArgumentError(string("the requested diagonal, $k, must be at least ", - "$(-n - 1) and at most $(n - 1) in an $n-by-$n matrix"))) + throw(ArgumentError(string(lazy"the requested diagonal, $k, must be at least ", + lazy"$(-n - 1) and at most $(n - 1) in an $n-by-$n matrix"))) elseif k < -1 fill!(M.ev, zero(T)) fill!(M.dv, zero(T)) @@ -359,8 +368,8 @@ end function triu!(M::SymTridiagonal{T}, k::Integer=0) where T n = length(M.dv) if !(-n + 1 <= k <= n + 1) - throw(ArgumentError(string("the requested diagonal, $k, must be at least ", - "$(-n + 1) and at most $(n + 1) in an $n-by-$n matrix"))) + throw(ArgumentError(string(lazy"the requested diagonal, $k, must be at least ", + lazy"$(-n + 1) and at most $(n + 1) in an $n-by-$n matrix"))) elseif k > 1 fill!(M.ev, zero(T)) fill!(M.dv, zero(T)) @@ -453,7 +462,7 @@ end if i == j @inbounds A.dv[i] = x else - throw(ArgumentError("cannot set off-diagonal entry ($i, $j)")) + throw(ArgumentError(lazy"cannot set off-diagonal entry ($i, $j)")) end return x end @@ -470,15 +479,15 @@ struct Tridiagonal{T,V<:AbstractVector{T}} <: AbstractMatrix{T} if (length(dl) != n-1 || length(du) != n-1) && !(length(d) == 0 && length(dl) == 0 && length(du) == 0) throw(ArgumentError(string("cannot construct Tridiagonal from incompatible ", "lengths of subdiagonal, diagonal and superdiagonal: ", - "($(length(dl)), $(length(d)), $(length(du)))"))) + lazy"($(length(dl)), $(length(d)), $(length(du)))"))) end - new{T,V}(dl, d, du) + new{T,V}(dl, d, Base.unalias(dl, du)) end # constructor used in lu! function Tridiagonal{T,V}(dl, d, du, du2) where {T,V<:AbstractVector{T}} require_one_based_indexing(dl, d, du, du2) # length checks? - new{T,V}(dl, d, du, du2) + new{T,V}(dl, d, Base.unalias(dl, du), du2) end end @@ -491,6 +500,10 @@ solvers, but may be converted into a regular matrix with [`convert(Array, _)`](@ref) (or `Array(_)` for short). The lengths of `dl` and `du` must be one less than the length of `d`. +!!! note + The subdiagonal `dl` and the superdiagonal `du` must not be aliased to each other. + If aliasing is detected, the constructor will use a copy of `du` as its argument. + # Examples ```jldoctest julia> dl = [1, 2, 3]; @@ -509,11 +522,21 @@ julia> Tridiagonal(dl, d, du) """ Tridiagonal(dl::V, d::V, du::V) where {T,V<:AbstractVector{T}} = Tridiagonal{T,V}(dl, d, du) Tridiagonal(dl::V, d::V, du::V, du2::V) where {T,V<:AbstractVector{T}} = Tridiagonal{T,V}(dl, d, du, du2) +Tridiagonal(dl::AbstractVector{T}, d::AbstractVector{S}, du::AbstractVector{U}) where {T,S,U} = + Tridiagonal{promote_type(T, S, U)}(dl, d, du) +Tridiagonal(dl::AbstractVector{T}, d::AbstractVector{S}, du::AbstractVector{U}, du2::AbstractVector{V}) where {T,S,U,V} = + Tridiagonal{promote_type(T, S, U, V)}(dl, d, du, du2) function Tridiagonal{T}(dl::AbstractVector, d::AbstractVector, du::AbstractVector) where {T} - Tridiagonal(map(x->convert(AbstractVector{T}, x), (dl, d, du))...) + l, d, u = map(x->convert(AbstractVector{T}, x), (dl, d, du)) + typeof(l) == typeof(d) == typeof(u) ? + Tridiagonal(l, d, u) : + throw(ArgumentError("diagonal vectors needed to be convertible to same type")) end function Tridiagonal{T}(dl::AbstractVector, d::AbstractVector, du::AbstractVector, du2::AbstractVector) where {T} - Tridiagonal(map(x->convert(AbstractVector{T}, x), (dl, d, du, du2))...) + l, d, u, u2 = map(x->convert(AbstractVector{T}, x), (dl, d, du, du2)) + typeof(l) == typeof(d) == typeof(u) == typeof(u2) ? + Tridiagonal(l, d, u, u2) : + throw(ArgumentError("diagonal vectors needed to be convertible to same type")) end """ @@ -562,18 +585,18 @@ function Tridiagonal{T,V}(A::Tridiagonal) where {T,V<:AbstractVector{T}} end size(M::Tridiagonal) = (n = length(M.d); (n, n)) +axes(M::Tridiagonal) = (ax = axes(M.d,1); (ax, ax)) function Matrix{T}(M::Tridiagonal) where {T} A = Matrix{T}(undef, size(M)) - n = length(M.d) - n == 0 && return A - n > 2 && fill!(A, zero(T)) - for i in 1:n-1 - A[i,i] = M.d[i] - A[i+1,i] = M.dl[i] - A[i,i+1] = M.du[i] - end - A[n,n] = M.d[n] + if haszero(T) # optimized path for types with zero(T) defined + size(A,1) > 2 && fill!(A, zero(T)) + copyto!(view(A, diagind(A)), M.d) + copyto!(view(A, diagind(A,1)), M.du) + copyto!(view(A, diagind(A,-1)), M.dl) + else + copyto!(A, M) + end A end Matrix(M::Tridiagonal{T}) where {T} = Matrix{promote_type(T, typeof(zero(T)))}(M) @@ -592,8 +615,6 @@ for func in (:conj, :copy, :real, :imag) end end -adjoint(S::Tridiagonal) = Adjoint(S) -transpose(S::Tridiagonal) = Transpose(S) adjoint(S::Tridiagonal{<:Real}) = Tridiagonal(S.du, S.d, S.dl) transpose(S::Tridiagonal{<:Number}) = Tridiagonal(S.du, S.d, S.dl) permutedims(T::Tridiagonal) = Tridiagonal(T.du, T.d, T.dl) @@ -621,8 +642,8 @@ function diag(M::Tridiagonal{T}, n::Integer=0) where T elseif abs(n) <= size(M,1) return fill!(similar(M.d, size(M,1)-abs(n)), zero(T)) else - throw(ArgumentError(string("requested diagonal, $n, must be at least $(-size(M, 1)) ", - "and at most $(size(M, 2)) for an $(size(M, 1))-by-$(size(M, 2)) matrix"))) + throw(ArgumentError(string(lazy"requested diagonal, $n, must be at least $(-size(M, 1)) ", + lazy"and at most $(size(M, 2)) for an $(size(M, 1))-by-$(size(M, 2)) matrix"))) end end @@ -674,8 +695,8 @@ end elseif j - i == 1 @inbounds A.du[i] = x elseif !iszero(x) - throw(ArgumentError(string("cannot set entry ($i, $j) off ", - "the tridiagonal band to a nonzero value ($x)"))) + throw(ArgumentError(string(lazy"cannot set entry ($i, $j) off ", + lazy"the tridiagonal band to a nonzero value ($x)"))) end return x end @@ -717,8 +738,8 @@ isdiag(M::Tridiagonal) = iszero(M.dl) && iszero(M.du) function tril!(M::Tridiagonal{T}, k::Integer=0) where T n = length(M.d) if !(-n - 1 <= k <= n - 1) - throw(ArgumentError(string("the requested diagonal, $k, must be at least ", - "$(-n - 1) and at most $(n - 1) in an $n-by-$n matrix"))) + throw(ArgumentError(string(lazy"the requested diagonal, $k, must be at least ", + lazy"$(-n - 1) and at most $(n - 1) in an $n-by-$n matrix"))) elseif k < -1 fill!(M.dl, zero(T)) fill!(M.d, zero(T)) @@ -735,8 +756,8 @@ end function triu!(M::Tridiagonal{T}, k::Integer=0) where T n = length(M.d) if !(-n + 1 <= k <= n + 1) - throw(ArgumentError(string("the requested diagonal, $k, must be at least ", - "$(-n + 1) and at most $(n + 1) in an $n-by-$n matrix"))) + throw(ArgumentError(string(lazy"the requested diagonal, $k, must be at least ", + lazy"$(-n + 1) and at most $(n + 1) in an $n-by-$n matrix"))) elseif k > 1 fill!(M.dl, zero(T)) fill!(M.d, zero(T)) @@ -904,7 +925,7 @@ function ldiv!(A::Tridiagonal, B::AbstractVecOrMat) LinearAlgebra.require_one_based_indexing(B) n = size(A, 1) if n != size(B,1) - throw(DimensionMismatch("matrix has dimensions ($n,$n) but right hand side has $(size(B,1)) rows")) + throw(DimensionMismatch(lazy"matrix has dimensions ($n,$n) but right hand side has $(size(B,1)) rows")) end nrhs = size(B, 2) @@ -912,9 +933,6 @@ function ldiv!(A::Tridiagonal, B::AbstractVecOrMat) dl = A.dl d = A.d du = A.du - if dl === du - throw(ArgumentError("off-diagonals of `A` must not alias")) - end @inbounds begin for i in 1:n-1 @@ -928,7 +946,7 @@ function ldiv!(A::Tridiagonal, B::AbstractVecOrMat) B[i+1,j] -= fact*B[i,j] end else - checknonsingular(i, RowMaximum()) + checknonsingular(i) end i < n-1 && (dl[i] = 0) else @@ -949,7 +967,7 @@ function ldiv!(A::Tridiagonal, B::AbstractVecOrMat) end end end - iszero(d[n]) && checknonsingular(n, RowMaximum()) + iszero(d[n]) && checknonsingular(n) # backward substitution for j in 1:nrhs B[n,j] /= d[n] diff --git a/stdlib/LinearAlgebra/src/uniformscaling.jl b/stdlib/LinearAlgebra/src/uniformscaling.jl index b014472a9cec2..b75886b8d99fb 100644 --- a/stdlib/LinearAlgebra/src/uniformscaling.jl +++ b/stdlib/LinearAlgebra/src/uniformscaling.jl @@ -1,6 +1,6 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -import Base: copy, adjoint, getindex, show, transpose, one, zero, inv, +import Base: copy, adjoint, getindex, show, transpose, one, zero, inv, float, hcat, vcat, hvcat, ^ """ @@ -124,6 +124,8 @@ conj(J::UniformScaling) = UniformScaling(conj(J.λ)) real(J::UniformScaling) = UniformScaling(real(J.λ)) imag(J::UniformScaling) = UniformScaling(imag(J.λ)) +float(J::UniformScaling) = UniformScaling(float(J.λ)) + transpose(J::UniformScaling) = J adjoint(J::UniformScaling) = UniformScaling(conj(J.λ)) @@ -159,7 +161,7 @@ isposdef(J::UniformScaling) = isposdef(J.λ) (-)(A::AbstractMatrix, J::UniformScaling) = A + (-J) # matrix functions -for f in ( :exp, :log, +for f in ( :exp, :log, :cis, :expm1, :log1p, :sqrt, :cbrt, :sin, :cos, :tan, @@ -172,6 +174,9 @@ for f in ( :exp, :log, :acsch, :asech, :acoth ) @eval Base.$f(J::UniformScaling) = UniformScaling($f(J.λ)) end +for f in (:sincos, :sincosd) + @eval Base.$f(J::UniformScaling) = map(UniformScaling, $f(J.λ)) +end # Unit{Lower/Upper}Triangular matrices become {Lower/Upper}Triangular under # addition with a UniformScaling @@ -270,6 +275,7 @@ end /(v::AbstractVector, J::UniformScaling) = reshape(v, length(v), 1) / J /(J::UniformScaling, x::Number) = UniformScaling(J.λ/x) +//(J::UniformScaling, x::Number) = UniformScaling(J.λ//x) \(J1::UniformScaling, J2::UniformScaling) = J1.λ == 0 ? throw(SingularException(1)) : UniformScaling(J1.λ\J2.λ) \(J::UniformScaling, A::AbstractVecOrMat) = J.λ == 0 ? throw(SingularException(1)) : J.λ\A @@ -293,7 +299,7 @@ function mul!(out::AbstractMatrix{T}, a::Number, B::UniformScaling, α::Number, end s = convert(T, a*B.λ*α) if !iszero(s) - @inbounds for i in diagind(out) + @inbounds for i in diagind(out, IndexStyle(out)) out[i] += s end end diff --git a/stdlib/LinearAlgebra/test/abstractq.jl b/stdlib/LinearAlgebra/test/abstractq.jl index 83a26c6050484..0eb88324e8c20 100644 --- a/stdlib/LinearAlgebra/test/abstractq.jl +++ b/stdlib/LinearAlgebra/test/abstractq.jl @@ -34,10 +34,17 @@ n = 5 T <: Complex && @test_throws ErrorException transpose(Q) @test convert(AbstractQ{complex(T)}, Q) isa MyQ{complex(T)} @test convert(AbstractQ{complex(T)}, Q') isa AdjointQ{<:complex(T),<:MyQ{complex(T)}} + @test *(Q) == Q @test Q*I ≈ Q.Q*I rtol=2eps(real(T)) @test Q'*I ≈ Q.Q'*I rtol=2eps(real(T)) @test I*Q ≈ Q.Q*I rtol=2eps(real(T)) @test I*Q' ≈ I*Q.Q' rtol=2eps(real(T)) + @test Q^3 ≈ Q*Q*Q + @test Q^2 ≈ Q*Q + @test Q^1 == Q + @test Q^(-1) == Q' + @test (Q')^(-1) == Q + @test (Q')^2 ≈ Q'*Q' @test abs(det(Q)) ≈ 1 @test logabsdet(Q)[1] ≈ 0 atol=2n*eps(real(T)) y = rand(T, n) diff --git a/stdlib/LinearAlgebra/test/adjtrans.jl b/stdlib/LinearAlgebra/test/adjtrans.jl index 555010913660a..2c533af37f912 100644 --- a/stdlib/LinearAlgebra/test/adjtrans.jl +++ b/stdlib/LinearAlgebra/test/adjtrans.jl @@ -6,6 +6,9 @@ using Test, LinearAlgebra const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") +isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "OffsetArrays.jl")) +using .Main.OffsetArrays + @testset "Adjoint and Transpose inner constructor basics" begin intvec, intmat = [1, 2], [1 2; 3 4] # Adjoint/Transpose eltype must match the type of the Adjoint/Transpose of the input eltype @@ -87,11 +90,15 @@ end @test size(Transpose(intvec)) == (1, length(intvec)) @test size(Transpose(intmat)) == reverse(size(intmat)) end - @testset "indices methods" begin + @testset "axes methods" begin @test axes(Adjoint(intvec)) == (Base.OneTo(1), Base.OneTo(length(intvec))) @test axes(Adjoint(intmat)) == reverse(axes(intmat)) @test axes(Transpose(intvec)) == (Base.OneTo(1), Base.OneTo(length(intvec))) @test axes(Transpose(intmat)) == reverse(axes(intmat)) + + A = OffsetArray([1,2], 2) + @test (@inferred axes(A')[2]) === axes(A,1) + @test (@inferred axes(A')[1]) === axes(A,2) end @testset "IndexStyle methods" begin @test IndexStyle(Adjoint(intvec)) == IndexLinear() @@ -681,4 +688,19 @@ end @test sprint(Base.print_matrix, Adjoint(o)) == sprint(Base.print_matrix, OneHotVecOrMat((1,2), (1,4))) end +@testset "copy_transpose!" begin + # scalar case + A = [randn() for _ in 1:2, _ in 1:3] + At = copy(transpose(A)) + B = zero.(At) + LinearAlgebra.copy_transpose!(B, axes(B, 1), axes(B, 2), A, axes(A, 1), axes(A, 2)) + @test B == At + # matrix of matrices + A = [randn(2,3) for _ in 1:2, _ in 1:3] + At = copy(transpose(A)) + B = zero.(At) + LinearAlgebra.copy_transpose!(B, axes(B, 1), axes(B, 2), A, axes(A, 1), axes(A, 2)) + @test B == At +end + end # module TestAdjointTranspose diff --git a/stdlib/LinearAlgebra/test/bidiag.jl b/stdlib/LinearAlgebra/test/bidiag.jl index a3e5a2f437e93..f9c4f09c474fe 100644 --- a/stdlib/LinearAlgebra/test/bidiag.jl +++ b/stdlib/LinearAlgebra/test/bidiag.jl @@ -19,6 +19,12 @@ using .Main.InfiniteArrays isdefined(Main, :FillArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "FillArrays.jl")) using .Main.FillArrays +isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "OffsetArrays.jl")) +using .Main.OffsetArrays + +isdefined(Main, :SizedArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "SizedArrays.jl")) +using .Main.SizedArrays + include("testutils.jl") # test_approx_eq_modphase n = 10 #Size of test matrix @@ -439,6 +445,9 @@ Random.seed!(1) for op in (+, -, *) @test Array(op(T, T2)) ≈ op(Tfull, Tfull2) end + A = kron(T.dv, T.dv') + @test T * A ≈ lmul!(T, copy(A)) + @test A * T ≈ rmul!(copy(A), T) end # test pass-through of mul! for SymTridiagonal*Bidiagonal TriSym = SymTridiagonal(T.dv, T.ev) @@ -446,7 +455,8 @@ Random.seed!(1) # test pass-through of mul! for AbstractTriangular*Bidiagonal Tri = UpperTriangular(diagm(1 => T.ev)) Dia = Diagonal(T.dv) - @test Array(Tri*T) ≈ Array(Tri)*Array(T) + @test Array(Tri*T) ≈ Array(Tri)*Array(T) ≈ rmul!(copy(Tri), T) + @test Array(T*Tri) ≈ Array(T)*Array(Tri) ≈ lmul!(T, copy(Tri)) # test mul! itself for these types for AA in (Tri, Dia) for f in (identity, transpose, adjoint) @@ -459,8 +469,10 @@ Random.seed!(1) for f in (identity, transpose, adjoint) C = relty == Int ? rand(float(elty), n, n) : rand(elty, n, n) B = rand(elty, n, n) - D = copy(C) + 2.0 * Array(T*f(B)) - mul!(C, T, f(B), 2.0, 1.0) ≈ D + D = C + 2.0 * Array(T*f(B)) + @test mul!(C, T, f(B), 2.0, 1.0) ≈ D + @test lmul!(T, copy(f(B))) ≈ T * f(B) + @test rmul!(copy(f(B)), T) ≈ f(B) * T end # Issue #31870 @@ -837,4 +849,52 @@ end @test all(iszero, diag(B, 1)) end +@testset "diagind" begin + B = Bidiagonal(1:4, 1:3, :U) + M = Matrix(B) + @testset for k in -4:4 + @test B[diagind(B,k)] == M[diagind(M,k)] + end +end + +@testset "custom axes" begin + dv, uv = OffsetArray(1:4), OffsetArray(1:3) + B = Bidiagonal(dv, uv, :U) + ax = axes(dv, 1) + @test axes(B) === (ax, ax) +end + +@testset "avoid matmul ambiguities with ::MyMatrix * ::AbstractMatrix" begin + A = [i+j for i in 1:2, j in 1:2] + S = SizedArrays.SizedArray{(2,2)}(A) + B = Bidiagonal([1:2;], [1;], :U) + @test S * B == A * B + @test B * S == B * A + C1, C2 = zeros(2,2), zeros(2,2) + @test mul!(C1, S, B) == mul!(C2, A, B) + @test mul!(C1, S, B, 1, 2) == mul!(C2, A, B, 1 ,2) + @test mul!(C1, B, S) == mul!(C2, B, A) + @test mul!(C1, B, S, 1, 2) == mul!(C2, B, A, 1 ,2) + + v = [i for i in 1:2] + sv = SizedArrays.SizedArray{(2,)}(v) + @test B * sv == B * v + C1, C2 = zeros(2), zeros(2) + @test mul!(C1, B, sv) == mul!(C2, B, v) + @test mul!(C1, B, sv, 1, 2) == mul!(C2, B, v, 1 ,2) +end + +@testset "Matrix conversion for non-numeric and undef" begin + B = Bidiagonal(Vector{BigInt}(undef, 4), fill(big(3), 3), :U) + M = Matrix(B) + B[diagind(B)] .= 4 + M[diagind(M)] .= 4 + @test diag(B) == diag(M) + + B = Bidiagonal(fill(Diagonal([1,3]), 3), fill(Diagonal([1,3]), 2), :U) + M = Matrix{eltype(B)}(B) + @test M isa Matrix{eltype(B)} + @test M == B +end + end # module TestBidiagonal diff --git a/stdlib/LinearAlgebra/test/blas.jl b/stdlib/LinearAlgebra/test/blas.jl index 4252d9ee7938b..80494da7babbe 100644 --- a/stdlib/LinearAlgebra/test/blas.jl +++ b/stdlib/LinearAlgebra/test/blas.jl @@ -126,6 +126,28 @@ Random.seed!(100) @test BLAS.iamax(b) == findmax(fabs, b)[2] * (step(ind) >= 0) end end + @testset "nrm2 with non-finite elements" begin + # These tests would have caught + # when running on appropriate hardware. + a = zeros(elty,n) + a[begin] = elty(-Inf) + @test BLAS.nrm2(a) === abs2(elty(Inf)) + a[begin] = elty(NaN) + @test BLAS.nrm2(a) === abs2(elty(NaN)) + end + @testset "deterministic mul!" begin + # mul! should be deterministic, see #53054 + function tester_53054() + C = ComplexF32 + mat = zeros(C, 1, 1) + for _ in 1:100 + v = [C(1-0.2im) C(2+0.3im)] + mul!(mat, v, v', C(1+im), 1) + end + return mat + end + @test allequal(tester_53054() for _ in 1:10000) + end @testset "scal" begin α = rand(elty) a = rand(elty,n) @@ -135,7 +157,7 @@ Random.seed!(100) end end - @testset "ger, her, syr" for x in (rand(elty, n), view(rand(elty,2n), 1:2:2n), view(rand(elty,n), n:-1:1)), + @testset "ger, geru, her, syr" for x in (rand(elty, n), view(rand(elty,2n), 1:2:2n), view(rand(elty,n), n:-1:1)), y in (rand(elty,n), view(rand(elty,3n), 1:3:3n), view(rand(elty,2n), 2n:-2:2)) A = rand(elty,n,n) @@ -144,6 +166,9 @@ Random.seed!(100) @test BLAS.ger!(α,x,y,copy(A)) ≈ A + α*x*y' @test_throws DimensionMismatch BLAS.ger!(α,Vector{elty}(undef,n+1),y,copy(A)) + @test BLAS.geru!(α,x,y,copy(A)) ≈ A + α*x*transpose(y) + @test_throws DimensionMismatch BLAS.geru!(α,Vector{elty}(undef,n+1),y,copy(A)) + A = rand(elty,n,n) A = A + transpose(A) @test issymmetric(A) @@ -447,6 +472,40 @@ Random.seed!(100) end end end + @testset "gemmt" begin + for (wrapper, uplo) in ((LowerTriangular, 'L'), (UpperTriangular, 'U')) + @test wrapper(BLAS.gemmt(uplo, 'N', 'N', I4, I4)) ≈ wrapper(I4) + @test wrapper(BLAS.gemmt(uplo, 'N', 'T', I4, I4)) ≈ wrapper(I4) + @test wrapper(BLAS.gemmt(uplo, 'T', 'N', I4, I4)) ≈ wrapper(I4) + @test wrapper(BLAS.gemmt(uplo, 'T', 'T', I4, I4)) ≈ wrapper(I4) + @test wrapper(BLAS.gemmt(uplo, 'N', 'N', el2, I4, I4)) ≈ wrapper(el2 * I4) + @test wrapper(BLAS.gemmt(uplo, 'N', 'T', el2, I4, I4)) ≈ wrapper(el2 * I4) + @test wrapper(BLAS.gemmt(uplo, 'T', 'N', el2, I4, I4)) ≈ wrapper(el2 * I4) + @test wrapper(BLAS.gemmt(uplo, 'T', 'T', el2, I4, I4)) ≈ wrapper(el2 * I4) + I4cp = copy(I4) + @test wrapper(BLAS.gemmt!(uplo, 'N', 'N', one(elty), I4, I4, elm1, I4cp)) ≈ wrapper(Z4) + @test I4cp ≈ Z4 + I4cp[:] = I4 + @test wrapper(BLAS.gemmt!(uplo, 'N', 'T', one(elty), I4, I4, elm1, I4cp)) ≈ wrapper(Z4) + @test I4cp ≈ Z4 + I4cp[:] = I4 + @test wrapper(BLAS.gemmt!(uplo, 'T', 'N', one(elty), I4, I4, elm1, I4cp)) ≈ wrapper(Z4) + @test I4cp ≈ Z4 + I4cp[:] = I4 + @test wrapper(BLAS.gemmt!(uplo, 'T', 'T', one(elty), I4, I4, elm1, I4cp)) ≈ wrapper(Z4) + @test I4cp ≈ Z4 + M1 = uplo == 'U' ? U4 : I4 + @test wrapper(BLAS.gemmt(uplo, 'N', 'N', I4, U4)) ≈ wrapper(M1) + M2 = uplo == 'U' ? I4 : U4' + @test wrapper(BLAS.gemmt(uplo, 'N', 'T', I4, U4)) ≈ wrapper(M2) + @test_throws DimensionMismatch BLAS.gemmt!(uplo, 'N', 'N', one(elty), I43, I4, elm1, I43) + @test_throws DimensionMismatch BLAS.gemmt!(uplo, 'N', 'N', one(elty), I4, I4, elm1, Matrix{elty}(I, 5, 5)) + @test_throws DimensionMismatch BLAS.gemmt!(uplo, 'N', 'N', one(elty), I43, I4, elm1, I4) + @test_throws DimensionMismatch BLAS.gemmt!(uplo, 'T', 'N', one(elty), I4, I43, elm1, I43) + @test_throws DimensionMismatch BLAS.gemmt!(uplo, 'N', 'T', one(elty), I43, I43, elm1, I43) + @test_throws DimensionMismatch BLAS.gemmt!(uplo, 'T', 'T', one(elty), I43, I43, elm1, Matrix{elty}(I, 3, 4)) + end + end @testset "gemm" begin @test all(BLAS.gemm('N', 'N', I4, I4) .== I4) @test all(BLAS.gemm('N', 'T', I4, I4) .== I4) @@ -455,7 +514,7 @@ Random.seed!(100) @test all(BLAS.gemm('N', 'N', el2, I4, I4) .== el2 * I4) @test all(BLAS.gemm('N', 'T', el2, I4, I4) .== el2 * I4) @test all(BLAS.gemm('T', 'N', el2, I4, I4) .== el2 * I4) - @test all(LinearAlgebra.BLAS.gemm('T', 'T', el2, I4, I4) .== el2 * I4) + @test all(BLAS.gemm('T', 'T', el2, I4, I4) .== el2 * I4) I4cp = copy(I4) @test all(BLAS.gemm!('N', 'N', one(elty), I4, I4, elm1, I4cp) .== Z4) @test all(I4cp .== Z4) @@ -532,7 +591,7 @@ Base.getindex(A::WrappedArray, i::Int) = A.A[i] Base.getindex(A::WrappedArray{T, N}, I::Vararg{Int, N}) where {T, N} = A.A[I...] Base.setindex!(A::WrappedArray, v, i::Int) = setindex!(A.A, v, i) Base.setindex!(A::WrappedArray{T, N}, v, I::Vararg{Int, N}) where {T, N} = setindex!(A.A, v, I...) -Base.unsafe_convert(::Type{Ptr{T}}, A::WrappedArray{T}) where T = Base.unsafe_convert(Ptr{T}, A.A) +Base.cconvert(::Type{Ptr{T}}, A::WrappedArray{T}) where T = Base.cconvert(Ptr{T}, A.A) Base.strides(A::WrappedArray) = strides(A.A) Base.elsize(::Type{WrappedArray{T,N}}) where {T,N} = Base.elsize(Array{T,N}) diff --git a/stdlib/LinearAlgebra/test/bunchkaufman.jl b/stdlib/LinearAlgebra/test/bunchkaufman.jl index 613e4d09a3cc6..d2305844db63e 100644 --- a/stdlib/LinearAlgebra/test/bunchkaufman.jl +++ b/stdlib/LinearAlgebra/test/bunchkaufman.jl @@ -21,9 +21,24 @@ a2img = randn(n,n)/2 breal = randn(n,2)/2 bimg = randn(n,2)/2 -@testset "$eltya argument A" for eltya in (Float32, Float64, ComplexF32, ComplexF64, Int) - a = eltya == Int ? rand(1:7, n, n) : convert(Matrix{eltya}, eltya <: Complex ? complex.(areal, aimg) : areal) - a2 = eltya == Int ? rand(1:7, n, n) : convert(Matrix{eltya}, eltya <: Complex ? complex.(a2real, a2img) : a2real) +areint = rand(1:7, n, n) +aimint = rand(1:7, n, n) +a2reint = rand(1:7, n, n) +a2imint = rand(1:7, n, n) +breint = rand(1:5, n, 2) +bimint = rand(1:5, n, 2) + +@testset "$eltya argument A" for eltya in (Float32, Float64, ComplexF32, ComplexF64, Int, ### + Float16, Complex{Float16}, BigFloat, Complex{BigFloat}, Complex{Int}, BigInt, + Complex{BigInt}, Rational{BigInt}, Complex{Rational{BigInt}}) + # a = eltya == Int ? rand(1:7, n, n) : convert(Matrix{eltya}, eltya <: Complex ? complex.(areal, aimg) : areal) + # a2 = eltya == Int ? rand(1:7, n, n) : convert(Matrix{eltya}, eltya <: Complex ? complex.(a2real, a2img) : a2real) + a = convert(Matrix{eltya}, eltya <: Complex ? (real(eltya) <: AbstractFloat ? + complex.(areal, aimg) : complex.(areint, aimint)) : (eltya <: AbstractFloat ? + areal : areint)) + a2 = convert(Matrix{eltya}, eltya <: Complex ? (real(eltya) <: AbstractFloat ? + complex.(a2real, a2img) : complex.(a2reint, a2imint)) : (eltya <: AbstractFloat ? + a2real : a2reint)) asym = transpose(a) + a # symmetric indefinite aher = a' + a # Hermitian indefinite apd = a' * a # Positive-definite @@ -34,9 +49,39 @@ bimg = randn(n,2)/2 view(apd , 1:n, 1:n))) ε = εa = eps(abs(float(one(eltya)))) + # Inertia tests + @testset "$uplo Bunch-Kaufman factor inertia" for uplo in (:L, :U) + @testset "rook pivoting: $rook" for rook in (false, true) + test_list = eltya <: Complex ? (Hermitian(aher, uplo), Hermitian(apd, uplo)) : + (Symmetric(transpose(a) + a, uplo), Hermitian(aher, uplo), + Hermitian(apd, uplo)) + ελ = n*max(eps(Float64), εa) # zero-eigenvalue threshold + ελ = typeof(Integer(one(real(eltya)))) <: Signed ? Rational{BigInt}(ελ) : + real(eltya(ελ)) + for M in test_list + bc = bunchkaufman(M, rook) + D = bc.D + λ = real(eltya <: Complex ? eigen(ComplexF64.(D)).values : + eigen(Float64.(D)).values) + σ₁ = norm(λ, Inf) + np = sum(λ .> ελ*σ₁) + nn = sum(λ .< -ελ*σ₁) + nz = n - np - nn + if real(eltya) <: AbstractFloat + @test inertia(bc) == (np, nn, nz) + else + @test inertia(bc; rtol=ελ) == (np, nn, nz) + end + end + end + end + # check that factorize gives a Bunch-Kaufman - @test isa(factorize(asym), LinearAlgebra.BunchKaufman) - @test isa(factorize(aher), LinearAlgebra.BunchKaufman) + if eltya <: Union{Float32, Float64, ComplexF32, ComplexF64, Int} + # Default behaviour only uses Bunch-Kaufman for these types, for now. + @test isa(factorize(asym), LinearAlgebra.BunchKaufman) + @test isa(factorize(aher), LinearAlgebra.BunchKaufman) + end @testset "$uplo Bunch-Kaufman factor of indefinite matrix" for uplo in (:L, :U) bc1 = bunchkaufman(Hermitian(aher, uplo)) @test LinearAlgebra.issuccess(bc1) @@ -89,15 +134,25 @@ bimg = randn(n,2)/2 @test Base.propertynames(bc1) == (:p, :P, :L, :U, :D) end - @testset "$eltyb argument B" for eltyb in (Float32, Float64, ComplexF32, ComplexF64, Int) - b = eltyb == Int ? rand(1:5, n, 2) : convert(Matrix{eltyb}, eltyb <: Complex ? complex.(breal, bimg) : breal) + @testset "$eltyb argument B" for eltyb in (Float32, Float64, ComplexF32, ComplexF64, Int, ### + Float16, Complex{Float16}, BigFloat, Complex{BigFloat}, Complex{Int}, BigInt, + Complex{BigInt}, Rational{BigInt}, Complex{Rational{BigInt}}) + # b = eltyb == Int ? rand(1:5, n, 2) : convert(Matrix{eltyb}, eltyb <: Complex ? complex.(breal, bimg) : breal) + b = convert(Matrix{eltyb}, eltyb <: Complex ? (real(eltyb) <: AbstractFloat ? + complex.(breal, bimg) : complex.(breint, bimint)) : (eltyb <: AbstractFloat ? + breal : breint)) for b in (b, view(b, 1:n, 1:2)) εb = eps(abs(float(one(eltyb)))) ε = max(εa,εb) + epsc = eltya <: Complex ? sqrt(2)*n : n # tolerance scale @testset "$uplo Bunch-Kaufman factor of indefinite matrix" for uplo in (:L, :U) bc1 = bunchkaufman(Hermitian(aher, uplo)) - @test aher*(bc1\b) ≈ b atol=1000ε + # @test aher*(bc1\b) ≈ b atol=1000ε + cda = eltya <: Complex ? cond(ComplexF64.(aher)) : cond(Float64.(aher)) + cda = real(eltya) <: AbstractFloat ? real(eltya(cda)) : cda + @test norm(aher*(bc1\b) - b) <= epsc*sqrt(eps(cda))*max( + norm(aher*(bc1\b)), norm(b)) end @testset "$uplo Bunch-Kaufman factors of a pos-def matrix" for uplo in (:U, :L) @@ -112,8 +167,14 @@ bimg = randn(n,2)/2 @test logdet(bc2) ≈ log(det(bc2)) @test logabsdet(bc2)[1] ≈ log(abs(det(bc2))) @test logabsdet(bc2)[2] == sign(det(bc2)) - @test inv(bc2)*apd ≈ Matrix(I, n, n) - @test apd*(bc2\b) ≈ b rtol=eps(cond(apd)) + # @test inv(bc2)*apd ≈ Matrix(I, n, n) rtol=Base.rtoldefault(real(eltya)) + # @test apd*(bc2\b) ≈ b rtol=eps(cond(apd)) + @test norm(inv(bc2)*apd - Matrix(I, n, n)) <= epsc*Base.rtoldefault( + real(eltya))*max(norm(inv(bc2)*apd), norm(Matrix(I, n, n))) + cda = eltya <: Complex ? cond(ComplexF64.(apd)) : cond(Float64.(apd)) + cda = real(eltya) <: AbstractFloat ? real(eltya(cda)) : cda + @test norm(apd*(bc2\b) - b) <= epsc*sqrt(eps(cda))*max( + norm(apd*(bc2\b)), norm(b)) @test ishermitian(bc2) @test !issymmetric(bc2) || eltya <: Real end diff --git a/stdlib/LinearAlgebra/test/dense.jl b/stdlib/LinearAlgebra/test/dense.jl index c2ab5df107527..afc1df817a544 100644 --- a/stdlib/LinearAlgebra/test/dense.jl +++ b/stdlib/LinearAlgebra/test/dense.jl @@ -878,7 +878,7 @@ end end end -@testset "matrix logarithm is type-inferrable" for elty in (Float32,Float64,ComplexF32,ComplexF64) +@testset "matrix logarithm is type-inferable" for elty in (Float32,Float64,ComplexF32,ComplexF64) A1 = randn(elty, 4, 4) @inferred Union{Matrix{elty},Matrix{complex(elty)}} log(A1) end @@ -1028,8 +1028,8 @@ end @test lyap(1.0+2.0im, 3.0+4.0im) == -1.5 - 2.0im end -@testset "Matrix to real power" for elty in (Float64, ComplexF64) -# Tests proposed at Higham, Deadman: Testing Matrix Function Algorithms Using Identities, March 2014 +@testset "$elty Matrix to real power" for elty in (Float64, ComplexF64) + # Tests proposed at Higham, Deadman: Testing Matrix Function Algorithms Using Identities, March 2014 #Aa : only positive real eigenvalues Aa = convert(Matrix{elty}, [5 4 2 1; 0 1 -1 -1; -1 -1 3 0; 1 1 -1 2]) @@ -1065,6 +1065,14 @@ end @test (A^(2/3))*(A^(1/3)) ≈ A @test (A^im)^(-im) ≈ A end + + Tschurpow = Union{Matrix{real(elty)}, Matrix{complex(elty)}} + @test (@inferred Tschurpow LinearAlgebra.schurpow(Aa, 2.0)) ≈ Aa^2 +end + +@testset "BigFloat triangular real power" begin + A = Float64[3 1; 0 3] + @test A^(3/4) ≈ big.(A)^(3/4) end @testset "diagonal integer matrix to real power" begin @@ -1229,4 +1237,52 @@ Base.:+(x::TypeWithZero, ::TypeWithoutZero) = x @test diagm(0 => [TypeWithoutZero()]) isa Matrix{TypeWithZero} end +@testset "cbrt(A::AbstractMatrix{T})" begin + N = 10 + + # Non-square + A = randn(N,N+2) + @test_throws DimensionMismatch cbrt(A) + + # Real valued diagonal + D = Diagonal(randn(N)) + T = cbrt(D) + @test T*T*T ≈ D + @test eltype(D) == eltype(T) + # Real valued triangular + U = UpperTriangular(randn(N,N)) + T = cbrt(U) + @test T*T*T ≈ U + @test eltype(U) == eltype(T) + L = LowerTriangular(randn(N,N)) + T = cbrt(L) + @test T*T*T ≈ L + @test eltype(L) == eltype(T) + # Real valued symmetric + S = (A -> (A+A')/2)(randn(N,N)) + T = cbrt(Symmetric(S,:U)) + @test T*T*T ≈ S + @test eltype(S) == eltype(T) + # Real valued symmetric + S = (A -> (A+A')/2)(randn(N,N)) + T = cbrt(Symmetric(S,:L)) + @test T*T*T ≈ S + @test eltype(S) == eltype(T) + # Real valued Hermitian + S = (A -> (A+A')/2)(randn(N,N)) + T = cbrt(Hermitian(S,:U)) + @test T*T*T ≈ S + @test eltype(S) == eltype(T) + # Real valued Hermitian + S = (A -> (A+A')/2)(randn(N,N)) + T = cbrt(Hermitian(S,:L)) + @test T*T*T ≈ S + @test eltype(S) == eltype(T) + # Real valued arbitrary + A = randn(N,N) + T = cbrt(A) + @test T*T*T ≈ A + @test eltype(A) == eltype(T) +end + end # module TestDense diff --git a/stdlib/LinearAlgebra/test/diagonal.jl b/stdlib/LinearAlgebra/test/diagonal.jl index 11d506cf64bf8..aa960aaa53193 100644 --- a/stdlib/LinearAlgebra/test/diagonal.jl +++ b/stdlib/LinearAlgebra/test/diagonal.jl @@ -18,6 +18,9 @@ using .Main.InfiniteArrays isdefined(Main, :FillArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "FillArrays.jl")) using .Main.FillArrays +isdefined(Main, :SizedArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "SizedArrays.jl")) +using .Main.SizedArrays + const n=12 # Size of matrix problem to test Random.seed!(1) @@ -47,6 +50,13 @@ Random.seed!(1) DI = Diagonal([1,2,3,4]) @test Diagonal(DI) === DI @test isa(Diagonal{elty}(DI), Diagonal{elty}) + + # diagonal matrices may be converted to Diagonal + local A = [1 0; 0 2] + local DA = convert(Diagonal{Float32,Vector{Float32}}, A) + @test DA isa Diagonal{Float32,Vector{Float32}} + @test DA == A + # issue #26178 @test_throws MethodError convert(Diagonal, [1,2,3,4]) @test_throws DimensionMismatch convert(Diagonal, [1 2 3 4]) @@ -108,6 +118,12 @@ Random.seed!(1) for func in (det, tr) @test func(D) ≈ func(DM) atol=n^2*eps(relty)*(1+(elty<:Complex)) end + + if eltype(D) <: Real + @test minimum(D) ≈ minimum(DM) + @test maximum(D) ≈ maximum(DM) + end + if relty <: BlasFloat for func in (exp, cis, sinh, cosh, tanh, sech, csch, coth) @test func(D) ≈ func(DM) atol=n^3*eps(relty) @@ -772,6 +788,11 @@ end D = Diagonal(fill(M, n)) @test D == Matrix{eltype(D)}(D) end + + S = SizedArray{(2,3)}(reshape([1:6;],2,3)) + D = Diagonal(fill(S,3)) + @test D * fill(S,2,3)' == fill(S * S', 3, 2) + @test fill(S,3,2)' * D == fill(S' * S, 2, 3) end @testset "Eigensystem for block diagonal (issue #30681)" begin @@ -792,7 +813,7 @@ end evecs = [ [[ 1/sqrt(2)+0im, 1/sqrt(2)*im ]] [[ 1/sqrt(2)+0im, -1/sqrt(2)*im ]] [[ 0.0, 0.0 ]] [[ 0.0, 0.0 ]] [[ 0.0, 0.0]]; [[ 0.0, 0.0, 0.0 ]] [[ 0.0, 0.0, 0.0 ]] [[ 1.0, 0.0, 0.0 ]] [[ 0.0, 1.0, 0.0 ]] [[ 0.0, 0.0, 1.0]] ] @test eigD.values == evals - @test eigD.vectors == evecs + @test eigD.vectors ≈ evecs @test D * eigD.vectors ≈ eigD.vectors * Diagonal(eigD.values) end @@ -805,7 +826,7 @@ end @test rdiv!(copy(B), D) ≈ B * Diagonal(inv.(D.diag)) end -@testset "multiplication with Symmetric/Hermitian" begin +@testset "multiplication/division with Symmetric/Hermitian" begin for T in (Float64, ComplexF64) D = Diagonal(randn(T, n)) A = randn(T, n, n); A = A'A @@ -818,6 +839,10 @@ end @test *(transform1(D), transform2(H)) ≈ *(transform1(Matrix(D)), transform2(Matrix(H))) @test *(transform1(S), transform2(D)) ≈ *(transform1(Matrix(S)), transform2(Matrix(D))) @test *(transform1(S), transform2(H)) ≈ *(transform1(Matrix(S)), transform2(Matrix(H))) + @test (transform1(H)/D) * D ≈ transform1(H) + @test (transform1(S)/D) * D ≈ transform1(S) + @test D * (D\transform2(H)) ≈ transform2(H) + @test D * (D\transform2(S)) ≈ transform2(S) end end end @@ -1213,4 +1238,71 @@ end @test *(Diagonal(ones(n)), Diagonal(1:n), Diagonal(ones(n)), Diagonal(1:n)) isa Diagonal end +@testset "diagind" begin + D = Diagonal(1:4) + M = Matrix(D) + @testset for k in -4:4 + @test D[diagind(D,k)] == M[diagind(M,k)] + end +end + +@testset "avoid matmul ambiguities with ::MyMatrix * ::AbstractMatrix" begin + A = [i+j for i in 1:2, j in 1:2] + S = SizedArrays.SizedArray{(2,2)}(A) + D = Diagonal([1:2;]) + @test S * D == A * D + @test D * S == D * A + C1, C2 = zeros(2,2), zeros(2,2) + @test mul!(C1, S, D) == mul!(C2, A, D) + @test mul!(C1, S, D, 1, 2) == mul!(C2, A, D, 1 ,2) + @test mul!(C1, D, S) == mul!(C2, D, A) + @test mul!(C1, D, S, 1, 2) == mul!(C2, D, A, 1 ,2) + + v = [i for i in 1:2] + sv = SizedArrays.SizedArray{(2,)}(v) + @test D * sv == D * v + C1, C2 = zeros(2), zeros(2) + @test mul!(C1, D, sv) == mul!(C2, D, v) + @test mul!(C1, D, sv, 1, 2) == mul!(C2, D, v, 1 ,2) +end + +@testset "copy" begin + @test copy(Diagonal(1:5)) === Diagonal(1:5) +end + +@testset "kron! for Diagonal" begin + a = Diagonal([2,2]) + b = Diagonal([1,1]) + c = Diagonal([0,0,0,0]) + kron!(c,b,a) + @test c == Diagonal([2,2,2,2]) + c=Diagonal(Vector{Float64}(undef, 4)) + kron!(c,a,b) + @test c == Diagonal([2,2,2,2]) +end + +@testset "mul/div with an adjoint vector" begin + A = [1.0;;] + x = [1.0] + yadj = Diagonal(A) \ x' + @test typeof(yadj) == typeof(x') + @test yadj == x' + yadj = Diagonal(A) * x' + @test typeof(yadj) == typeof(x') + @test yadj == x' +end + +@testset "Matrix conversion for non-numeric and undef" begin + D = Diagonal(Vector{BigInt}(undef, 4)) + M = Matrix(D) + D[diagind(D)] .= 4 + M[diagind(M)] .= 4 + @test diag(D) == diag(M) + + D = Diagonal(fill(Diagonal([1,3]), 2)) + M = Matrix{eltype(D)}(D) + @test M isa Matrix{eltype(D)} + @test M == D +end + end # module TestDiagonal diff --git a/stdlib/LinearAlgebra/test/eigen.jl b/stdlib/LinearAlgebra/test/eigen.jl index 413a8df0474fa..73d9147a82a09 100644 --- a/stdlib/LinearAlgebra/test/eigen.jl +++ b/stdlib/LinearAlgebra/test/eigen.jl @@ -45,12 +45,13 @@ aimg = randn(n,n)/2 @test eigvecs(f) === f.vectors @test Array(f) ≈ a - for T in (Tridiagonal(a), Hermitian(Tridiagonal(a))) + for T in (Tridiagonal(a), Hermitian(Tridiagonal(a), :U), Hermitian(Tridiagonal(a), :L)) f = eigen(T) d, v = f for i in 1:size(a,2) @test T*v[:,i] ≈ d[i]*v[:,i] end + @test eigvals(T) ≈ d @test det(T) ≈ det(f) @test inv(T) ≈ inv(f) end @@ -241,6 +242,29 @@ end @test F.vectors isa Matrix{ComplexF16} @test F.values ≈ F32.values @test F.vectors ≈ F32.vectors + + for T in (Float16, ComplexF16) + D = Diagonal(T[1,2,4]) + A = Array(D) + B = eigen(A) + @test B isa Eigen{Float16, Float16, Matrix{Float16}, Vector{Float16}} + @test B.values isa Vector{Float16} + @test B.vectors isa Matrix{Float16} + end + D = Diagonal(ComplexF16[im,2,4]) + A = Array(D) + B = eigen(A) + @test B isa Eigen{Float16, ComplexF16, Matrix{Float16}, Vector{ComplexF16}} + @test B.values isa Vector{ComplexF16} + @test B.vectors isa Matrix{Float16} +end + +@testset "complex eigen inference (#52289)" begin + A = ComplexF64[1.0 0.0; 0.0 8.0] + TC = Eigen{ComplexF64, ComplexF64, Matrix{ComplexF64}, Vector{ComplexF64}} + TR = Eigen{ComplexF64, Float64, Matrix{ComplexF64}, Vector{Float64}} + λ, v = @inferred Union{TR,TC} eigen(A) + @test λ == [1.0, 8.0] end end # module TestEigen diff --git a/stdlib/LinearAlgebra/test/generic.jl b/stdlib/LinearAlgebra/test/generic.jl index 303e78cfd0c58..6318b8e405ede 100644 --- a/stdlib/LinearAlgebra/test/generic.jl +++ b/stdlib/LinearAlgebra/test/generic.jl @@ -15,6 +15,9 @@ using .Main.OffsetArrays isdefined(Main, :DualNumbers) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "DualNumbers.jl")) using .Main.DualNumbers +isdefined(Main, :FillArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "FillArrays.jl")) +using .Main.FillArrays + Random.seed!(123) n = 5 # should be odd @@ -603,6 +606,12 @@ end end end +@testset "avoid stackoverflow in dot" begin + @test_throws "cannot evaluate dot recursively" dot('a', 'c') + @test_throws "cannot evaluate dot recursively" dot('a', 'b':'c') + @test_throws "x and y are of different lengths" dot(1, 1:2) +end + @testset "generalized dot #32739" begin for elty in (Int, Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFloat}) n = 10 @@ -636,4 +645,25 @@ end @test condskeel(A) ≈ condskeel(A, [8,8,8]) end +@testset "copytrito!" begin + n = 10 + A = rand(n, n) + for uplo in ('L', 'U') + B = zeros(n, n) + copytrito!(B, A, uplo) + C = uplo == 'L' ? tril(A) : triu(A) + @test B ≈ C + end +end + +@testset "immutable arrays" begin + A = FillArrays.Fill(big(3), (4, 4)) + M = Array(A) + @test triu(A) == triu(M) + @test triu(A, -1) == triu(M, -1) + @test tril(A) == tril(M) + @test tril(A, 1) == tril(M, 1) + @test det(A) == det(M) +end + end # module TestGeneric diff --git a/stdlib/LinearAlgebra/test/hessenberg.jl b/stdlib/LinearAlgebra/test/hessenberg.jl index 105b9f8970ec8..73786682480cb 100644 --- a/stdlib/LinearAlgebra/test/hessenberg.jl +++ b/stdlib/LinearAlgebra/test/hessenberg.jl @@ -8,6 +8,9 @@ const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") isdefined(Main, :Furlongs) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "Furlongs.jl")) using .Main.Furlongs +isdefined(Main, :SizedArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "SizedArrays.jl")) +using .Main.SizedArrays + # for tuple tests below ≅(x,y) = all(p -> p[1] ≈ p[2], zip(x,y)) @@ -240,4 +243,20 @@ using .Main.ImmutableArrays @test convert(AbstractMatrix{Float64}, H)::UpperHessenberg{Float64,ImmutableArray{Float64,2,Array{Float64,2}}} == H end +@testset "custom axes" begin + SZA = SizedArrays.SizedArray{(2,2)}([1 2; 3 4]) + S = UpperHessenberg(SZA) + r = SizedArrays.SOneTo(2) + @test axes(S) === (r,r) +end + +@testset "copyto! with aliasing (#39460)" begin + M = Matrix(reshape(1:36, 6, 6)) + A = UpperHessenberg(view(M, 1:5, 1:5)) + A2 = copy(A) + B = UpperHessenberg(view(M, 2:6, 2:6)) + @test copyto!(B, A) == A2 +end + + end # module TestHessenberg diff --git a/stdlib/LinearAlgebra/test/lapack.jl b/stdlib/LinearAlgebra/test/lapack.jl index 141b47d1bacef..652c6c2e27e6c 100644 --- a/stdlib/LinearAlgebra/test/lapack.jl +++ b/stdlib/LinearAlgebra/test/lapack.jl @@ -9,6 +9,7 @@ using LinearAlgebra: BlasInt @test_throws ArgumentError LinearAlgebra.LAPACK.chkside('Z') @test_throws ArgumentError LinearAlgebra.LAPACK.chkdiag('Z') @test_throws ArgumentError LinearAlgebra.LAPACK.chktrans('Z') +@test_throws ArgumentError LinearAlgebra.LAPACK.chkvalidparam(1, "job", 2, (0,1)) @testset "syevr" begin Random.seed!(123) @@ -35,6 +36,12 @@ using LinearAlgebra: BlasInt @test vals_test ≈ vals @test Z_test*(Diagonal(vals)*Z_test') ≈ Asym @test_throws DimensionMismatch LAPACK.sygvd!(1, 'V', 'U', copy(Asym), zeros(elty, 6, 6)) + + @test_throws "jobz must be one of ('N', 'V'), but 'X' was passed" LAPACK.syevr!('X', Asym) + @test_throws "jobz must be one of ('N', 'V'), but 'X' was passed" LAPACK.syev!('X', 'U', Asym) + @test_throws "uplo argument must be 'U' (upper) or 'L' (lower), got 'M'" LAPACK.syev!('N', 'M', Asym) + @test_throws "jobz must be one of ('N', 'V'), but 'X' was passed" LAPACK.syevd!('X', 'U', Asym) + @test_throws "uplo argument must be 'U' (upper) or 'L' (lower), got 'M'" LAPACK.syevd!('N', 'M', Asym) end end @@ -112,7 +119,9 @@ end D = LAPACK.gbtrs!('N',2,1,6,AB,ipiv,D) A = diagm(-2 => dl2, -1 => dl, 0 => d, 1 => du) @test A\C ≈ D - @test_throws DimensionMismatch LAPACK.gbtrs!('N',2,1,6,AB,ipiv,Matrix{elty}(undef,7,6)) + M = Matrix{elty}(undef,7,6) + @test_throws DimensionMismatch LAPACK.gbtrs!('N',2,1,6,AB,ipiv,M) + @test_throws ArgumentError LAPACK.gbtrs!('M',2,1,6,AB,ipiv,M) @test_throws LinearAlgebra.LAPACKException LAPACK.gbtrf!(2,1,6,zeros(elty,6,6)) end end @@ -141,9 +150,11 @@ end x10, x11 = Vector{LinearAlgebra.BlasInt}.(undef, (10, 11)) @test_throws DimensionMismatch LAPACK.gels!('N',A10x10,B11x11) @test_throws DimensionMismatch LAPACK.gels!('T',A10x10,B11x11) + @test_throws ArgumentError LAPACK.gels!('X',A10x10,B11x11) @test_throws DimensionMismatch LAPACK.gesv!(A10x10,B11x11) @test_throws DimensionMismatch LAPACK.getrs!('N',A10x10,x10,B11x11) @test_throws DimensionMismatch LAPACK.getrs!('T',A10x10,x10,B11x11) + @test_throws ArgumentError LAPACK.getrs!('X',A10x10,x10,B11x11) @test_throws DimensionMismatch LAPACK.getri!(A10x10,x11) end end @@ -177,12 +188,20 @@ end @test U ≈ lU @test S ≈ lS @test V' ≈ lVt + @test_throws ArgumentError LAPACK.gesvd!('X','S',A) + @test_throws ArgumentError LAPACK.gesvd!('S','X',A) B = rand(elty,10,10) # xggsvd3 replaced xggsvd in LAPACK 3.6.0 if LAPACK.version() < v"3.6.0" - @test_throws DimensionMismatch LAPACK.ggsvd!('S','S','S',A,B) + @test_throws DimensionMismatch LAPACK.ggsvd!('N','N','N',A,B) + @test_throws ArgumentError LAPACK.ggsvd!('X','N','N',A,B) + @test_throws ArgumentError LAPACK.ggsvd!('N','X','N',A,B) + @test_throws ArgumentError LAPACK.ggsvd!('N','N','X',A,B) else - @test_throws DimensionMismatch LAPACK.ggsvd3!('S','S','S',A,B) + @test_throws DimensionMismatch LAPACK.ggsvd3!('N','N','N',A,B) + @test_throws ArgumentError LAPACK.ggsvd3!('X','N','N',A,B) + @test_throws ArgumentError LAPACK.ggsvd3!('N','X','N',A,B) + @test_throws ArgumentError LAPACK.ggsvd3!('N','N','X',A,B) end end end @@ -224,6 +243,7 @@ end X = rand(elty,10) B,Y,z = LAPACK.gels!('N',copy(A),copy(X)) @test A\X ≈ Y + @test_throws ArgumentError LAPACK.gels!('X',A,X) end end @@ -252,6 +272,9 @@ end fA = eigen(A, sortby=nothing) @test fA.values ≈ Aw @test fA.vectors ≈ Avr + + @test_throws ArgumentError LAPACK.geev!('X','V',A) + @test_throws ArgumentError LAPACK.geev!('N','X',A) end end @@ -284,6 +307,7 @@ end @test_throws DimensionMismatch LAPACK.gttrs!('N', x11, d, du, x9, y10, b) @test_throws DimensionMismatch LAPACK.gttrs!('N', dl, d, x11, x9, y10, b) @test_throws DimensionMismatch LAPACK.gttrs!('N', dl, d, du, x9, y10, x11) + @test_throws ArgumentError LAPACK.gttrs!('X', dl, d, du, x9, y10, x11) A = lu(Tridiagonal(dl,d,du)) b = rand(elty,10,5) c = copy(b) @@ -298,10 +322,17 @@ end A = rand(elty,10,10) A,tau = LAPACK.gelqf!(A) @test_throws DimensionMismatch LAPACK.orglq!(A,tau,11) - @test_throws DimensionMismatch LAPACK.ormlq!('R','N',A,tau,rand(elty,11,11)) - @test_throws DimensionMismatch LAPACK.ormlq!('L','N',A,tau,rand(elty,11,11)) - @test_throws DimensionMismatch LAPACK.ormlq!('R','N',A,zeros(elty,11),rand(elty,10,10)) - @test_throws DimensionMismatch LAPACK.ormlq!('L','N',A,zeros(elty,11),rand(elty,10,10)) + temp = rand(elty,11,11) + @test_throws DimensionMismatch LAPACK.ormlq!('R','N',A,tau,temp) + @test_throws DimensionMismatch LAPACK.ormlq!('L','N',A,tau,temp) + @test_throws ArgumentError LAPACK.ormlq!('X','N',A,tau,temp) + @test_throws ArgumentError LAPACK.ormlq!('R','X',A,tau,temp) + temp = zeros(elty,11) + B = copy(A) + @test_throws DimensionMismatch LAPACK.ormlq!('R','N',A,temp,B) + @test_throws DimensionMismatch LAPACK.ormlq!('L','N',A,temp,B) + @test_throws ArgumentError LAPACK.ormlq!('X','N',A,temp,B) + @test_throws ArgumentError LAPACK.ormlq!('L','X',A,temp,B) B = copy(A) C = LAPACK.orglq!(B,tau) @@ -312,30 +343,51 @@ end @test_throws DimensionMismatch LAPACK.orgqr!(A,tau,11) B = copy(A) @test LAPACK.orgqr!(B,tau) ≈ LAPACK.ormqr!('R','N',A,tau,Matrix{elty}(I, 10, 10)) - @test_throws DimensionMismatch LAPACK.ormqr!('R','N',A,tau,rand(elty,11,11)) - @test_throws DimensionMismatch LAPACK.ormqr!('L','N',A,tau,rand(elty,11,11)) - @test_throws DimensionMismatch LAPACK.ormqr!('R','N',A,zeros(elty,11),rand(elty,10,10)) - @test_throws DimensionMismatch LAPACK.ormqr!('L','N',A,zeros(elty,11),rand(elty,10,10)) + temp = rand(elty,11,11) + @test_throws DimensionMismatch LAPACK.ormqr!('R','N',A,tau,temp) + @test_throws DimensionMismatch LAPACK.ormqr!('L','N',A,tau,temp) + @test_throws ArgumentError LAPACK.ormqr!('X','N',A,tau,temp) + @test_throws ArgumentError LAPACK.ormqr!('L','X',A,tau,temp) + B = copy(A) + temp = zeros(elty,11) + @test_throws DimensionMismatch LAPACK.ormqr!('R','N',A,temp,B) + @test_throws DimensionMismatch LAPACK.ormqr!('L','N',A,temp,B) + @test_throws ArgumentError LAPACK.ormqr!('X','N',A,temp,B) + @test_throws ArgumentError LAPACK.ormqr!('L','X',A,temp,B) A = rand(elty,10,10) A,tau = LAPACK.geqlf!(A) @test_throws DimensionMismatch LAPACK.orgql!(A,tau,11) B = copy(A) @test LAPACK.orgql!(B,tau) ≈ LAPACK.ormql!('R','N',A,tau,Matrix{elty}(I, 10, 10)) - @test_throws DimensionMismatch LAPACK.ormql!('R','N',A,tau,rand(elty,11,11)) - @test_throws DimensionMismatch LAPACK.ormql!('L','N',A,tau,rand(elty,11,11)) - @test_throws DimensionMismatch LAPACK.ormql!('R','N',A,zeros(elty,11),rand(elty,10,10)) - @test_throws DimensionMismatch LAPACK.ormql!('L','N',A,zeros(elty,11),rand(elty,10,10)) + temp = rand(elty,11,11) + @test_throws DimensionMismatch LAPACK.ormql!('R','N',A,tau,temp) + @test_throws DimensionMismatch LAPACK.ormql!('L','N',A,tau,temp) + @test_throws ArgumentError LAPACK.ormql!('X','N',A,tau,temp) + @test_throws ArgumentError LAPACK.ormql!('L','X',A,tau,temp) + temp = zeros(elty,11) + B = copy(A) + @test_throws DimensionMismatch LAPACK.ormql!('R','N',A,temp,B) + @test_throws DimensionMismatch LAPACK.ormql!('L','N',A,temp,B) + @test_throws ArgumentError LAPACK.ormql!('X','N',A,temp,B) + @test_throws ArgumentError LAPACK.ormql!('L','X',A,temp,B) A = rand(elty,10,10) A,tau = LAPACK.gerqf!(A) @test_throws DimensionMismatch LAPACK.orgrq!(A,tau,11) B = copy(A) @test LAPACK.orgrq!(B,tau) ≈ LAPACK.ormrq!('R','N',A,tau,Matrix{elty}(I, 10, 10)) - @test_throws DimensionMismatch LAPACK.ormrq!('R','N',A,tau,rand(elty,11,11)) - @test_throws DimensionMismatch LAPACK.ormrq!('L','N',A,tau,rand(elty,11,11)) - @test_throws DimensionMismatch LAPACK.ormrq!('R','N',A,zeros(elty,11),rand(elty,10,10)) - @test_throws DimensionMismatch LAPACK.ormrq!('L','N',A,zeros(elty,11),rand(elty,10,10)) + temp = rand(elty,11,11) + @test_throws DimensionMismatch LAPACK.ormrq!('R','N',A,tau,temp) + @test_throws DimensionMismatch LAPACK.ormrq!('L','N',A,tau,temp) + @test_throws ArgumentError LAPACK.ormrq!('X','N',A,tau,temp) + @test_throws ArgumentError LAPACK.ormrq!('L','X',A,tau,temp) + B = copy(A) + temp = zeros(elty,11) + @test_throws DimensionMismatch LAPACK.ormrq!('R','N',A,temp,B) + @test_throws DimensionMismatch LAPACK.ormrq!('L','N',A,temp,B) + @test_throws ArgumentError LAPACK.ormrq!('X','N',A,temp,B) + @test_throws ArgumentError LAPACK.ormrq!('L','X',A,temp,B) A = rand(elty,10,11) Q = copy(A) @@ -351,21 +403,29 @@ end T = zeros(elty,10,11) @test_throws DimensionMismatch LAPACK.gemqrt!('L','N',V,T,C) @test_throws DimensionMismatch LAPACK.gemqrt!('R','N',V,T,C) + @test_throws ArgumentError LAPACK.gemqrt!('X','N',V,T,C) + @test_throws ArgumentError LAPACK.gemqrt!('R','X',V,T,C) C = rand(elty,10,10) V = rand(elty,11,10) T = zeros(elty,10,10) @test_throws DimensionMismatch LAPACK.gemqrt!('R','N',V,T,C) @test_throws DimensionMismatch LAPACK.gemqrt!('L','N',V,T,C) + @test_throws ArgumentError LAPACK.gemqrt!('X','N',V,T,C) + @test_throws ArgumentError LAPACK.gemqrt!('L','X',V,T,C) # test size(T) = (nb,k) ensures 1 <= nb <= k T = zeros(elty,10,10) V = rand(elty,5,10) @test_throws DimensionMismatch LAPACK.gemqrt!('L','N',V,T,C) + @test_throws ArgumentError LAPACK.gemqrt!('X','N',V,T,C) + @test_throws ArgumentError LAPACK.gemqrt!('L','X',V,T,C) C = rand(elty,10,10) V = rand(elty,10,10) T = zeros(elty,11,10) @test_throws DimensionMismatch LAPACK.gemqrt!('R','N',V,T,C) + @test_throws ArgumentError LAPACK.gemqrt!('X','N',V,T,C) + @test_throws ArgumentError LAPACK.gemqrt!('R','X',V,T,C) @test_throws DimensionMismatch LAPACK.orghr!(1, 10, C, zeros(elty,11)) end @@ -377,8 +437,12 @@ end A = A + transpose(A) #symmetric! B = copy(A) B,ipiv = LAPACK.sytrf!('U',B) + @test_throws ArgumentError LAPACK.sytrf!('X',B) @test triu(inv(A)) ≈ triu(LAPACK.sytri!('U',B,ipiv)) rtol=eps(cond(A)) - @test_throws DimensionMismatch LAPACK.sytrs!('U',B,ipiv,rand(elty,11,5)) + @test_throws ArgumentError LAPACK.sytri!('X',B,ipiv) + temp = rand(elty,11,5) + @test_throws DimensionMismatch LAPACK.sytrs!('U',B,ipiv,temp) + @test_throws ArgumentError LAPACK.sytrs!('X',B,ipiv,temp) @test LAPACK.sytrf!('U',zeros(elty,0,0)) == (zeros(elty,0,0),zeros(BlasInt,0),zero(BlasInt)) end @@ -389,7 +453,10 @@ end B = copy(A) B,ipiv = LAPACK.sytrf_rook!('U', B) @test triu(inv(A)) ≈ triu(LAPACK.sytri_rook!('U', B, ipiv)) rtol=eps(cond(A)) - @test_throws DimensionMismatch LAPACK.sytrs_rook!('U', B, ipiv, rand(elty, 11, 5)) + @test_throws ArgumentError LAPACK.sytri_rook!('X', B, ipiv) + temp = rand(elty, 11, 5) + @test_throws DimensionMismatch LAPACK.sytrs_rook!('U', B, ipiv, temp) + @test_throws ArgumentError LAPACK.sytrs_rook!('X', B, ipiv, temp) @test LAPACK.sytrf_rook!('U',zeros(elty, 0, 0)) == (zeros(elty, 0, 0),zeros(BlasInt, 0),zero(BlasInt)) A = rand(elty, 10, 10) A = A + transpose(A) #symmetric! @@ -398,7 +465,9 @@ end cnd = cond(A) b,A = LAPACK.sysv_rook!('U', A, b) @test b ≈ c rtol=eps(cnd) - @test_throws DimensionMismatch LAPACK.sysv_rook!('U',A,rand(elty,11)) + temp = rand(elty,11) + @test_throws DimensionMismatch LAPACK.sysv_rook!('U',A,temp) + @test_throws ArgumentError LAPACK.sysv_rook!('X',A,temp) # syconvf_rook error handling # way argument is wrong @@ -416,8 +485,11 @@ end A = A + A' #hermitian! B = copy(A) B,ipiv = LAPACK.hetrf!('U',B) - @test_throws DimensionMismatch LAPACK.hetrs!('U',B,ipiv,rand(elty,11,5)) - @test_throws DimensionMismatch LAPACK.hetrs_rook!('U',B,ipiv,rand(elty,11,5)) + temp = rand(elty,11,5) + @test_throws DimensionMismatch LAPACK.hetrs!('U',B,ipiv,temp) + @test_throws ArgumentError LAPACK.hetrs!('X',B,ipiv,temp) + @test_throws DimensionMismatch LAPACK.hetrs_rook!('U',B,ipiv,temp) + @test_throws ArgumentError LAPACK.hetrs_rook!('X',B,ipiv,temp) end end @@ -425,11 +497,21 @@ end @testset for elty in (Float32, Float64) d = rand(elty,10) e = rand(elty,9) - @test_throws DimensionMismatch LAPACK.stev!('U',d,rand(elty,11)) - @test_throws DimensionMismatch LAPACK.stebz!('A','B',zero(elty),zero(elty),0,0,-1.,d,rand(elty,10)) - @test_throws DimensionMismatch LAPACK.stegr!('N','A',d,rand(elty,11),zero(elty),zero(elty),0,0) - @test_throws DimensionMismatch LAPACK.stein!(d,zeros(elty,11),zeros(elty,10),zeros(BlasInt,10),zeros(BlasInt,10)) - @test_throws DimensionMismatch LAPACK.stein!(d,e,zeros(elty,11),zeros(BlasInt,10),zeros(BlasInt,10)) + temp = rand(elty,11) + @test_throws DimensionMismatch LAPACK.stev!('N',d,temp) + @test_throws ArgumentError LAPACK.stev!('X',d,temp) + temp = rand(elty,10) + @test_throws DimensionMismatch LAPACK.stebz!('A','B',zero(elty),zero(elty),0,0,-1.,d,temp) + @test_throws ArgumentError LAPACK.stebz!('X','B',zero(elty),zero(elty),0,0,-1.,d,temp) + @test_throws ArgumentError LAPACK.stebz!('A','X',zero(elty),zero(elty),0,0,-1.,d,temp) + temp11 = rand(elty,11) + @test_throws DimensionMismatch LAPACK.stegr!('N','A',d,temp11,zero(elty),zero(elty),0,0) + @test_throws ArgumentError LAPACK.stegr!('X','A',d,temp11,zero(elty),zero(elty),0,0) + @test_throws ArgumentError LAPACK.stegr!('N','X',d,temp11,zero(elty),zero(elty),0,0) + tempblasint10 = zeros(BlasInt,10) + tempblasint10_2 = zeros(BlasInt,10) + @test_throws DimensionMismatch LAPACK.stein!(d,temp11,temp,tempblasint10,tempblasint10_2) + @test_throws DimensionMismatch LAPACK.stein!(d,e,temp11,tempblasint10,tempblasint10_2) end end @@ -439,7 +521,13 @@ end A = triu(A) B = copy(A) @test inv(A) ≈ LAPACK.trtri!('U','N',B) - @test_throws DimensionMismatch LAPACK.trtrs!('U','N','N',B,zeros(elty,11,10)) + @test_throws ArgumentError LAPACK.trtri!('X','N',B) + @test_throws ArgumentError LAPACK.trtri!('U','X',B) + temp = zeros(elty,11,10) + @test_throws DimensionMismatch LAPACK.trtrs!('U','N','N',B,temp) + @test_throws ArgumentError LAPACK.trtrs!('X','N','N',B,temp) + @test_throws ArgumentError LAPACK.trtrs!('U','X','N',B,temp) + @test_throws ArgumentError LAPACK.trtrs!('U','N','X',B,temp) end end @@ -479,6 +567,8 @@ end LinearAlgebra.LAPACK.larf!('L', v, τ, C1) LinearAlgebra.LAPACK.larf!('R', v, conj(τ), C2) @test C ≈ C2*C1 + + @test_throws ArgumentError LAPACK.larf!('X', v, τ, C1) end end @@ -489,6 +579,8 @@ end @test_throws DimensionMismatch LAPACK.tgsen!(zeros(BlasInt,10),Z,Z,zeros(elty,11,11),Z) @test_throws DimensionMismatch LAPACK.tgsen!(zeros(BlasInt,10),Z,Z,Z,zeros(elty,11,11)) @test_throws DimensionMismatch LAPACK.trsyl!('N','N',Z,Z,zeros(elty,11,11)) + @test_throws ArgumentError LAPACK.trsyl!('X','N',Z,Z,zeros(elty,11,11)) + @test_throws ArgumentError LAPACK.trsyl!('N','X',Z,Z,zeros(elty,11,11)) @test_throws DimensionMismatch LAPACK.tzrzf!(zeros(elty,10,5)) A = triu(rand(elty,4,4)) @@ -508,6 +600,7 @@ end b,A = LAPACK.sysv!('U',A,b) @test b ≈ c @test_throws DimensionMismatch LAPACK.sysv!('U',A,rand(elty,11)) + @test_throws ArgumentError LAPACK.sysv!('X',A,rand(elty,11)) end end @@ -520,14 +613,17 @@ end c = A \ b b,A = LAPACK.hesv!('U',A,b) @test b ≈ c - @test_throws DimensionMismatch LAPACK.hesv!('U',A,rand(elty,11)) + temp = rand(elty,11) + @test_throws DimensionMismatch LAPACK.hesv!('U',A,temp) + @test_throws ArgumentError LAPACK.hesv!('X',A,temp) A = rand(elty,10,10) A = A + A' #hermitian! b = rand(elty,10) c = A \ b b,A = LAPACK.hesv_rook!('U',A,b) @test b ≈ c - @test_throws DimensionMismatch LAPACK.hesv_rook!('U',A,rand(elty,11)) + @test_throws DimensionMismatch LAPACK.hesv_rook!('U',A,temp) + @test_throws ArgumentError LAPACK.hesv_rook!('X',A,temp) end end @@ -563,8 +659,12 @@ end C = copy(B) if elty <: Complex @test A\B ≈ LAPACK.pttrs!('U',rdv,ev,C) - @test_throws DimensionMismatch LAPACK.pttrs!('U',rdv,Vector{elty}(undef,10),C) - @test_throws DimensionMismatch LAPACK.pttrs!('U',rdv,ev,Matrix{elty}(undef,11,11)) + tempvec = Vector{elty}(undef,10) + tempmat = Matrix{elty}(undef,11,11) + @test_throws DimensionMismatch LAPACK.pttrs!('U',rdv,tempvec,C) + @test_throws DimensionMismatch LAPACK.pttrs!('U',rdv,ev,tempmat) + @test_throws ArgumentError LAPACK.pttrs!('X',rdv,tempvec,C) + @test_throws ArgumentError LAPACK.pttrs!('X',rdv,ev,tempmat) else @test A\B ≈ LAPACK.pttrs!(rdv,ev,C) @test_throws DimensionMismatch LAPACK.pttrs!(rdv,Vector{elty}(undef,10),C) @@ -591,6 +691,8 @@ end offsizemat = Matrix{elty}(undef, n+1, n+1) @test_throws DimensionMismatch LAPACK.posv!('U', D, offsizemat) @test_throws DimensionMismatch LAPACK.potrs!('U', D, offsizemat) + @test_throws ArgumentError LAPACK.posv!('X', D, offsizemat) + @test_throws ArgumentError LAPACK.potrs!('X', D, offsizemat) @test LAPACK.potrs!('U',Matrix{elty}(undef,0,0),elty[]) == elty[] end @@ -613,6 +715,10 @@ end B = rand(elty,11,11) @test_throws DimensionMismatch LAPACK.gges!('V','V',A,B) @test_throws DimensionMismatch LAPACK.gges3!('V','V',A,B) + @test_throws ArgumentError LAPACK.gges!('X','V',A,B) + @test_throws ArgumentError LAPACK.gges3!('X','V',A,B) + @test_throws ArgumentError LAPACK.gges!('V','X',A,B) + @test_throws ArgumentError LAPACK.gges3!('V','X',A,B) end end @@ -632,8 +738,14 @@ end select,Vln,Vrn = LAPACK.trevc!('B','S',select,copy(T)) @test Vrn ≈ v @test Vln ≈ Vl - @test_throws ArgumentError LAPACK.trevc!('V','S',select,copy(T)) - @test_throws DimensionMismatch LAPACK.trrfs!('U','N','N',T,rand(elty,10,10),rand(elty,10,11)) + @test_throws ArgumentError LAPACK.trevc!('V','S',select,T) + @test_throws ArgumentError LAPACK.trevc!('R','X',select,T) + temp1010 = rand(elty,10,10) + temp1011 = rand(elty,10,11) + @test_throws DimensionMismatch LAPACK.trrfs!('U','N','N',T,temp1010,temp1011) + @test_throws ArgumentError LAPACK.trrfs!('X','N','N',T,temp1010,temp1011) + @test_throws ArgumentError LAPACK.trrfs!('U','X','N',T,temp1010,temp1011) + @test_throws ArgumentError LAPACK.trrfs!('U','N','X',T,temp1010,temp1011) end end @@ -685,6 +797,19 @@ end end end +@testset "lacpy!" begin + @testset for elty in (Float32, Float64, ComplexF32, ComplexF64) + n = 10 + A = rand(elty, n, n) + for uplo in ('L', 'U', 'N') + B = zeros(elty, n, n) + LinearAlgebra.LAPACK.lacpy!(B, A, uplo) + C = uplo == 'L' ? tril(A) : (uplo == 'U' ? triu(A) : A) + @test B ≈ C + end + end +end + @testset "Julia vs LAPACK" begin # Test our own linear algebra functionality against LAPACK @testset for elty in (Float32, Float64, ComplexF32, ComplexF64) diff --git a/stdlib/LinearAlgebra/test/lu.jl b/stdlib/LinearAlgebra/test/lu.jl index 2facbc55c8aff..a4cbdbe3eb9b9 100644 --- a/stdlib/LinearAlgebra/test/lu.jl +++ b/stdlib/LinearAlgebra/test/lu.jl @@ -97,7 +97,9 @@ dimg = randn(n)/2 dlu = convert.(eltya, [1, 1]) dia = convert.(eltya, [-2, -2, -2]) tri = Tridiagonal(dlu, dia, dlu) - @test_throws ArgumentError lu!(tri) + L = lu(tri) + @test lu!(tri) == L + @test UpperTriangular(tri) == L.U end end @testset for eltyb in (Float32, Float64, ComplexF32, ComplexF64, Int) @@ -243,9 +245,13 @@ end @test_throws ZeroPivotException lu!(copy(A), NoPivot(); check = true) @test !issuccess(lu(A, NoPivot(); check = false)) @test !issuccess(lu!(copy(A), NoPivot(); check = false)) - F = lu(A; check = false) + F = lu(A, NoPivot(); check = false) @test sprint((io, x) -> show(io, "text/plain", x), F) == "Failed factorization of type $(typeof(F))" + F2 = lu(A; allowsingular = true) + @test !issuccess(F2) + @test issuccess(F2, allowsingular = true) + @test occursin("U factor (rank-deficient)", sprint((io, x) -> show(io, "text/plain", x), F2)) end @testset "conversion" begin diff --git a/stdlib/LinearAlgebra/test/matmul.jl b/stdlib/LinearAlgebra/test/matmul.jl index 86606654e911a..5bcbb99a314fd 100644 --- a/stdlib/LinearAlgebra/test/matmul.jl +++ b/stdlib/LinearAlgebra/test/matmul.jl @@ -23,6 +23,13 @@ mul_wrappers = [ @test @inferred(f(A)) === A g(A) = LinearAlgebra.wrap(A, 'T') @test @inferred(g(A)) === transpose(A) + # https://github.com/JuliaLang/julia/issues/52202 + @test Base.infer_return_type((Vector{Float64},)) do v + LinearAlgebra.wrap(v, 'N') + end == Vector{Float64} + h(A) = LinearAlgebra.wrap(LinearAlgebra._unwrap(A), LinearAlgebra.wrapper_char(A)) + @test @inferred(h(transpose(A))) === transpose(A) + @test @inferred(h(adjoint(A))) === transpose(A) end @testset "matrices with zero dimensions" begin @@ -127,7 +134,7 @@ end @test mul!(C, transpose(A), B) == A' * B @test mul!(C, A, transpose(B)) == A * B' @test mul!(C, transpose(A), transpose(B)) == A' * B' - @test LinearAlgebra.mul!(C, adjoint(A), transpose(B)) == A' * transpose(B) + @test mul!(C, adjoint(A), transpose(B)) == A' * transpose(B) # Inplace multiply-add α = rand(-10:10) @@ -143,8 +150,8 @@ end @test mul!(C0(), adjoint(A), transpose(B), α, β) == α * A' * transpose(B) .+ βC #test DimensionMismatch for generic_matmatmul - @test_throws DimensionMismatch LinearAlgebra.mul!(C, adjoint(A), transpose(fill(1, 4, 4))) - @test_throws DimensionMismatch LinearAlgebra.mul!(C, adjoint(fill(1, 4, 4)), transpose(B)) + @test_throws DimensionMismatch mul!(C, adjoint(A), transpose(fill(1, 4, 4))) + @test_throws DimensionMismatch mul!(C, adjoint(fill(1, 4, 4)), transpose(B)) end vv = [1, 2] CC = Matrix{Int}(undef, 2, 2) @@ -227,7 +234,7 @@ end @test C == AB mul!(C, A, B, 2, -1) @test C == AB - LinearAlgebra._generic_matmatmul!(C, 'N', 'N', A, B, LinearAlgebra.MulAddMul(2, -1)) + LinearAlgebra.generic_matmatmul!(C, 'N', 'N', A, B, LinearAlgebra.MulAddMul(2, -1)) @test C == AB end @@ -236,9 +243,9 @@ end BB = rand(Float64, 6, 6) CC = zeros(Float64, 6, 6) for A in (copy(AA), view(AA, 1:6, 1:6)), B in (copy(BB), view(BB, 1:6, 1:6)), C in (copy(CC), view(CC, 1:6, 1:6)) - @test LinearAlgebra.mul!(C, transpose(A), transpose(B)) == transpose(A) * transpose(B) - @test LinearAlgebra.mul!(C, A, adjoint(B)) == A * transpose(B) - @test LinearAlgebra.mul!(C, adjoint(A), B) == transpose(A) * B + @test mul!(C, transpose(A), transpose(B)) == transpose(A) * transpose(B) + @test mul!(C, A, adjoint(B)) == A * transpose(B) + @test mul!(C, adjoint(A), B) == transpose(A) * B # Inplace multiply-add α = rand(Float64) @@ -253,15 +260,57 @@ end end end +@testset "allocations in BLAS-mul" begin + for n in (2, 3, 6) + A = rand(Float64, n, n) + B = rand(Float64, n, n) + C = zeros(Float64, n, n) + # gemm + for t in (identity, adjoint, transpose) + At = t(A) + Bt = t(B) + mul!(C, At, B) + @test 0 == @allocations mul!(C, At, B) + mul!(C, A, Bt) + @test 0 == @allocations mul!(C, A, Bt) + mul!(C, At, Bt) + @test 0 == @allocations mul!(C, At, Bt) + end + # syrk/herk + @test 0 == @allocations mul!(C, transpose(A), A) + @test 0 == @allocations mul!(C, adjoint(A), A) + @test 0 == @allocations mul!(C, A, transpose(A)) + @test 0 == @allocations mul!(C, A, adjoint(A)) + # complex times real + Cc = complex(C) + Ac = complex(A) + for t in (identity, adjoint, transpose) + Bt = t(B) + @test 0 == @allocations mul!(Cc, Ac, Bt) + end + end +end + @testset "mixed Blas-non-Blas matmul" begin AA = rand(-10:10, 6, 6) - BB = rand(Float64, 6, 6) + BB = ones(Float64, 6, 6) CC = zeros(Float64, 6, 6) for A in (copy(AA), view(AA, 1:6, 1:6)), B in (copy(BB), view(BB, 1:6, 1:6)), C in (copy(CC), view(CC, 1:6, 1:6)) - @test LinearAlgebra.mul!(C, A, B) == A * B - @test LinearAlgebra.mul!(C, transpose(A), transpose(B)) == transpose(A) * transpose(B) - @test LinearAlgebra.mul!(C, A, adjoint(B)) == A * transpose(B) - @test LinearAlgebra.mul!(C, adjoint(A), B) == transpose(A) * B + @test mul!(C, A, B) == A * B + @test mul!(C, transpose(A), transpose(B)) == transpose(A) * transpose(B) + @test mul!(C, A, adjoint(B)) == A * transpose(B) + @test mul!(C, adjoint(A), B) == transpose(A) * B + end +end + +@testset "allocations in mixed Blas-non-Blas matmul" begin + for n in (2, 3, 6) + A = rand(-10:10, n, n) + B = ones(Float64, n, n) + C = zeros(Float64, n, n) + @test 0 == @allocations mul!(C, A, B) + @test 0 == @allocations mul!(C, A, transpose(B)) + @test 0 == @allocations mul!(C, adjoint(A), B) end end @@ -667,12 +716,9 @@ end import Base: *, adjoint, transpose import LinearAlgebra: Adjoint, Transpose (*)(x::RootInt, y::RootInt) = x.i * y.i +(*)(x::RootInt, y::Integer) = x.i * y adjoint(x::RootInt) = x transpose(x::RootInt) = x -Adjoint(x::RootInt) = x -Transpose(x::RootInt) = x -# TODO once Adjoint/Transpose constructors call adjoint/transpose recursively -# rather than Adjoint/Transpose, the additional definitions should become unnecessary @test Base.promote_op(*, RootInt, RootInt) === Int @@ -690,7 +736,7 @@ Transpose(x::RootInt) = x @test A * a == [56] end -function test_mul(C, A, B) +function test_mul(C, A, B, S) mul!(C, A, B) @test Array(A) * Array(B) ≈ C @test A * B ≈ C @@ -699,10 +745,10 @@ function test_mul(C, A, B) # but consider all number types involved: rtol = max(rtoldefault.(real.(eltype.((C, A, B))))...) - rand!(C) + rand!(C, S) T = promote_type(eltype.((A, B))...) - α = rand(T) - β = rand(T) + α = T <: AbstractFloat ? rand(T) : rand(T(-10):T(10)) + β = T <: AbstractFloat ? rand(T) : rand(T(-10):T(10)) βArrayC = β * Array(C) βC = β * C mul!(C, A, B, α, β) @@ -711,7 +757,7 @@ function test_mul(C, A, B) end @testset "mul! vs * for special types" begin - eltypes = [Float32, Float64, Int64] + eltypes = [Float32, Float64, Int64(-100):Int64(100)] for k in [3, 4, 10] T = rand(eltypes) bi1 = Bidiagonal(rand(T, k), rand(T, k - 1), rand([:U, :L])) @@ -724,26 +770,26 @@ end specialmatrices = (bi1, bi2, tri1, tri2, stri1, stri2) for A in specialmatrices B = specialmatrices[rand(1:length(specialmatrices))] - test_mul(C, A, B) + test_mul(C, A, B, T) end for S in specialmatrices l = rand(1:6) B = randn(k, l) C = randn(k, l) - test_mul(C, S, B) + test_mul(C, S, B, T) A = randn(l, k) C = randn(l, k) - test_mul(C, A, S) + test_mul(C, A, S, T) end end for T in eltypes A = Bidiagonal(rand(T, 2), rand(T, 1), rand([:U, :L])) B = Bidiagonal(rand(T, 2), rand(T, 1), rand([:U, :L])) C = randn(2, 2) - test_mul(C, A, B) + test_mul(C, A, B, T) B = randn(2, 9) C = randn(2, 9) - test_mul(C, A, B) + test_mul(C, A, B, T) end let tri44 = Tridiagonal(randn(3), randn(4), randn(3)) @@ -827,6 +873,16 @@ end @test Xv1' * Xv3' ≈ XcXc end +@testset "copyto! for matrices of matrices" begin + A = [randn(ComplexF64, 2,3) for _ in 1:2, _ in 1:3] + for (tfun, tM) in ((identity, 'N'), (transpose, 'T'), (adjoint, 'C')) + At = copy(tfun(A)) + B = zero.(At) + copyto!(B, axes(B, 1), axes(B, 2), tM, A, axes(A, tM == 'N' ? 1 : 2), axes(A, tM == 'N' ? 2 : 1)) + @test B == At + end +end + @testset "method ambiguity" begin # Ambiguity test is run inside a clean process. # https://github.com/JuliaLang/julia/issues/28804 @@ -871,7 +927,7 @@ end # Just in case dispatching on the surface API `mul!` is changed in the future, # let's test the function where the tiled multiplication is defined. fill!(C, 0) - LinearAlgebra._generic_matmatmul!(C, 'N', 'N', A, B, LinearAlgebra.MulAddMul(-1, 0)) + LinearAlgebra.generic_matmatmul!(C, 'N', 'N', A, B, LinearAlgebra.MulAddMul(-1, 0)) @test D ≈ C end diff --git a/stdlib/LinearAlgebra/test/qr.jl b/stdlib/LinearAlgebra/test/qr.jl index 184971da304f7..e339706598a8a 100644 --- a/stdlib/LinearAlgebra/test/qr.jl +++ b/stdlib/LinearAlgebra/test/qr.jl @@ -504,4 +504,28 @@ end @test x ≈ xf end +@testset "issue #53451" begin + # in the issue it was noted that QR factorizations of zero-column matrices + # were possible, but zero row-matrices errored, because LAPACK does not + # accept these empty matrices. now, the `geqrt!` call should be forwarded only + # if both matrix dimensions are positive. + + for dimA in (0, 1, 2, 4) + for F in (Float32, Float64, ComplexF32, ComplexF64, BigFloat) + # this should have worked before, Q is square, and R is 0 × 0: + A_zero_cols = rand(F, dimA, 0) + qr_zero_cols = qr(A_zero_cols) + @test size(qr_zero_cols.Q) == (dimA, dimA) + @test size(qr_zero_cols.R) == (0, 0) + @test qr_zero_cols.Q == LinearAlgebra.I(dimA) + + # this should work now, Q is 0 × 0, and R has `dimA` columns: + A_zero_rows = rand(F, 0, dimA) + qr_zero_rows = qr(A_zero_rows) + @test size(qr_zero_rows.Q) == (0, 0) + @test size(qr_zero_rows.R) == (0, dimA) + end + end +end + end # module TestQR diff --git a/stdlib/LinearAlgebra/test/runtests.jl b/stdlib/LinearAlgebra/test/runtests.jl index 29581313c18d5..d64da9899ca86 100644 --- a/stdlib/LinearAlgebra/test/runtests.jl +++ b/stdlib/LinearAlgebra/test/runtests.jl @@ -1,5 +1,10 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +using Test, LinearAlgebra for file in readlines(joinpath(@__DIR__, "testgroups")) include(file * ".jl") end + +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(LinearAlgebra)) +end diff --git a/stdlib/LinearAlgebra/test/special.jl b/stdlib/LinearAlgebra/test/special.jl index 7e96af369e310..a5198892ff995 100644 --- a/stdlib/LinearAlgebra/test/special.jl +++ b/stdlib/LinearAlgebra/test/special.jl @@ -111,8 +111,11 @@ Random.seed!(1) struct TypeWithZero end Base.promote_rule(::Type{TypeWithoutZero}, ::Type{TypeWithZero}) = TypeWithZero Base.convert(::Type{TypeWithZero}, ::TypeWithoutZero) = TypeWithZero() + Base.zero(x::Union{TypeWithoutZero, TypeWithZero}) = zero(typeof(x)) Base.zero(::Type{<:Union{TypeWithoutZero, TypeWithZero}}) = TypeWithZero() LinearAlgebra.symmetric(::TypeWithoutZero, ::Symbol) = TypeWithoutZero() + LinearAlgebra.symmetric_type(::Type{TypeWithoutZero}) = TypeWithoutZero + Base.copy(A::TypeWithoutZero) = A Base.transpose(::TypeWithoutZero) = TypeWithoutZero() d = fill(TypeWithoutZero(), 3) du = fill(TypeWithoutZero(), 2) diff --git a/stdlib/LinearAlgebra/test/structuredbroadcast.jl b/stdlib/LinearAlgebra/test/structuredbroadcast.jl index 2ca1904b2ff2d..3767fc10055f2 100644 --- a/stdlib/LinearAlgebra/test/structuredbroadcast.jl +++ b/stdlib/LinearAlgebra/test/structuredbroadcast.jl @@ -59,6 +59,43 @@ using Test, LinearAlgebra @test broadcast!(*, Z, X, Y) == broadcast(*, fX, fY) end end + UU = UnitUpperTriangular(rand(N,N)) + UL = UnitLowerTriangular(rand(N,N)) + unittriangulars = (UU, UL) + Ttris = typeof.((UpperTriangular(parent(UU)), LowerTriangular(parent(UU)))) + funittriangulars = map(Array, unittriangulars) + for (X, fX, Ttri) in zip(unittriangulars, funittriangulars, Ttris) + @test (Q = broadcast(sin, X); typeof(Q) == Ttri && Q == broadcast(sin, fX)) + @test broadcast!(sin, Z, X) == broadcast(sin, fX) + @test (Q = broadcast(cos, X); Q isa Matrix && Q == broadcast(cos, fX)) + @test broadcast!(cos, Z, X) == broadcast(cos, fX) + @test (Q = broadcast(*, s, X); typeof(Q) == Ttri && Q == broadcast(*, s, fX)) + @test broadcast!(*, Z, s, X) == broadcast(*, s, fX) + @test (Q = broadcast(+, fV, fA, X); Q isa Matrix && Q == broadcast(+, fV, fA, fX)) + @test broadcast!(+, Z, fV, fA, X) == broadcast(+, fV, fA, fX) + @test (Q = broadcast(*, s, fV, fA, X); Q isa Matrix && Q == broadcast(*, s, fV, fA, fX)) + @test broadcast!(*, Z, s, fV, fA, X) == broadcast(*, s, fV, fA, fX) + + @test X .* 2.0 == X .* (2.0,) == fX .* 2.0 + @test X .* 2.0 isa Ttri + @test X .* (2.0,) isa Ttri + @test isequal(X .* Inf, fX .* Inf) + + two = 2 + @test X .^ 2 == X .^ (2,) == fX .^ 2 == X .^ two + @test X .^ 2 isa typeof(X) # special cased, as isstructurepreserving + @test X .^ (2,) isa Ttri + @test X .^ two isa Ttri + @test X .^ 0 == fX .^ 0 + @test X .^ -1 == fX .^ -1 + + for (Y, fY) in zip(unittriangulars, funittriangulars) + @test broadcast(+, X, Y) == broadcast(+, fX, fY) + @test broadcast!(+, Z, X, Y) == broadcast(+, fX, fY) + @test broadcast(*, X, Y) == broadcast(*, fX, fY) + @test broadcast!(*, Z, X, Y) == broadcast(*, fX, fY) + end + end end @testset "broadcast! where the destination is a structured matrix" begin @@ -142,6 +179,11 @@ end @test map!(*, Z, X, Y) == broadcast(*, fX, fY) end end + # these would be valid for broadcast, but not for map + @test_throws DimensionMismatch map(+, D, Diagonal(rand(1))) + @test_throws DimensionMismatch map(+, D, Diagonal(rand(1)), D) + @test_throws DimensionMismatch map(+, D, D, Diagonal(rand(1))) + @test_throws DimensionMismatch map(+, Diagonal(rand(1)), D, D) end @testset "Issue #33397" begin @@ -238,4 +280,62 @@ end # structured broadcast with function returning non-number type @test tuple.(Diagonal([1, 2])) == [(1,) (0,); (0,) (2,)] +@testset "broadcast over structured matrices with matrix elements" begin + function standardbroadcastingtests(D, T) + M = [x for x in D] + Dsum = D .+ D + @test Dsum isa T + @test Dsum == M .+ M + Dcopy = copy.(D) + @test Dcopy isa T + @test Dcopy == D + Df = float.(D) + @test Df isa T + @test Df == D + @test eltype(eltype(Df)) <: AbstractFloat + @test (x -> (x,)).(D) == (x -> (x,)).(M) + @test (x -> 1).(D) == ones(Int,size(D)) + @test all(==(2), ndims.(D)) + @test_throws MethodError size.(D) + end + @testset "Diagonal" begin + @testset "square" begin + A = [1 3; 2 4] + D = Diagonal([A, A]) + standardbroadcastingtests(D, Diagonal) + @test sincos.(D) == sincos.(Matrix{eltype(D)}(D)) + M = [x for x in D] + @test cos.(D) == cos.(M) + end + + @testset "different-sized square blocks" begin + D = Diagonal([ones(3,3), fill(3.0,2,2)]) + standardbroadcastingtests(D, Diagonal) + end + + @testset "rectangular blocks" begin + D = Diagonal([ones(Bool,3,4), ones(Bool,2,3)]) + standardbroadcastingtests(D, Diagonal) + end + + @testset "incompatible sizes" begin + A = reshape(1:12, 4, 3) + B = reshape(1:12, 3, 4) + D1 = Diagonal(fill(A, 2)) + D2 = Diagonal(fill(B, 2)) + @test_throws DimensionMismatch D1 .+ D2 + end + end + @testset "Bidiagonal" begin + A = [1 3; 2 4] + B = Bidiagonal(fill(A,3), fill(A,2), :U) + standardbroadcastingtests(B, Bidiagonal) + end + @testset "UpperTriangular" begin + A = [1 3; 2 4] + U = UpperTriangular([(i+j)*A for i in 1:3, j in 1:3]) + standardbroadcastingtests(U, UpperTriangular) + end +end + end diff --git a/stdlib/LinearAlgebra/test/symmetric.jl b/stdlib/LinearAlgebra/test/symmetric.jl index 224b7b31a50df..e2a6d2b74ff18 100644 --- a/stdlib/LinearAlgebra/test/symmetric.jl +++ b/stdlib/LinearAlgebra/test/symmetric.jl @@ -4,6 +4,14 @@ module TestSymmetric using Test, LinearAlgebra, Random +const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") + +isdefined(Main, :Quaternions) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "Quaternions.jl")) +using .Main.Quaternions + +isdefined(Main, :SizedArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "SizedArrays.jl")) +using .Main.SizedArrays + Random.seed!(1010) @testset "Pauli σ-matrices: $σ" for σ in map(Hermitian, @@ -459,9 +467,91 @@ end @test dot(symblockml, symblockml) ≈ dot(msymblockml, msymblockml) end end + + @testset "kronecker product of symmetric and Hermitian matrices" begin + for mtype in (Symmetric, Hermitian) + symau = mtype(a, :U) + symal = mtype(a, :L) + msymau = Matrix(symau) + msymal = Matrix(symal) + for eltyc in (Float32, Float64, ComplexF32, ComplexF64, BigFloat, Int) + creal = randn(n, n)/2 + cimag = randn(n, n)/2 + c = eltya == Int ? rand(1:7, n, n) : convert(Matrix{eltya}, eltya <: Complex ? complex.(creal, cimag) : creal) + symcu = mtype(c, :U) + symcl = mtype(c, :L) + msymcu = Matrix(symcu) + msymcl = Matrix(symcl) + @test kron(symau, symcu) ≈ kron(msymau, msymcu) + @test kron(symau, symcl) ≈ kron(msymau, msymcl) + @test kron(symal, symcu) ≈ kron(msymal, msymcu) + @test kron(symal, symcl) ≈ kron(msymal, msymcl) + end + end + end + end +end + +@testset "non-isbits algebra" begin + for ST in (Symmetric, Hermitian), uplo in (:L, :U) + M = Matrix{Complex{BigFloat}}(undef,2,2) + M[1,1] = rand() + M[2,2] = rand() + M[1+(uplo==:L), 1+(uplo==:U)] = rand(ComplexF64) + S = ST(M, uplo) + MS = Matrix(S) + @test real(S) == real(MS) + @test imag(S) == imag(MS) + @test conj(S) == conj(MS) + @test conj!(copy(S)) == conj(MS) + @test -S == -MS + @test S + S == MS + MS + @test S - S == MS - MS + @test S*2 == 2*S == 2*MS + @test S/2 == MS/2 + @test kron(S,S) == kron(MS,MS) + end + @testset "mixed uplo" begin + Mu = Matrix{Complex{BigFloat}}(undef,2,2) + Mu[1,1] = Mu[2,2] = 3 + Mu[1,2] = 2 + 3im + Ml = Matrix{Complex{BigFloat}}(undef,2,2) + Ml[1,1] = Ml[2,2] = 4 + Ml[2,1] = 4 + 5im + for ST in (Symmetric, Hermitian) + Su = ST(Mu, :U) + MSu = Matrix(Su) + Sl = ST(Ml, :L) + MSl = Matrix(Sl) + @test Su + Sl == Sl + Su == MSu + MSl + @test Su - Sl == -(Sl - Su) == MSu - MSl + @test kron(Su,Sl) == kron(MSu,MSl) + @test kron(Sl,Su) == kron(MSl,MSu) + end end end +# bug identified in PR #52318: dot products of quaternionic Hermitian matrices, +# or any number type where conj(a)*conj(b) ≠ conj(a*b): +@testset "dot Hermitian quaternion #52318" begin + A, B = [Quaternion.(randn(3,3), randn(3, 3), randn(3, 3), randn(3,3)) |> t -> t + t' for i in 1:2] + @test A == Hermitian(A) && B == Hermitian(B) + @test dot(A, B) ≈ dot(Hermitian(A), Hermitian(B)) + A, B = [Quaternion.(randn(3,3), randn(3, 3), randn(3, 3), randn(3,3)) |> t -> t + transpose(t) for i in 1:2] + @test A == Symmetric(A) && B == Symmetric(B) + @test dot(A, B) ≈ dot(Symmetric(A), Symmetric(B)) +end + +# let's make sure the analogous bug will not show up with kronecker products +@testset "kron Hermitian quaternion #52318" begin + A, B = [Quaternion.(randn(3,3), randn(3, 3), randn(3, 3), randn(3,3)) |> t -> t + t' for i in 1:2] + @test A == Hermitian(A) && B == Hermitian(B) + @test kron(A, B) ≈ kron(Hermitian(A), Hermitian(B)) + A, B = [Quaternion.(randn(3,3), randn(3, 3), randn(3, 3), randn(3,3)) |> t -> t + transpose(t) for i in 1:2] + @test A == Symmetric(A) && B == Symmetric(B) + @test kron(A, B) ≈ kron(Symmetric(A), Symmetric(B)) +end + #Issue #7647: test xsyevr, xheevr, xstevr drivers. @testset "Eigenvalues in interval for $(typeof(Mi7647))" for Mi7647 in (Symmetric(diagm(0 => 1.0:3.0)), @@ -574,7 +664,6 @@ end end end -const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") isdefined(Main, :ImmutableArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "ImmutableArrays.jl")) using .Main.ImmutableArrays @@ -711,9 +800,9 @@ end end @testset "symmetric()/hermitian() for Numbers" begin - @test LinearAlgebra.symmetric(1, :U) == 1 + @test LinearAlgebra.symmetric(1) == LinearAlgebra.symmetric(1, :U) == 1 @test LinearAlgebra.symmetric_type(Int) == Int - @test LinearAlgebra.hermitian(1, :U) == 1 + @test LinearAlgebra.hermitian(1) == LinearAlgebra.hermitian(1, :U) == 1 @test LinearAlgebra.hermitian_type(Int) == Int end @@ -884,4 +973,56 @@ end end end +@testset "symmetric/hermitian for matrices" begin + A = [1 2; 3 4] + @test LinearAlgebra.symmetric(A) === Symmetric(A) + @test LinearAlgebra.symmetric(A, :L) === Symmetric(A, :L) + @test LinearAlgebra.hermitian(A) === Hermitian(A) + @test LinearAlgebra.hermitian(A, :L) === Hermitian(A, :L) +end + +@testset "custom axes" begin + SZA = SizedArrays.SizedArray{(2,2)}([1 2; 3 4]) + for T in (Symmetric, Hermitian) + S = T(SZA) + r = SizedArrays.SOneTo(2) + @test axes(S) === (r,r) + end +end + +@testset "Matrix elements" begin + M = [UpperTriangular([1 2; 3 4]) for i in 1:2, j in 1:2] + for T in (Symmetric, Hermitian) + H = T(M) + A = Array(H) + @test A isa Matrix + @test A == H + A = Array{Matrix{Int}}(H) + @test A isa Matrix{Matrix{Int}} + @test A == H + end +end + +@testset "conj for immutable" begin + S = Symmetric(reshape((1:16)*im, 4, 4)) + @test conj(S) == conj(Array(S)) + H = Hermitian(reshape((1:16)*im, 4, 4)) + @test conj(H) == conj(Array(H)) +end + +@testset "copyto! with aliasing (#39460)" begin + M = Matrix(reshape(1:36, 6, 6)) + @testset for T in (Symmetric, Hermitian), uploA in (:U, :L), uploB in (:U, :L) + A = T(view(M, 1:5, 1:5), uploA) + A2 = copy(A) + B = T(view(M, 2:6, 2:6), uploB) + @test copyto!(B, A) == A2 + + A = view(M, 2:4, 2:4) + B = T(view(M, 1:3, 1:3), uploB) + B2 = copy(B) + @test copyto!(A, B) == B2 + end +end + end # module TestSymmetric diff --git a/stdlib/LinearAlgebra/test/symmetriceigen.jl b/stdlib/LinearAlgebra/test/symmetriceigen.jl index c28c17255c222..b3a5472c511f4 100644 --- a/stdlib/LinearAlgebra/test/symmetriceigen.jl +++ b/stdlib/LinearAlgebra/test/symmetriceigen.jl @@ -8,7 +8,7 @@ using Test, LinearAlgebra ## Cholesky decomposition based # eigenvalue sorting - sf = x->(real(x),imag(x)) + sf = x->(imag(x),real(x)) ## Real valued A = Float64[1 1 0 0; 1 2 1 0; 0 1 3 1; 0 0 1 4] @@ -40,6 +40,9 @@ using Test, LinearAlgebra end @testset "issue #49533" begin + # eigenvalue sorting + sf = x->(imag(x),real(x)) + ## Real valued A = Float64[1 1 0 0; 1 2 1 0; 0 1 3 1; 0 0 1 4] B = Matrix(Diagonal(Float64[1:4;])) @@ -62,7 +65,6 @@ end B = [2.0+2.0im 1.0+1.0im 4.0+4.0im 3.0+3.0im; 0 3.0+2.0im 1.0+1.0im 3.0+4.0im; 3.0+3.0im 1.0+4.0im 0 0; 0 1.0+2.0im 3.0+1.0im 1.0+1.0im] BH = B'B # eigen - sf = x->(real(x),imag(x)) e1,v1 = eigen(A, Hermitian(BH)) @test A*v1 ≈ Hermitian(BH)*v1*Diagonal(e1) e2,v2 = eigen(Hermitian(AH), B) @@ -75,4 +77,78 @@ end @test eigvals(AH, BH; sortby=sf) ≈ eigvals(Hermitian(AH), Hermitian(BH); sortby=sf) end +@testset "bk-lu-eigen-eigvals" begin + # Bunchkaufman decomposition based + + # eigenvalue sorting + sf = x->(imag(x),real(x)) + + # Real-valued random matrix + N = 10 + A = randn(N,N) + B = randn(N,N) + BH = (B+B')/2 + # eigen + e0 = eigvals(A,BH; sortby=sf) + e,v = eigen(A,bunchkaufman(Hermitian(BH,:L)); sortby=sf) + @test e0 ≈ e + @test A*v ≈ BH*v*Diagonal(e) + e,v = eigen(A,bunchkaufman(Hermitian(BH,:U)); sortby=sf) + @test e0 ≈ e + @test A*v ≈ BH*v*Diagonal(e) + e,v = eigen(A,lu(Hermitian(BH,:L)); sortby=sf) + @test e0 ≈ e + @test A*v ≈ BH*v*Diagonal(e) + e,v = eigen(A,lu(Hermitian(BH,:U)); sortby=sf) + @test e0 ≈ e + @test A*v ≈ BH*v*Diagonal(e) + # eigvals + e0 = eigvals(A,BH; sortby=sf) + el = eigvals(A,bunchkaufman(Hermitian(BH,:L)); sortby=sf) + eu = eigvals(A,bunchkaufman(Hermitian(BH,:U)); sortby=sf) + @test e0 ≈ el + @test e0 ≈ eu + el = eigvals(A,lu(Hermitian(BH,:L)); sortby=sf) + eu = eigvals(A,lu(Hermitian(BH,:U)); sortby=sf) + @test e0 ≈ el + @test e0 ≈ eu + + # Complex-valued random matrix + N = 10 + A = complex.(randn(N,N),randn(N,N)) + B = complex.(randn(N,N),randn(N,N)) + BH = (B+B')/2 + # eigen + e0 = eigvals(A,BH; sortby=sf) + e,v = eigen(A,bunchkaufman(Hermitian(BH,:L)); sortby=sf) + @test e0 ≈ e + @test A*v ≈ BH*v*Diagonal(e) + e,v = eigen(A,bunchkaufman(Hermitian(BH,:U)); sortby=sf) + @test e0 ≈ e + @test A*v ≈ BH*v*Diagonal(e) + e,v = eigen(A,lu(Hermitian(BH,:L)); sortby=sf) + @test e0 ≈ e + @test A*v ≈ BH*v*Diagonal(e) + e,v = eigen(A,lu(Hermitian(BH,:U)); sortby=sf) + @test e0 ≈ e + @test A*v ≈ BH*v*Diagonal(e) + # eigvals + e0 = eigvals(A,BH; sortby=sf) + el = eigvals(A,bunchkaufman(Hermitian(BH,:L)); sortby=sf) + eu = eigvals(A,bunchkaufman(Hermitian(BH,:U)); sortby=sf) + @test e0 ≈ el + @test e0 ≈ eu + el = eigvals(A,lu(Hermitian(BH,:L)); sortby=sf) + eu = eigvals(A,lu(Hermitian(BH,:U)); sortby=sf) + @test e0 ≈ el + @test e0 ≈ eu +end + +@testset "Hermitian tridiagonal eigen with Complex{Int} elements (#52801)" begin + dv, ev = fill(complex(2), 4), fill(3-4im, 3) + HT = Hermitian(Tridiagonal(ev, dv, ev)) + λ, V = eigen(HT) + @test HT * V ≈ V * Diagonal(λ) +end + end # module TestSymmetricEigen diff --git a/stdlib/LinearAlgebra/test/triangular.jl b/stdlib/LinearAlgebra/test/triangular.jl index aaf433c95b7b0..9c23ec92fdc74 100644 --- a/stdlib/LinearAlgebra/test/triangular.jl +++ b/stdlib/LinearAlgebra/test/triangular.jl @@ -8,6 +8,14 @@ using LinearAlgebra: BlasFloat, errorbounds, full!, transpose!, UnitUpperTriangular, UnitLowerTriangular, mul!, rdiv!, rmul!, lmul! +const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") + +isdefined(Main, :SizedArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "SizedArrays.jl")) +using .Main.SizedArrays + +isdefined(Main, :FillArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "FillArrays.jl")) +using .Main.FillArrays + debug && println("Triangular matrices") n = 9 @@ -18,7 +26,7 @@ debug && println("Test basic type functionality") @test LowerTriangular(randn(3, 3)) |> t -> [size(t, i) for i = 1:3] == [size(Matrix(t), i) for i = 1:3] # The following test block tries to call all methods in base/linalg/triangular.jl in order for a combination of input element types. Keep the ordering when adding code. -for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFloat}, Int) +@testset for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFloat}, Int) # Begin loop for first Triangular matrix for (t1, uplo1) in ((UpperTriangular, :U), (UnitUpperTriangular, :U), @@ -27,6 +35,7 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo # Construct test matrix A1 = t1(elty1 == Int ? rand(1:7, n, n) : convert(Matrix{elty1}, (elty1 <: Complex ? complex.(randn(n, n), randn(n, n)) : randn(n, n)) |> t -> cholesky(t't).U |> t -> uplo1 === :U ? t : copy(t'))) + M1 = Matrix(A1) @test t1(A1) === A1 @test t1{elty1}(A1) === A1 # test the ctor works for AbstractMatrix @@ -60,7 +69,7 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo @test simA1 == A1 # getindex - let mA1 = Matrix(A1) + let mA1 = M1 # linear indexing for i in 1:length(A1) @test A1[i] == mA1[i] @@ -133,8 +142,8 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo #tril/triu if uplo1 === :L @test tril(A1,0) == A1 - @test tril(A1,-1) == LowerTriangular(tril(Matrix(A1), -1)) - @test tril(A1,1) == t1(tril(tril(Matrix(A1), 1))) + @test tril(A1,-1) == LowerTriangular(tril(M1, -1)) + @test tril(A1,1) == t1(tril(tril(M1, 1))) @test tril(A1, -n - 2) == zeros(size(A1)) @test tril(A1, n) == A1 @test triu(A1,0) == t1(diagm(0 => diag(A1))) @@ -144,8 +153,8 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo @test triu(A1, n + 2) == zeros(size(A1)) else @test triu(A1,0) == A1 - @test triu(A1,1) == UpperTriangular(triu(Matrix(A1), 1)) - @test triu(A1,-1) == t1(triu(triu(Matrix(A1), -1))) + @test triu(A1,1) == UpperTriangular(triu(M1, 1)) + @test triu(A1,-1) == t1(triu(triu(M1, -1))) @test triu(A1, -n) == A1 @test triu(A1, n + 2) == zeros(size(A1)) @test tril(A1,0) == t1(diagm(0 => diag(A1))) @@ -161,10 +170,10 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo # [c]transpose[!] (test views as well, see issue #14317) let vrange = 1:n-1, viewA1 = t1(view(A1.data, vrange, vrange)) # transpose - @test copy(transpose(A1)) == transpose(Matrix(A1)) + @test copy(transpose(A1)) == transpose(M1) @test copy(transpose(viewA1)) == transpose(Matrix(viewA1)) # adjoint - @test copy(A1') == Matrix(A1)' + @test copy(A1') == M1' @test copy(viewA1') == Matrix(viewA1)' # transpose! @test transpose!(copy(A1)) == transpose(A1) @@ -177,23 +186,28 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo end # diag - @test diag(A1) == diag(Matrix(A1)) + @test diag(A1) == diag(M1) # tr - @test tr(A1)::elty1 == tr(Matrix(A1)) + @test tr(A1)::elty1 == tr(M1) # real - @test real(A1) == real(Matrix(A1)) - @test imag(A1) == imag(Matrix(A1)) - @test abs.(A1) == abs.(Matrix(A1)) + @test real(A1) == real(M1) + @test imag(A1) == imag(M1) + @test abs.(A1) == abs.(M1) + + # zero + if A1 isa UpperTriangular || A1 isa LowerTriangular + @test zero(A1) == zero(parent(A1)) + end # Unary operations - @test -A1 == -Matrix(A1) + @test -A1 == -M1 # copy and copyto! (test views as well, see issue #14317) let vrange = 1:n-1, viewA1 = t1(view(A1.data, vrange, vrange)) # copy - @test copy(A1) == copy(Matrix(A1)) + @test copy(A1) == copy(M1) @test copy(viewA1) == copy(Matrix(viewA1)) # copyto! B = similar(A1) @@ -239,6 +253,11 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo A2tmp = unitt(A1) mul!(A1tmp, cr, A2tmp) @test A1tmp == cr * A2tmp + + A1tmp .= A1 + @test mul!(A1tmp, A2tmp, cr, 0, 2) == 2A1 + A1tmp .= A1 + @test mul!(A1tmp, cr, A2tmp, 0, 2) == 2A1 else A1tmp = copy(A1) rmul!(A1tmp, ci) @@ -265,25 +284,25 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo end # Binary operations - @test A1*0.5 == Matrix(A1)*0.5 - @test 0.5*A1 == 0.5*Matrix(A1) - @test A1/0.5 == Matrix(A1)/0.5 - @test 0.5\A1 == 0.5\Matrix(A1) + @test A1*0.5 == M1*0.5 + @test 0.5*A1 == 0.5*M1 + @test A1/0.5 == M1/0.5 + @test 0.5\A1 == 0.5\M1 # inversion - @test inv(A1) ≈ inv(lu(Matrix(A1))) - inv(Matrix(A1)) # issue #11298 + @test inv(A1) ≈ inv(lu(M1)) + inv(M1) # issue #11298 @test isa(inv(A1), t1) # make sure the call to LAPACK works right if elty1 <: BlasFloat - @test LinearAlgebra.inv!(copy(A1)) ≈ inv(lu(Matrix(A1))) + @test LinearAlgebra.inv!(copy(A1)) ≈ inv(lu(M1)) end # Determinant - @test det(A1) ≈ det(lu(Matrix(A1))) atol=sqrt(eps(real(float(one(elty1)))))*n*n - @test logdet(A1) ≈ logdet(lu(Matrix(A1))) atol=sqrt(eps(real(float(one(elty1)))))*n*n + @test det(A1) ≈ det(lu(M1)) atol=sqrt(eps(real(float(one(elty1)))))*n*n + @test logdet(A1) ≈ logdet(lu(M1)) atol=sqrt(eps(real(float(one(elty1)))))*n*n lada, ladb = logabsdet(A1) - flada, fladb = logabsdet(lu(Matrix(A1))) + flada, fladb = logabsdet(lu(M1)) @test lada ≈ flada atol=sqrt(eps(real(float(one(elty1)))))*n*n @test ladb ≈ fladb atol=sqrt(eps(real(float(one(elty1)))))*n*n @@ -306,7 +325,7 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo for p in (1.0, Inf) @test cond(A1,p) ≈ cond(A1,p) atol=(cond(A1,p)+cond(A1,p)) end - @test cond(A1,2) == cond(Matrix(A1),2) + @test cond(A1,2) == cond(M1,2) end if !(elty1 in (BigFloat, Complex{BigFloat})) # Not implemented yet @@ -315,9 +334,9 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo svdvals(A1) end - @test ((A1*A1)::t1) ≈ Matrix(A1) * Matrix(A1) - @test ((A1/A1)::t1) ≈ Matrix(A1) / Matrix(A1) - @test ((A1\A1)::t1) ≈ Matrix(A1) \ Matrix(A1) + @test ((A1*A1)::t1) ≈ M1 * M1 + @test ((A1/A1)::t1) ≈ M1 / M1 + @test ((A1\A1)::t1) ≈ M1 \ M1 # Begin loop for second Triangular matrix for elty2 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFloat}, Int) @@ -329,7 +348,7 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo debug && println("elty1: $elty1, A1: $t1, elty2: $elty2, A2: $t2") A2 = t2(elty2 == Int ? rand(1:7, n, n) : convert(Matrix{elty2}, (elty2 <: Complex ? complex.(randn(n, n), randn(n, n)) : randn(n, n)) |> t -> cholesky(t't).U |> t -> uplo2 === :U ? t : copy(t'))) - + M2 = Matrix(A2) # Convert if elty1 <: Real && !(elty2 <: Integer) @test convert(AbstractMatrix{elty2}, A1) == t1(convert(Matrix{elty2}, A1.data)) @@ -338,21 +357,22 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo end # Binary operations - @test A1 + A2 == Matrix(A1) + Matrix(A2) - @test A1 - A2 == Matrix(A1) - Matrix(A2) + @test A1 + A2 == M1 + M2 + @test A1 - A2 == M1 - M2 + @test kron(A1,A2) == kron(M1,M2) # Triangular-Triangular multiplication and division - @test A1*A2 ≈ Matrix(A1)*Matrix(A2) - @test transpose(A1)*A2 ≈ transpose(Matrix(A1))*Matrix(A2) - @test transpose(A1)*adjoint(A2) ≈ transpose(Matrix(A1))*adjoint(Matrix(A2)) - @test adjoint(A1)*transpose(A2) ≈ adjoint(Matrix(A1))*transpose(Matrix(A2)) - @test A1'A2 ≈ Matrix(A1)'Matrix(A2) - @test A1*transpose(A2) ≈ Matrix(A1)*transpose(Matrix(A2)) - @test A1*A2' ≈ Matrix(A1)*Matrix(A2)' - @test transpose(A1)*transpose(A2) ≈ transpose(Matrix(A1))*transpose(Matrix(A2)) - @test A1'A2' ≈ Matrix(A1)'Matrix(A2)' - @test A1/A2 ≈ Matrix(A1)/Matrix(A2) - @test A1\A2 ≈ Matrix(A1)\Matrix(A2) + @test A1*A2 ≈ M1*M2 + @test transpose(A1)*A2 ≈ transpose(M1)*M2 + @test transpose(A1)*adjoint(A2) ≈ transpose(M1)*adjoint(M2) + @test adjoint(A1)*transpose(A2) ≈ adjoint(M1)*transpose(M2) + @test A1'A2 ≈ M1'M2 + @test A1*transpose(A2) ≈ M1*transpose(M2) + @test A1*A2' ≈ M1*M2' + @test transpose(A1)*transpose(A2) ≈ transpose(M1)*transpose(M2) + @test A1'A2' ≈ M1'M2' + @test A1/A2 ≈ M1/M2 + @test A1\A2 ≈ M1\M2 if uplo1 === :U && uplo2 === :U if t1 === UnitUpperTriangular && t2 === UnitUpperTriangular @test A1*A2 isa UnitUpperTriangular @@ -393,20 +413,20 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo @test_throws DimensionMismatch A2' * offsizeA @test_throws DimensionMismatch A2 * offsizeA if (uplo1 == uplo2 && elty1 == elty2 != Int && t1 != UnitLowerTriangular && t1 != UnitUpperTriangular) - @test rdiv!(copy(A1), A2)::t1 ≈ A1/A2 ≈ Matrix(A1)/Matrix(A2) - @test ldiv!(A2, copy(A1))::t1 ≈ A2\A1 ≈ Matrix(A2)\Matrix(A1) + @test rdiv!(copy(A1), A2)::t1 ≈ A1/A2 ≈ M1/M2 + @test ldiv!(A2, copy(A1))::t1 ≈ A2\A1 ≈ M2\M1 end if (uplo1 != uplo2 && elty1 == elty2 != Int && t2 != UnitLowerTriangular && t2 != UnitUpperTriangular) - @test lmul!(adjoint(A1), copy(A2)) ≈ A1'*A2 ≈ Matrix(A1)'*Matrix(A2) - @test lmul!(transpose(A1), copy(A2)) ≈ transpose(A1)*A2 ≈ transpose(Matrix(A1))*Matrix(A2) - @test ldiv!(adjoint(A1), copy(A2)) ≈ A1'\A2 ≈ Matrix(A1)'\Matrix(A2) - @test ldiv!(transpose(A1), copy(A2)) ≈ transpose(A1)\A2 ≈ transpose(Matrix(A1))\Matrix(A2) + @test lmul!(adjoint(A1), copy(A2)) ≈ A1'*A2 ≈ M1'*M2 + @test lmul!(transpose(A1), copy(A2)) ≈ transpose(A1)*A2 ≈ transpose(M1)*M2 + @test ldiv!(adjoint(A1), copy(A2)) ≈ A1'\A2 ≈ M1'\M2 + @test ldiv!(transpose(A1), copy(A2)) ≈ transpose(A1)\A2 ≈ transpose(M1)\M2 end if (uplo1 != uplo2 && elty1 == elty2 != Int && t1 != UnitLowerTriangular && t1 != UnitUpperTriangular) - @test rmul!(copy(A1), adjoint(A2)) ≈ A1*A2' ≈ Matrix(A1)*Matrix(A2)' - @test rmul!(copy(A1), transpose(A2)) ≈ A1*transpose(A2) ≈ Matrix(A1)*transpose(Matrix(A2)) - @test rdiv!(copy(A1), adjoint(A2)) ≈ A1/A2' ≈ Matrix(A1)/Matrix(A2)' - @test rdiv!(copy(A1), transpose(A2)) ≈ A1/transpose(A2) ≈ Matrix(A1)/transpose(Matrix(A2)) + @test rmul!(copy(A1), adjoint(A2)) ≈ A1*A2' ≈ M1*M2' + @test rmul!(copy(A1), transpose(A2)) ≈ A1*transpose(A2) ≈ M1*transpose(M2) + @test rdiv!(copy(A1), adjoint(A2)) ≈ A1/A2' ≈ M1/M2' + @test rdiv!(copy(A1), transpose(A2)) ≈ A1/transpose(A2) ≈ M1/transpose(M2) end end end @@ -417,55 +437,55 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo debug && println("elty1: $elty1, A1: $t1, B: $eltyB") Tri = Tridiagonal(rand(eltyB,n-1),rand(eltyB,n),rand(eltyB,n-1)) - @test lmul!(Tri,copy(A1)) ≈ Tri*Matrix(A1) + @test lmul!(Tri,copy(A1)) ≈ Tri*M1 Tri = Tridiagonal(rand(eltyB,n-1),rand(eltyB,n),rand(eltyB,n-1)) C = Matrix{promote_type(elty1,eltyB)}(undef, n, n) mul!(C, Tri, A1) - @test C ≈ Tri*Matrix(A1) + @test C ≈ Tri*M1 Tri = Tridiagonal(rand(eltyB,n-1),rand(eltyB,n),rand(eltyB,n-1)) mul!(C, A1, Tri) - @test C ≈ Matrix(A1)*Tri + @test C ≈ M1*Tri # Triangular-dense Matrix/vector multiplication - @test A1*B[:,1] ≈ Matrix(A1)*B[:,1] - @test A1*B ≈ Matrix(A1)*B - @test transpose(A1)*B[:,1] ≈ transpose(Matrix(A1))*B[:,1] - @test A1'B[:,1] ≈ Matrix(A1)'B[:,1] - @test transpose(A1)*B ≈ transpose(Matrix(A1))*B - @test A1'B ≈ Matrix(A1)'B - @test A1*transpose(B) ≈ Matrix(A1)*transpose(B) - @test adjoint(A1)*transpose(B) ≈ Matrix(A1)'*transpose(B) - @test transpose(A1)*adjoint(B) ≈ transpose(Matrix(A1))*adjoint(B) - @test A1*B' ≈ Matrix(A1)*B' - @test B*A1 ≈ B*Matrix(A1) - @test transpose(B[:,1])*A1 ≈ transpose(B[:,1])*Matrix(A1) - @test B[:,1]'A1 ≈ B[:,1]'Matrix(A1) - @test transpose(B)*A1 ≈ transpose(B)*Matrix(A1) - @test transpose(B)*adjoint(A1) ≈ transpose(B)*Matrix(A1)' - @test adjoint(B)*transpose(A1) ≈ adjoint(B)*transpose(Matrix(A1)) - @test B'A1 ≈ B'Matrix(A1) - @test B*transpose(A1) ≈ B*transpose(Matrix(A1)) - @test B*A1' ≈ B*Matrix(A1)' - @test transpose(B[:,1])*transpose(A1) ≈ transpose(B[:,1])*transpose(Matrix(A1)) - @test B[:,1]'A1' ≈ B[:,1]'Matrix(A1)' - @test transpose(B)*transpose(A1) ≈ transpose(B)*transpose(Matrix(A1)) - @test B'A1' ≈ B'Matrix(A1)' + @test A1*B[:,1] ≈ M1*B[:,1] + @test A1*B ≈ M1*B + @test transpose(A1)*B[:,1] ≈ transpose(M1)*B[:,1] + @test A1'B[:,1] ≈ M1'B[:,1] + @test transpose(A1)*B ≈ transpose(M1)*B + @test A1'B ≈ M1'B + @test A1*transpose(B) ≈ M1*transpose(B) + @test adjoint(A1)*transpose(B) ≈ M1'*transpose(B) + @test transpose(A1)*adjoint(B) ≈ transpose(M1)*adjoint(B) + @test A1*B' ≈ M1*B' + @test B*A1 ≈ B*M1 + @test transpose(B[:,1])*A1 ≈ transpose(B[:,1])*M1 + @test B[:,1]'A1 ≈ B[:,1]'M1 + @test transpose(B)*A1 ≈ transpose(B)*M1 + @test transpose(B)*adjoint(A1) ≈ transpose(B)*M1' + @test adjoint(B)*transpose(A1) ≈ adjoint(B)*transpose(M1) + @test B'A1 ≈ B'M1 + @test B*transpose(A1) ≈ B*transpose(M1) + @test B*A1' ≈ B*M1' + @test transpose(B[:,1])*transpose(A1) ≈ transpose(B[:,1])*transpose(M1) + @test B[:,1]'A1' ≈ B[:,1]'M1' + @test transpose(B)*transpose(A1) ≈ transpose(B)*transpose(M1) + @test B'A1' ≈ B'M1' if eltyB == elty1 - @test mul!(similar(B), A1, B) ≈ Matrix(A1)*B - @test mul!(similar(B), A1, adjoint(B)) ≈ Matrix(A1)*B' - @test mul!(similar(B), A1, transpose(B)) ≈ Matrix(A1)*transpose(B) - @test mul!(similar(B), adjoint(A1), adjoint(B)) ≈ Matrix(A1)'*B' - @test mul!(similar(B), transpose(A1), transpose(B)) ≈ transpose(Matrix(A1))*transpose(B) - @test mul!(similar(B), transpose(A1), adjoint(B)) ≈ transpose(Matrix(A1))*B' - @test mul!(similar(B), adjoint(A1), transpose(B)) ≈ Matrix(A1)'*transpose(B) - @test mul!(similar(B), adjoint(A1), B) ≈ Matrix(A1)'*B - @test mul!(similar(B), transpose(A1), B) ≈ transpose(Matrix(A1))*B + @test mul!(similar(B), A1, B) ≈ M1*B + @test mul!(similar(B), A1, adjoint(B)) ≈ M1*B' + @test mul!(similar(B), A1, transpose(B)) ≈ M1*transpose(B) + @test mul!(similar(B), adjoint(A1), adjoint(B)) ≈ M1'*B' + @test mul!(similar(B), transpose(A1), transpose(B)) ≈ transpose(M1)*transpose(B) + @test mul!(similar(B), transpose(A1), adjoint(B)) ≈ transpose(M1)*B' + @test mul!(similar(B), adjoint(A1), transpose(B)) ≈ M1'*transpose(B) + @test mul!(similar(B), adjoint(A1), B) ≈ M1'*B + @test mul!(similar(B), transpose(A1), B) ≈ transpose(M1)*B # test also vector methods B1 = vec(B[1,:]) - @test mul!(similar(B1), A1, B1) ≈ Matrix(A1)*B1 - @test mul!(similar(B1), adjoint(A1), B1) ≈ Matrix(A1)'*B1 - @test mul!(similar(B1), transpose(A1), B1) ≈ transpose(Matrix(A1))*B1 + @test mul!(similar(B1), A1, B1) ≈ M1*B1 + @test mul!(similar(B1), adjoint(A1), B1) ≈ M1'*B1 + @test mul!(similar(B1), transpose(A1), B1) ≈ transpose(M1)*B1 end #error handling Ann, Bmm, bm = A1, Matrix{eltyB}(undef, n+1, n+1), Vector{eltyB}(undef, n+1) @@ -477,34 +497,30 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo @test_throws DimensionMismatch rmul!(Bmm, transpose(Ann)) # ... and division - @test A1\B[:,1] ≈ Matrix(A1)\B[:,1] - @test A1\B ≈ Matrix(A1)\B - @test transpose(A1)\B[:,1] ≈ transpose(Matrix(A1))\B[:,1] - @test A1'\B[:,1] ≈ Matrix(A1)'\B[:,1] - @test transpose(A1)\B ≈ transpose(Matrix(A1))\B - @test A1'\B ≈ Matrix(A1)'\B - @test A1\transpose(B) ≈ Matrix(A1)\transpose(B) - @test A1\B' ≈ Matrix(A1)\B' - @test transpose(A1)\transpose(B) ≈ transpose(Matrix(A1))\transpose(B) - @test A1'\B' ≈ Matrix(A1)'\B' + @test A1\B[:,1] ≈ M1\B[:,1] + @test A1\B ≈ M1\B + @test transpose(A1)\B[:,1] ≈ transpose(M1)\B[:,1] + @test A1'\B[:,1] ≈ M1'\B[:,1] + @test transpose(A1)\B ≈ transpose(M1)\B + @test A1'\B ≈ M1'\B + @test A1\transpose(B) ≈ M1\transpose(B) + @test A1\B' ≈ M1\B' + @test transpose(A1)\transpose(B) ≈ transpose(M1)\transpose(B) + @test A1'\B' ≈ M1'\B' Ann, bm = A1, Vector{elty1}(undef,n+1) @test_throws DimensionMismatch Ann\bm @test_throws DimensionMismatch Ann'\bm @test_throws DimensionMismatch transpose(Ann)\bm if t1 == UpperTriangular || t1 == LowerTriangular - if elty1 === eltyB <: BlasFloat - @test_throws LAPACKException ldiv!(t1(zeros(elty1, n, n)), fill(eltyB(1), n)) - else - @test_throws SingularException ldiv!(t1(zeros(elty1, n, n)), fill(eltyB(1), n)) - end + @test_throws SingularException ldiv!(t1(zeros(elty1, n, n)), fill(eltyB(1), n)) end - @test B/A1 ≈ B/Matrix(A1) - @test B/transpose(A1) ≈ B/transpose(Matrix(A1)) - @test B/A1' ≈ B/Matrix(A1)' - @test transpose(B)/A1 ≈ transpose(B)/Matrix(A1) - @test B'/A1 ≈ B'/Matrix(A1) - @test transpose(B)/transpose(A1) ≈ transpose(B)/transpose(Matrix(A1)) - @test B'/A1' ≈ B'/Matrix(A1)' + @test B/A1 ≈ B/M1 + @test B/transpose(A1) ≈ B/transpose(M1) + @test B/A1' ≈ B/M1' + @test transpose(B)/A1 ≈ transpose(B)/M1 + @test B'/A1 ≈ B'/M1 + @test transpose(B)/transpose(A1) ≈ transpose(B)/transpose(M1) + @test B'/A1' ≈ B'/M1' # Error bounds !(elty1 in (BigFloat, Complex{BigFloat})) && !(eltyB in (BigFloat, Complex{BigFloat})) && errorbounds(A1, A1\B, B) @@ -513,6 +529,23 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo end end +@testset "non-strided arithmetic" begin + for (T,T1) in ((UpperTriangular, UnitUpperTriangular), (LowerTriangular, UnitLowerTriangular)) + U = T(reshape(1:16, 4, 4)) + M = Matrix(U) + @test -U == -M + U1 = T1(reshape(1:16, 4, 4)) + M1 = Matrix(U1) + @test -U1 == -M1 + for op in (+, -) + for (A, MA) in ((U, M), (U1, M1)), (B, MB) in ((U, M), (U1, M1)) + @test op(A, B) == op(MA, MB) + end + end + @test imag(U) == zero(U) + end +end + # Matrix square root Atn = UpperTriangular([-1 1 2; 0 -2 2; 0 0 -3]) Atp = UpperTriangular([1 1 2; 0 2 2; 0 0 3]) @@ -564,7 +597,7 @@ end end end -@testset "check matrix logarithm type-inferrable" for elty in (Float32,Float64,ComplexF32,ComplexF64) +@testset "check matrix logarithm type-inferable" for elty in (Float32,Float64,ComplexF32,ComplexF64) A = UpperTriangular(exp(triu(randn(elty, n, n)))) @inferred Union{typeof(A),typeof(complex(A))} log(A) @test exp(Matrix(log(A))) ≈ A @@ -780,6 +813,14 @@ end end end +@testset "indexing partly initialized matrices" begin + M = Matrix{BigFloat}(undef, 2, 2) + U = UpperTriangular(M) + @test iszero(U[2,1]) + L = LowerTriangular(M) + @test iszero(L[1,2]) +end + @testset "special printing of Lower/UpperTriangular" begin @test occursin(r"3×3 (LinearAlgebra\.)?LowerTriangular{Int64, Matrix{Int64}}:\n 2 ⋅ ⋅\n 2 2 ⋅\n 2 2 2", sprint(show, MIME"text/plain"(), LowerTriangular(2ones(Int64,3,3)))) @@ -789,6 +830,11 @@ end sprint(show, MIME"text/plain"(), UpperTriangular(2ones(Int64,3,3)))) @test occursin(r"3×3 (LinearAlgebra\.)?UnitUpperTriangular{Int64, Matrix{Int64}}:\n 1 2 2\n ⋅ 1 2\n ⋅ ⋅ 1", sprint(show, MIME"text/plain"(), UnitUpperTriangular(2ones(Int64,3,3)))) + + # don't access non-structural elements while displaying + M = Matrix{BigFloat}(undef, 2, 2) + @test sprint(show, UpperTriangular(M)) == "BigFloat[#undef #undef; 0.0 #undef]" + @test sprint(show, LowerTriangular(M)) == "BigFloat[#undef 0.0; #undef #undef]" end @testset "adjoint/transpose triangular/vector multiplication" begin @@ -866,4 +912,146 @@ end end end +@testset "tril!/triu! for non-bitstype matrices" begin + @testset "numeric" begin + M = Matrix{BigFloat}(undef, 3, 3) + tril!(M) + L = LowerTriangular(ones(3,3)) + copytrito!(M, L, 'L') + @test M == L + + M = Matrix{BigFloat}(undef, 3, 3) + triu!(M) + U = UpperTriangular(ones(3,3)) + copytrito!(M, U, 'U') + @test M == U + end + @testset "array elements" begin + M = fill(ones(2,2), 4, 4) + tril!(M) + L = LowerTriangular(fill(fill(2,2,2),4,4)) + copytrito!(M, L, 'L') + @test M == L + + M = fill(ones(2,2), 4, 4) + triu!(M) + U = UpperTriangular(fill(fill(2,2,2),4,4)) + copytrito!(M, U, 'U') + @test M == U + end +end + +@testset "avoid matmul ambiguities with ::MyMatrix * ::AbstractMatrix" begin + A = [i+j for i in 1:2, j in 1:2] + S = SizedArrays.SizedArray{(2,2)}(A) + U = UpperTriangular(ones(2,2)) + @test S * U == A * U + @test U * S == U * A + C1, C2 = zeros(2,2), zeros(2,2) + @test mul!(C1, S, U) == mul!(C2, A, U) + @test mul!(C1, S, U, 1, 2) == mul!(C2, A, U, 1 ,2) + @test mul!(C1, U, S) == mul!(C2, U, A) + @test mul!(C1, U, S, 1, 2) == mul!(C2, U, A, 1 ,2) + + v = [i for i in 1:2] + sv = SizedArrays.SizedArray{(2,)}(v) + @test U * sv == U * v + C1, C2 = zeros(2), zeros(2) + @test mul!(C1, U, sv) == mul!(C2, U, v) + @test mul!(C1, U, sv, 1, 2) == mul!(C2, U, v, 1 ,2) +end + +@testset "custom axes" begin + SZA = SizedArrays.SizedArray{(2,2)}([1 2; 3 4]) + for T in (UpperTriangular, LowerTriangular, UnitUpperTriangular, UnitLowerTriangular) + S = T(SZA) + r = SizedArrays.SOneTo(2) + @test axes(S) === (r,r) + end +end + +@testset "arithmetic with an immutable parent" begin + F = FillArrays.Fill(2, (4,4)) + for UT in (UnitUpperTriangular, UnitLowerTriangular) + U = UT(F) + @test -U == -Array(U) + end + + F = FillArrays.Fill(3im, (4,4)) + for U in (UnitUpperTriangular(F), UnitLowerTriangular(F)) + @test imag(F) == imag(collect(F)) + end +end + +@testset "error paths" begin + A = zeros(1,1); B = zeros(2,2) + @testset "inplace mul scaling with incompatible sizes" begin + for T in (UpperTriangular, LowerTriangular, UnitUpperTriangular, UnitLowerTriangular) + @test_throws DimensionMismatch mul!(T(A), T(B), 3) + @test_throws DimensionMismatch mul!(T(A), 3, T(B)) + end + end + @testset "copyto with incompatible sizes" begin + for T in (UpperTriangular, LowerTriangular, UnitUpperTriangular, UnitLowerTriangular) + @test_throws BoundsError copyto!(T(A), T(B)) + end + end +end + +@testset "arithmetic with partly uninitialized matrices" begin + @testset "$(typeof(A))" for A in (Matrix{BigFloat}(undef,2,2), Matrix{Complex{BigFloat}}(undef,2,2)') + A[2,1] = eltype(A) <: Complex ? 4 + 3im : 4 + B = Matrix{eltype(A)}(undef, size(A)) + for MT in (LowerTriangular, UnitLowerTriangular) + if MT == LowerTriangular + A[1,1] = A[2,2] = eltype(A) <: Complex ? 4 + 3im : 4 + end + L = MT(A) + B .= 0 + copyto!(B, L) + @test copy(L) == B + @test L * 2 == 2 * L == 2B + @test L/2 == B/2 + @test 2\L == 2\B + @test real(L) == real(B) + @test imag(L) == imag(B) + @test kron(L,L) == kron(B,B) + @test transpose!(MT(copy(A))) == transpose(L) broken=!(A isa Matrix) + @test adjoint!(MT(copy(A))) == adjoint(L) broken=!(A isa Matrix) + end + end + + @testset "$(typeof(A))" for A in (Matrix{BigFloat}(undef,2,2), Matrix{Complex{BigFloat}}(undef,2,2)') + A[1,2] = eltype(A) <: Complex ? 4 + 3im : 4 + B = Matrix{eltype(A)}(undef, size(A)) + for MT in (UpperTriangular, UnitUpperTriangular) + if MT == UpperTriangular + A[1,1] = A[2,2] = eltype(A) <: Complex ? 4 + 3im : 4 + end + U = MT(A) + B .= 0 + copyto!(B, U) + @test copy(U) == B + @test U * 2 == 2 * U == 2B + @test U/2 == B/2 + @test 2\U == 2\B + @test real(U) == real(B) + @test imag(U) == imag(B) + @test kron(U,U) == kron(B,B) + @test transpose!(MT(copy(A))) == transpose(U) broken=!(A isa Matrix) + @test adjoint!(MT(copy(A))) == adjoint(U) broken=!(A isa Matrix) + end + end +end + +@testset "copyto! with aliasing (#39460)" begin + M = Matrix(reshape(1:36, 6, 6)) + @testset for T in (UpperTriangular, LowerTriangular) + A = T(view(M, 1:5, 1:5)) + A2 = copy(A) + B = T(view(M, 2:6, 2:6)) + @test copyto!(B, A) == A2 + end +end + end # module TestTriangular diff --git a/stdlib/LinearAlgebra/test/tridiag.jl b/stdlib/LinearAlgebra/test/tridiag.jl index 68a792b55d396..4878ab689985c 100644 --- a/stdlib/LinearAlgebra/test/tridiag.jl +++ b/stdlib/LinearAlgebra/test/tridiag.jl @@ -15,6 +15,9 @@ using .Main.InfiniteArrays isdefined(Main, :FillArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "FillArrays.jl")) using .Main.FillArrays +isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "OffsetArrays.jl")) +using .Main.OffsetArrays + include("testutils.jl") # test_approx_eq_modphase #Test equivalence of eigenvectors/singular vectors taking into account possible phase (sign) differences @@ -82,7 +85,7 @@ end @test TT == Matrix(TT) @test TT.dl === y @test TT.d === x - @test TT.du === y + @test TT.du == y @test typeof(TT)(TT) === TT end ST = SymTridiagonal{elty}([1,2,3,4], [1,2,3]) @@ -95,12 +98,12 @@ end @test isa(ST, SymTridiagonal{elty,Vector{elty}}) TT = Tridiagonal{elty,Vector{elty}}(GenericArray(dl), d, GenericArray(dl)) @test isa(TT, Tridiagonal{elty,Vector{elty}}) - @test_throws MethodError SymTridiagonal(d, GenericArray(dl)) - @test_throws MethodError SymTridiagonal(GenericArray(d), dl) - @test_throws MethodError Tridiagonal(GenericArray(dl), d, GenericArray(dl)) - @test_throws MethodError Tridiagonal(dl, GenericArray(d), dl) - @test_throws MethodError SymTridiagonal{elty}(d, GenericArray(dl)) - @test_throws MethodError Tridiagonal{elty}(GenericArray(dl), d,GenericArray(dl)) + @test_throws ArgumentError SymTridiagonal(d, GenericArray(dl)) + @test_throws ArgumentError SymTridiagonal(GenericArray(d), dl) + @test_throws ArgumentError Tridiagonal(GenericArray(dl), d, GenericArray(dl)) + @test_throws ArgumentError Tridiagonal(dl, GenericArray(d), dl) + @test_throws ArgumentError SymTridiagonal{elty}(d, GenericArray(dl)) + @test_throws ArgumentError Tridiagonal{elty}(GenericArray(dl), d,GenericArray(dl)) STI = SymTridiagonal([1,2,3,4], [1,2,3]) TTI = Tridiagonal([1,2,3], [1,2,3,4], [1,2,3]) TTI2 = Tridiagonal([1,2,3], [1,2,3,4], [1,2,3], [1,2]) @@ -505,6 +508,11 @@ end @test SymTridiagonal([1, 2], [0])^3 == [1 0; 0 8] end +@testset "Issue #48505" begin + @test SymTridiagonal([1,2,3],[4,5.0]) == [1.0 4.0 0.0; 4.0 2.0 5.0; 0.0 5.0 3.0] + @test Tridiagonal([1, 2], [4, 5, 1], [6.0, 7]) == [4.0 6.0 0.0; 1.0 5.0 7.0; 0.0 2.0 1.0] +end + @testset "convert for SymTridiagonal" begin STF32 = SymTridiagonal{Float32}(fill(1f0, 5), fill(1f0, 4)) @test convert(SymTridiagonal{Float64}, STF32)::SymTridiagonal{Float64} == STF32 @@ -521,6 +529,14 @@ end @test Tridiagonal(4:5, 1:3, 1:2) == [1 1 0; 4 2 2; 0 5 3] end +@testset "Prevent off-diagonal aliasing in Tridiagonal" begin + e = ones(4) + f = e[1:end-1] + T = Tridiagonal(f, 2e, f) + T ./= 10 + @test all(==(0.1), f) +end + @testset "Issue #26994 (and the empty case)" begin T = SymTridiagonal([1.0],[3.0]) x = ones(1) @@ -805,4 +821,21 @@ end end end +@testset "custom axes" begin + dv, uv = OffsetArray(1:4), OffsetArray(1:3) + B = Tridiagonal(uv, dv, uv) + ax = axes(dv, 1) + @test axes(B) === (ax, ax) + B = SymTridiagonal(dv, uv) + @test axes(B) === (ax, ax) +end + +@testset "Matrix conversion for non-numeric and undef" begin + T = Tridiagonal(fill(big(3), 3), Vector{BigInt}(undef, 4), fill(big(3), 3)) + M = Matrix(T) + T[diagind(T)] .= 4 + M[diagind(M)] .= 4 + @test diag(T) == diag(M) +end + end # module TestTridiagonal diff --git a/stdlib/LinearAlgebra/test/uniformscaling.jl b/stdlib/LinearAlgebra/test/uniformscaling.jl index 975cbf7bc59bc..92547e8648d8a 100644 --- a/stdlib/LinearAlgebra/test/uniformscaling.jl +++ b/stdlib/LinearAlgebra/test/uniformscaling.jl @@ -24,6 +24,7 @@ Random.seed!(1234543) @test -one(UniformScaling(2)) == UniformScaling(-1) @test opnorm(UniformScaling(1+im)) ≈ sqrt(2) @test convert(UniformScaling{Float64}, 2I) === 2.0I + @test float(2I) === 2.0*I end @testset "getindex" begin @@ -67,7 +68,7 @@ end # on complex plane J = UniformScaling(randn(ComplexF64)) - for f in ( exp, log, + for f in ( exp, log, cis, sqrt, sin, cos, tan, asin, acos, atan, @@ -80,6 +81,10 @@ end @test f(J) ≈ f(M(J)) end + for f in (sincos, sincosd) + @test all(splat(≈), zip(f(J), f(M(J)))) + end + # on real axis for (λ, fs) in ( # functions defined for x ∈ ℝ @@ -152,6 +157,7 @@ end @test α.\UniformScaling(α) == UniformScaling(1.0) @test α * UniformScaling(1.0) == UniformScaling(1.0) * α @test UniformScaling(α)/α == UniformScaling(1.0) + @test 2I//3 == (2//3)*I @test (2I)^α == (2I).^α == (2^α)I β = rand() diff --git a/stdlib/Logging/Project.toml b/stdlib/Logging/Project.toml index af931e68e07d1..3fc288e25f0b7 100644 --- a/stdlib/Logging/Project.toml +++ b/stdlib/Logging/Project.toml @@ -1,5 +1,9 @@ name = "Logging" uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" +version = "1.11.0" + +[deps] +StyledStrings = "f489334b-da3d-4c2e-b8f0-e476e12c162b" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/Logging/docs/src/index.md b/stdlib/Logging/docs/src/index.md index 55d24c7ae0a26..c2bde11720f4c 100644 --- a/stdlib/Logging/docs/src/index.md +++ b/stdlib/Logging/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/Logging/docs/src/index.md" +``` + # [Logging](@id man-logging) The [`Logging`](@ref Logging.Logging) module provides a way to record the history and progress of a @@ -298,6 +302,8 @@ Logging.Debug Logging.Info Logging.Warn Logging.Error +Logging.BelowMinLevel +Logging.AboveMaxLevel ``` ### [Processing events with AbstractLogger](@id AbstractLogger-interface) diff --git a/stdlib/Logging/src/ConsoleLogger.jl b/stdlib/Logging/src/ConsoleLogger.jl index 747f8a2b22966..08e4a8c6b2efe 100644 --- a/stdlib/Logging/src/ConsoleLogger.jl +++ b/stdlib/Logging/src/ConsoleLogger.jl @@ -12,10 +12,10 @@ Log levels less than `min_level` are filtered out. Message formatting can be controlled by setting keyword arguments: * `meta_formatter` is a function which takes the log event metadata - `(level, _module, group, id, file, line)` and returns a color (as would be - passed to printstyled), prefix and suffix for the log message. The - default is to prefix with the log level and a suffix containing the module, - file and line location. + `(level, _module, group, id, file, line)` and returns a face name (used in + the constructed [`AnnotatedString`](@ref Base.AnnotatedString)), prefix and + suffix for the log message. The default is to prefix with the log level and + a suffix containing the module, file and line location. * `show_limited` limits the printing of large data structures to something which can fit on the screen by setting the `:limit` `IOContext` key during formatting. @@ -58,10 +58,10 @@ end showvalue(io, ex::Exception) = showerror(io, ex) function default_logcolor(level::LogLevel) - level < Info ? Base.debug_color() : - level < Warn ? Base.info_color() : - level < Error ? Base.warn_color() : - Base.error_color() + level < Info ? :log_debug : + level < Warn ? :log_info : + level < Error ? :log_warn : + :log_error end function default_metafmt(level::LogLevel, _module, group, id, file, line) @@ -103,6 +103,8 @@ function termlength(str) return N end +termlength(str::Base.AnnotatedString) = textwidth(str) + function handle_message(logger::ConsoleLogger, level::LogLevel, message, _module, group, id, filepath, line; kwargs...) @nospecialize @@ -115,8 +117,17 @@ function handle_message(logger::ConsoleLogger, level::LogLevel, message, _module end # Generate a text representation of the message and all key value pairs, - # split into lines. - msglines = [(indent=0, msg=l) for l in split(chomp(convert(String, string(message))::String), '\n')] + # split into lines. This is specialised to improve type inference, + # and reduce the risk of resulting method invalidations. + message = string(message) + msglines = if Base._isannotated(message) && !isempty(Base.annotations(message)) + message = Base.AnnotatedString(String(message), Base.annotations(message)) + @NamedTuple{indent::Int, msg::Union{SubString{Base.AnnotatedString{String}}, SubString{String}}}[ + (indent=0, msg=l) for l in split(chomp(message), '\n')] + else + [(indent=0, msg=l) for l in split( + chomp(convert(String, message)::String), '\n')] + end stream::IO = logger.stream if !(isopen(stream)::Bool) stream = stderr @@ -145,6 +156,10 @@ function handle_message(logger::ConsoleLogger, level::LogLevel, message, _module # Format lines as text with appropriate indentation and with a box # decoration on the left. color, prefix, suffix = logger.meta_formatter(level, _module, group, id, filepath, line)::Tuple{Union{Symbol,Int},String,String} + lcolor = StyledStrings.Legacy.legacy_color(color) + if !isnothing(lcolor) + color = StyledStrings.Face(foreground=lcolor) + end minsuffixpad = 2 buf = IOBuffer() iob = IOContext(buf, stream) @@ -158,19 +173,19 @@ function handle_message(logger::ConsoleLogger, level::LogLevel, message, _module nonpadwidth = 2 + length(suffix) end for (i, (indent, msg)) in enumerate(msglines) - boxstr = length(msglines) == 1 ? "[ " : - i == 1 ? "┌ " : - i < length(msglines) ? "│ " : - "└ " - printstyled(iob, boxstr, bold=true, color=color) + boxstr = length(msglines) == 1 ? "[" : + i == 1 ? "┌" : + i < length(msglines) ? "│" : + "└" + print(iob, styled"{$color,bold:$boxstr} ") if i == 1 && !isempty(prefix) - printstyled(iob, prefix, " ", bold=true, color=color) + print(iob, styled"{$color,bold:$prefix} ") end print(iob, " "^indent, msg) if i == length(msglines) && !isempty(suffix) npad = max(0, justify_width - nonpadwidth) + minsuffixpad print(iob, " "^npad) - printstyled(iob, suffix, color=:light_black) + print(iob, styled"{shadow:$suffix}") end println(iob) end diff --git a/stdlib/Logging/src/Logging.jl b/stdlib/Logging/src/Logging.jl index 0743c650326cc..3822bde2e630b 100644 --- a/stdlib/Logging/src/Logging.jl +++ b/stdlib/Logging/src/Logging.jl @@ -8,11 +8,13 @@ and available by default. """ module Logging +using StyledStrings + # Import the CoreLogging implementation into Logging as new const bindings. # Doing it this way (rather than with import) makes these symbols accessible to # tab completion. for sym in [ - :LogLevel, :BelowMinLevel, :AboveMaxLevel, + :LogLevel, :AbstractLogger, :NullLogger, :handle_message, :shouldlog, :min_enabled_level, :catch_exceptions, @@ -54,6 +56,18 @@ const Warn = Base.CoreLogging.Warn Alias for [`LogLevel(2000)`](@ref LogLevel). """ const Error = Base.CoreLogging.Error +""" + BelowMinLevel + +Alias for [`LogLevel(-1_000_001)`](@ref LogLevel). +""" +const BelowMinLevel = Base.CoreLogging.BelowMinLevel +""" + AboveMaxLevel + +Alias for [`LogLevel(1_000_001)`](@ref LogLevel). +""" +const AboveMaxLevel = Base.CoreLogging.AboveMaxLevel using Base.CoreLogging: closed_stream diff --git a/stdlib/Logging/test/runtests.jl b/stdlib/Logging/test/runtests.jl index b6b4813964536..a244facee3468 100644 --- a/stdlib/Logging/test/runtests.jl +++ b/stdlib/Logging/test/runtests.jl @@ -6,6 +6,10 @@ import Logging: min_enabled_level, shouldlog, handle_message @noinline func1() = backtrace() +# see "custom log macro" testset +CustomLog = LogLevel(-500) +macro customlog(exs...) Base.CoreLogging.logmsg_code((Base.CoreLogging.@_sourceinfo)..., esc(CustomLog), exs...) end + @testset "Logging" begin @testset "Core" begin @@ -48,26 +52,35 @@ end end @test String(take!(buf)) == "" + # Check that the AnnotatedString path works too + with_logger(logger) do + @info Base.AnnotatedString("test") + end + @test String(take!(buf)) == + """ + [ Info: test + """ + @testset "Default metadata formatting" begin @test Logging.default_metafmt(Logging.Debug, Base, :g, :i, expanduser("~/somefile.jl"), 42) == - (:blue, "Debug:", "@ Base ~/somefile.jl:42") + (:log_debug, "Debug:", "@ Base ~/somefile.jl:42") @test Logging.default_metafmt(Logging.Info, Main, :g, :i, "a.jl", 1) == - (:cyan, "Info:", "") + (:log_info, "Info:", "") @test Logging.default_metafmt(Logging.Warn, Main, :g, :i, "b.jl", 2) == - (:yellow, "Warning:", "@ Main b.jl:2") + (:log_warn, "Warning:", "@ Main b.jl:2") @test Logging.default_metafmt(Logging.Error, Main, :g, :i, "", 0) == - (:light_red, "Error:", "@ Main :0") + (:log_error, "Error:", "@ Main :0") # formatting of nothing @test Logging.default_metafmt(Logging.Warn, nothing, :g, :i, "b.jl", 2) == - (:yellow, "Warning:", "@ b.jl:2") + (:log_warn, "Warning:", "@ b.jl:2") @test Logging.default_metafmt(Logging.Warn, Main, :g, :i, nothing, 2) == - (:yellow, "Warning:", "@ Main") + (:log_warn, "Warning:", "@ Main") @test Logging.default_metafmt(Logging.Warn, Main, :g, :i, "b.jl", nothing) == - (:yellow, "Warning:", "@ Main b.jl") + (:log_warn, "Warning:", "@ Main b.jl") @test Logging.default_metafmt(Logging.Warn, nothing, :g, :i, nothing, 2) == - (:yellow, "Warning:", "") + (:log_warn, "Warning:", "") @test Logging.default_metafmt(Logging.Warn, Main, :g, :i, "b.jl", 2:5) == - (:yellow, "Warning:", "@ Main b.jl:2-5") + (:log_warn, "Warning:", "@ Main b.jl:2-5") end function dummy_metafmt(level, _module, group, id, file, line) @@ -252,9 +265,9 @@ end # Basic colorization test @test genmsg("line1\nline2", color=true) == """ - \e[36m\e[1m┌ \e[22m\e[39m\e[36m\e[1mPREFIX \e[22m\e[39mline1 - \e[36m\e[1m│ \e[22m\e[39mline2 - \e[36m\e[1m└ \e[22m\e[39m\e[90mSUFFIX\e[39m + \e[36m\e[1m┌\e[39m\e[22m \e[36m\e[1mPREFIX\e[39m\e[22m line1 + \e[36m\e[1m│\e[39m\e[22m line2 + \e[36m\e[1m└\e[39m\e[22m \e[90mSUFFIX\e[39m """ end @@ -275,4 +288,22 @@ end @test m.run() end +@testset "custom log macro" begin + @test_logs (CustomLog, "a") min_level=CustomLog @customlog "a" + + buf = IOBuffer() + io = IOContext(buf, :displaysize=>(30,80), :color=>false) + logger = ConsoleLogger(io, CustomLog) + + with_logger(logger) do + @customlog "a" + end + @test occursin("LogLevel(-500): a", String(take!(buf))) +end + +@testset "Docstrings" begin + undoc = Docs.undocumented_names(Logging) + @test isempty(undoc) +end + end diff --git a/stdlib/MPFR_jll/Project.toml b/stdlib/MPFR_jll/Project.toml index e4b24d070db55..eaa8d0988b2ca 100644 --- a/stdlib/MPFR_jll/Project.toml +++ b/stdlib/MPFR_jll/Project.toml @@ -1,6 +1,6 @@ name = "MPFR_jll" uuid = "3a97d323-0669-5f0c-9066-3539efd106a3" -version = "4.2.0+1" +version = "4.2.1+0" [deps] GMP_jll = "781609d7-10c4-51f6-84f2-b8444358ff6d" diff --git a/stdlib/MPFR_jll/src/MPFR_jll.jl b/stdlib/MPFR_jll/src/MPFR_jll.jl index c184a9801102f..219ab0cad41be 100644 --- a/stdlib/MPFR_jll/src/MPFR_jll.jl +++ b/stdlib/MPFR_jll/src/MPFR_jll.jl @@ -3,7 +3,6 @@ ## dummy stub for https://github.com/JuliaBinaryWrappers/MPFR_jll.jl baremodule MPFR_jll using Base, Libdl, GMP_jll -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] diff --git a/stdlib/MPFR_jll/test/runtests.jl b/stdlib/MPFR_jll/test/runtests.jl index 81b6e06ed7b49..fc931b462fa9c 100644 --- a/stdlib/MPFR_jll/test/runtests.jl +++ b/stdlib/MPFR_jll/test/runtests.jl @@ -4,5 +4,5 @@ using Test, Libdl, MPFR_jll @testset "MPFR_jll" begin vn = VersionNumber(unsafe_string(ccall((:mpfr_get_version,libmpfr), Cstring, ()))) - @test vn == v"4.2.0" + @test vn == v"4.2.1" end diff --git a/stdlib/Makefile b/stdlib/Makefile index 6b09344ac422d..ebc40c9db2b12 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -47,7 +47,8 @@ STDLIBS = Artifacts Base64 CRC32c Dates FileWatching \ $(JLL_NAMES) STDLIBS_EXT = Pkg Statistics LazyArtifacts LibCURL DelimitedFiles Downloads ArgTools \ - Tar NetworkOptions SuiteSparse SparseArrays SHA Distributed + Tar NetworkOptions SuiteSparse SparseArrays StyledStrings SHA Distributed \ + JuliaSyntaxHighlighting $(foreach module, $(STDLIBS_EXT), $(eval $(call stdlib-external,$(module),$(shell echo $(module) | tr a-z A-Z)))) diff --git a/stdlib/Manifest.toml b/stdlib/Manifest.toml new file mode 100644 index 0000000000000..4e4f48b4e6af4 --- /dev/null +++ b/stdlib/Manifest.toml @@ -0,0 +1,300 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.12.0-DEV" +manifest_format = "2.0" +project_hash = "d3a1f6b706609fe0c59521e1d770be6e2b8c489d" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.2" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" +version = "1.11.0" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +version = "1.11.0" + +[[deps.CRC32c]] +uuid = "8bf52ea8-c179-5cab-976a-9e18b702a9bc" +version = "1.11.0" + +[[deps.CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "1.1.1+0" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" +version = "1.11.0" + +[[deps.DelimitedFiles]] +deps = ["Mmap"] +git-tree-sha1 = "9e2f36d3c96a820c678f2f1f1782582fcf685bae" +uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" +version = "1.9.1" + +[[deps.Distributed]] +deps = ["Random", "Serialization", "Sockets"] +uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" +version = "1.11.0" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +version = "1.11.0" + +[[deps.Future]] +deps = ["Random"] +uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" +version = "1.11.0" + +[[deps.GMP_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "781609d7-10c4-51f6-84f2-b8444358ff6d" +version = "6.3.0+0" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +version = "1.11.0" + +[[deps.JuliaSyntaxHighlighting]] +deps = ["StyledStrings"] +uuid = "dc6e5ff7-fb65-4e79-a425-ec3bc9c03011" +version = "1.11.0" + +[[deps.LLD_jll]] +deps = ["Artifacts", "Libdl", "Zlib_jll", "libLLVM_jll"] +uuid = "d55e3150-da41-5e91-b323-ecfd1eec6109" +version = "16.0.6+4" + +[[deps.LLVMLibUnwind_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "47c5dbc3-30ba-59ef-96a6-123e260183d9" +version = "12.0.1+0" + +[[deps.LazyArtifacts]] +deps = ["Artifacts", "Pkg"] +uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3" +version = "1.11.0" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.4" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "8.6.0+0" + +[[deps.LibGit2]] +deps = ["LibGit2_jll", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" +version = "1.11.0" + +[[deps.LibGit2_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"] +uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +version = "1.8.0+0" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "MbedTLS_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.11.0+1" + +[[deps.LibUV_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "183b4373-6708-53ba-ad28-60e28bb38547" +version = "2.0.1+16" + +[[deps.LibUnwind_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "745a5e78-f969-53e9-954f-d19f2f74f4e3" +version = "1.7.2+2" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +version = "1.11.0" + +[[deps.LinearAlgebra]] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +version = "1.11.0" + +[[deps.Logging]] +deps = ["StyledStrings"] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" +version = "1.11.0" + +[[deps.MPFR_jll]] +deps = ["Artifacts", "GMP_jll", "Libdl"] +uuid = "3a97d323-0669-5f0c-9066-3539efd106a3" +version = "4.2.1+0" + +[[deps.Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" +version = "1.11.0" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.6+0" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" +version = "1.11.0" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2024.3.11" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.2.0" + +[[deps.OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.26+2" + +[[deps.OpenLibm_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "05823500-19ac-5b8b-9628-191a04bc5112" +version = "0.8.1+2" + +[[deps.PCRE2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" +version = "10.43.0+0" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.12.0" +weakdeps = ["REPL"] + + [deps.Pkg.extensions] + REPLExt = "REPL" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" +version = "1.11.0" + +[[deps.Profile]] +uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" +version = "1.11.0" + +[[deps.REPL]] +deps = ["InteractiveUtils", "Markdown", "Sockets", "StyledStrings", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" +version = "1.11.0" + +[[deps.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +version = "1.11.0" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" +version = "1.11.0" + +[[deps.SharedArrays]] +deps = ["Distributed", "Mmap", "Random", "Serialization"] +uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" +version = "1.11.0" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" +version = "1.11.0" + +[[deps.SparseArrays]] +deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +version = "1.11.0" + +[[deps.Statistics]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "ae3bb1eb3bba077cd276bc5cfc337cc65c3075c0" +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +version = "1.11.1" +weakdeps = ["SparseArrays"] + + [deps.Statistics.extensions] + SparseArraysExt = ["SparseArrays"] + +[[deps.StyledStrings]] +uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" +version = "1.11.0" + +[[deps.SuiteSparse_jll]] +deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] +uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" +version = "7.6.1+0" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +version = "1.11.0" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" +version = "1.11.0" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" +version = "1.11.0" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.3.1+0" + +[[deps.dSFMT_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "05ff407c-b0c1-5878-9df8-858cc2e60c36" +version = "2.2.5+0" + +[[deps.libLLVM_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8f36deef-c2a5-5394-99ed-8e07531fb29a" +version = "16.0.6+4" + +[[deps.libblastrampoline_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.8.0+1" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.60.0+0" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.5.0+0" diff --git a/stdlib/Markdown/Project.toml b/stdlib/Markdown/Project.toml index 229e58749d233..b40de17b9422d 100644 --- a/stdlib/Markdown/Project.toml +++ b/stdlib/Markdown/Project.toml @@ -1,5 +1,6 @@ name = "Markdown" uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" +version = "1.11.0" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" diff --git a/stdlib/Markdown/docs/src/index.md b/stdlib/Markdown/docs/src/index.md index 44f2f2dbfd688..ad620c22eae5d 100644 --- a/stdlib/Markdown/docs/src/index.md +++ b/stdlib/Markdown/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/Markdown/docs/src/index.md" +``` + # [Markdown](@id markdown_stdlib) This section describes Julia's markdown syntax, which is enabled by the @@ -75,7 +79,7 @@ the text enclosed in square brackets, `[ ]`, is the name of the link and the tex parentheses, `( )`, is the URL. ``` -A paragraph containing a link to [Julia](http://www.julialang.org). +A paragraph containing a link to [Julia](https://www.julialang.org). ``` It's also possible to add cross-references to other documented functions/methods/variables within @@ -388,6 +392,16 @@ If no title text is specified after the admonition type, then the type name will Admonitions, like most other toplevel elements, can contain other toplevel elements (e.g. lists, images). +## [Markdown String Literals](@id stdlib-markdown-literals) + +The `md""` macro allows you to embed Markdown strings directly into your Julia code. +This macro is designed to simplify the inclusion of Markdown-formatted text within your Julia source files. + +### Usage + +```julia +result = md"This is a **custom** Markdown string with [a link](http://example.com)." +``` ## Markdown Syntax Extensions Julia's markdown supports interpolation in a very similar way to basic string literals, with the @@ -398,3 +412,14 @@ complex features (such as references) without cluttering the basic syntax. In principle, the Markdown parser itself can also be arbitrarily extended by packages, or an entirely custom flavour of Markdown can be used, but this should generally be unnecessary. + + +## [API reference](@id stdlib-markdown-api) + +```@docs +Markdown.MD +Markdown.@md_str +Markdown.@doc_str +Markdown.html +Markdown.latex +``` diff --git a/stdlib/Markdown/src/Markdown.jl b/stdlib/Markdown/src/Markdown.jl index 781fcbdafddc8..93d8dbc39fc59 100644 --- a/stdlib/Markdown/src/Markdown.jl +++ b/stdlib/Markdown/src/Markdown.jl @@ -1,7 +1,11 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license """ -Tools for working with the Markdown file format. Mainly for documentation. + Markdown + +Tools for working with the Markdown markup language for formatted text, used within Julia for documentation. +The `Markdown` module provides the (internal) [`MD`](@ref) type as well as the string +literals `md"..."` and `doc"..."`. """ module Markdown @@ -40,6 +44,22 @@ function docexpr(source::LineNumberNode, mod::Module, s, flavor = :julia) :($doc_str($(mdexpr(s, flavor)), $(QuoteNode(source)), $mod)) end +""" + @md_str -> MD + +Parse the given string as Markdown text and return a corresponding [`MD`](@ref) object. + +# Examples +```jldoctest +julia> s = md"# Hello, world!" + Hello, world! + ≡≡≡≡≡≡≡≡≡≡≡≡≡ + +julia> typeof(s) +Markdown.MD + +``` +""" macro md_str(s, t...) mdexpr(s, t...) end @@ -51,6 +71,25 @@ function doc_str(md, source::LineNumberNode, mod::Module) end doc_str(md::AbstractString, source::LineNumberNode, mod::Module) = doc_str(parse(md), source, mod) +""" + @doc_str -> MD + +Parse the given string as Markdown text, add line and module information and return a +corresponding [`MD`](@ref) object. + +`@doc_str` can be used in conjunction with the [`Base.Docs`](@ref) module. Please also refer to +the manual section on [documentation](@ref man-documentation) for more information. + +# Examples +``` +julia> s = doc"f(x) = 2*x" + f(x) = 2*x + +julia> typeof(s) +Markdown.MD + +``` +""" macro doc_str(s::AbstractString, t...) docexpr(__source__, __module__, s, t...) end diff --git a/stdlib/Markdown/src/parse/parse.jl b/stdlib/Markdown/src/parse/parse.jl index 452d90d1176e1..0f3cbe857c2f9 100644 --- a/stdlib/Markdown/src/parse/parse.jl +++ b/stdlib/Markdown/src/parse/parse.jl @@ -1,5 +1,12 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +""" + MD + +`MD` represents a Markdown document. Note that the `MD` constructor should not generally be +used directly, since it constructs the internal data structures. Instead, you can construct +`MD` objects using the exported macros [`@md_str`](@ref) and [`@doc_str`](@ref). +""" mutable struct MD content::Vector{Any} meta::Dict{Symbol, Any} @@ -8,6 +15,8 @@ mutable struct MD new(content, meta) end +public MD + MD(xs...) = MD(vcat(xs...)) function MD(cfg::Config, xs...) diff --git a/stdlib/Markdown/src/render/html.jl b/stdlib/Markdown/src/render/html.jl index a48180509400f..e7d436f2ccbda 100644 --- a/stdlib/Markdown/src/render/html.jl +++ b/stdlib/Markdown/src/render/html.jl @@ -182,6 +182,21 @@ htmlinline(io::IO, x) = tohtml(io, x) export html +""" + html([io::IO], md) + +Output the contents of the Markdown object `md` in HTML format, either +writing to an (optional) `io` stream or returning a string. + +One can alternatively use `show(io, "text/html", md)` or `repr("text/html", md)`, which +differ in that they wrap the output in a `
...
` element. + +# Examples +```jldoctest +julia> html(md"hello _world_") +"

hello world

\\n" +``` +""" html(md) = sprint(html, md) function show(io::IO, ::MIME"text/html", md::MD) diff --git a/stdlib/Markdown/src/render/latex.jl b/stdlib/Markdown/src/render/latex.jl index d18a2e760ef3d..df52b2849f2b0 100644 --- a/stdlib/Markdown/src/render/latex.jl +++ b/stdlib/Markdown/src/render/latex.jl @@ -167,6 +167,20 @@ function latexesc(io, s::AbstractString) end end +""" + latex([io::IO], md) + +Output the contents of the Markdown object `md` in LaTeX format, either +writing to an (optional) `io` stream or returning a string. + +One can alternatively use `show(io, "text/latex", md)` or `repr("text/latex", md)`. + +# Examples +```jldoctest +julia> latex(md"hello _world_") +"hello \\\\emph{world}\\n\\n" +``` +""" latex(md) = sprint(latex, md) latexinline(md) = sprint(latexinline, md) latexesc(s) = sprint(latexesc, s) diff --git a/stdlib/Markdown/test/runtests.jl b/stdlib/Markdown/test/runtests.jl index 84f0868747567..116282a0bea3b 100644 --- a/stdlib/Markdown/test/runtests.jl +++ b/stdlib/Markdown/test/runtests.jl @@ -1293,3 +1293,7 @@ end # see issue #42139 @test md"<一轮红日初升>" |> html == """

<一轮红日初升>

\n""" end + +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(Markdown)) +end diff --git a/stdlib/MbedTLS_jll/Project.toml b/stdlib/MbedTLS_jll/Project.toml index 27d4884d099ad..1fe9b5e702c61 100644 --- a/stdlib/MbedTLS_jll/Project.toml +++ b/stdlib/MbedTLS_jll/Project.toml @@ -1,6 +1,6 @@ name = "MbedTLS_jll" uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" -version = "2.28.2+1" +version = "2.28.6+0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/stdlib/MbedTLS_jll/src/MbedTLS_jll.jl b/stdlib/MbedTLS_jll/src/MbedTLS_jll.jl index e46da42a9a638..6367213e2c4ab 100644 --- a/stdlib/MbedTLS_jll/src/MbedTLS_jll.jl +++ b/stdlib/MbedTLS_jll/src/MbedTLS_jll.jl @@ -4,7 +4,6 @@ baremodule MbedTLS_jll using Base, Libdl -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] diff --git a/stdlib/MbedTLS_jll/test/runtests.jl b/stdlib/MbedTLS_jll/test/runtests.jl index 2d82fa564cd18..5813a813a1e1f 100644 --- a/stdlib/MbedTLS_jll/test/runtests.jl +++ b/stdlib/MbedTLS_jll/test/runtests.jl @@ -6,5 +6,5 @@ using Test, Libdl, MbedTLS_jll vstr = zeros(UInt8, 32) ccall((:mbedtls_version_get_string, libmbedcrypto), Cvoid, (Ref{UInt8},), vstr) vn = VersionNumber(unsafe_string(pointer(vstr))) - @test vn == v"2.28.2" + @test vn == v"2.28.6" end diff --git a/stdlib/Mmap/Project.toml b/stdlib/Mmap/Project.toml index f3dab686d2eaa..ce4b65ccbb06a 100644 --- a/stdlib/Mmap/Project.toml +++ b/stdlib/Mmap/Project.toml @@ -1,5 +1,6 @@ name = "Mmap" uuid = "a63ad114-7e13-5084-954f-fe012c677804" +version = "1.11.0" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/Mmap/docs/src/index.md b/stdlib/Mmap/docs/src/index.md index 5c40f11db4a4c..5ec2d5064eaf0 100644 --- a/stdlib/Mmap/docs/src/index.md +++ b/stdlib/Mmap/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/Mmap/docs/src/index.md" +``` + # Memory-mapped I/O Low level module for mmap (memory mapping of files). diff --git a/stdlib/Mmap/src/Mmap.jl b/stdlib/Mmap/src/Mmap.jl index 9dd02d5aa9d04..6d328c40cd7b3 100644 --- a/stdlib/Mmap/src/Mmap.jl +++ b/stdlib/Mmap/src/Mmap.jl @@ -367,8 +367,9 @@ Forces synchronization between the in-memory version of a memory-mapped `Array` [`BitArray`](@ref) and the on-disk version. """ function sync!(m::Array, flags::Integer=MS_SYNC) - offset = rem(UInt(pointer(m)), PAGESIZE) - ptr = pointer(m) - offset + ptr = pointer(m) + offset = rem(UInt(ptr), PAGESIZE) + ptr = ptr - offset mmaplen = sizeof(m) + offset GC.@preserve m @static if Sys.isunix() systemerror("msync", @@ -429,8 +430,9 @@ Advises the kernel on the intended usage of the memory-mapped `array`, with the `flag` being one of the available `MADV_*` constants. """ function madvise!(m::Array, flag::Integer=MADV_NORMAL) - offset = rem(UInt(pointer(m)), PAGESIZE) - ptr = pointer(m) - offset + ptr = pointer(m) + offset = rem(UInt(ptr), PAGESIZE) + ptr = ptr - offset mmaplen = sizeof(m) + offset GC.@preserve m begin systemerror("madvise", diff --git a/stdlib/Mmap/test/runtests.jl b/stdlib/Mmap/test/runtests.jl index 0b3cb0b9f1a42..ebd16a45ba0ed 100644 --- a/stdlib/Mmap/test/runtests.jl +++ b/stdlib/Mmap/test/runtests.jl @@ -339,3 +339,7 @@ open(file, "r+") do s finalize(A); A = nothing; GC.gc() end rm(file) + +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(Mmap)) +end diff --git a/stdlib/MozillaCACerts_jll/Project.toml b/stdlib/MozillaCACerts_jll/Project.toml index cef860fda4acd..181171a4c04c1 100644 --- a/stdlib/MozillaCACerts_jll/Project.toml +++ b/stdlib/MozillaCACerts_jll/Project.toml @@ -1,6 +1,7 @@ name = "MozillaCACerts_jll" uuid = "14a3606d-f60d-562e-9121-12d972cd8159" -version = "2023.01.10" +# Keep in sync with `deps/libgit2.version`. +version = "2024.03.11" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/MozillaCACerts_jll/src/MozillaCACerts_jll.jl b/stdlib/MozillaCACerts_jll/src/MozillaCACerts_jll.jl index 244c1204563d5..1d5df0236ae9e 100644 --- a/stdlib/MozillaCACerts_jll/src/MozillaCACerts_jll.jl +++ b/stdlib/MozillaCACerts_jll/src/MozillaCACerts_jll.jl @@ -4,7 +4,6 @@ baremodule MozillaCACerts_jll using Base -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] diff --git a/stdlib/OpenBLAS_jll/Project.toml b/stdlib/OpenBLAS_jll/Project.toml index ad5041cf4ac81..10e8285027d42 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.24+0" +version = "0.3.26+2" [deps] # See note in `src/OpenBLAS_jll.jl` about this dependency. diff --git a/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl b/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl index a0c11ab047142..2f151f63f4413 100644 --- a/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl +++ b/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl @@ -13,7 +13,6 @@ using Base, Libdl, Base.BinaryPlatforms # using CompilerSupportLibraries_jll # Because of this however, we have to manually load the libraries we # _do_ care about, namely libgfortran -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] diff --git a/stdlib/OpenLibm_jll/src/OpenLibm_jll.jl b/stdlib/OpenLibm_jll/src/OpenLibm_jll.jl index f2dee45a279cd..297cd25512894 100644 --- a/stdlib/OpenLibm_jll/src/OpenLibm_jll.jl +++ b/stdlib/OpenLibm_jll/src/OpenLibm_jll.jl @@ -3,7 +3,6 @@ ## dummy stub for https://github.com/JuliaBinaryWrappers/OpenLibm_jll.jl baremodule OpenLibm_jll using Base, Libdl -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] diff --git a/stdlib/PCRE2_jll/Project.toml b/stdlib/PCRE2_jll/Project.toml index 788d6b733234f..f9b3affb51b63 100644 --- a/stdlib/PCRE2_jll/Project.toml +++ b/stdlib/PCRE2_jll/Project.toml @@ -1,6 +1,6 @@ name = "PCRE2_jll" uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" -version = "10.42.0+1" +version = "10.43.0+0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/stdlib/PCRE2_jll/src/PCRE2_jll.jl b/stdlib/PCRE2_jll/src/PCRE2_jll.jl index e7f685820830b..d825ac74db5a8 100644 --- a/stdlib/PCRE2_jll/src/PCRE2_jll.jl +++ b/stdlib/PCRE2_jll/src/PCRE2_jll.jl @@ -3,7 +3,6 @@ ## dummy stub for https://github.com/JuliaBinaryWrappers/PCRE2_jll.jl baremodule PCRE2_jll using Base, Libdl -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] diff --git a/stdlib/PCRE2_jll/test/runtests.jl b/stdlib/PCRE2_jll/test/runtests.jl index d593b07af31ce..af0ed9434d2b6 100644 --- a/stdlib/PCRE2_jll/test/runtests.jl +++ b/stdlib/PCRE2_jll/test/runtests.jl @@ -6,5 +6,5 @@ using Test, Libdl, PCRE2_jll vstr = zeros(UInt8, 32) @test ccall((:pcre2_config_8, libpcre2_8), Cint, (UInt32, Ref{UInt8}), 11, vstr) > 0 vn = VersionNumber(split(unsafe_string(pointer(vstr)), " ")[1]) - @test vn == v"10.42.0" + @test vn == v"10.43.0" end diff --git a/stdlib/Pkg.version b/stdlib/Pkg.version index c39a6c331a37f..499b6b9174c44 100644 --- a/stdlib/Pkg.version +++ b/stdlib/Pkg.version @@ -1,4 +1,4 @@ PKG_BRANCH = master -PKG_SHA1 = b02fb95979c71dc5834aad739ad61622cccf4a16 +PKG_SHA1 = 8f772ffa72d86c81f03cddce2e779d6b330414e3 PKG_GIT_URL := https://github.com/JuliaLang/Pkg.jl.git PKG_TAR_URL = https://api.github.com/repos/JuliaLang/Pkg.jl/tarball/$1 diff --git a/stdlib/Printf/Project.toml b/stdlib/Printf/Project.toml index 9fa4e3633cae1..019b7e94ef9bd 100644 --- a/stdlib/Printf/Project.toml +++ b/stdlib/Printf/Project.toml @@ -1,5 +1,6 @@ name = "Printf" uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" +version = "1.11.0" [deps] Unicode = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" diff --git a/stdlib/Printf/docs/src/index.md b/stdlib/Printf/docs/src/index.md index 48e38e2b2ce5b..1c6f98ce22e58 100644 --- a/stdlib/Printf/docs/src/index.md +++ b/stdlib/Printf/docs/src/index.md @@ -1,6 +1,14 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/Printf/docs/src/index.md" +``` + # [Printf](@id man-printf) +The `Printf` module provides formatted output functions similar to the C standard library's `printf`. It allows formatted printing to an output stream or to a string. + ```@docs Printf.@printf Printf.@sprintf +Printf.Format +Printf.format ``` diff --git a/stdlib/Printf/src/Printf.jl b/stdlib/Printf/src/Printf.jl index cb336a8d9c18b..9b636d1180598 100644 --- a/stdlib/Printf/src/Printf.jl +++ b/stdlib/Printf/src/Printf.jl @@ -1,11 +1,15 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license - +""" +The `Printf` module provides formatted output functions similar to the C standard library's `printf`. It allows formatted printing to an output stream or to a string. +""" module Printf using Base.Ryu export @printf, @sprintf +public format, Format + # format specifier categories const Ints = Union{Val{'d'}, Val{'i'}, Val{'u'}, Val{'x'}, Val{'X'}, Val{'o'}} const Floats = Union{Val{'e'}, Val{'E'}, Val{'f'}, Val{'F'}, Val{'g'}, Val{'G'}, Val{'a'}, Val{'A'}} @@ -237,7 +241,7 @@ function Format(f::AbstractString) !(b in b"diouxXDOUeEfFgGaAcCsSpn") && throw(InvalidFormatStringError("'$(Char(b))' is not a valid type specifier", f, last_percent_pos, pos-1)) type = Val{Char(b)} if type <: Ints && precision > 0 - # note - we should also set zero to false if dynamic precison > 0 + # note - we should also set zero to false if dynamic precision > 0 # this is taken care of in fmt() for Ints zero = false elseif (type <: Strings || type <: Chars) && !parsedprecdigits @@ -650,7 +654,7 @@ const __BIG_FLOAT_MAX__ = 8192 else # right aligned n = width - (newpos - pos) - if zero + if zero && isfinite(x) ex = (arg < 0 || (plus | space)) + (T <: Union{Val{'a'}, Val{'A'}} ? 2 : 0) so = pos + ex len = (newpos - pos) - ex diff --git a/stdlib/Printf/test/runtests.jl b/stdlib/Printf/test/runtests.jl index 33970f78648e2..abe547c00ed0d 100644 --- a/stdlib/Printf/test/runtests.jl +++ b/stdlib/Printf/test/runtests.jl @@ -116,12 +116,15 @@ end @test (Printf.@sprintf "%+f" Inf) == "+Inf" @test (Printf.@sprintf "% f" Inf) == " Inf" @test (Printf.@sprintf "% #f" Inf) == " Inf" + @test (Printf.@sprintf "%07f" Inf) == " Inf" @test (Printf.@sprintf "%f" -Inf) == "-Inf" @test (Printf.@sprintf "%+f" -Inf) == "-Inf" + @test (Printf.@sprintf "%07f" -Inf) == " -Inf" @test (Printf.@sprintf "%f" NaN) == "NaN" @test (Printf.@sprintf "%+f" NaN) == "+NaN" @test (Printf.@sprintf "% f" NaN) == " NaN" @test (Printf.@sprintf "% #f" NaN) == " NaN" + @test (Printf.@sprintf "%07f" NaN) == " NaN" @test (Printf.@sprintf "%e" big"Inf") == "Inf" @test (Printf.@sprintf "%e" big"NaN") == "NaN" @@ -169,12 +172,15 @@ end @test (Printf.@sprintf "%+e" Inf) == "+Inf" @test (Printf.@sprintf "% e" Inf) == " Inf" @test (Printf.@sprintf "% #e" Inf) == " Inf" + @test (Printf.@sprintf "%07e" Inf) == " Inf" @test (Printf.@sprintf "%e" -Inf) == "-Inf" @test (Printf.@sprintf "%+e" -Inf) == "-Inf" + @test (Printf.@sprintf "%07e" -Inf) == " -Inf" @test (Printf.@sprintf "%e" NaN) == "NaN" @test (Printf.@sprintf "%+e" NaN) == "+NaN" @test (Printf.@sprintf "% e" NaN) == " NaN" @test (Printf.@sprintf "% #e" NaN) == " NaN" + @test (Printf.@sprintf "%07e" NaN) == " NaN" @test (Printf.@sprintf "%e" big"Inf") == "Inf" @test (Printf.@sprintf "%e" big"NaN") == "NaN" @@ -1145,4 +1151,11 @@ end @test_throws Printf.InvalidFormatStringError Printf.Format("%z") end +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(Printf)) +end + +# issue #52749 +@test @sprintf("%.160g", 1.38e-23) == "1.380000000000000060010582465734078799297660966782642624395399644741944111814291318296454846858978271484375e-23" + end # @testset "Printf" diff --git a/stdlib/Profile/Project.toml b/stdlib/Profile/Project.toml index 334d475832b6d..ad0107ecf9404 100644 --- a/stdlib/Profile/Project.toml +++ b/stdlib/Profile/Project.toml @@ -1,8 +1,6 @@ name = "Profile" uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" - -[deps] -Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" +version = "1.11.0" [extras] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" diff --git a/stdlib/Profile/docs/src/index.md b/stdlib/Profile/docs/src/index.md index 1fbab0ea534d8..5b4db77b9cb16 100644 --- a/stdlib/Profile/docs/src/index.md +++ b/stdlib/Profile/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/Profile/docs/src/index.md" +``` + # [Profiling](@id lib-profiling) ## CPU Profiling @@ -106,6 +110,7 @@ The methods in `Profile.Allocs` are not exported and need to be called e.g. as ` ```@docs Profile.Allocs.clear +Profile.Allocs.print Profile.Allocs.fetch Profile.Allocs.start Profile.Allocs.stop @@ -129,5 +134,24 @@ Traces and records julia objects on the heap. This only records objects known to garbage collector. Memory allocated by external libraries not managed by the garbage collector will not show up in the snapshot. +To avoid OOMing while recording the snapshot, we added a streaming option to stream out the heap snapshot +into four files, + +```julia-repl +julia> using Profile + +julia> Profile.take_heap_snapshot("snapshot"; streaming=true) +``` + +where "snapshot" is the filepath as the prefix for the generated files. + +Once the snapshot files are generated, they could be assembled offline with the following command: + +```julia-repl +julia> using Profile + +julia> Profile.HeapSnapshot.assemble_snapshot("snapshot", "snapshot.heapsnapshot") +``` + The resulting heap snapshot file can be uploaded to chrome devtools to be viewed. For more information, see the [chrome devtools docs](https://developer.chrome.com/docs/devtools/memory-problems/heap-snapshots/#view_snapshots). diff --git a/stdlib/Profile/src/Allocs.jl b/stdlib/Profile/src/Allocs.jl index e45f4dca9607f..31d703a151ad8 100644 --- a/stdlib/Profile/src/Allocs.jl +++ b/stdlib/Profile/src/Allocs.jl @@ -1,5 +1,12 @@ module Allocs +global print # Allocs.print is separate from both Base.print and Profile.print +public @profile, + clear, + print, + fetch + +using ..Profile: Profile, ProfileFormat, StackFrameTree, print_flat, print_tree using Base.StackTraces: StackTrace, StackFrame, lookup using Base: InterpreterIP @@ -33,7 +40,7 @@ end Profile.Allocs.@profile [sample_rate=0.1] expr Profile allocations that happen during `expr`, returning -both the result and and AllocResults struct. +both the result and AllocResults struct. A sample rate of 1.0 will record everything; 0.0 will record nothing. @@ -47,18 +54,17 @@ julia> last(sort(results.allocs, by=x->x.size)) Profile.Allocs.Alloc(Vector{Any}, Base.StackTraces.StackFrame[_new_array_ at array.c:127, ...], 5576) ``` -The best way to visualize these is currently with the -[PProf.jl](https://github.com/JuliaPerf/PProf.jl) package, -by invoking `PProf.Allocs.pprof`. +See the profiling tutorial in the Julia documentation for more information. + +!!! compat "Julia 1.11" -!!! note - The current implementation of the Allocations Profiler does not - capture types for all allocations. Allocations for which the profiler - could not capture the type are represented as having type - `Profile.Allocs.UnknownType`. + Older versions of Julia could not capture types in all cases. In older versions of + Julia, if you see an allocation of type `Profile.Allocs.UnknownType`, it means that + the profiler doesn't know what type of object was allocated. This mainly happened when + the allocation was coming from generated code produced by the compiler. See + [issue #43688](https://github.com/JuliaLang/julia/issues/43688) for more info. - You can read more about the missing types and the plan to improve this, here: - . + Since Julia 1.11, all allocations should have a type reported. !!! compat "Julia 1.8" The allocation profiler was added in Julia 1.8. @@ -138,7 +144,7 @@ end # Without this, the Alloc's stacktrace prints for lines and lines and lines... function Base.show(io::IO, a::Alloc) stacktrace_sample = length(a.stacktrace) >= 1 ? "$(a.stacktrace[1]), ..." : "" - print(io, "$Alloc($(a.type), $StackFrame[$stacktrace_sample], $(a.size))") + Base.print(io, "$Alloc($(a.type), $StackFrame[$stacktrace_sample], $(a.size))") end const BacktraceCache = Dict{BTElement,Vector{StackFrame}} @@ -216,4 +222,201 @@ function stacktrace_memoized( return stack end +function warning_empty() + @warn """ + There were no samples collected. + Run your program longer (perhaps by running it multiple times), + or adjust the frequency of samples to record every event with + the `sample_rate=1.0` kwarg.""" +end + + +""" + Profile.Allocs.print([io::IO = stdout,] [data::AllocResults = fetch()]; kwargs...) + +Prints profiling results to `io` (by default, `stdout`). If you do not +supply a `data` vector, the internal buffer of accumulated backtraces +will be used. + +See `Profile.print` for an explanation of the valid keyword arguments. +""" +print(; kwargs...) = + Profile.print(stdout, fetch(); kwargs...) +print(io::IO; kwargs...) = + Profile.print(io, fetch(); kwargs...) +print(io::IO, data::AllocResults; kwargs...) = + Profile.print(io, data; kwargs...) +Profile.print(data::AllocResults; kwargs...) = + Profile.print(stdout, data; kwargs...) + +function Profile.print(io::IO, + data::AllocResults, + ; + format = :tree, + C = false, + #combine = true, + maxdepth::Int = typemax(Int), + mincount::Int = 0, + noisefloor = 0, + sortedby::Symbol = :filefuncline, + groupby::Union{Symbol,AbstractVector{Symbol}} = :none, + recur::Symbol = :off, + ) + pf = ProfileFormat(;C, maxdepth, mincount, noisefloor, sortedby, recur) + Profile.print(io, data, pf, format) + return +end + +function Profile.print(io::IO, data::AllocResults, fmt::ProfileFormat, format::Symbol) + cols::Int = Base.displaysize(io)[2] + fmt.recur ∈ (:off, :flat, :flatc) || throw(ArgumentError("recur value not recognized")) + data = data.allocs + if format === :tree + tree(io, data, cols, fmt) + elseif format === :flat + fmt.recur === :off || throw(ArgumentError("format flat only implements recur=:off")) + flat(io, data, cols, fmt) + else + throw(ArgumentError("output format $(repr(format)) not recognized")) + end + nothing +end + + +function parse_flat(::Type{T}, data::Vector{Alloc}, C::Bool) where T + lilist = StackFrame[] + n = Int[] + m = Int[] + lilist_idx = Dict{T, Int}() + recursive = Set{T}() + totalbytes = 0 + for r in data + first = true + empty!(recursive) + nb = r.size # or 1 for counting + totalbytes += nb + for frame in r.stacktrace + !C && frame.from_c && continue + key = (T === UInt64 ? ip : frame) + idx = get!(lilist_idx, key, length(lilist) + 1) + if idx > length(lilist) + push!(recursive, key) + push!(lilist, frame) + push!(n, nb) + push!(m, 0) + elseif !(key in recursive) + push!(recursive, key) + n[idx] += nb + end + if first + m[idx] += nb + first = false + end + end + end + @assert length(lilist) == length(n) == length(m) == length(lilist_idx) + return (lilist, n, m, totalbytes) +end + +function flat(io::IO, data::Vector{Alloc}, cols::Int, fmt::ProfileFormat) + fmt.combine || error(ArgumentError("combine=false")) + lilist, n, m, totalbytes = parse_flat(fmt.combine ? StackFrame : UInt64, data, fmt.C) + filenamemap = Dict{Symbol,String}() + if isempty(lilist) + warning_empty() + return true + end + print_flat(io, lilist, n, m, cols, filenamemap, fmt) + Base.println(io, "Total snapshots: ", length(data)) + Base.println(io, "Total bytes: ", totalbytes) + return false +end + +function tree!(root::StackFrameTree{T}, all::Vector{Alloc}, C::Bool, recur::Symbol) where {T} + tops = Vector{StackFrameTree{T}}() + build = Dict{T, StackFrameTree{T}}() + for r in all + first = true + nb = r.size # or 1 for counting + root.recur = 0 + root.count += nb + parent = root + for i in reverse(eachindex(r.stacktrace)) + frame = r.stacktrace[i] + key = (T === UInt64 ? ip : frame) + if (recur === :flat && !frame.from_c) || recur === :flatc + # see if this frame already has a parent + this = get!(build, frame, parent) + if this !== parent + # Rewind the `parent` tree back, if this exact ip (FIXME) was already present *higher* in the current tree + push!(tops, parent) + parent = this + end + end + !C && frame.from_c && continue + this = get!(StackFrameTree{T}, parent.down, key) + if recur === :off || this.recur == 0 + this.frame = frame + this.up = parent + this.count += nb + this.recur = 1 + else + this.count_recur += 1 + end + parent = this + end + parent.overhead += nb + if recur !== :off + # We mark all visited nodes to so we'll only count those branches + # once for each backtrace. Reset that now for the next backtrace. + empty!(build) + push!(tops, parent) + for top in tops + while top.recur != 0 + top.max_recur < top.recur && (top.max_recur = top.recur) + top.recur = 0 + top = top.up + end + end + empty!(tops) + end + let this = parent + while this !== root + this.flat_count += nb + this = this.up + end + end + end + function cleanup!(node::StackFrameTree) + stack = [node] + while !isempty(stack) + node = pop!(stack) + node.recur = 0 + empty!(node.builder_key) + empty!(node.builder_value) + append!(stack, values(node.down)) + end + nothing + end + cleanup!(root) + return root +end + +function tree(io::IO, data::Vector{Alloc}, cols::Int, fmt::ProfileFormat) + fmt.combine || error(ArgumentError("combine=false")) + if fmt.combine + root = tree!(StackFrameTree{StackFrame}(), data, fmt.C, fmt.recur) + else + root = tree!(StackFrameTree{UInt64}(), data, fmt.C, fmt.recur) + end + print_tree(io, root, cols, fmt, false) + if isempty(root.down) + warning_empty() + return true + end + Base.println(io, "Total snapshots: ", length(data)) + Base.println(io, "Total bytes: ", root.count) + return false +end + end diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index c37cdd0af0368..062b608b25c59 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -1,10 +1,42 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license """ -Profiling support, main entry point is the [`@profile`](@ref) macro. + Profile + +Profiling support. + +## CPU profiling +- `@profile foo()` to profile a specific call. +- `Profile.print()` to print the report. +- `Profile.clear()` to clear the buffer. +- Send a $(Sys.isbsd() ? "SIGINFO (ctrl-t)" : "SIGUSR1") signal to the process to automatically trigger a profile and print. + +## Memory profiling +- `Profile.Allocs.@profile [sample_rate=0.1] foo()` to sample allocations within a specific call. A sample rate of 1.0 will record everything; 0.0 will record nothing. +- `Profile.Allocs.print()` to print the report. +- `Profile.Allocs.clear()` to clear the buffer. + +## Heap profiling +- `Profile.take_heap_snapshot()` to record a `.heapsnapshot` record of the heap. +- Set `JULIA_PROFILE_PEEK_HEAP_SNAPSHOT=true` to capture a heap snapshot when signal $(Sys.isbsd() ? "SIGINFO (ctrl-t)" : "SIGUSR1") is sent. """ module Profile +global print +export @profile +public clear, + print, + fetch, + retrieve, + add_fake_meta, + flatten, + callers, + init, + take_heap_snapshot, + take_page_profile, + clear_malloc_data, + Allocs + import Base.StackTraces: lookup, UNKNOWN, show_spec_linfo, StackFrame const nmeta = 4 # number of metadata fields per block (threadid, taskid, cpu_cycle_clock, thread_sleeping) @@ -12,8 +44,6 @@ const nmeta = 4 # number of metadata fields per block (threadid, taskid, cpu_cyc # deprecated functions: use `getdict` instead lookup(ip::UInt) = lookup(convert(Ptr{Cvoid}, ip)) -export @profile - """ @profile @@ -201,6 +231,13 @@ The keyword arguments can be any combination of: - `tasks::Union{Int,AbstractVector{Int}}` -- Specify which tasks to include snapshots from in the report. Note that this does not control which tasks samples are collected within. + +!!! compat "Julia 1.8" + The `groupby`, `threads`, and `tasks` keyword arguments were introduced in Julia 1.8. + +!!! note + Profiling on windows is limited to the main thread. Other threads have not been sampled and will not show in the report. + """ function print(io::IO, data::Vector{<:Unsigned} = fetch(), @@ -220,7 +257,7 @@ function print(io::IO, pf = ProfileFormat(;C, combine, maxdepth, mincount, noisefloor, sortedby, recur) if groupby === :none - print(io, data, lidict, pf, format, threads, tasks, false) + print_group(io, data, lidict, pf, format, threads, tasks, false) else if !in(groupby, [:thread, :task, [:task, :thread], [:thread, :task]]) error(ArgumentError("Unrecognized groupby option: $groupby. Options are :none (default), :task, :thread, [:task, :thread], or [:thread, :task]")) @@ -244,7 +281,7 @@ function print(io::IO, printstyled(io, "Task $(Base.repr(taskid))$nl"; bold=true, color=Base.debug_color()) for threadid in threadids printstyled(io, " Thread $threadid "; bold=true, color=Base.info_color()) - nosamples = print(io, data, lidict, pf, format, threadid, taskid, true) + nosamples = print_group(io, data, lidict, pf, format, threadid, taskid, true) nosamples && (any_nosamples = true) println(io) end @@ -262,7 +299,7 @@ function print(io::IO, printstyled(io, "Thread $threadid$nl"; bold=true, color=Base.info_color()) for taskid in taskids printstyled(io, " Task $(Base.repr(taskid)) "; bold=true, color=Base.debug_color()) - nosamples = print(io, data, lidict, pf, format, threadid, taskid, true) + nosamples = print_group(io, data, lidict, pf, format, threadid, taskid, true) nosamples && (any_nosamples = true) println(io) end @@ -274,7 +311,7 @@ function print(io::IO, isempty(taskids) && (any_nosamples = true) for taskid in taskids printstyled(io, "Task $(Base.repr(taskid)) "; bold=true, color=Base.debug_color()) - nosamples = print(io, data, lidict, pf, format, threads, taskid, true) + nosamples = print_group(io, data, lidict, pf, format, threads, taskid, true) nosamples && (any_nosamples = true) println(io) end @@ -284,7 +321,7 @@ function print(io::IO, isempty(threadids) && (any_nosamples = true) for threadid in threadids printstyled(io, "Thread $threadid "; bold=true, color=Base.info_color()) - nosamples = print(io, data, lidict, pf, format, threadid, tasks, true) + nosamples = print_group(io, data, lidict, pf, format, threadid, tasks, true) nosamples && (any_nosamples = true) println(io) end @@ -306,7 +343,7 @@ See `Profile.print([io], data)` for an explanation of the valid keyword argument print(data::Vector{<:Unsigned} = fetch(), lidict::Union{LineInfoDict, LineInfoFlatDict} = getdict(data); kwargs...) = print(stdout, data, lidict; kwargs...) -function print(io::IO, data::Vector{<:Unsigned}, lidict::Union{LineInfoDict, LineInfoFlatDict}, fmt::ProfileFormat, +function print_group(io::IO, data::Vector{<:Unsigned}, lidict::Union{LineInfoDict, LineInfoFlatDict}, fmt::ProfileFormat, format::Symbol, threads::Union{Int,AbstractVector{Int}}, tasks::Union{UInt,AbstractVector{UInt}}, is_subsection::Bool = false) cols::Int = Base.displaysize(io)[2] @@ -859,7 +896,6 @@ function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, ma ndigline = ndigits(maximum(frame.frame.line for frame in frames)) + 6 ntext = max(30, cols - ndigoverhead - nindent - ndigcounts - ndigline - 6) widthfile = 2*ntext÷5 # min 12 - widthfunc = 3*ntext÷5 # min 18 strs = Vector{String}(undef, length(frames)) showextra = false if level > nindent @@ -901,11 +937,12 @@ function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, ma ":", li.line == -1 ? "?" : string(li.line), "; ", - ltruncto(fname, widthfunc)) + fname) end else strs[i] = string(stroverhead, "╎", base, strcount, " [unknown stackframe]") end + strs[i] = ltruncto(strs[i], cols) end return strs end @@ -1161,17 +1198,17 @@ end # Utilities function rtruncto(str::String, w::Int) - if length(str) <= w + if textwidth(str) <= w return str else - return string("...", str[prevind(str, end, w-4):end]) + return string("…", str[prevind(str, end, w-2):end]) end end function ltruncto(str::String, w::Int) - if length(str) <= w + if textwidth(str) <= w return str else - return string(str[1:nextind(str, 1, w-4)], "...") + return string(str[1:nextind(str, 1, w-2)], "…") end end @@ -1213,9 +1250,8 @@ end """ - Profile.take_heap_snapshot(io::IOStream, all_one::Bool=false) - Profile.take_heap_snapshot(filepath::String, all_one::Bool=false) - Profile.take_heap_snapshot(all_one::Bool=false; dir::String) + Profile.take_heap_snapshot(filepath::String, all_one::Bool=false, streaming=false) + Profile.take_heap_snapshot(all_one::Bool=false; dir::String, streaming=false) Write a snapshot of the heap, in the JSON format expected by the Chrome Devtools Heap Snapshot viewer (.heapsnapshot extension) to a file @@ -1225,16 +1261,68 @@ full file path, or IO stream. If `all_one` is true, then report the size of every object as one so they can be easily counted. Otherwise, report the actual size. + +If `streaming` is true, we will stream the snapshot data out into four files, using filepath +as the prefix, to avoid having to hold the entire snapshot in memory. This option should be +used for any setting where your memory is constrained. These files can then be reassembled +by calling Profile.HeapSnapshot.assemble_snapshot(), which can +be done offline. + +NOTE: We strongly recommend setting streaming=true for performance reasons. Reconstructing +the snapshot from the parts requires holding the entire snapshot in memory, so if the +snapshot is large, you can run out of memory while processing it. Streaming allows you to +reconstruct the snapshot offline, after your workload is done running. +If you do attempt to collect a snapshot with streaming=false (the default, for +backwards-compatibility) and your process is killed, note that this will always save the +parts in the same directory as your provided filepath, so you can still reconstruct the +snapshot after the fact, via `assemble_snapshot()`. """ -function take_heap_snapshot(io::IOStream, all_one::Bool=false) - Base.@_lock_ios(io, ccall(:jl_gc_take_heap_snapshot, Cvoid, (Ptr{Cvoid}, Cchar), io.handle, Cchar(all_one))) -end -function take_heap_snapshot(filepath::String, all_one::Bool=false) - open(filepath, "w") do io - take_heap_snapshot(io, all_one) +function take_heap_snapshot(filepath::AbstractString, all_one::Bool=false; streaming::Bool=false) + if streaming + _stream_heap_snapshot(filepath, all_one) + else + # Support the legacy, non-streaming mode, by first streaming the parts, then + # reassembling it after we're done. + prefix = filepath + _stream_heap_snapshot(prefix, all_one) + Profile.HeapSnapshot.assemble_snapshot(prefix, filepath) + Profile.HeapSnapshot.cleanup_streamed_files(prefix) end return filepath end +function take_heap_snapshot(io::IO, all_one::Bool=false) + # Support the legacy, non-streaming mode, by first streaming the parts to a tempdir, + # then reassembling it after we're done. + dir = tempdir() + prefix = joinpath(dir, "snapshot") + _stream_heap_snapshot(prefix, all_one) + Profile.HeapSnapshot.assemble_snapshot(prefix, io) +end +function _stream_heap_snapshot(prefix::AbstractString, all_one::Bool) + # Nodes and edges are binary files + open("$prefix.nodes", "w") do nodes + open("$prefix.edges", "w") do edges + open("$prefix.strings", "w") do strings + # The following file is json data + open("$prefix.metadata.json", "w") do json + Base.@_lock_ios(nodes, + Base.@_lock_ios(edges, + Base.@_lock_ios(strings, + Base.@_lock_ios(json, + ccall(:jl_gc_take_heap_snapshot, + Cvoid, + (Ptr{Cvoid},Ptr{Cvoid},Ptr{Cvoid},Ptr{Cvoid}, Cchar), + nodes.handle, edges.handle, strings.handle, json.handle, + Cchar(all_one)) + ) + ) + ) + ) + end + end + end + end +end function take_heap_snapshot(all_one::Bool=false; dir::Union{Nothing,S}=nothing) where {S <: AbstractString} fname = "$(getpid())_$(time_ns()).heapsnapshot" if isnothing(dir) @@ -1253,8 +1341,24 @@ function take_heap_snapshot(all_one::Bool=false; dir::Union{Nothing,S}=nothing) return take_heap_snapshot(fpath, all_one) end +""" + Profile.take_page_profile(io::IOStream) + Profile.take_page_profile(filepath::String) + +Write a JSON snapshot of the pages from Julia's pool allocator, printing for every pool allocated object, whether it's garbage, or its type. +""" +function take_page_profile(io::IOStream) + Base.@_lock_ios(io, ccall(:jl_gc_take_page_profile, Cvoid, (Ptr{Cvoid},), io.handle)) +end +function take_page_profile(filepath::String) + open(filepath, "w") do io + take_page_profile(io) + end + return filepath +end include("Allocs.jl") +include("heapsnapshot_reassemble.jl") include("precompile.jl") end # module diff --git a/stdlib/Profile/src/heapsnapshot_reassemble.jl b/stdlib/Profile/src/heapsnapshot_reassemble.jl new file mode 100644 index 0000000000000..50da13e550d82 --- /dev/null +++ b/stdlib/Profile/src/heapsnapshot_reassemble.jl @@ -0,0 +1,247 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +module HeapSnapshot + +""" + assemble_snapshot(filepath::AbstractString, out_file::AbstractString) + +Assemble a .heapsnapshot file from the .json files produced by `Profile.take_snapshot`. +""" + +# SoA layout to reduce padding +struct Edges + type::Vector{Int8} # index into `snapshot.meta.edge_types` + name_or_index::Vector{UInt} # Either an index into `snapshot.strings`, or the index in an array, depending on edge_type + to_pos::Vector{UInt} # index into `snapshot.nodes` +end +function Edges(n::Int) + Edges( + Vector{Int8}(undef, n), + Vector{UInt}(undef, n), + Vector{UInt}(undef, n), + ) +end +Base.length(n::Edges) = length(n.type) + +# trace_node_id and detachedness are always 0 in the snapshots Julia produces so we don't store them +struct Nodes + type::Vector{Int8} # index into `snapshot.meta.node_types` + name_idx::Vector{UInt32} # index into `snapshot.strings` + id::Vector{UInt} # unique id, in julia it is the address of the object + self_size::Vector{Int} # size of the object itself, not including the size of its fields + edge_count::Vector{UInt} # number of outgoing edges + edges::Edges # outgoing edges + # This is the main complexity of the .heapsnapshot format, and it's the reason we need + # to read in all the data before writing it out. The edges vector contains all edges, + # but organized by which node they came from. First, it contains all the edges coming + # out of node 0, then all edges leaving node 1, etc. So we need to have visited all + # edges, and assigned them to their corresponding nodes, before we can emit the file. + edge_idxs::Vector{Vector{UInt}} # indexes into edges, keeping per-node outgoing edge ids +end +function Nodes(n::Int, e::Int) + Nodes( + Vector{Int8}(undef, n), + Vector{UInt32}(undef, n), + Vector{UInt}(undef, n), + Vector{Int}(undef, n), + Vector{UInt32}(undef, n), + Edges(e), + [Vector{UInt}() for _ in 1:n], # Take care to construct n separate empty vectors + ) +end +Base.length(n::Nodes) = length(n.type) + +const k_node_number_of_fields = 7 + +# Like Base.dec, but doesn't allocate a string and writes directly to the io object +# We know all of the numbers we're about to write fit into a UInt and are non-negative +let _dec_d100 = UInt16[(0x30 + i % 10) << 0x8 + (0x30 + i ÷ 10) for i = 0:99] + global _write_decimal_number + _write_decimal_number(io, x::Integer, buf) = _write_decimal_number(io, unsigned(x), buf) + function _write_decimal_number(io, x::Unsigned, digits_buf) + buf = digits_buf + n = ndigits(x) + i = n + @inbounds while i >= 2 + d, r = divrem(x, 0x64) + d100 = _dec_d100[(r % Int)::Int + 1] + buf[i-1] = d100 % UInt8 + buf[i] = (d100 >> 0x8) % UInt8 + x = oftype(x, d) + i -= 2 + end + if i > 0 + @inbounds buf[i] = 0x30 + (rem(x, 0xa) % UInt8)::UInt8 + end + write(io, @view buf[max(i, 1):n]) + end +end + +function assemble_snapshot(in_prefix, out_file::AbstractString = in_prefix) + open(out_file, "w") do io + assemble_snapshot(in_prefix, io) + end +end + +# Manually parse and write the .json files, given that we don't have JSON import/export in +# julia's stdlibs. +function assemble_snapshot(in_prefix, io::IO) + preamble = read(string(in_prefix, ".metadata.json"), String) + pos = last(findfirst("node_count\":", preamble)) + 1 + endpos = findnext(==(','), preamble, pos) - 1 + node_count = parse(Int, String(@view preamble[pos:endpos])) + + pos = last(findnext("edge_count\":", preamble, endpos)) + 1 + endpos = findnext(==(','), preamble, pos) - 1 + edge_count = parse(Int, String(@view preamble[pos:endpos])) + + nodes = Nodes(node_count, edge_count) + + orphans = Set{UInt}() # nodes that have no incoming edges + # Parse nodes with empty edge counts that we need to fill later + nodes_file = open(string(in_prefix, ".nodes"), "r") + for i in 1:length(nodes) + node_type = read(nodes_file, Int8) + node_name_idx = read(nodes_file, UInt) + id = read(nodes_file, UInt) + self_size = read(nodes_file, Int) + @assert read(nodes_file, Int) == 0 # trace_node_id + @assert read(nodes_file, Int8) == 0 # detachedness + + nodes.type[i] = node_type + nodes.name_idx[i] = node_name_idx + nodes.id[i] = id + nodes.self_size[i] = self_size + nodes.edge_count[i] = 0 # edge_count + # populate the orphans set with node index + push!(orphans, i-1) + end + + # Parse the edges to fill in the edge counts for nodes and correct the to_node offsets + edges_file = open(string(in_prefix, ".edges"), "r") + for i in 1:length(nodes.edges) + edge_type = read(edges_file, Int8) + edge_name_or_index = read(edges_file, UInt) + from_node = read(edges_file, UInt) + to_node = read(edges_file, UInt) + + nodes.edges.type[i] = edge_type + nodes.edges.name_or_index[i] = edge_name_or_index + nodes.edges.to_pos[i] = to_node * k_node_number_of_fields # 7 fields per node, the streaming format doesn't multiply the offset by 7 + nodes.edge_count[from_node + 1] += UInt32(1) # C and JSON use 0-based indexing + push!(nodes.edge_idxs[from_node + 1], i) # Index into nodes.edges + # remove the node from the orphans if it has at least one incoming edge + if to_node in orphans + delete!(orphans, to_node) + end + end + + _digits_buf = zeros(UInt8, ndigits(typemax(UInt))) + println(io, @view(preamble[1:end-1]), ",") # remove trailing "}" to reopen the object + + println(io, "\"nodes\":[") + for i in 1:length(nodes) + i > 1 && println(io, ",") + _write_decimal_number(io, nodes.type[i], _digits_buf) + print(io, ",") + _write_decimal_number(io, nodes.name_idx[i], _digits_buf) + print(io, ",") + _write_decimal_number(io, nodes.id[i], _digits_buf) + print(io, ",") + _write_decimal_number(io, nodes.self_size[i], _digits_buf) + print(io, ",") + _write_decimal_number(io, nodes.edge_count[i], _digits_buf) + print(io, ",0,0") + end + print(io, "],\"edges\":[") + e = 1 + for n in 1:length(nodes) + count = nodes.edge_count[n] + len_edges = length(nodes.edge_idxs[n]) + @assert count == len_edges "For node $n: $count != $len_edges" + for i in nodes.edge_idxs[n] + e > 1 && print(io, ",") + println(io) + _write_decimal_number(io, nodes.edges.type[i], _digits_buf) + print(io, ",") + _write_decimal_number(io, nodes.edges.name_or_index[i], _digits_buf) + print(io, ",") + _write_decimal_number(io, nodes.edges.to_pos[i], _digits_buf) + if !(nodes.edges.to_pos[i] % k_node_number_of_fields == 0) + @warn "Bug in to_pos for edge $i from node $n: $(nodes.edges.to_pos[i])" + end + e += 1 + end + end + println(io, "],") + + println(io, "\"strings\":[") + open(string(in_prefix, ".strings"), "r") do strings_io + first = true + while !eof(strings_io) + str_size = read(strings_io, UInt) + str_bytes = read(strings_io, str_size) + str = String(str_bytes) + if first + first = false + else + print(io, ",\n") + end + print_str_escape_json(io, str) + end + end + print(io, "]}") + + # remove the uber node from the orphans + if 0 in orphans + delete!(orphans, 0) + end + + @assert isempty(orphans) "Orphaned nodes: $(orphans), node count: $(length(nodes)), orphan node count: $(length(orphans))" + + return nothing +end + +""" + cleanup_streamed_files(prefix::AbstractString) + +Remove files streamed during `take_heap_snapshot` in streaming mode. +""" +function cleanup_streamed_files(prefix::AbstractString) + rm(string(prefix, ".metadata.json")) + rm(string(prefix, ".nodes")) + rm(string(prefix, ".edges")) + rm(string(prefix, ".strings")) + return nothing +end + +function print_str_escape_json(stream::IO, s::AbstractString) + print(stream, '"') + for c in s + if c == '"' + print(stream, "\\\"") + elseif c == '\\' + print(stream, "\\\\") + elseif c == '\b' + print(stream, "\\b") + elseif c == '\f' + print(stream, "\\f") + elseif c == '\n' + print(stream, "\\n") + elseif c == '\r' + print(stream, "\\r") + elseif c == '\t' + print(stream, "\\t") + elseif '\x00' <= c <= '\x1f' + print(stream, "\\u", lpad(string(UInt16(c), base=16), 4, '0')) + elseif !isvalid(c) + # we have to do this because vscode's viewer doesn't like the replace character + print(stream, "[invalid unicode character]") + else + print(stream, c) + end + end + print(stream, '"') +end + +end diff --git a/stdlib/Profile/test/allocs.jl b/stdlib/Profile/test/allocs.jl index ae0cbab945f01..d4930a2b7f5ed 100644 --- a/stdlib/Profile/test/allocs.jl +++ b/stdlib/Profile/test/allocs.jl @@ -1,6 +1,13 @@ using Test using Profile: Allocs +Allocs.clear() +let iobuf = IOBuffer() + for format in (:tree, :flat) + Test.@test_logs (:warn, r"^There were no samples collected\.") Allocs.print(iobuf; format, C=true) + end +end + @testset "alloc profiler doesn't segfault" begin res = Allocs.@profile sample_rate=1.0 begin # test the allocations during compilation @@ -13,6 +20,20 @@ using Profile: Allocs @test first_alloc.size > 0 @test length(first_alloc.stacktrace) > 0 @test length(string(first_alloc.type)) > 0 + + # test printing options + for options in ((format=:tree, C=true), + (format=:tree, maxdepth=2), + (format=:flat, C=true), + (), + (format=:flat, sortedby=:count), + (format=:tree, recur=:flat), + ) + iobuf = IOBuffer() + Allocs.print(iobuf; options...) + str = String(take!(iobuf)) + @test !isempty(str) + end end @testset "alloc profiler works when there are multiple tasks on multiple threads" begin @@ -143,12 +164,17 @@ end end @testset "alloc profiler catches allocs from buffer resize" begin + f(a) = for _ in 1:100; push!(a, 1); end + f(Int[]) + resize!(Int[], 1) a = Int[] - Allocs.@profile sample_rate=1 for _ in 1:100; push!(a, 1); end - + Allocs.clear() + Allocs.@profile sample_rate=1 f(a) + Allocs.@profile sample_rate=1 resize!(a, 1_000_000) # 4MB prof = Allocs.fetch() Allocs.clear() - @test length(prof.allocs) >= 1 - @test length([a for a in prof.allocs if a.type == Profile.Allocs.BufferType]) >= 1 + @test 3 <= length(prof.allocs) <= 10 + @test length([a for a in prof.allocs if a.type === Allocs.BufferType]) == 1 + @test length([a for a in prof.allocs if a.type === Memory{Int}]) >= 2 end diff --git a/stdlib/Profile/test/heapsnapshot_reassemble.jl b/stdlib/Profile/test/heapsnapshot_reassemble.jl new file mode 100644 index 0000000000000..e1d6621647671 --- /dev/null +++ b/stdlib/Profile/test/heapsnapshot_reassemble.jl @@ -0,0 +1,54 @@ +using Test + +@testset "_write_decimal_number" begin + _digits_buf = zeros(UInt8, ndigits(typemax(UInt))) + io = IOBuffer() + + test_write(d) = begin + Profile.HeapSnapshot._write_decimal_number(io, d, _digits_buf) + s = String(take!(io)) + seekstart(io) + return s + end + @test test_write(0) == "0" + @test test_write(99) == "99" + + @test test_write(UInt8(0)) == "0" + @test test_write(UInt32(0)) == "0" + @test test_write(Int32(0)) == "0" + + @test test_write(UInt8(99)) == "99" + @test test_write(UInt32(99)) == "99" + @test test_write(Int32(99)) == "99" + + # Sample among possible UInts we might print + for x in typemin(UInt8):typemax(UInt8) + @test test_write(x) == string(x) + end + for x in typemin(UInt):typemax(UInt)÷10001:typemax(UInt) + @test test_write(x) == string(x) + end +end + +function test_print_str_escape_json(input::AbstractString, expected::AbstractString) + output = IOBuffer() + Profile.HeapSnapshot.print_str_escape_json(output, input) + @test String(take!(output)) == expected +end + +@testset "print_str_escape_json" begin + # Test basic string escaping + test_print_str_escape_json("\"hello\"", "\"\\\"hello\\\"\"") + + # Test escaping of control characters + test_print_str_escape_json("\x01\x02\x03", "\"\\u0001\\u0002\\u0003\"") + + # Test escaping of other special characters + test_print_str_escape_json("\b\f\n\r\t", "\"\\b\\f\\n\\r\\t\"") + + # Test handling of mixed characters + test_print_str_escape_json("abc\ndef\"ghi", "\"abc\\ndef\\\"ghi\"") + + # Test handling of empty string + test_print_str_escape_json("", "\"\"") +end diff --git a/stdlib/Profile/test/runtests.jl b/stdlib/Profile/test/runtests.jl index 95ec7f857dad7..cbfdde61d7054 100644 --- a/stdlib/Profile/test/runtests.jl +++ b/stdlib/Profile/test/runtests.jl @@ -38,28 +38,18 @@ let r = Profile.retrieve() end end -let iobuf = IOBuffer() - Profile.print(iobuf, format=:tree, C=true) - str = String(take!(iobuf)) - @test !isempty(str) - truncate(iobuf, 0) - Profile.print(iobuf, format=:tree, maxdepth=2) - str = String(take!(iobuf)) - @test !isempty(str) - truncate(iobuf, 0) - Profile.print(iobuf, format=:flat, C=true) - str = String(take!(iobuf)) - @test !isempty(str) - truncate(iobuf, 0) - Profile.print(iobuf) - @test !isempty(String(take!(iobuf))) - truncate(iobuf, 0) - Profile.print(iobuf, format=:flat, sortedby=:count) - @test !isempty(String(take!(iobuf))) - Profile.print(iobuf, format=:tree, recur=:flat) +# test printing options +for options in ((format=:tree, C=true), + (format=:tree, maxdepth=2), + (format=:flat, C=true), + (), + (format=:flat, sortedby=:count), + (format=:tree, recur=:flat), + ) + iobuf = IOBuffer() + Profile.print(iobuf; options...) str = String(take!(iobuf)) @test !isempty(str) - truncate(iobuf, 0) end @testset "Profile.print() groupby options" begin @@ -181,7 +171,7 @@ let cmd = Base.julia_cmd() p = open(`$cmd -e $script`) t = Timer(120) do t # should be under 10 seconds, so give it 2 minutes then report failure - println("KILLING BY PROFILE TEST WATCHDOG\n") + println("KILLING debuginfo registration test BY PROFILE TEST WATCHDOG\n") kill(p, Base.SIGTERM) sleep(10) kill(p, Base.SIGKILL) @@ -201,16 +191,21 @@ if Sys.isbsd() || Sys.islinux() print(stderr, "started\n") eof(stdin) """ - iob = Base.BufferStream() + iob = Base.BufferStream() # make an unbounded buffer, so we can just read after waiting for exit notify_exit = Base.PipeEndpoint() - p = run(pipeline(`$cmd -e $script`, stdin=notify_exit, stderr=iob, stdout=devnull), wait=false) + p = run(`$cmd -e $script`, notify_exit, devnull, iob, wait=false) + eof = @async try # set up a monitor task to set EOF on iob after p exits + wait(p) + finally + closewrite(iob) + end t = Timer(120) do t # should be under 10 seconds, so give it 2 minutes then report failure - println("KILLING BY PROFILE TEST WATCHDOG\n") + println("KILLING siginfo/sigusr1 test BY PROFILE TEST WATCHDOG\n") kill(p, Base.SIGTERM) sleep(10) kill(p, Base.SIGKILL) - close(p) + close(notify_exit) end try s = readuntil(iob, "started", keep=true) @@ -229,18 +224,18 @@ if Sys.isbsd() || Sys.islinux() @test occursin("Overhead ╎", s) end close(notify_exit) # notify test finished - s = read(iob, String) # consume test output - wait(p) # wait for test completion - @test success(p) + wait(eof) # wait for test completion + s = read(iob, String) # consume test output from buffer close(t) catch close(notify_exit) + wait(eof) # wait for test completion errs = read(iob, String) # consume test output isempty(errs) || println("CHILD STDERR after test failure: ", errs) - wait(p) # wait for test completion close(t) rethrow() end + @test success(p) end end end @@ -294,4 +289,21 @@ end rm(tmpdir, force = true, recursive = true) end +@testset "PageProfile" begin + fname = "$(getpid())_$(time_ns())" + fpath = joinpath(tempdir(), fname) + Profile.take_page_profile(fpath) + open(fpath) do fs + @test readline(fs) != "" + end + rm(fpath) +end + include("allocs.jl") + +@testset "Docstrings" begin + undoc = Docs.undocumented_names(Profile) + @test_broken isempty(undoc) + @test undoc == [:Allocs] +end +include("heapsnapshot_reassemble.jl") diff --git a/stdlib/Project.toml b/stdlib/Project.toml new file mode 100644 index 0000000000000..cc7ba99dd4e4f --- /dev/null +++ b/stdlib/Project.toml @@ -0,0 +1,61 @@ +[deps] +ArgTools = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" +Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +CRC32c = "8bf52ea8-c179-5cab-976a-9e18b702a9bc" +CompilerSupportLibraries_jll = "e66e0078-7015-5450-92f7-15fbd957f2ae" +Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" +DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab" +Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" +Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +FileWatching = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +Future = "9fa8497b-333b-5362-9e8d-4d0656e87820" +GMP_jll = "781609d7-10c4-51f6-84f2-b8444358ff6d" +InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +JuliaSyntaxHighlighting = "dc6e5ff7-fb65-4e79-a425-ec3bc9c03011" +LLD_jll = "d55e3150-da41-5e91-b323-ecfd1eec6109" +LLVMLibUnwind_jll = "47c5dbc3-30ba-59ef-96a6-123e260183d9" +LazyArtifacts = "4af54fe1-eca0-43a8-85a7-787d91b784e3" +LibCURL = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +LibCURL_jll = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433" +LibGit2_jll = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +LibSSH2_jll = "29816b5a-b9ab-546f-933c-edad1886dfa8" +LibUV_jll = "183b4373-6708-53ba-ad28-60e28bb38547" +LibUnwind_jll = "745a5e78-f969-53e9-954f-d19f2f74f4e3" +Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" +MPFR_jll = "3a97d323-0669-5f0c-9066-3539efd106a3" +Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" +MbedTLS_jll = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +Mmap = "a63ad114-7e13-5084-954f-fe012c677804" +MozillaCACerts_jll = "14a3606d-f60d-562e-9121-12d972cd8159" +NetworkOptions = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +OpenBLAS_jll = "4536629a-c528-5b80-bd46-f80d51c5b363" +OpenLibm_jll = "05823500-19ac-5b8b-9628-191a04bc5112" +PCRE2_jll = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" +Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" +Profile = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" +REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce" +Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" +SharedArrays = "1a1011a3-84de-559e-8e89-a11a2f7dc383" +Sockets = "6462fe0b-24de-5631-8697-dd941f90decc" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +StyledStrings = "f489334b-da3d-4c2e-b8f0-e476e12c162b" +SuiteSparse_jll = "bea87d4a-7f5b-5778-9afe-8cc45184846c" +TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +Tar = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" +Unicode = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" +Zlib_jll = "83775a58-1f1d-513f-b197-d71354ab007a" +dSFMT_jll = "05ff407c-b0c1-5878-9df8-858cc2e60c36" +libLLVM_jll = "8f36deef-c2a5-5394-99ed-8e07531fb29a" +libblastrampoline_jll = "8e850b90-86db-534c-a0d3-1478176c7d93" +nghttp2_jll = "8e850ede-7688-5339-a07c-302acd2aaf8d" +p7zip_jll = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" diff --git a/stdlib/REPL/Project.toml b/stdlib/REPL/Project.toml index 2c3ab32fdc327..6318bd0258ab3 100644 --- a/stdlib/REPL/Project.toml +++ b/stdlib/REPL/Project.toml @@ -1,10 +1,12 @@ name = "REPL" uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" +version = "1.11.0" [deps] InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" Sockets = "6462fe0b-24de-5631-8697-dd941f90decc" +StyledStrings = "f489334b-da3d-4c2e-b8f0-e476e12c162b" Unicode = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" [extras] diff --git a/stdlib/REPL/docs/src/index.md b/stdlib/REPL/docs/src/index.md index d1064d9c063b4..d2a17e3a6b4a3 100644 --- a/stdlib/REPL/docs/src/index.md +++ b/stdlib/REPL/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/REPL/docs/src/index.md" +``` + # The Julia REPL Julia comes with a full-featured interactive command-line REPL (read-eval-print loop) built into @@ -7,8 +11,9 @@ shell modes. The REPL can be started by simply calling `julia` with no arguments on the executable: ```@eval +using REPL io = IOBuffer() -Base.banner(io) +REPL.banner(io) banner = String(take!(io)) import Markdown Markdown.parse("```\n\$ julia\n\n$(banner)\njulia>\n```") @@ -577,8 +582,9 @@ Main It is possible to change this contextual module via the function `REPL.activate(m)` where `m` is a `Module` or by typing the module in the REPL -and pressing the keybinding Alt-m (the cursor must be on the module name). The -active module is shown in the prompt: +and pressing the keybinding Alt-m with the cursor on the module name (Esc-m on MacOS). +Pressing the keybinding on an empty prompt toggles the context between the previously active +non-`Main` module and `Main`. The active module is shown in the prompt (unless it is `Main`): ```julia-repl julia> using REPL @@ -598,9 +604,13 @@ julia> Core # using the keybinding to change module (Core) julia> -(Core) julia> Main # going back to Main via keybinding +(Core) julia> # going back to Main via keybinding julia> + +julia> # going back to previously-active Core via keybinding + +(Core) julia> ``` Functions that take an optional module argument often defaults to the REPL diff --git a/stdlib/REPL/src/LineEdit.jl b/stdlib/REPL/src/LineEdit.jl index efe893870d46e..a910bfaebab06 100644 --- a/stdlib/REPL/src/LineEdit.jl +++ b/stdlib/REPL/src/LineEdit.jl @@ -6,8 +6,7 @@ import ..REPL using REPL: AbstractREPL, Options using ..Terminals -import ..Terminals: raw!, width, height, cmove, getX, - getY, clear_line, beep +import ..Terminals: raw!, width, height, clear_line, beep import Base: ensureroom, show, AnyDict, position using Base: something @@ -382,23 +381,27 @@ function check_for_hint(s::MIState) return clear_hint(st) end completions, partial, should_complete = complete_line(st.p.complete, st, s.active_module)::Tuple{Vector{String},String,Bool} + isempty(completions) && return clear_hint(st) # Don't complete for single chars, given e.g. `x` completes to `xor` if length(partial) > 1 && should_complete - if length(completions) == 1 - hint = only(completions)[sizeof(partial)+1:end] - if !isempty(hint) # completion on a complete name returns itself so check that there's something to hint + singlecompletion = length(completions) == 1 + p = singlecompletion ? completions[1] : common_prefix(completions) + if singlecompletion || p in completions # i.e. complete `@time` even though `@time_imports` etc. exists + # The completion `p` and the input `partial` may not share the same initial + # characters, for instance when completing to subscripts or superscripts. + # So, in general, make sure that the hint starts at the correct position by + # incrementing its starting position by as many characters as the input. + startind = 1 # index of p from which to start providing the hint + maxind = ncodeunits(p) + for _ in partial + startind = nextind(p, startind) + startind > maxind && break + end + if startind ≤ maxind # completion on a complete name returns itself so check that there's something to hint + hint = p[startind:end] st.hint = hint return true end - elseif length(completions) > 1 - p = common_prefix(completions) - if p in completions # i.e. complete `@time` even though `@time_imports` etc. exists - hint = p[sizeof(partial)+1:end] - if !isempty(hint) - st.hint = hint - return true - end - end end end return clear_hint(st) @@ -476,14 +479,12 @@ prompt_string(f::Function) = Base.invokelatest(f) function maybe_show_hint(s::PromptState) isa(s.hint, String) || return nothing # The hint being "" then nothing is used to first clear a previous hint, then skip printing the hint - # the clear line cannot be printed each time because it breaks column movement if isempty(s.hint) - print(terminal(s), "\e[0K") # clear remainder of line which had a hint s.hint = nothing else Base.printstyled(terminal(s), s.hint, color=:light_black) cmove_left(terminal(s), textwidth(s.hint)) - s.hint = "" # being "" signals to do one clear line remainder to clear the hint next time if still empty + s.hint = "" # being "" signals to do one clear line remainder to clear the hint next time the screen is refreshed end return nothing end @@ -493,8 +494,13 @@ function refresh_multi_line(s::PromptState; kw...) close(s.refresh_wait) s.refresh_wait = nothing end + if s.hint isa String + # clear remainder of line which is unknown here if it had a hint before unbeknownst to refresh_multi_line + # the clear line cannot be printed each time because it would break column movement + print(terminal(s), "\e[0K") + end r = refresh_multi_line(terminal(s), s; kw...) - maybe_show_hint(s) + maybe_show_hint(s) # now maybe write the hint back to the screen return r end refresh_multi_line(s::ModeState; kw...) = refresh_multi_line(terminal(s), s; kw...) @@ -809,9 +815,9 @@ end # returns the removed portion as a String function edit_splice!(s::BufferLike, r::Region=region(s), ins::String = ""; rigid_mark::Bool=true) A, B = first(r), last(r) - A >= B && isempty(ins) && return String(ins) + A >= B && isempty(ins) && return ins buf = buffer(s) - pos = position(buf) + pos = position(buf) # n.b. position(), etc, are 0-indexed adjust_pos = true if A <= pos < B seek(buf, A) @@ -820,18 +826,29 @@ function edit_splice!(s::BufferLike, r::Region=region(s), ins::String = ""; rigi else adjust_pos = false end - if A < buf.mark < B || A == buf.mark == B - # rigid_mark is used only if the mark is strictly "inside" - # the region, or the region is empty and the mark is at the boundary - buf.mark = rigid_mark ? A : A + sizeof(ins) - elseif buf.mark >= B - buf.mark += sizeof(ins) - B + A + mark = buf.mark + if mark != -1 + if A < mark < B || A == mark == B + # rigid_mark is used only if the mark is strictly "inside" + # the region, or the region is empty and the mark is at the boundary + mark = rigid_mark ? A : A + sizeof(ins) + elseif mark >= B + mark += sizeof(ins) - B + A + end + buf.mark = -1 end - ensureroom(buf, B) # handle !buf.reinit from take! - ret = splice!(buf.data, A+1:B, codeunits(String(ins))) # position(), etc, are 0-indexed - buf.size = buf.size + sizeof(ins) - B + A - adjust_pos && seek(buf, position(buf) + sizeof(ins)) - return String(copy(ret)) + # Implement ret = splice!(buf.data, A+1:B, codeunits(ins)) for a stream + pos = position(buf) + seek(buf, A) + ret = read(buf, A >= B ? 0 : B - A) + trail = read(buf) + seek(buf, A) + write(buf, ins) + write(buf, trail) + truncate(buf, position(buf)) + seek(buf, pos + (adjust_pos ? sizeof(ins) : 0)) + buf.mark = mark + return String(ret) end edit_splice!(s::MIState, ins::AbstractString) = edit_splice!(s, region(s), ins) @@ -1473,16 +1490,32 @@ end current_word_with_dots(s::MIState) = current_word_with_dots(buffer(s)) +previous_active_module::Module = Main + function activate_module(s::MIState) word = current_word_with_dots(s); - isempty(word) && return beep(s) - try - mod = Base.Core.eval(Base.active_module(), Base.Meta.parse(word)) - REPL.activate(mod) - edit_clear(s) - catch + empty = isempty(word) + mod = if empty + previous_active_module + else + try + Base.Core.eval(Base.active_module(), Base.Meta.parse(word)) + catch + nothing + end + end + if !(mod isa Module) || mod == Base.active_module() beep(s) + return + end + empty && edit_insert(s, ' ') # makes the `edit_clear` below actually update the prompt + if Base.active_module() == Main || mod == Main + # At least one needs to be Main. Disallows toggling between two non-Main modules because it's + # otherwise hard to get back to Main + global previous_active_module = Base.active_module() end + REPL.activate(mod) + edit_clear(s) end history_prev(::EmptyHistoryProvider) = ("", false) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 25b2ff00602d6..f75e91da67be2 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -14,27 +14,93 @@ REPL.run_repl(repl) """ module REPL -const PRECOMPILE_STATEMENTS = Vector{String}() +Base.Experimental.@optlevel 1 +Base.Experimental.@max_methods 1 -function __init__() - Base.REPL_MODULE_REF[] = REPL - # We can encounter the situation where the sub-ordinate process used - # during precompilation of REPL, can load a valid cache-file. - # We need to replay the statements such that the parent process - # can also include those. See JuliaLang/julia#51532 - if Base.JLOptions().trace_compile !== C_NULL && !isempty(PRECOMPILE_STATEMENTS) - for statement in PRECOMPILE_STATEMENTS - ccall(:jl_write_precompile_statement, Cvoid, (Cstring,), statement) +function UndefVarError_hint(io::IO, ex::UndefVarError) + var = ex.var + if var === :or + print(io, "\nSuggestion: Use `||` for short-circuiting boolean OR.") + elseif var === :and + print(io, "\nSuggestion: Use `&&` for short-circuiting boolean AND.") + elseif var === :help + println(io) + # Show friendly help message when user types help or help() and help is undefined + show(io, MIME("text/plain"), Base.Docs.parsedoc(Base.Docs.keywords[:help])) + elseif var === :quit + print(io, "\nSuggestion: To exit Julia, use Ctrl-D, or type exit() and press enter.") + end + if isdefined(ex, :scope) + scope = ex.scope + if scope isa Module + bnd = ccall(:jl_get_module_binding, Any, (Any, Any, Cint), scope, var, true)::Core.Binding + if isdefined(bnd, :owner) + owner = bnd.owner + if owner === bnd + print(io, "\nSuggestion: add an appropriate import or assignment. This global was declared but not assigned.") + end + else + owner = ccall(:jl_binding_owner, Ptr{Cvoid}, (Any, Any), scope, var) + if C_NULL == owner + # No global of this name exists in this module. + # This is the common case, so do not print that information. + # It could be the binding was exported by two modules, which we can detect + # by the `usingfailed` flag in the binding: + if isdefined(bnd, :flags) && Bool(bnd.flags >> 4 & 1) # magic location of the `usingfailed` flag + print(io, "\nHint: It looks like two or more modules export different ", + "bindings with this name, resulting in ambiguity. Try explicitly ", + "importing it from a particular module, or qualifying the name ", + "with the module it should come from.") + else + print(io, "\nSuggestion: check for spelling errors or missing imports.") + end + owner = bnd + else + owner = unsafe_pointer_to_objref(owner)::Core.Binding + end + end + if owner !== bnd + # this could use jl_binding_dbgmodule for the exported location in the message too + print(io, "\nSuggestion: this global was defined as `$(owner.globalref)` but not assigned a value.") + end + elseif scope === :static_parameter + print(io, "\nSuggestion: run Test.detect_unbound_args to detect method arguments that do not fully constrain a type parameter.") + elseif scope === :local + print(io, "\nSuggestion: check for an assignment to a local variable that shadows a global of the same name.") end else - empty!(PRECOMPILE_STATEMENTS) + scope = undef end + if scope !== Base && !_UndefVarError_warnfor(io, Base, var) + warned = false + for m in Base.loaded_modules_order + m === Core && continue + m === Base && continue + m === Main && continue + m === scope && continue + warned |= _UndefVarError_warnfor(io, m, var) + end + warned || + _UndefVarError_warnfor(io, Core, var) || + _UndefVarError_warnfor(io, Main, var) + end + return nothing end -Base.Experimental.@optlevel 1 -Base.Experimental.@max_methods 1 +function _UndefVarError_warnfor(io::IO, m::Module, var::Symbol) + Base.isbindingresolved(m, var) || return false + (Base.isexported(m, var) || Base.ispublic(m, var)) || return false + print(io, "\nHint: a global variable of this name also exists in $m.") + return true +end -using Base.Meta, Sockets +function __init__() + Base.REPL_MODULE_REF[] = REPL + Base.Experimental.register_error_hint(UndefVarError_hint, UndefVarError) + return nothing +end + +using Base.Meta, Sockets, StyledStrings import InteractiveUtils export @@ -73,12 +139,10 @@ import ..LineEdit: history_first, history_last, history_search, - accept_result, setmodifiers!, terminal, MIState, PromptState, - TextInterface, mode_idx include("REPLCompletions.jl") @@ -148,6 +212,27 @@ const repl_ast_transforms = Any[softscope] # defaults for new REPL backends # to e.g. install packages on demand const install_packages_hooks = Any[] +# N.B.: Any functions starting with __repl_entry cut off backtraces when printing in the REPL. +# We need to do this for both the actual eval and macroexpand, since the latter can cause custom macro +# code to run (and error). +__repl_entry_lower_with_loc(mod::Module, @nospecialize(ast), toplevel_file::Ref{Ptr{UInt8}}, toplevel_line::Ref{Cint}) = + ccall(:jl_expand_with_loc, Any, (Any, Any, Ptr{UInt8}, Cint), ast, mod, toplevel_file[], toplevel_line[]) +__repl_entry_eval_expanded_with_loc(mod::Module, @nospecialize(ast), toplevel_file::Ref{Ptr{UInt8}}, toplevel_line::Ref{Cint}) = + ccall(:jl_toplevel_eval_flex, Any, (Any, Any, Cint, Cint, Ptr{Ptr{UInt8}}, Ptr{Cint}), mod, ast, 1, 1, toplevel_file, toplevel_line) + +function toplevel_eval_with_hooks(mod::Module, @nospecialize(ast), toplevel_file=Ref{Ptr{UInt8}}(Base.unsafe_convert(Ptr{UInt8}, :REPL)), toplevel_line=Ref{Cint}(1)) + if !isexpr(ast, :toplevel) + ast = __repl_entry_lower_with_loc(mod, ast, toplevel_file, toplevel_line) + check_for_missing_packages_and_run_hooks(ast) + return __repl_entry_eval_expanded_with_loc(mod, ast, toplevel_file, toplevel_line) + end + local value=nothing + for i = 1:length(ast.args) + value = toplevel_eval_with_hooks(mod, ast.args[i], toplevel_file, toplevel_line) + end + return value +end + function eval_user_input(@nospecialize(ast), backend::REPLBackend, mod::Module) lasterr = nothing Base.sigatomic_begin() @@ -158,13 +243,10 @@ function eval_user_input(@nospecialize(ast), backend::REPLBackend, mod::Module) put!(backend.response_channel, Pair{Any, Bool}(lasterr, true)) else backend.in_eval = true - if !isempty(install_packages_hooks) - check_for_missing_packages_and_run_hooks(ast) - end for xf in backend.ast_transforms ast = Base.invokelatest(xf, ast) end - value = Core.eval(mod, ast) + value = toplevel_eval_with_hooks(mod, ast) backend.in_eval = false setglobal!(Base.MainInclude, :ans, value) put!(backend.response_channel, Pair{Any, Bool}(value, false)) @@ -187,13 +269,14 @@ function check_for_missing_packages_and_run_hooks(ast) mods = modules_to_be_loaded(ast) filter!(mod -> isnothing(Base.identify_package(String(mod))), mods) # keep missing modules if !isempty(mods) + isempty(install_packages_hooks) && Base.require_stdlib(Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg")) for f in install_packages_hooks Base.invokelatest(f, mods) && return end end end -function modules_to_be_loaded(ast::Expr, mods::Vector{Symbol} = Symbol[]) +function _modules_to_be_loaded!(ast::Expr, mods::Vector{Symbol}) ast.head === :quote && return mods # don't search if it's not going to be run during this eval if ast.head === :using || ast.head === :import for arg in ast.args @@ -208,12 +291,24 @@ function modules_to_be_loaded(ast::Expr, mods::Vector{Symbol} = Symbol[]) end end end - for arg in ast.args - if isexpr(arg, (:block, :if, :using, :import)) - modules_to_be_loaded(arg, mods) + if ast.head !== :thunk + for arg in ast.args + if isexpr(arg, (:block, :if, :using, :import)) + _modules_to_be_loaded!(arg, mods) + end + end + else + code = ast.args[1] + for arg in code.code + isa(arg, Expr) || continue + _modules_to_be_loaded!(arg, mods) end end - filter!(mod -> !in(String(mod), ["Base", "Main", "Core"]), mods) # Exclude special non-package modules +end + +function modules_to_be_loaded(ast::Expr, mods::Vector{Symbol} = Symbol[]) + _modules_to_be_loaded!(ast, mods) + filter!(mod::Symbol -> !in(mod, (:Base, :Main, :Core)), mods) # Exclude special non-package modules return unique(mods) end @@ -898,7 +993,7 @@ end find_hist_file() = get(ENV, "JULIA_HISTORY", !isempty(DEPOT_PATH) ? joinpath(DEPOT_PATH[1], "logs", "repl_history.jl") : - error("DEPOT_PATH is empty and and ENV[\"JULIA_HISTORY\"] not set.")) + error("DEPOT_PATH is empty and ENV[\"JULIA_HISTORY\"] not set.")) backend(r::AbstractREPL) = r.backendref @@ -1131,24 +1226,23 @@ function setup_interface( ']' => function (s::MIState,o...) if isempty(s) || position(LineEdit.buffer(s)) == 0 pkgid = Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg") - if Base.locate_package(pkgid) !== nothing # Only try load Pkg if we can find it - Pkg = Base.require(pkgid) - # Pkg should have loaded its REPL mode by now, let's find it so we can transition to it. - pkg_mode = nothing + REPLExt = Base.require_stdlib(pkgid, "REPLExt") + pkg_mode = nothing + if REPLExt isa Module && isdefined(REPLExt, :PkgCompletionProvider) for mode in repl.interface.modes - if mode isa LineEdit.Prompt && mode.complete isa Pkg.REPLMode.PkgCompletionProvider + if mode isa LineEdit.Prompt && mode.complete isa REPLExt.PkgCompletionProvider pkg_mode = mode break end end - # TODO: Cache the `pkg_mode`? - if pkg_mode !== nothing - buf = copy(LineEdit.buffer(s)) - transition(s, pkg_mode) do - LineEdit.state(s, pkg_mode).input_buffer = buf - end - return + end + # TODO: Cache the `pkg_mode`? + if pkg_mode !== nothing + buf = copy(LineEdit.buffer(s)) + transition(s, pkg_mode) do + LineEdit.state(s, pkg_mode).input_buffer = buf end + return end end edit_insert(s, ']') @@ -1285,7 +1379,7 @@ function setup_interface( # execute the statement terminal = LineEdit.terminal(s) # This is slightly ugly but ok for now raw!(terminal, false) && disable_bracketed_paste(terminal) - LineEdit.mode(s).on_done(s, LineEdit.buffer(s), true) + @invokelatest LineEdit.mode(s).on_done(s, LineEdit.buffer(s), true) raw!(terminal, true) && enable_bracketed_paste(terminal) LineEdit.push_undo(s) # when the last line is incomplete end @@ -1422,10 +1516,80 @@ ends_with_semicolon(code::AbstractString) = ends_with_semicolon(String(code)) ends_with_semicolon(code::Union{String,SubString{String}}) = contains(_rm_strings_and_comments(code), r";\s*$") +function banner(io::IO = stdout; short = false) + if Base.GIT_VERSION_INFO.tagged_commit + commit_string = Base.TAGGED_RELEASE_BANNER + elseif isempty(Base.GIT_VERSION_INFO.commit) + commit_string = "" + else + days = Int(floor((ccall(:jl_clock_now, Float64, ()) - Base.GIT_VERSION_INFO.fork_master_timestamp) / (60 * 60 * 24))) + days = max(0, days) + unit = days == 1 ? "day" : "days" + distance = Base.GIT_VERSION_INFO.fork_master_distance + commit = Base.GIT_VERSION_INFO.commit_short + + if distance == 0 + commit_string = "Commit $(commit) ($(days) $(unit) old master)" + else + branch = Base.GIT_VERSION_INFO.branch + commit_string = "$(branch)/$(commit) (fork: $(distance) commits, $(days) $(unit))" + end + end + + commit_date = isempty(Base.GIT_VERSION_INFO.date_string) ? "" : " ($(split(Base.GIT_VERSION_INFO.date_string)[1]))" + + if get(io, :color, false)::Bool + c = Base.text_colors + tx = c[:normal] # text + jl = c[:normal] # julia + d1 = c[:bold] * c[:blue] # first dot + d2 = c[:bold] * c[:red] # second dot + d3 = c[:bold] * c[:green] # third dot + d4 = c[:bold] * c[:magenta] # fourth dot + + if short + print(io,""" + $(d3)o$(tx) | Version $(VERSION)$(commit_date) + $(d2)o$(tx) $(d4)o$(tx) | $(commit_string) + """) + else + print(io,""" $(d3)_$(tx) + $(d1)_$(tx) $(jl)_$(tx) $(d2)_$(d3)(_)$(d4)_$(tx) | Documentation: https://docs.julialang.org + $(d1)(_)$(jl) | $(d2)(_)$(tx) $(d4)(_)$(tx) | + $(jl)_ _ _| |_ __ _$(tx) | Type \"?\" for help, \"]?\" for Pkg help. + $(jl)| | | | | | |/ _` |$(tx) | + $(jl)| | |_| | | | (_| |$(tx) | Version $(VERSION)$(commit_date) + $(jl)_/ |\\__'_|_|_|\\__'_|$(tx) | $(commit_string) + $(jl)|__/$(tx) | + + """) + end + else + if short + print(io,""" + o | Version $(VERSION)$(commit_date) + o o | $(commit_string) + """) + else + print(io,""" + _ + _ _ _(_)_ | Documentation: https://docs.julialang.org + (_) | (_) (_) | + _ _ _| |_ __ _ | Type \"?\" for help, \"]?\" for Pkg help. + | | | | | | |/ _` | | + | | |_| | | | (_| | | Version $(VERSION)$(commit_date) + _/ |\\__'_|_|_|\\__'_| | $(commit_string) + |__/ | + + """) + end + end +end + function run_frontend(repl::StreamREPL, backend::REPLBackendRef) repl.frontend_task = current_task() have_color = hascolor(repl) - Base.banner(repl.stream) + banner(repl.stream) d = REPLDisplay(repl) dopushdisplay = !in(d,Base.Multimedia.displays) dopushdisplay && pushdisplay(d) @@ -1525,7 +1689,6 @@ function __current_ast_transforms(backend) end end - function numbered_prompt!(repl::LineEditREPL=Base.active_repl, backend=nothing) n = Ref{Int}(0) set_prompt(repl, n) diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index 04e376b013114..5ffa718f38604 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -4,10 +4,11 @@ module REPLCompletions export completions, shell_completions, bslash_completions, completion_text -using Core: CodeInfo, MethodInstance, CodeInstance, Const +using Core: Const const CC = Core.Compiler using Base.Meta -using Base: propertynames, something +using Base: propertynames, something, IdSet +using Base.Filesystem: _readdirx abstract type Completion end @@ -176,7 +177,7 @@ function complete_symbol(@nospecialize(ex), name::String, @nospecialize(ffunc), # as excluding Main.Main.Main, etc., because that's most likely not what # the user wants p = let mod=mod, modname=nameof(mod) - (s::Symbol) -> !Base.isdeprecated(mod, s) && s != modname && ffunc(mod, s)::Bool + (s::Symbol) -> !Base.isdeprecated(mod, s) && s != modname && ffunc(mod, s)::Bool && !(mod === Main && s === :MainInclude) end # Looking for a binding in a module if mod == context_module @@ -190,11 +191,14 @@ function complete_symbol(@nospecialize(ex), name::String, @nospecialize(ffunc), append!(suggestions, filtered_mod_names(p, mod, name, true, false)) end elseif val !== nothing # looking for a property of an instance - for property in propertynames(val, false) - # TODO: support integer arguments (#36872) - if property isa Symbol && startswith(string(property), name) - push!(suggestions, PropertyCompletion(val, property)) + try + for property in propertynames(val, false) + # TODO: support integer arguments (#36872) + if property isa Symbol && startswith(string(property), name) + push!(suggestions, PropertyCompletion(val, property)) + end end + catch end elseif field_completion_eligible(t) # Looking for a member of a type @@ -258,8 +262,111 @@ const sorted_keyvals = ["false", "true"] complete_keyval(s::Union{String,SubString{String}}) = complete_from_list(KeyvalCompletion, sorted_keyvals, s) -function complete_path(path::AbstractString, pos::Int; - use_envpath=false, shell_escape=false, +function do_raw_escape(s) + # escape_raw_string with delim='`' and ignoring the rule for the ending \ + return replace(s, r"(\\+)`" => s"\1\\`") +end +function do_shell_escape(s) + return Base.shell_escape_posixly(s) +end +function do_string_escape(s) + return escape_string(s, ('\"','$')) +end + +const PATH_cache_lock = Base.ReentrantLock() +const PATH_cache = Set{String}() +PATH_cache_task::Union{Task,Nothing} = nothing # used for sync in tests +next_cache_update::Float64 = 0.0 +function maybe_spawn_cache_PATH() + global PATH_cache_task, next_cache_update + @lock PATH_cache_lock begin + PATH_cache_task isa Task && !istaskdone(PATH_cache_task) && return + time() < next_cache_update && return + PATH_cache_task = Threads.@spawn REPLCompletions.cache_PATH() + Base.errormonitor(PATH_cache_task) + end +end + +# caches all reachable files in PATH dirs +function cache_PATH() + path = get(ENV, "PATH", nothing) + path isa String || return + + global next_cache_update + + # Calling empty! on PATH_cache would be annoying for async typing hints as completions would temporarily disappear. + # So keep track of what's added this time and at the end remove any that didn't appear this time from the global cache. + this_PATH_cache = Set{String}() + + @debug "caching PATH files" PATH=path + pathdirs = split(path, @static Sys.iswindows() ? ";" : ":") + + next_yield_time = time() + 0.01 + + t = @elapsed for pathdir in pathdirs + actualpath = try + realpath(pathdir) + catch ex + ex isa Base.IOError || rethrow() + # Bash doesn't expect every folder in PATH to exist, so neither shall we + continue + end + + if actualpath != pathdir && in(actualpath, pathdirs) + # Remove paths which (after resolving links) are in the env path twice. + # Many distros eg. point /bin to /usr/bin but have both in the env path. + continue + end + + path_entries = try + _readdirx(pathdir) + catch e + # Bash allows dirs in PATH that can't be read, so we should as well. + if isa(e, Base.IOError) || isa(e, Base.ArgumentError) + continue + else + # We only handle IOError and ArgumentError here + rethrow() + end + end + for entry in path_entries + # In a perfect world, we would filter on whether the file is executable + # here, or even on whether the current user can execute the file in question. + try + if isfile(entry) + @lock PATH_cache_lock push!(PATH_cache, entry.name) + push!(this_PATH_cache, entry.name) + end + catch e + # `isfile()` can throw in rare cases such as when probing a + # symlink that points to a file within a directory we do not + # have read access to. + if isa(e, Base.IOError) + continue + else + rethrow() + end + end + if time() >= next_yield_time + yield() # to avoid blocking typing when -t1 + next_yield_time = time() + 0.01 + end + end + end + + @lock PATH_cache_lock begin + intersect!(PATH_cache, this_PATH_cache) # remove entries from PATH_cache that weren't found this time + next_cache_update = time() + 10 # earliest next update can run is 10s after + end + + @debug "caching PATH files took $t seconds" length(pathdirs) length(PATH_cache) + return PATH_cache +end + +function complete_path(path::AbstractString; + use_envpath=false, + shell_escape=false, + raw_escape=false, string_escape=false) @assert !(shell_escape && string_escape) if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path) @@ -272,82 +379,70 @@ function complete_path(path::AbstractString, pos::Int; else dir, prefix = splitdir(path) end - local files - try + entries = try if isempty(dir) - files = readdir() + _readdirx() elseif isdir(dir) - files = readdir(dir) + _readdirx(dir) else - return Completion[], 0:-1, false + return Completion[], dir, false end - catch - return Completion[], 0:-1, false + catch ex + ex isa Base.IOError || rethrow() + return Completion[], dir, false end matches = Set{String}() - for file in files - if startswith(file, prefix) - p = joinpath(dir, file) - is_dir = try isdir(p) catch; false end - push!(matches, is_dir ? joinpath(file, "") : file) + for entry in entries + if startswith(entry.name, prefix) + is_dir = try isdir(entry) catch ex; ex isa Base.IOError ? false : rethrow() end + push!(matches, is_dir ? entry.name * "/" : entry.name) end end - if use_envpath && length(dir) == 0 - # Look for files in PATH as well - local pathdirs = split(ENV["PATH"], @static Sys.iswindows() ? ";" : ":") - - for pathdir in pathdirs - local actualpath - try - actualpath = realpath(pathdir) - catch - # Bash doesn't expect every folder in PATH to exist, so neither shall we - continue - end - - if actualpath != pathdir && in(actualpath,pathdirs) - # Remove paths which (after resolving links) are in the env path twice. - # Many distros eg. point /bin to /usr/bin but have both in the env path. - continue + if use_envpath && isempty(dir) + # Look for files in PATH as well. These are cached in `cache_PATH` in an async task to not block typing. + # If we cannot get lock because its still caching just pass over this so that typing isn't laggy. + maybe_spawn_cache_PATH() # only spawns if enough time has passed and the previous caching task has completed + @lock PATH_cache_lock begin + for file in PATH_cache + startswith(file, prefix) && push!(matches, file) end + end + end - local filesinpath - try - filesinpath = readdir(pathdir) - catch e - # Bash allows dirs in PATH that can't be read, so we should as well. - if isa(e, Base.IOError) || isa(e, Base.ArgumentError) - continue - else - # We only handle IOError and ArgumentError here - rethrow() - end - end + matches = ((shell_escape ? do_shell_escape(s) : string_escape ? do_string_escape(s) : s) for s in matches) + matches = ((raw_escape ? do_raw_escape(s) : s) for s in matches) + matches = Completion[PathCompletion(s) for s in matches] + return matches, dir, !isempty(matches) +end - for file in filesinpath - # In a perfect world, we would filter on whether the file is executable - # here, or even on whether the current user can execute the file in question. - if startswith(file, prefix) && isfile(joinpath(pathdir, file)) - push!(matches, file) - end - end +function complete_path(path::AbstractString, + pos::Int; + use_envpath=false, + shell_escape=false, + string_escape=false) + ## TODO: enable this depwarn once Pkg is fixed + #Base.depwarn("complete_path with pos argument is deprecated because the return value [2] is incorrect to use", :complete_path) + paths, dir, success = complete_path(path; use_envpath, shell_escape, string_escape) + if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path) + # if the path is just "~", don't consider the expanded username as a prefix + if path == "~" + dir, prefix = homedir(), "" + else + dir, prefix = splitdir(homedir() * path[2:end]) end + else + dir, prefix = splitdir(path) end - - function do_escape(s) - return shell_escape ? replace(s, r"(\s|\\)" => s"\\\0") : - string_escape ? escape_string(s, ('\"','$')) : - s + startpos = pos - lastindex(prefix) + 1 + Sys.iswindows() && map!(paths, paths) do c::PathCompletion + # emulation for unnecessarily complicated return value, since / is a + # perfectly acceptable path character which does not require quoting + # but is required by Pkg's awkward parser handling + return endswith(c.path, "/") ? PathCompletion(chop(c.path) * "\\\\") : c end - - matchList = Completion[PathCompletion(do_escape(s)) for s in matches] - startpos = pos - lastindex(do_escape(prefix)) + 1 - # The pos - lastindex(prefix) + 1 is correct due to `lastindex(prefix)-lastindex(prefix)==0`, - # hence we need to add one to get the first index. This is also correct when considering - # pos, because pos is the `lastindex` a larger string which `endswith(path)==true`. - return matchList, startpos:pos, !isempty(matchList) + return paths, startpos:pos, success end function complete_expanduser(path::AbstractString, r) @@ -432,25 +527,7 @@ function find_start_brace(s::AbstractString; c_start='(', c_end=')') return (startind:lastindex(s), method_name_end) end -struct REPLInterpreterCache - dict::IdDict{MethodInstance,CodeInstance} -end -REPLInterpreterCache() = REPLInterpreterCache(IdDict{MethodInstance,CodeInstance}()) -const REPL_INTERPRETER_CACHE = REPLInterpreterCache() - -function get_code_cache() - # XXX Avoid storing analysis results into the cache that persists across precompilation, - # as [sys|pkg]image currently doesn't support serializing externally created `CodeInstance`. - # Otherwise, `CodeInstance`s created by `REPLInterpreter`, that are much less optimized - # that those produced by `NativeInterpreter`, will leak into the native code cache, - # potentially causing runtime slowdown. - # (see https://github.com/JuliaLang/julia/issues/48453). - if Base.generating_output() - return REPLInterpreterCache() - else - return REPL_INTERPRETER_CACHE - end -end +struct REPLCacheToken end struct REPLInterpreter <: CC.AbstractInterpreter limit_aggressive_inference::Bool @@ -458,27 +535,21 @@ struct REPLInterpreter <: CC.AbstractInterpreter inf_params::CC.InferenceParams opt_params::CC.OptimizationParams inf_cache::Vector{CC.InferenceResult} - code_cache::REPLInterpreterCache function REPLInterpreter(limit_aggressive_inference::Bool=false; world::UInt = Base.get_world_counter(), inf_params::CC.InferenceParams = CC.InferenceParams(; aggressive_constant_propagation=true, unoptimize_throw_blocks=false), opt_params::CC.OptimizationParams = CC.OptimizationParams(), - inf_cache::Vector{CC.InferenceResult} = CC.InferenceResult[], - code_cache::REPLInterpreterCache = get_code_cache()) - return new(limit_aggressive_inference, world, inf_params, opt_params, inf_cache, code_cache) + inf_cache::Vector{CC.InferenceResult} = CC.InferenceResult[]) + return new(limit_aggressive_inference, world, inf_params, opt_params, inf_cache) end end CC.InferenceParams(interp::REPLInterpreter) = interp.inf_params CC.OptimizationParams(interp::REPLInterpreter) = interp.opt_params -CC.get_world_counter(interp::REPLInterpreter) = interp.world +CC.get_inference_world(interp::REPLInterpreter) = interp.world CC.get_inference_cache(interp::REPLInterpreter) = interp.inf_cache -CC.code_cache(interp::REPLInterpreter) = CC.WorldView(interp.code_cache, CC.WorldRange(interp.world)) -CC.get(wvc::CC.WorldView{REPLInterpreterCache}, mi::MethodInstance, default) = get(wvc.cache.dict, mi, default) -CC.getindex(wvc::CC.WorldView{REPLInterpreterCache}, mi::MethodInstance) = getindex(wvc.cache.dict, mi) -CC.haskey(wvc::CC.WorldView{REPLInterpreterCache}, mi::MethodInstance) = haskey(wvc.cache.dict, mi) -CC.setindex!(wvc::CC.WorldView{REPLInterpreterCache}, ci::CodeInstance, mi::MethodInstance) = setindex!(wvc.cache.dict, ci, mi) +CC.cache_owner(::REPLInterpreter) = REPLCacheToken() # REPLInterpreter is only used for type analysis, so it should disable optimization entirely CC.may_optimize(::REPLInterpreter) = false @@ -508,10 +579,10 @@ CC.bail_out_toplevel_call(::REPLInterpreter, ::CC.InferenceLoopState, ::CC.Infer # `REPLInterpreter` is specifically used by `repl_eval_ex`, where all top-level frames are # `repl_frame` always. However, this assumption wouldn't stand if `REPLInterpreter` were to # be employed, for instance, by `typeinf_ext_toplevel`. -is_repl_frame(sv::CC.InferenceState) = sv.linfo.def isa Module && !sv.cached +is_repl_frame(sv::CC.InferenceState) = sv.linfo.def isa Module && sv.cache_mode === CC.CACHE_MODE_NULL function is_call_graph_uncached(sv::CC.InferenceState) - sv.cached && return false + CC.is_cached(sv) && return false parent = sv.parent parent === nothing && return true return is_call_graph_uncached(parent::CC.InferenceState) @@ -522,9 +593,9 @@ function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef, sv::CC.InferenceState) if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv)) if CC.isdefined_globalref(g) - return Const(ccall(:jl_get_globalref_value, Any, (Any,), g)) + return CC.RTEffects(Const(ccall(:jl_get_globalref_value, Any, (Any,), g)), Union{}, CC.EFFECTS_TOTAL) end - return Union{} + return CC.RTEffects(Union{}, UndefVarError, CC.EFFECTS_THROWS) end return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef, sv::CC.InferenceState) @@ -534,7 +605,7 @@ function is_repl_frame_getproperty(sv::CC.InferenceState) def = sv.linfo.def def isa Method || return false def.name === :getproperty || return false - sv.cached && return false + CC.is_cached(sv) && return false return is_repl_frame(sv.parent) end @@ -566,7 +637,7 @@ function CC.concrete_eval_eligible(interp::REPLInterpreter, @nospecialize(f), sv::CC.InferenceState) if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv)) neweffects = CC.Effects(result.effects; consistent=CC.ALWAYS_TRUE) - result = CC.MethodCallResult(result.rt, result.edgecycle, result.edgelimited, + result = CC.MethodCallResult(result.rt, result.exct, result.edgecycle, result.edgelimited, result.edge, neweffects) end ret = @invoke CC.concrete_eval_eligible(interp::CC.AbstractInterpreter, f::Any, @@ -670,6 +741,26 @@ function complete_methods(ex_org::Expr, context_module::Module=Main, shift::Bool end MAX_ANY_METHOD_COMPLETIONS::Int = 10 +function recursive_explore_names!(seen::IdSet, callee_module::Module, initial_module::Module, exploredmodules::IdSet{Module}=IdSet{Module}()) + push!(exploredmodules, callee_module) + for name in names(callee_module; all=true, imported=true) + if !Base.isdeprecated(callee_module, name) && !startswith(string(name), '#') && isdefined(initial_module, name) + func = getfield(callee_module, name) + if !isa(func, Module) + funct = Core.Typeof(func) + push!(seen, funct) + elseif isa(func, Module) && func ∉ exploredmodules + recursive_explore_names!(seen, func, initial_module, exploredmodules) + end + end + end +end +function recursive_explore_names(callee_module::Module, initial_module::Module) + seen = IdSet{Any}() + recursive_explore_names!(seen, callee_module, initial_module) + seen +end + function complete_any_methods(ex_org::Expr, callee_module::Module, context_module::Module, moreargs::Bool, shift::Bool) out = Completion[] args_ex, kwargs_ex, kwargs_flag = try @@ -685,32 +776,8 @@ function complete_any_methods(ex_org::Expr, callee_module::Module, context_modul # semicolon for the ".?(" syntax moreargs && push!(args_ex, Vararg{Any}) - seen = Base.IdSet() - for name in names(callee_module; all=true) - if !Base.isdeprecated(callee_module, name) && isdefined(callee_module, name) && !startswith(string(name), '#') - func = getfield(callee_module, name) - if !isa(func, Module) - funct = Core.Typeof(func) - if !in(funct, seen) - push!(seen, funct) - complete_methods!(out, funct, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false) - end - elseif callee_module === Main && isa(func, Module) - callee_module2 = func - for name in names(callee_module2) - if !Base.isdeprecated(callee_module2, name) && isdefined(callee_module2, name) && !startswith(string(name), '#') - func = getfield(callee_module, name) - if !isa(func, Module) - funct = Core.Typeof(func) - if !in(funct, seen) - push!(seen, funct) - complete_methods!(out, funct, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false) - end - end - end - end - end - end + for seen_name in recursive_explore_names(callee_module, callee_module) + complete_methods!(out, seen_name, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false) end if !shift @@ -719,7 +786,7 @@ function complete_any_methods(ex_org::Expr, callee_module::Module, context_modul isa(c, TextCompletion) && return false isa(c, MethodCompletion) || return true sig = Base.unwrap_unionall(c.method.sig)::DataType - return !all(T -> T === Any || T === Vararg{Any}, sig.parameters[2:end]) + return !all(@nospecialize(T) -> T === Any || T === Vararg{Any}, sig.parameters[2:end]) end end @@ -827,10 +894,11 @@ function afterusing(string::String, startpos::Int) return occursin(r"^\b(using|import)\s*((\w+[.])*\w+\s*,\s*)*$", str[fr:end]) end -function close_path_completion(str, startpos, r, paths, pos) +function close_path_completion(dir, paths, str, pos) length(paths) == 1 || return false # Only close if there's a single choice... - _path = str[startpos:prevind(str, first(r))] * (paths[1]::PathCompletion).path - path = expanduser(unescape_string(replace(_path, "\\\$"=>"\$", "\\\""=>"\""))) + path = (paths[1]::PathCompletion).path + path = unescape_string(replace(path, "\\\$"=>"\$")) + path = joinpath(dir, path) # ...except if it's a directory... try isdir(path) @@ -970,8 +1038,12 @@ function complete_keyword_argument(partial, last_idx, context_module) end suggestions = Completion[KeywordArgumentCompletion(kwarg) for kwarg in kwargs] - append!(suggestions, complete_symbol(nothing, last_word, Returns(true), context_module)) - append!(suggestions, complete_keyval(last_word)) + + # Only add these if not in kwarg space. i.e. not in `foo(; ` + if kwargs_flag == 0 + append!(suggestions, complete_symbol(nothing, last_word, Returns(true), context_module)) + append!(suggestions, complete_keyval(last_word)) + end return sort!(suggestions, by=completion_text), wordrange end @@ -992,7 +1064,10 @@ function project_deps_get_completion_candidates(pkgstarts::String, project_file: return Completion[PackageCompletion(name) for name in loading_candidates] end -function complete_identifiers!(suggestions::Vector{Completion}, @nospecialize(ffunc::Function), context_module::Module, string::String, name::String, pos::Int, dotpos::Int, startpos::Int, comp_keywords=false) +function complete_identifiers!(suggestions::Vector{Completion}, @nospecialize(ffunc), + context_module::Module, string::String, name::String, + pos::Int, dotpos::Int, startpos::Int; + comp_keywords=false) ex = nothing if comp_keywords append!(suggestions, complete_keyword(name)) @@ -1034,10 +1109,41 @@ function complete_identifiers!(suggestions::Vector{Completion}, @nospecialize(ff if something(findlast(in(non_identifier_chars), s), 0) < something(findlast(isequal('.'), s), 0) lookup_name, name = rsplit(s, ".", limit=2) name = String(name) - ex = Meta.parse(lookup_name, raise=false, depwarn=false) end isexpr(ex, :incomplete) && (ex = nothing) + elseif isexpr(ex, (:using, :import)) + arg1 = ex.args[1] + if isexpr(arg1, :.) + # We come here for cases like: + # - `string`: "using Mod1.Mod2.M" + # - `ex`: :(using Mod1.Mod2) + # - `name`: "M" + # Now we transform `ex` to `:(Mod1.Mod2)` to allow `complete_symbol` to + # complete for inner modules whose name starts with `M`. + # Note that `ffunc` is set to `module_filter` within `completions` + ex = nothing + firstdot = true + for arg = arg1.args + if arg === :. + # override `context_module` if multiple `.` accessors are used + if firstdot + firstdot = false + else + context_module = parentmodule(context_module) + end + elseif arg isa Symbol + if ex === nothing + ex = arg + else + ex = Expr(:., ex, QuoteNode(arg)) + end + else # invalid expression + ex = nothing + break + end + end + end elseif isexpr(ex, :call) && length(ex.args) > 1 isinfix = s[end] != ')' # A complete call expression that does not finish with ')' is an infix call. @@ -1049,6 +1155,11 @@ function complete_identifiers!(suggestions::Vector{Completion}, @nospecialize(ff if isinfix ex = ex.args[end] end + elseif isexpr(ex, :macrocall) && length(ex.args) > 1 + # allow symbol completions within potentially incomplete macrocalls + if s[end] ≠ '`' && s[end] ≠ ')' + ex = ex.args[end] + end end end append!(suggestions, complete_symbol(ex, name, ffunc, context_module)) @@ -1113,45 +1224,84 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif ok && return ret startpos = first(varrange) + 4 dotpos = something(findprev(isequal('.'), string, first(varrange)-1), 0) - return complete_identifiers!(Completion[], ffunc, context_module, string, - string[startpos:pos], pos, dotpos, startpos) + name = string[startpos:pos] + return complete_identifiers!(Completion[], ffunc, context_module, string, name, pos, + dotpos, startpos) elseif inc_tag === :cmd - m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial)) - startpos = nextind(partial, reverseind(partial, m.offset)) - r = startpos:pos - - # This expansion with "\\ "=>' ' replacement and shell_escape=true - # assumes the path isn't further quoted within the cmd backticks. - expanded = complete_expanduser(replace(string[r], r"\\ " => " "), r) - expanded[3] && return expanded # If user expansion available, return it - - paths, r, success = complete_path(replace(string[r], r"\\ " => " "), pos, - shell_escape=true) - - return sort!(paths, by=p->p.path), r, success + # TODO: should this call shell_completions instead of partially reimplementing it? + let m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial)) # fuzzy shell_parse in reverse + startpos = nextind(partial, reverseind(partial, m.offset)) + r = startpos:pos + scs::String = string[r] + + expanded = complete_expanduser(scs, r) + expanded[3] && return expanded # If user expansion available, return it + + path::String = replace(scs, r"(\\+)\g1(\\?)`" => "\1\2`") # fuzzy unescape_raw_string: match an even number of \ before ` and replace with half as many + # This expansion with "\\ "=>' ' replacement and shell_escape=true + # assumes the path isn't further quoted within the cmd backticks. + path = replace(path, r"\\ " => " ", r"\$" => "\$") # fuzzy shell_parse (reversed by shell_escape_posixly) + paths, dir, success = complete_path(path, shell_escape=true, raw_escape=true) + + if success && !isempty(dir) + let dir = do_raw_escape(do_shell_escape(dir)) + # if escaping of dir matches scs prefix, remove that from the completions + # otherwise make it the whole completion + if endswith(dir, "/") && startswith(scs, dir) + r = (startpos + sizeof(dir)):pos + elseif startswith(scs, dir * "/") + r = nextind(string, startpos + sizeof(dir)):pos + else + map!(paths, paths) do c::PathCompletion + return PathCompletion(dir * "/" * c.path) + end + end + end + end + return sort!(paths, by=p->p.path), r, success + end elseif inc_tag === :string # Find first non-escaped quote - m = match(r"\"(?!\\)", reverse(partial)) - startpos = nextind(partial, reverseind(partial, m.offset)) - r = startpos:pos + let m = match(r"\"(?!\\)", reverse(partial)) + startpos = nextind(partial, reverseind(partial, m.offset)) + r = startpos:pos + scs::String = string[r] + + expanded = complete_expanduser(scs, r) + expanded[3] && return expanded # If user expansion available, return it + + path = try + unescape_string(replace(scs, "\\\$"=>"\$")) + catch ex + ex isa ArgumentError || rethrow() + nothing + end + if !isnothing(path) + paths, dir, success = complete_path(path::String, string_escape=true) - expanded = complete_expanduser(string[r], r) - expanded[3] && return expanded # If user expansion available, return it + if close_path_completion(dir, paths, path, pos) + paths[1] = PathCompletion((paths[1]::PathCompletion).path * "\"") + end - path_prefix = try - unescape_string(replace(string[r], "\\\$"=>"\$", "\\\""=>"\"")) - catch - nothing - end - if !isnothing(path_prefix) - paths, r, success = complete_path(path_prefix, pos, string_escape=true) + if success && !isempty(dir) + let dir = do_string_escape(dir) + # if escaping of dir matches scs prefix, remove that from the completions + # otherwise make it the whole completion + if endswith(dir, "/") && startswith(scs, dir) + r = (startpos + sizeof(dir)):pos + elseif startswith(scs, dir * "/") + r = nextind(string, startpos + sizeof(dir)):pos + else + map!(paths, paths) do c::PathCompletion + return PathCompletion(dir * "/" * c.path) + end + end + end + end - if close_path_completion(string, startpos, r, paths, pos) - paths[1] = PathCompletion((paths[1]::PathCompletion).path * "\"") + # Fallthrough allowed so that Latex symbols can be completed in strings + success && return sort!(paths, by=p->p.path), r, success end - - # Fallthrough allowed so that Latex symbols can be completed in strings - success && return sort!(paths, by=p->p.path), r, success end end @@ -1199,14 +1349,15 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif append!(suggestions, project_deps_get_completion_candidates(s, dir)) end isdir(dir) || continue - for pname in readdir(dir) + for entry in _readdirx(dir) + pname = entry.name if pname[1] != '.' && pname != "METADATA" && pname != "REQUIRE" && startswith(pname, s) # Valid file paths are # .jl # /src/.jl # .jl/src/.jl - if isfile(joinpath(dir, pname)) + if isfile(entry) endswith(pname, ".jl") && push!(suggestions, PackageCompletion(pname[1:prevind(pname, end-2)])) else @@ -1215,7 +1366,7 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif else pname end - if isfile(joinpath(dir, pname, "src", + if isfile(joinpath(entry, "src", "$mod_name.jl")) push!(suggestions, PackageCompletion(mod_name)) end @@ -1224,71 +1375,82 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif end end end - ffunc = (mod,x)->(Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getfield(mod, x), Module)) + ffunc = module_filter comp_keywords = false end startpos == 0 && (pos = -1) dotpos < startpos && (dotpos = startpos - 1) - return complete_identifiers!(suggestions, ffunc, context_module, string, - name, pos, dotpos, startpos, comp_keywords) + return complete_identifiers!(suggestions, ffunc, context_module, string, name, pos, + dotpos, startpos; + comp_keywords) end +module_filter(mod::Module, x::Symbol) = + Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getglobal(mod, x), Module) + function shell_completions(string, pos) # First parse everything up to the current position scs = string[1:pos] - local args, last_parse - try - args, last_parse = Base.shell_parse(scs, true)::Tuple{Expr,UnitRange{Int}} - catch + args, last_arg_start = try + Base.shell_parse(scs, true)::Tuple{Expr,Int} + catch ex + ex isa ArgumentError || ex isa ErrorException || rethrow() return Completion[], 0:-1, false end ex = args.args[end]::Expr # Now look at the last thing we parsed isempty(ex.args) && return Completion[], 0:-1, false - arg = ex.args[end] - if all(s -> isa(s, AbstractString), ex.args) - arg = arg::AbstractString - # Treat this as a path - - # As Base.shell_parse throws away trailing spaces (unless they are escaped), - # we need to special case here. - # If the last char was a space, but shell_parse ignored it search on "". - ignore_last_word = arg != " " && scs[end] == ' ' - prefix = ignore_last_word ? "" : join(ex.args) + lastarg = ex.args[end] + # As Base.shell_parse throws away trailing spaces (unless they are escaped), + # we need to special case here. + # If the last char was a space, but shell_parse ignored it search on "". + if isexpr(lastarg, :incomplete) || isexpr(lastarg, :error) + partial = string[last_arg_start:pos] + ret, range = completions(partial, lastindex(partial)) + range = range .+ (last_arg_start - 1) + return ret, range, true + elseif endswith(scs, ' ') && !endswith(scs, "\\ ") + r = pos+1:pos + paths, dir, success = complete_path("", use_envpath=false, shell_escape=true) + return paths, r, success + elseif all(@nospecialize(arg) -> arg isa AbstractString, ex.args) + # Join these and treat this as a path + path::String = join(ex.args) + r = last_arg_start:pos # Also try looking into the env path if the user wants to complete the first argument - use_envpath = !ignore_last_word && length(args.args) < 2 + use_envpath = length(args.args) < 2 - return complete_path(prefix, pos, use_envpath=use_envpath, shell_escape=true) - elseif isexpr(arg, :incomplete) || isexpr(arg, :error) - partial = scs[last_parse] - ret, range = completions(partial, lastindex(partial)) - range = range .+ (first(last_parse) - 1) - return ret, range, true - end - return Completion[], 0:-1, false -end + # TODO: call complete_expanduser here? + + paths, dir, success = complete_path(path, use_envpath=use_envpath, shell_escape=true) -function UndefVarError_hint(io::IO, ex::UndefVarError) - var = ex.var - if var === :or - print(io, "\nsuggestion: Use `||` for short-circuiting boolean OR.") - elseif var === :and - print(io, "\nsuggestion: Use `&&` for short-circuiting boolean AND.") - elseif var === :help - println(io) - # Show friendly help message when user types help or help() and help is undefined - show(io, MIME("text/plain"), Base.Docs.parsedoc(Base.Docs.keywords[:help])) - elseif var === :quit - print(io, "\nsuggestion: To exit Julia, use Ctrl-D, or type exit() and press enter.") + if success && !isempty(dir) + let dir = do_shell_escape(dir) + # if escaping of dir matches scs prefix, remove that from the completions + # otherwise make it the whole completion + partial = string[last_arg_start:pos] + if endswith(dir, "/") && startswith(partial, dir) + r = (last_arg_start + sizeof(dir)):pos + elseif startswith(partial, dir * "/") + r = nextind(string, last_arg_start + sizeof(dir)):pos + else + map!(paths, paths) do c::PathCompletion + return PathCompletion(dir * "/" * c.path) + end + end + end + end + + return paths, r, success end + return Completion[], 0:-1, false end function __init__() - Base.Experimental.register_error_hint(UndefVarError_hint, UndefVarError) COMPLETION_WORLD[] = Base.get_world_counter() - nothing + return nothing end end # module diff --git a/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl b/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl index a1f94852b38ec..ddcfc111cf962 100644 --- a/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl +++ b/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl @@ -176,7 +176,7 @@ Returns `selected(m)`. !!! compat "Julia 1.6" The `cursor` argument requires Julia 1.6 or later. """ -request(m::AbstractMenu; kwargs...) = request(terminal, m; kwargs...) +request(m::AbstractMenu; kwargs...) = request(default_terminal(), m; kwargs...) function request(term::REPL.Terminals.TTYTerminal, m::AbstractMenu; cursor::Union{Int, Base.RefValue{Int}}=1, suppress_output=false) if cursor isa Int @@ -252,7 +252,7 @@ end Shorthand for `println(msg); request(m)`. """ -request(msg::AbstractString, m::AbstractMenu; kwargs...) = request(terminal, msg, m; kwargs...) +request(msg::AbstractString, m::AbstractMenu; kwargs...) = request(default_terminal(), msg, m; kwargs...) function request(term::REPL.Terminals.TTYTerminal, msg::AbstractString, m::AbstractMenu; kwargs...) println(term.out_stream, msg) diff --git a/stdlib/REPL/src/TerminalMenus/Pager.jl b/stdlib/REPL/src/TerminalMenus/Pager.jl index c823a5dedd1ba..091f87801e7a4 100644 --- a/stdlib/REPL/src/TerminalMenus/Pager.jl +++ b/stdlib/REPL/src/TerminalMenus/Pager.jl @@ -39,4 +39,4 @@ function pager(terminal, object) pager = Pager(String(take!(buffer)); pagesize = div(lines, 2)) return request(terminal, pager) end -pager(object) = pager(terminal, object) +pager(object) = pager(default_terminal(), object) diff --git a/stdlib/REPL/src/TerminalMenus/TerminalMenus.jl b/stdlib/REPL/src/TerminalMenus/TerminalMenus.jl index 87869e84d9838..9fcddef2fd484 100644 --- a/stdlib/REPL/src/TerminalMenus/TerminalMenus.jl +++ b/stdlib/REPL/src/TerminalMenus/TerminalMenus.jl @@ -2,13 +2,11 @@ module TerminalMenus -terminal = nothing # The user terminal +using REPL: REPL -import REPL - -function __init__() - global terminal - terminal = REPL.Terminals.TTYTerminal(get(ENV, "TERM", Sys.iswindows() ? "" : "dumb"), stdin, stdout, stderr) +function default_terminal(; in::IO=stdin, out::IO=stdout, err::IO=stderr) + return REPL.Terminals.TTYTerminal( + get(ENV, "TERM", Sys.iswindows() ? "" : "dumb"), in, out, err) end include("util.jl") diff --git a/stdlib/REPL/src/Terminals.jl b/stdlib/REPL/src/Terminals.jl index dac19406b3fc1..821ed224f1829 100644 --- a/stdlib/REPL/src/Terminals.jl +++ b/stdlib/REPL/src/Terminals.jl @@ -30,9 +30,7 @@ import Base: displaysize, flush, pipe_reader, - pipe_writer, - read, - readuntil + pipe_writer ## AbstractTerminal: abstract supertype of all terminals ## diff --git a/stdlib/REPL/src/docview.jl b/stdlib/REPL/src/docview.jl index cc3a7fe980190..feeeecbd97165 100644 --- a/stdlib/REPL/src/docview.jl +++ b/stdlib/REPL/src/docview.jl @@ -9,7 +9,9 @@ using Base.Docs: catdoc, modules, DocStr, Binding, MultiDoc, keywords, isfield, import Base.Docs: doc, formatdoc, parsedoc, apropos -using Base: with_output_color, mapany +using Base: with_output_color, mapany, isdeprecated, isexported + +using Base.Filesystem: _readdirx import REPL @@ -166,7 +168,7 @@ struct Logged{F} collection::Set{Pair{Module,Symbol}} end function (la::Logged)(m::Module, s::Symbol) - m !== la.mod && !Base.ispublic(m, s) && push!(la.collection, m => s) + m !== la.mod && Base.isdefined(m, s) && !Base.ispublic(m, s) && push!(la.collection, m => s) la.f(m, s) end (la::Logged)(args...) = la.f(args...) @@ -184,7 +186,7 @@ log_nonpublic_access(expr, ::Module, _) = expr function insert_internal_warning(md::Markdown.MD, internal_access::Set{Pair{Module,Symbol}}) if !isempty(internal_access) - items = Any[Any[Markdown.Paragraph(Any[Markdown.Code("", s)])] for s in sort("$mod.$sym" for (mod, sym) in internal_access)] + items = Any[Any[Markdown.Paragraph(Any[Markdown.Code("", s)])] for s in sort!(["$mod.$sym" for (mod, sym) in internal_access])] admonition = Markdown.Admonition("warning", "Warning", Any[ Markdown.Paragraph(Any["The following bindings may be internal; they may change or be removed in future versions:"]), Markdown.List(items, -1, false)]) @@ -193,18 +195,11 @@ function insert_internal_warning(md::Markdown.MD, internal_access::Set{Pair{Modu md end function insert_internal_warning(other, internal_access::Set{Pair{Module,Symbol}}) - println("oops.") + # We don't know how to insert an internal symbol warning into non-markdown + # content, so we don't. other end -""" - Docs.doc(binding, sig) - -Return all documentation that matches both `binding` and `sig`. - -If `getdoc` returns a non-`nothing` result on the value of the binding, then a -dynamic docstring is returned instead of one based on the binding itself. -""" function doc(binding::Binding, sig::Type = Union{}) if defined(binding) result = getdoc(resolve(binding), sig) @@ -242,8 +237,6 @@ function doc(binding::Binding, sig::Type = Union{}) md = catdoc(mapany(parsedoc, results)...) # Save metadata in the generated markdown. if isa(md, Markdown.MD) - # We don't know how to insert an internal symbol warning into non-markdown - # content, so we don't. md.meta[:results] = results md.meta[:binding] = binding md.meta[:typesig] = sig @@ -388,9 +381,9 @@ function find_readme(m::Module)::Union{String, Nothing} path = dirname(mpath) top_path = pkgdir(m) while true - for file in readdir(path; join=true, sort=true) - isfile(file) && (basename(lowercase(file)) in ["readme.md", "readme"]) || continue - return file + for entry in _readdirx(path; sort=true) + isfile(entry) && (lowercase(entry.name) in ["readme.md", "readme"]) || continue + return entry.path end path == top_path && break # go no further than pkgdir path = dirname(path) # work up through nested modules @@ -433,8 +426,31 @@ end # repl search and completions for help +# This type is returned from `accessible` and denotes a binding that is accessible within +# some context. It differs from `Base.Docs.Binding`, which is also used by the REPL, in +# that it doesn't track the defining module for a symbol unless the symbol is public but +# not exported, i.e. it's accessible but requires qualification. Using this type rather +# than `Base.Docs.Binding` simplifies things considerably, partially because REPL searching +# is based on `String`s, which this type stores, but `Base.Docs.Binding` stores a module +# and symbol and does not have any notion of the context from which the binding is accessed. +struct AccessibleBinding + source::Union{String,Nothing} + name::String +end + +function AccessibleBinding(mod::Module, name::Symbol) + m = isexported(mod, name) ? nothing : String(nameof(mod)) + return AccessibleBinding(m, String(name)) +end +AccessibleBinding(name::Symbol) = AccessibleBinding(nothing, String(name)) + +function Base.show(io::IO, b::AccessibleBinding) + b.source === nothing || print(io, b.source, '.') + print(io, b.name) +end quote_spaces(x) = any(isspace, x) ? "'" * x * "'" : x +quote_spaces(x::AccessibleBinding) = AccessibleBinding(x.source, quote_spaces(x.name)) function repl_search(io::IO, s::Union{Symbol,String}, mod::Module) pre = "search:" @@ -678,6 +694,9 @@ function matchinds(needle, haystack; acronym::Bool = false) return is end +matchinds(needle, (; name)::AccessibleBinding; acronym::Bool=false) = + matchinds(needle, name; acronym) + longer(x, y) = length(x) ≥ length(y) ? (x, true) : (y, false) bestmatch(needle, haystack) = @@ -737,7 +756,17 @@ function fuzzyscore(needle::AbstractString, haystack::AbstractString) 1 - (string_distance(needle, lena, haystack, lenb) / max(lena, lenb)) end -function fuzzysort(search::String, candidates::Vector{String}) +function fuzzyscore(needle::AbstractString, haystack::AccessibleBinding) + score = fuzzyscore(needle, haystack.name) + haystack.source === nothing && return score + # Apply a "penalty" of half an edit if the comparator binding is public but not + # exported so that exported/local names that exactly match the search query are + # listed first + penalty = 1 / (2 * max(length(needle), length(haystack.name))) + return max(score - penalty, 0) +end + +function fuzzysort(search::String, candidates::Vector{AccessibleBinding}) scores = map(cand -> fuzzyscore(search, cand), candidates) candidates[sortperm(scores)] |> reverse end @@ -762,12 +791,14 @@ function levenshtein(s1, s2) return d[m+1, n+1] end -function levsort(search::String, candidates::Vector{String}) - scores = map(cand -> (Float64(levenshtein(search, cand)), -fuzzyscore(search, cand)), candidates) +function levsort(search::String, candidates::Vector{AccessibleBinding}) + scores = map(candidates) do cand + (Float64(levenshtein(search, cand.name)), -fuzzyscore(search, cand)) + end candidates = candidates[sortperm(scores)] i = 0 for outer i = 1:length(candidates) - levenshtein(search, candidates[i]) > 3 && break + levenshtein(search, candidates[i].name) > 3 && break end return candidates[1:i] end @@ -785,24 +816,39 @@ function printmatch(io::IO, word, match) end end +function printmatch(io::IO, word, match::AccessibleBinding) + match.source === nothing || print(io, match.source, '.') + printmatch(io, word, match.name) +end + +function matchlength(x::AccessibleBinding) + n = length(x.name) + if x.source !== nothing + n += length(x.source) + 1 # the +1 is for the `.` separator + end + return n +end +matchlength(x) = length(x) + function printmatches(io::IO, word, matches; cols::Int = _displaysize(io)[2]) total = 0 for match in matches - total + length(match) + 1 > cols && break + ml = matchlength(match) + total + ml + 1 > cols && break fuzzyscore(word, match) < 0.5 && break print(io, " ") printmatch(io, word, match) - total += length(match) + 1 + total += ml + 1 end end printmatches(args...; cols::Int = _displaysize(stdout)[2]) = printmatches(stdout, args..., cols = cols) -function print_joined_cols(io::IO, ss::Vector{String}, delim = "", last = delim; cols::Int = _displaysize(io)[2]) +function print_joined_cols(io::IO, ss::Vector{AccessibleBinding}, delim = "", last = delim; cols::Int = _displaysize(io)[2]) i = 0 total = 0 for outer i = 1:length(ss) - total += length(ss[i]) + total += matchlength(ss[i]) total + max(i-2,0)*length(delim) + (i>1 ? 1 : 0)*length(last) > cols && (i-=1; break) end join(io, ss[1:i], delim, last) @@ -824,27 +870,31 @@ print_correction(word, mod::Module) = print_correction(stdout, word, mod) # Completion data - moduleusings(mod) = ccall(:jl_module_usings, Any, (Any,), mod) -filtervalid(names) = filter(x->!occursin(r"#", x), map(string, names)) - -accessible(mod::Module) = - Symbol[filter!(s -> !Base.isdeprecated(mod, s), names(mod, all=true, imported=true)); - map(names, moduleusings(mod))...; - collect(keys(Base.Docs.keywords))] |> unique |> filtervalid +function accessible(mod::Module) + bindings = Set(AccessibleBinding(s) for s in names(mod; all=true, imported=true) + if !isdeprecated(mod, s)) + for used in moduleusings(mod) + union!(bindings, (AccessibleBinding(used, s) for s in names(used) + if !isdeprecated(used, s))) + end + union!(bindings, (AccessibleBinding(k) for k in keys(Base.Docs.keywords))) + filter!(b -> !occursin('#', b.name), bindings) + return collect(bindings) +end function doc_completions(name, mod::Module=Main) res = fuzzysort(name, accessible(mod)) # to insert an entry like `raw""` for `"@raw_str"` in `res` - ms = match.(r"^@(.*?)_str$", res) + ms = map(c -> match(r"^@(.*?)_str$", c.name), res) idxs = findall(!isnothing, ms) # avoid messing up the order while inserting for i in reverse!(idxs) c = only((ms[i]::AbstractMatch).captures) - insert!(res, i, "$(c)\"\"") + insert!(res, i, AccessibleBinding(res[i].source, "$(c)\"\"")) end res end @@ -921,18 +971,6 @@ stripmd(x::Markdown.Footnote) = "$(stripmd(x.id)) $(stripmd(x.text))" stripmd(x::Markdown.Table) = join([join(map(stripmd, r), " ") for r in x.rows], " ") -""" - apropos([io::IO=stdout], pattern::Union{AbstractString,Regex}) - -Search available docstrings for entries containing `pattern`. - -When `pattern` is a string, case is ignored. Results are printed to `io`. - -`apropos` can be called from the help mode in the REPL by wrapping the query in double quotes: -``` -help?> "pattern" -``` -""" apropos(string) = apropos(stdout, string) apropos(io::IO, string) = apropos(io, Regex("\\Q$string", "i")) diff --git a/stdlib/REPL/src/emoji_symbols.jl b/stdlib/REPL/src/emoji_symbols.jl index 49a55c97f6564..d6d4a03321d0a 100644 --- a/stdlib/REPL/src/emoji_symbols.jl +++ b/stdlib/REPL/src/emoji_symbols.jl @@ -27,6 +27,7 @@ result = mapfoldr(emoji_data, merge, [ # overwrite the old with names that changed but still keep old ones that were removed "https://raw.githubusercontent.com/iamcal/emoji-data/0f0cf4ea8845eb52d26df2a48c3c31c3b8cad14e/emoji_pretty.json", "https://raw.githubusercontent.com/iamcal/emoji-data/e512953312c012f6bd00e3f2ef6bf152ca3710f8/emoji_pretty.json", + "https://raw.githubusercontent.com/iamcal/emoji-data/a8174c74675355c8c6a9564516b2e961fe7257ef/emoji_pretty.json", ]; init=Dict() ) @@ -132,6 +133,7 @@ const emoji_symbols = Dict( "\\:bath:" => "🛀", "\\:bathtub:" => "🛁", "\\:battery:" => "🔋", + "\\:beans:" => "🫘", "\\:bear:" => "🐻", "\\:bearded_person:" => "🧔", "\\:beaver:" => "🦫", @@ -151,6 +153,7 @@ const emoji_symbols = Dict( "\\:bird:" => "🐦", "\\:birthday:" => "🎂", "\\:bison:" => "🦬", + "\\:biting_lip:" => "🫦", "\\:black_circle:" => "⚫", "\\:black_heart:" => "🖤", "\\:black_joker:" => "🃏", @@ -198,6 +201,7 @@ const emoji_symbols = Dict( "\\:broom:" => "🧹", "\\:brown_heart:" => "🤎", "\\:bubble_tea:" => "🧋", + "\\:bubbles:" => "🫧", "\\:bucket:" => "🪣", "\\:bug:" => "🐛", "\\:bulb:" => "💡", @@ -309,6 +313,7 @@ const emoji_symbols = Dict( "\\:cool:" => "🆒", "\\:cop:" => "👮", "\\:copyright:" => "©", + "\\:coral:" => "🪸", "\\:corn:" => "🌽", "\\:couple:" => "👫", "\\:couple_with_heart:" => "💑", @@ -325,6 +330,7 @@ const emoji_symbols = Dict( "\\:crossed_fingers:" => "🤞", "\\:crossed_flags:" => "🎌", "\\:crown:" => "👑", + "\\:crutch:" => "🩼", "\\:cry:" => "😢", "\\:crying_cat_face:" => "😿", "\\:crystal_ball:" => "🔮", @@ -367,7 +373,9 @@ const emoji_symbols = Dict( "\\:dollar:" => "💵", "\\:dolls:" => "🎎", "\\:dolphin:" => "🐬", + "\\:donkey:" => "🫏", "\\:door:" => "🚪", + "\\:dotted_line_face:" => "🫥", "\\:doughnut:" => "🍩", "\\:dragon:" => "🐉", "\\:dragon_face:" => "🐲", @@ -397,6 +405,7 @@ const emoji_symbols = Dict( "\\:elevator:" => "🛗", "\\:elf:" => "🧝", "\\:email:" => "✉", + "\\:empty_nest:" => "🪹", "\\:end:" => "🔚", "\\:envelope_with_arrow:" => "📩", "\\:euro:" => "💶", @@ -408,12 +417,16 @@ const emoji_symbols = Dict( "\\:expressionless:" => "😑", "\\:eyeglasses:" => "👓", "\\:eyes:" => "👀", + "\\:face_holding_back_tears:" => "🥹", "\\:face_palm:" => "🤦", "\\:face_vomiting:" => "🤮", "\\:face_with_cowboy_hat:" => "🤠", + "\\:face_with_diagonal_mouth:" => "🫤", "\\:face_with_hand_over_mouth:" => "🤭", "\\:face_with_head_bandage:" => "🤕", "\\:face_with_monocle:" => "🧐", + "\\:face_with_open_eyes_and_hand_over_mouth:" => "🫢", + "\\:face_with_peeking_eye:" => "🫣", "\\:face_with_raised_eyebrow:" => "🤨", "\\:face_with_rolling_eyes:" => "🙄", "\\:face_with_symbols_on_mouth:" => "🤬", @@ -452,10 +465,12 @@ const emoji_symbols = Dict( "\\:floppy_disk:" => "💾", "\\:flower_playing_cards:" => "🎴", "\\:flushed:" => "😳", + "\\:flute:" => "🪈", "\\:fly:" => "🪰", "\\:flying_disc:" => "🥏", "\\:flying_saucer:" => "🛸", "\\:foggy:" => "🌁", + "\\:folding_hand_fan:" => "🪭", "\\:fondue:" => "🫕", "\\:foot:" => "🦶", "\\:football:" => "🏈", @@ -482,6 +497,7 @@ const emoji_symbols = Dict( "\\:ghost:" => "👻", "\\:gift:" => "🎁", "\\:gift_heart:" => "💝", + "\\:ginger_root:" => "🫚", "\\:giraffe_face:" => "🦒", "\\:girl:" => "👧", "\\:glass_of_milk:" => "🥛", @@ -491,6 +507,7 @@ const emoji_symbols = Dict( "\\:goat:" => "🐐", "\\:goggles:" => "🥽", "\\:golf:" => "⛳", + "\\:goose:" => "🪿", "\\:gorilla:" => "🦍", "\\:grapes:" => "🍇", "\\:green_apple:" => "🍏", @@ -498,6 +515,7 @@ const emoji_symbols = Dict( "\\:green_heart:" => "💚", "\\:green_salad:" => "🥗", "\\:grey_exclamation:" => "❕", + "\\:grey_heart:" => "🩶", "\\:grey_question:" => "❔", "\\:grimacing:" => "😬", "\\:grin:" => "😁", @@ -506,11 +524,14 @@ const emoji_symbols = Dict( "\\:guide_dog:" => "🦮", "\\:guitar:" => "🎸", "\\:gun:" => "🔫", + "\\:hair_pick:" => "🪮", "\\:haircut:" => "💇", "\\:hamburger:" => "🍔", "\\:hammer:" => "🔨", + "\\:hamsa:" => "🪬", "\\:hamster:" => "🐹", "\\:hand:" => "✋", + "\\:hand_with_index_finger_and_thumb_crossed:" => "🫰", "\\:handbag:" => "👜", "\\:handball:" => "🤾", "\\:handshake:" => "🤝", @@ -524,12 +545,14 @@ const emoji_symbols = Dict( "\\:heart_decoration:" => "💟", "\\:heart_eyes:" => "😍", "\\:heart_eyes_cat:" => "😻", + "\\:heart_hands:" => "🫶", "\\:heartbeat:" => "💓", "\\:heartpulse:" => "💗", "\\:hearts:" => "♥", "\\:heavy_check_mark:" => "✔", "\\:heavy_division_sign:" => "➗", "\\:heavy_dollar_sign:" => "💲", + "\\:heavy_equals_sign:" => "🟰", "\\:heavy_minus_sign:" => "➖", "\\:heavy_multiplication_x:" => "✖", "\\:heavy_plus_sign:" => "➕", @@ -559,16 +582,19 @@ const emoji_symbols = Dict( "\\:hugging_face:" => "🤗", "\\:hushed:" => "😯", "\\:hut:" => "🛖", + "\\:hyacinth:" => "🪻", "\\:i_love_you_hand_sign:" => "🤟", "\\:ice_cream:" => "🍨", "\\:ice_cube:" => "🧊", "\\:ice_hockey_stick_and_puck:" => "🏒", "\\:icecream:" => "🍦", "\\:id:" => "🆔", + "\\:identification_card:" => "🪪", "\\:ideograph_advantage:" => "🉐", "\\:imp:" => "👿", "\\:inbox_tray:" => "📥", "\\:incoming_envelope:" => "📨", + "\\:index_pointing_at_the_viewer:" => "🫵", "\\:information_desk_person:" => "💁", "\\:information_source:" => "ℹ", "\\:innocent:" => "😇", @@ -580,7 +606,9 @@ const emoji_symbols = Dict( "\\:japanese_castle:" => "🏯", "\\:japanese_goblin:" => "👺", "\\:japanese_ogre:" => "👹", + "\\:jar:" => "🫙", "\\:jeans:" => "👖", + "\\:jellyfish:" => "🪼", "\\:jigsaw:" => "🧩", "\\:joy:" => "😂", "\\:joy_cat:" => "😹", @@ -589,6 +617,7 @@ const emoji_symbols = Dict( "\\:kangaroo:" => "🦘", "\\:key:" => "🔑", "\\:keycap_ten:" => "🔟", + "\\:khanda:" => "🪯", "\\:kimono:" => "👘", "\\:kiss:" => "💋", "\\:kissing:" => "😗", @@ -631,11 +660,14 @@ const emoji_symbols = Dict( "\\:left_luggage:" => "🛅", "\\:left_right_arrow:" => "↔", "\\:leftwards_arrow_with_hook:" => "↩", + "\\:leftwards_hand:" => "🫲", + "\\:leftwards_pushing_hand:" => "🫷", "\\:leg:" => "🦵", "\\:lemon:" => "🍋", "\\:leo:" => "♌", "\\:leopard:" => "🐆", "\\:libra:" => "♎", + "\\:light_blue_heart:" => "🩵", "\\:light_rail:" => "🚈", "\\:link:" => "🔗", "\\:lion_face:" => "🦁", @@ -650,10 +682,12 @@ const emoji_symbols = Dict( "\\:long_drum:" => "🪘", "\\:loop:" => "➿", "\\:lotion_bottle:" => "🧴", + "\\:lotus:" => "🪷", "\\:loud_sound:" => "🔊", "\\:loudspeaker:" => "📢", "\\:love_hotel:" => "🏩", "\\:love_letter:" => "💌", + "\\:low_battery:" => "🪫", "\\:low_brightness:" => "🔅", "\\:luggage:" => "🧳", "\\:lungs:" => "🫁", @@ -679,6 +713,7 @@ const emoji_symbols = Dict( "\\:mans_shoe:" => "👞", "\\:manual_wheelchair:" => "🦽", "\\:maple_leaf:" => "🍁", + "\\:maracas:" => "🪇", "\\:martial_arts_uniform:" => "🥋", "\\:mask:" => "😷", "\\:massage:" => "💆", @@ -688,6 +723,7 @@ const emoji_symbols = Dict( "\\:mechanical_leg:" => "🦿", "\\:mega:" => "📣", "\\:melon:" => "🍈", + "\\:melting_face:" => "🫠", "\\:memo:" => "📝", "\\:menorah_with_nine_branches:" => "🕎", "\\:mens:" => "🚹", @@ -702,6 +738,7 @@ const emoji_symbols = Dict( "\\:minibus:" => "🚐", "\\:minidisc:" => "💽", "\\:mirror:" => "🪞", + "\\:mirror_ball:" => "🪩", "\\:mobile_phone_off:" => "📴", "\\:money_mouth_face:" => "🤑", "\\:money_with_wings:" => "💸", @@ -711,6 +748,7 @@ const emoji_symbols = Dict( "\\:monorail:" => "🚝", "\\:moon:" => "🌔", "\\:moon_cake:" => "🥮", + "\\:moose:" => "🫎", "\\:mortar_board:" => "🎓", "\\:mosque:" => "🕌", "\\:mosquito:" => "🦟", @@ -739,6 +777,7 @@ const emoji_symbols = Dict( "\\:necktie:" => "👔", "\\:negative_squared_cross_mark:" => "❎", "\\:nerd_face:" => "🤓", + "\\:nest_with_eggs:" => "🪺", "\\:nesting_dolls:" => "🪆", "\\:neutral_face:" => "😐", "\\:new:" => "🆕", @@ -800,7 +839,9 @@ const emoji_symbols = Dict( "\\:page_facing_up:" => "📄", "\\:page_with_curl:" => "📃", "\\:pager:" => "📟", + "\\:palm_down_hand:" => "🫳", "\\:palm_tree:" => "🌴", + "\\:palm_up_hand:" => "🫴", "\\:palms_up_together:" => "🤲", "\\:pancakes:" => "🥞", "\\:panda_face:" => "🐼", @@ -812,6 +853,7 @@ const emoji_symbols = Dict( "\\:partly_sunny:" => "⛅", "\\:partying_face:" => "🥳", "\\:passport_control:" => "🛂", + "\\:pea_pod:" => "🫛", "\\:peach:" => "🍑", "\\:peacock:" => "🦚", "\\:peanuts:" => "🥜", @@ -829,6 +871,7 @@ const emoji_symbols = Dict( "\\:person_in_steamy_room:" => "🧖", "\\:person_in_tuxedo:" => "🤵", "\\:person_with_blond_hair:" => "👱", + "\\:person_with_crown:" => "🫅", "\\:person_with_headscarf:" => "🧕", "\\:person_with_pouting_face:" => "🙎", "\\:petri_dish:" => "🧫", @@ -843,10 +886,12 @@ const emoji_symbols = Dict( "\\:pinched_fingers:" => "🤌", "\\:pinching_hand:" => "🤏", "\\:pineapple:" => "🍍", + "\\:pink_heart:" => "🩷", "\\:pisces:" => "♓", "\\:pizza:" => "🍕", "\\:placard:" => "🪧", "\\:place_of_worship:" => "🛐", + "\\:playground_slide:" => "🛝", "\\:pleading_face:" => "🥺", "\\:plunger:" => "🪠", "\\:point_down:" => "👇", @@ -866,9 +911,12 @@ const emoji_symbols = Dict( "\\:pouch:" => "👝", "\\:poultry_leg:" => "🍗", "\\:pound:" => "💷", + "\\:pouring_liquid:" => "🫗", "\\:pouting_cat:" => "😾", "\\:pray:" => "🙏", "\\:prayer_beads:" => "📿", + "\\:pregnant_man:" => "🫃", + "\\:pregnant_person:" => "🫄", "\\:pregnant_woman:" => "🤰", "\\:pretzel:" => "🥨", "\\:prince:" => "🤴", @@ -914,7 +962,10 @@ const emoji_symbols = Dict( "\\:rice_cracker:" => "🍘", "\\:rice_scene:" => "🎑", "\\:right-facing_fist:" => "🤜", + "\\:rightwards_hand:" => "🫱", + "\\:rightwards_pushing_hand:" => "🫸", "\\:ring:" => "💍", + "\\:ring_buoy:" => "🛟", "\\:ringed_planet:" => "🪐", "\\:robot_face:" => "🤖", "\\:rock:" => "🪨", @@ -937,6 +988,7 @@ const emoji_symbols = Dict( "\\:sagittarius:" => "♐", "\\:sake:" => "🍶", "\\:salt:" => "🧂", + "\\:saluting_face:" => "🫡", "\\:sandal:" => "👡", "\\:sandwich:" => "🥪", "\\:santa:" => "🎅", @@ -964,6 +1016,7 @@ const emoji_symbols = Dict( "\\:seedling:" => "🌱", "\\:selfie:" => "🤳", "\\:sewing_needle:" => "🪡", + "\\:shaking_face:" => "🫨", "\\:shallow_pan_of_food:" => "🥘", "\\:shark:" => "🦈", "\\:shaved_ice:" => "🍧", @@ -1124,6 +1177,7 @@ const emoji_symbols = Dict( "\\:triangular_ruler:" => "📐", "\\:trident:" => "🔱", "\\:triumph:" => "😤", + "\\:troll:" => "🧌", "\\:trolleybus:" => "🚎", "\\:trophy:" => "🏆", "\\:tropical_drink:" => "🍹", @@ -1188,6 +1242,7 @@ const emoji_symbols = Dict( "\\:wedding:" => "💒", "\\:whale2:" => "🐋", "\\:whale:" => "🐳", + "\\:wheel:" => "🛞", "\\:wheelchair:" => "♿", "\\:white_check_mark:" => "✅", "\\:white_circle:" => "⚪", @@ -1202,7 +1257,9 @@ const emoji_symbols = Dict( "\\:wind_chime:" => "🎐", "\\:window:" => "🪟", "\\:wine_glass:" => "🍷", + "\\:wing:" => "🪽", "\\:wink:" => "😉", + "\\:wireless:" => "🛜", "\\:wolf:" => "🐺", "\\:woman:" => "👩", "\\:womans_clothes:" => "👚", @@ -1215,6 +1272,7 @@ const emoji_symbols = Dict( "\\:worried:" => "😟", "\\:wrench:" => "🔧", "\\:wrestlers:" => "🤼", + "\\:x-ray:" => "🩻", "\\:x:" => "❌", "\\:yarn:" => "🧶", "\\:yawning_face:" => "🥱", diff --git a/stdlib/REPL/src/latex_symbols.jl b/stdlib/REPL/src/latex_symbols.jl index 9e71819f6562b..00be62dbb170a 100644 --- a/stdlib/REPL/src/latex_symbols.jl +++ b/stdlib/REPL/src/latex_symbols.jl @@ -4,7 +4,7 @@ # This is used for tab substitution in the REPL. # The initial symbol listing was generated from the W3C symbol mapping file: -# http://www.w3.org/Math/characters/unicode.xml +# https://www.w3.org/Math/characters/unicode.xml # by the following Julia script: #= import REPL @@ -984,17 +984,16 @@ const latex_symbols = Dict( "\\droang" => "̚", # left angle above (non-spacing) "\\wideutilde" => "̰", # under tilde accent (multiple characters and non-spacing) "\\not" => "̸", # combining long solidus overlay - "\\upMu" => "Μ", # capital mu, greek - "\\upNu" => "Ν", # capital nu, greek - "\\upOmicron" => "Ο", # capital omicron, greek - "\\upepsilon" => "ε", # rounded small epsilon, greek - "\\upomicron" => "ο", # small omicron, greek - "\\upvarbeta" => "ϐ", # rounded small beta, greek - "\\upoldKoppa" => "Ϙ", # greek letter archaic koppa - "\\upoldkoppa" => "ϙ", # greek small letter archaic koppa - "\\upstigma" => "ϛ", # greek small letter stigma - "\\upkoppa" => "ϟ", # greek small letter koppa - "\\upsampi" => "ϡ", # greek small letter sampi + "\\Mu" => "Μ", # capital mu, greek + "\\Nu" => "Ν", # capital nu, greek + "\\Omicron" => "Ο", # capital omicron, greek + "\\omicron" => "ο", # small omicron, greek + "\\varbeta" => "ϐ", # rounded small beta, greek + "\\oldKoppa" => "Ϙ", # greek letter archaic koppa + "\\oldkoppa" => "ϙ", # greek small letter archaic koppa + "\\stigma" => "ϛ", # greek small letter stigma + "\\koppa" => "ϟ", # greek small letter koppa + "\\sampi" => "ϡ", # greek small letter sampi "\\tieconcat" => "⁀", # character tie, z notation sequence concatenation "\\leftharpoonaccent" => "⃐", # combining left harpoon above "\\rightharpoonaccent" => "⃑", # combining right harpoon above @@ -2623,10 +2622,10 @@ const latex_symbols = Dict( "\\4/5" => "⅘", # vulgar fraction four fifths "\\1/6" => "⅙", # vulgar fraction one sixth "\\5/6" => "⅚", # vulgar fraction five sixths - "\\1/8" => "⅛", # vulgar fraction one eigth - "\\3/8" => "⅜", # vulgar fraction three eigths - "\\5/8" => "⅝", # vulgar fraction five eigths - "\\7/8" => "⅞", # vulgar fraction seventh eigths + "\\1/8" => "⅛", # vulgar fraction one eighth + "\\3/8" => "⅜", # vulgar fraction three eighths + "\\5/8" => "⅝", # vulgar fraction five eighths + "\\7/8" => "⅞", # vulgar fraction seventh eighths "\\1/" => "⅟", # fraction numerator one "\\0/3" => "↉", # vulgar fraction zero thirds "\\1/4" => "¼", # vulgar fraction one quarter diff --git a/stdlib/REPL/src/precompile.jl b/stdlib/REPL/src/precompile.jl index 2e75bd69e197b..7299742eaef1c 100644 --- a/stdlib/REPL/src/precompile.jl +++ b/stdlib/REPL/src/precompile.jl @@ -4,8 +4,14 @@ module Precompile # Can't use this during incremental: `@eval Module() begin`` import ..REPL +# Prepare this staging area with all the loaded packages available +for (_pkgid, _mod) in Base.loaded_modules + if !(_pkgid.name in ("Main", "Core", "Base", "REPL")) + eval(:(const $(Symbol(_mod)) = $_mod)) + end +end -# Ugly hack for our cache file to not have a dependency edge on FakePTYs. +# Ugly hack for our cache file to not have a dependency edge on the FakePTYs file. Base._track_dependencies[] = false try Base.include(@__MODULE__, joinpath(Sys.BINDIR, "..", "share", "julia", "test", "testhelpers", "FakePTYs.jl")) @@ -16,6 +22,7 @@ end using Base.Meta import Markdown +import StyledStrings ## Debugging options # Disable parallel precompiles generation by setting `false` @@ -40,7 +47,7 @@ foo(x) = 1 @time @eval foo(1) ; pwd $CTRL_C -$CTRL_R$CTRL_C +$CTRL_R$CTRL_C# ? reinterpret using Ra\t$CTRL_C \\alpha\t$CTRL_C @@ -51,7 +58,7 @@ $UP_ARROW$DOWN_ARROW$CTRL_C f(x) = x03 f(1,2) [][1] -cd("complet_path\t\t$CTRL_C +cd("complete_path\t\t$CTRL_C """ julia_exepath() = joinpath(Sys.BINDIR, Base.julia_exename()) @@ -78,11 +85,18 @@ generate_precompile_statements() = try # Collect statements from running a REPL process and replaying our REPL script touch(precompile_file) pts, ptm = open_fake_pty() - cmdargs = `-e 'import REPL; REPL.Terminals.is_precompiling[] = true'` - p = run(addenv(addenv(```$(julia_exepath()) -O0 --trace-compile=$precompile_file - --cpu-target=native --startup-file=no --compiled-modules=existing --color=yes -i $cmdargs```, procenv), - "JULIA_PKG_PRECOMPILE_AUTO" => "0"), - pts, pts, pts; wait=false) + # we don't want existing REPL caches to be used so ignore them + setup_cmd = """ + push!(Base.ignore_compiled_cache, Base.PkgId(Base.UUID("3fa0cd96-eef1-5676-8a61-b3b8758bbffb"), "REPL")) + import REPL + REPL.Terminals.is_precompiling[] = true + """ + p = run( + addenv(```$(julia_exepath()) -O0 --trace-compile=$precompile_file + --cpu-target=native --startup-file=no --compiled-modules=existing + --color=yes -i -e "$setup_cmd"```, procenv), + pts, pts, pts; wait=false + ) Base.close_stdio(pts) # Prepare a background process to copy output from process until `pts` is closed output_copy = Base.BufferStream() @@ -93,6 +107,7 @@ generate_precompile_statements() = try Sys.iswindows() && (sleep(0.1); yield(); yield()) # workaround hang - probably a libuv issue? write(output_copy, l) end + write(debug_output, "\n#### EOF ####\n") catch ex if !(ex isa Base.IOError && ex.code == Base.UV_EIO) rethrow() # ignore EIO on ptm after pts dies @@ -117,7 +132,10 @@ generate_precompile_statements() = try bytesavailable(output_copy) > 0 && readavailable(output_copy) # push our input write(debug_output, "\n#### inputting statement: ####\n$(repr(l))\n####\n") - write(ptm, l, "\n") + # If the line ends with a CTRL_C, don't write an extra newline, which would + # cause a second empty prompt. Our code below expects one new prompt per + # input line and can race out of sync with the unexpected second line. + endswith(l, CTRL_C) ? write(ptm, l) : write(ptm, l, "\n") readuntil(output_copy, "\n") # wait for the next prompt-like to appear readuntil(output_copy, "\n") @@ -131,6 +149,7 @@ generate_precompile_statements() = try sleep(0.1) end end + write(debug_output, "\n#### COMPLETED - Closing REPL ####\n") write(ptm, "$CTRL_D") wait(tee) success(p) || Base.pipeline_error(p) @@ -148,7 +167,7 @@ generate_precompile_statements() = try open(precompile_file, "r") do io while true - # We need to allways call eof(io) for bytesavailable(io) to work + # We need to always call eof(io) for bytesavailable(io) to work eof(io) && istaskdone(repl_inputter) && eof(io) && break if bytesavailable(io) == 0 sleep(0.1) @@ -159,10 +178,10 @@ generate_precompile_statements() = try end close(precompile_copy) wait(buffer_reader) - close(statements_step) return :ok end !PARALLEL_PRECOMPILATION && wait(step) + bind(statements_step, step) # Make statements unique statements = Set{String}() @@ -176,9 +195,9 @@ generate_precompile_statements() = try if !isexpr(ps, :call) # these are typically comments @debug "skipping statement because it does not parse as an expression" statement + delete!(statements, statement) continue end - push!(REPL.PRECOMPILE_STATEMENTS, statement) popfirst!(ps.args) # precompile(...) ps.head = :tuple # println(ps) @@ -192,7 +211,7 @@ generate_precompile_statements() = try end end - fetch(step) == :ok || throw("Collecting precompiles failed.") + fetch(step) == :ok || throw("Collecting precompiles failed: $(c.excp)") return nothing finally GC.gc(true); GC.gc(false); # reduce memory footprint @@ -200,9 +219,5 @@ end generate_precompile_statements() -# As a last step in system image generation, -# remove some references to build time environment for a more reproducible build. -Base.Filesystem.temp_cleanup_purge(force=true) - precompile(Tuple{typeof(getproperty), REPL.REPLBackend, Symbol}) end # Precompile diff --git a/stdlib/REPL/test/TerminalMenus/runtests.jl b/stdlib/REPL/test/TerminalMenus/runtests.jl index c594958a36670..9455632d9f418 100644 --- a/stdlib/REPL/test/TerminalMenus/runtests.jl +++ b/stdlib/REPL/test/TerminalMenus/runtests.jl @@ -17,9 +17,9 @@ function simulate_input(menu::TerminalMenus.AbstractMenu, keys...; kwargs...) write(new_stdin, "$key") end end - TerminalMenus.terminal.in_stream = new_stdin + terminal = TerminalMenus.default_terminal(; in=new_stdin, out=devnull) - return request(menu; suppress_output=true, kwargs...) + return request(terminal, menu; suppress_output=true, kwargs...) end include("radio_menu.jl") diff --git a/stdlib/REPL/test/docview.jl b/stdlib/REPL/test/docview.jl index d7caf7eef7d7c..123ff820bc939 100644 --- a/stdlib/REPL/test/docview.jl +++ b/stdlib/REPL/test/docview.jl @@ -4,9 +4,9 @@ using Test import REPL, REPL.REPLCompletions import Markdown -function get_help_io(input) +function get_help_io(input, mod=Main) buf = IOBuffer() - eval(REPL.helpmode(buf, input)) + eval(REPL.helpmode(buf, input, mod)) String(take!(buf)) end get_help_standard(input) = string(eval(REPL.helpmode(IOBuffer(), input))) @@ -40,7 +40,7 @@ end symbols = "@" .* checks .* "_str" results = checks .* "\"\"" for (i,r) in zip(symbols,results) - @test r ∈ REPL.doc_completions(i) + @test r ∈ string.(REPL.doc_completions(i)) end end @testset "fuzzy score" begin @@ -56,6 +56,13 @@ end # Unicode @test 1.0 > REPL.fuzzyscore("αkδψm", "αkδm") > 0.0 @test 1.0 > REPL.fuzzyscore("αkδψm", "α") > 0.0 + + exact_match_export = REPL.fuzzyscore("thing", REPL.AccessibleBinding(:thing)) + exact_match_public = REPL.fuzzyscore("thing", REPL.AccessibleBinding("A", "thing")) + inexact_match_export = REPL.fuzzyscore("thing", REPL.AccessibleBinding(:thang)) + inexact_match_public = REPL.fuzzyscore("thing", REPL.AccessibleBinding("A", "thang")) + @test exact_match_export > exact_match_public > inexact_match_export > inexact_match_public + @test exact_match_export ≈ 1.0 end @testset "Unicode doc lookup (#41589)" begin @@ -132,3 +139,29 @@ module InternalWarningsTests @test docstring("A.B3") == "No docstring or readme file found for public module `$(@__MODULE__).A.B3`.\n\nModule does not have any public names.\n" end end + +# Issue #51344, don't print "internal binding" warning for non-existent bindings. +@test string(eval(REPL.helpmode("Base.no_such_symbol"))) == "No documentation found.\n\nBinding `Base.no_such_symbol` does not exist.\n" + +module TestSuggestPublic + export dingo + public dango + dingo(x) = x + 1 + dango(x) = x = 2 +end +using .TestSuggestPublic +helplines(s) = map(strip, split(get_help_io(s, @__MODULE__), '\n'; keepempty=false)) +@testset "search lists public names" begin + lines = helplines("dango") + # Ensure that public names that exactly match the search query are listed first + # even if they aren't exported, as long as no exact exported/local match exists + @test startswith(lines[1], "search: TestSuggestPublic.dango dingo") + @test lines[2] == "Couldn't find dango" # 🙈🍡 + @test startswith(lines[3], "Perhaps you meant TestSuggestPublic.dango, dingo") +end +dango() = "🍡" +@testset "search prioritizes exported names" begin + # Prioritize exported/local names if they exactly match + lines = helplines("dango") + @test startswith(lines[1], "search: dango TestSuggestPublic.dango dingo") +end diff --git a/stdlib/REPL/test/precompilation.jl b/stdlib/REPL/test/precompilation.jl new file mode 100644 index 0000000000000..228cbd212a2c1 --- /dev/null +++ b/stdlib/REPL/test/precompilation.jl @@ -0,0 +1,52 @@ + +## Tests that compilation in the interactive session startup are as expected + +using Test +Base.include(@__MODULE__, joinpath(Sys.BINDIR, "..", "share", "julia", "test", "testhelpers", "FakePTYs.jl")) +import .FakePTYs: open_fake_pty + +if !Sys.iswindows() + # TODO: reenable this on Windows. Without it we're not checking that Windows startup has no compilation. + # On Windows CI runners using `open_fake_pty` is causing: + # ---- + # `stty: 'standard input': Inappropriate ioctl for device + # Unhandled Task ERROR: failed process: Process(`stty raw -echo onlcr -ocrnl opost`, ProcessExited(1)) [1] + # ---- + @testset "No interactive startup compilation" begin + f, _ = mktemp() + + # start an interactive session + cmd = `$(Base.julia_cmd()[1]) --trace-compile=$f -q --startup-file=no -i` + pts, ptm = open_fake_pty() + p = run(cmd, pts, pts, pts; wait=false) + Base.close_stdio(pts) + std = readuntil(ptm, "julia>") + # check for newlines instead of equality with "julia>" because color may be on + occursin("\n", std) && @info "There was output before the julia prompt:\n$std" + sleep(1) # sometimes precompiles output just after prompt appears + tracecompile_out = read(f, String) + close(ptm) # close after reading so we don't get precompiles from error shutdown + + # given this test checks that startup is snappy, it's best to add workloads to + # contrib/generate_precompile.jl rather than increase this number. But if that's not + # possible, it'd be helpful to add a comment with the statement and a reason below + expected_precompiles = 0 + + n_precompiles = count(r"precompile\(", tracecompile_out) + + @test n_precompiles <= expected_precompiles + + if n_precompiles == 0 + @debug "REPL: trace compile output: (none)" + elseif n_precompiles > expected_precompiles + @info "REPL: trace compile output:\n$tracecompile_out" + else + @debug "REPL: trace compile output:\n$tracecompile_out" + end + # inform if lowered + if expected_precompiles > 0 && (n_precompiles < expected_precompiles) + @info "REPL: Actual number of precompiles has dropped below expected." n_precompiles expected_precompiles + end + + end +end diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index 0fb41ddacefc7..09ff10e770e84 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -6,6 +6,8 @@ using Random import REPL.LineEdit using Markdown +empty!(Base.Experimental._hint_handlers) # unregister error hints so they can be tested separately + @test isassigned(Base.REPL_MODULE_REF) const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") @@ -18,7 +20,6 @@ include(joinpath(BASE_TEST_PATH, "testenv.jl")) include("FakeTerminals.jl") import .FakeTerminals.FakeTerminal - function kill_timer(delay) # Give ourselves a generous timer here, just to prevent # this causing e.g. a CI hang when there's something unexpected in the output. @@ -113,7 +114,7 @@ fake_repl() do stdin_write, stdout_read, repl Base.wait(repltask) end -# These are integration tests. If you want to unit test test e.g. completion, or +# These are integration tests. If you want to unit test e.g. completion, or # exact LineEdit behavior, put them in the appropriate test files. # Furthermore since we are emulating an entire terminal, there may be control characters # in the mix. If verification needs to be done, keep it to the bare minimum. Basically @@ -1479,6 +1480,35 @@ end end end +# Test that the REPL can find `using` statements inside macro expansions +global packages_requested = Any[] +old_hooks = copy(REPL.install_packages_hooks) +empty!(REPL.install_packages_hooks) +push!(REPL.install_packages_hooks, function(pkgs) + append!(packages_requested, pkgs) +end) + +fake_repl() do stdin_write, stdout_read, repl + repltask = @async begin + REPL.run_repl(repl) + end + + # Just consume all the output - we only test that the callback ran + read_resp_task = @async while !eof(stdout_read) + readavailable(stdout_read) + end + + write(stdin_write, "macro usingfoo(); :(using FooNotFound); end\n") + write(stdin_write, "@usingfoo\n") + write(stdin_write, "\x4") + Base.wait(repltask) + close(stdin_write) + close(stdout_read) + Base.wait(read_resp_task) +end +@test packages_requested == Any[:FooNotFound] +empty!(REPL.install_packages_hooks); append!(REPL.install_packages_hooks, old_hooks) + # err should reprint error if deeper than top-level fake_repl() do stdin_write, stdout_read, repl repltask = @async begin @@ -1492,7 +1522,7 @@ fake_repl() do stdin_write, stdout_read, repl # generate top-level error write(stdin_write, "foobar\n") readline(stdout_read) - @test readline(stdout_read) == "\e[0mERROR: UndefVarError: `foobar` not defined" + @test readline(stdout_read) == "\e[0mERROR: UndefVarError: `foobar` not defined in `Main`" @test readline(stdout_read) == "" readuntil(stdout_read, "julia> ", keep=true) # check that top-level error did not change `err` @@ -1507,13 +1537,13 @@ fake_repl() do stdin_write, stdout_read, repl readuntil(stdout_read, "julia> ", keep=true) write(stdin_write, "foo()\n") readline(stdout_read) - @test readline(stdout_read) == "\e[0mERROR: UndefVarError: `foobar` not defined" + @test readline(stdout_read) == "\e[0mERROR: UndefVarError: `foobar` not defined in `Main`" readuntil(stdout_read, "julia> ", keep=true) # check that deeper error did set `err` write(stdin_write, "err\n") readline(stdout_read) @test readline(stdout_read) == "\e[0m1-element ExceptionStack:" - @test readline(stdout_read) == "UndefVarError: `foobar` not defined" + @test readline(stdout_read) == "UndefVarError: `foobar` not defined in `Main`" @test readline(stdout_read) == "Stacktrace:" readuntil(stdout_read, "\n\n", keep=true) readuntil(stdout_read, "julia> ", keep=true) @@ -1674,6 +1704,56 @@ fake_repl() do stdin_write, stdout_read, repl @test contains(txt, "Some type information was truncated. Use `show(err)` to see complete types.") end +try # test the functionality of `UndefVarError_hint` against `Base.remove_linenums!` + @assert isempty(Base.Experimental._hint_handlers) + Base.Experimental.register_error_hint(REPL.UndefVarError_hint, UndefVarError) + + # check the requirement to trigger the hint via `UndefVarError_hint` + @test !isdefined(Main, :remove_linenums!) && Base.ispublic(Base, :remove_linenums!) + + fake_repl() do stdin_write, stdout_read, repl + backend = REPL.REPLBackend() + repltask = @async REPL.run_repl(repl; backend) + write(stdin_write, + "remove_linenums!\n\"ZZZZZ\"\n") + txt = readuntil(stdout_read, "ZZZZZ") + write(stdin_write, '\x04') + wait(repltask) + @test occursin("Hint: a global variable of this name also exists in Base.", txt) + end +finally + empty!(Base.Experimental._hint_handlers) +end + +try # test the functionality of `UndefVarError_hint` against import clashes + @assert isempty(Base.Experimental._hint_handlers) + Base.Experimental.register_error_hint(REPL.UndefVarError_hint, UndefVarError) + + @eval module X + + module A + export x + x = 1 + end # A + + module B + export x + x = 2 + end # B + + using .A, .B + + end # X + + expected_message = string("\nHint: It looks like two or more modules export different ", + "bindings with this name, resulting in ambiguity. Try explicitly ", + "importing it from a particular module, or qualifying the name ", + "with the module it should come from.") + @test_throws expected_message X.x +finally + empty!(Base.Experimental._hint_handlers) +end + # Hints for tab completes fake_repl() do stdin_write, stdout_read, repl @@ -1710,6 +1790,17 @@ fake_repl() do stdin_write, stdout_read, repl end @test LineEdit.state(repl.mistate).hint === nothing + # issue #52376 + write(stdin_write, "\x15") + write(stdin_write, "\\_ailuj") + while LineEdit.state(repl.mistate).hint !== nothing + sleep(0.1) + end + @test LineEdit.state(repl.mistate).hint === nothing + s5 = readuntil(stdout_read, "\\_ailuj") + write(stdin_write, "\t") + s6 = readuntil(stdout_read, "ₐᵢₗᵤⱼ") + write(stdin_write, "\x15\x04") Base.wait(repltask) end @@ -1726,3 +1817,20 @@ fake_repl(options=REPL.Options(confirm_exit=false,hascolor=true,hint_tab_complet Base.wait(repltask) @test !occursin("vailable", String(readavailable(stdout_read))) end + +# banner +let io = IOBuffer() + @test REPL.banner(io) === nothing + seek(io, 0) + @test countlines(io) == 9 + take!(io) + @test REPL.banner(io; short=true) === nothing + seek(io, 0) + @test countlines(io) == 2 +end + +@testset "Docstrings" begin + undoc = Docs.undocumented_names(REPL) + @test_broken isempty(undoc) + @test undoc == [:AbstractREPL, :BasicREPL, :LineEditREPL, :StreamREPL] +end diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index ef1cf8814af74..0a73a944ec8ea 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -4,12 +4,14 @@ using REPL.REPLCompletions using Test using Random using REPL - @testset "Check symbols previously not shown by REPL.doc_completions()" begin + +@testset "Check symbols previously not shown by REPL.doc_completions()" begin symbols = ["?","=","[]","[","]","{}","{","}",";","","'","&&","||","julia","Julia","new","@var_str"] - for i in symbols - @test i ∈ REPL.doc_completions(i, Main) - end + for i in symbols + @test i ∈ string.(REPL.doc_completions(i, Main)) end +end + let ex = quote module CompletionFoo using Random @@ -140,6 +142,10 @@ let ex = quote struct WeirdNames end Base.propertynames(::WeirdNames) = (Symbol("oh no!"), Symbol("oh yes!")) + # https://github.com/JuliaLang/julia/issues/52551#issuecomment-1858543413 + export exported_symbol + exported_symbol(::WeirdNames) = nothing + end # module CompletionFoo test_repl_comp_dict = CompletionFoo.test_dict test_repl_comp_customdict = CompletionFoo.test_customdict @@ -325,7 +331,7 @@ end let s = "\\alpha" c, r = test_bslashcomplete(s) @test c[1] == "α" - @test r == 1:length(s) + @test r == 1:lastindex(s) @test length(c) == 1 end @@ -642,7 +648,7 @@ let s = "CompletionFoo.?([1,2,3], 2.0)" c, r, res = test_complete(s) @test !res @test length(c) == 1 - @test occursin("test(x::AbstractArray{T}, y) where T<:Real", c[1]) + @test occursin("test(x::AbstractArray{T}, y) where T<:Real", only(c)) # In particular, this checks that test(args...) is not a valid completion # since it is strictly less specific than test(x::AbstractArray{T}, y) end @@ -676,15 +682,15 @@ let s = "CompletionFoo.?(false, \"a\", 3, " c, r, res = test_complete(s) @test !res @test length(c) == 2 - @test occursin("test(args...)", c[1]) - @test occursin("test11(a::Integer, b, c)", c[2]) + @test any(s->occursin("test(args...)", s), c) + @test any(s->occursin("test11(a::Integer, b, c)", s), c) end let s = "CompletionFoo.?(false, \"a\", 3, " c, r, res = test_complete_noshift(s) @test !res @test length(c) == 1 - @test occursin("test11(a::Integer, b, c)", c[1]) + @test occursin("test11(a::Integer, b, c)", only(c)) end let s = "CompletionFoo.?(\"a\", 3, " @@ -707,7 +713,7 @@ let s = "CompletionFoo.?()" c, r, res = test_complete_noshift(s) @test !res @test length(c) == 1 - @test occursin("test10(s::String...)", c[1]) + @test occursin("test10(s::String...)", only(c)) end #= TODO: restrict the number of completions when a semicolon is present in ".?(" syntax @@ -725,7 +731,7 @@ let s = "CompletionFoo.?(3; len2=5, " c, r, res = test_complete_noshift(s) @test !res @test length(c) == 1 - @test occursin("kwtest3(a::Integer; namedarg, foobar, slurp...)", c[1]) + @test occursin("kwtest3(a::Integer; namedarg, foobar, slurp...)", only(c)) # the other two kwtest3 methods should not appear because of specificity end =# @@ -740,6 +746,9 @@ end #TODO: @test_nocompletion("CompletionFoo.?(3; len2=5; ") +# https://github.com/JuliaLang/julia/issues/52551 +@test !isempty(test_complete("?(")) + ################################################################# # Test method completion with varargs @@ -1041,7 +1050,7 @@ let s, c, r s = "@show \"/dev/nul\"" c,r = completions(s, 15) c = map(completion_text, c) - @test "null" in c + @test "null\"" in c @test r == 13:15 @test s[r] == "nul" @@ -1065,8 +1074,8 @@ let s, c, r if !isdir(joinpath(s, "tmp")) c,r = test_scomplete(s) @test !("tmp/" in c) - @test r === length(s) + 1:0 - @test s[r] == "" + @test !("$s/tmp/" in c) + @test r === (sizeof(s) + 1):sizeof(s) end s = "cd \$(Iter" @@ -1091,7 +1100,7 @@ let s, c, r touch(file) s = string(tempdir(), "/repl\\ ") c,r = test_scomplete(s) - @test ["repl\\ completions"] == c + @test ["'repl completions'"] == c @test s[r] == "repl\\ " rm(file) end @@ -1119,28 +1128,44 @@ let s, c, r end # Tests detecting of files in the env path (in shell mode) - let path, file - path = tempdir() - unreadable = joinpath(tempdir(), "replcompletion-unreadable") + mktempdir() do path + unreadable = joinpath(path, "replcompletion-unreadable") + file = joinpath(path, "tmp-executable") + touch(file) + chmod(file, 0o755) + mkdir(unreadable) + hidden_file = joinpath(unreadable, "hidden") + touch(hidden_file) - try - file = joinpath(path, "tmp-executable") - touch(file) - chmod(file, 0o755) - mkdir(unreadable) - chmod(unreadable, 0o000) + # Create symlink to a file that is in an unreadable directory + chmod(hidden_file, 0o755) + chmod(unreadable, 0o000) + symlink(hidden_file, joinpath(path, "replcompletions-link")) + try # PATH can also contain folders which we aren't actually allowed to read. withenv("PATH" => string(path, ":", unreadable)) do s = "tmp-execu" + # Files reachable by PATH are cached async when PATH is seen to have been changed by `complete_path` + # so changes are unlikely to appear in the first complete. For testing purposes we can wait for + # caching to finish + @lock REPL.REPLCompletions.PATH_cache_lock begin + # force the next cache update to happen immediately + REPL.REPLCompletions.next_cache_update = 0 + end + c,r = test_scomplete(s) + wait(REPL.REPLCompletions.PATH_cache_task::Task) # wait for caching to complete c,r = test_scomplete(s) @test "tmp-executable" in c @test r == 1:9 @test s[r] == "tmp-execu" + + c,r = test_scomplete("replcompletions-link") + @test isempty(c) end finally - rm(file) - rm(unreadable) + # If we don't fix the permissions here, our cleanup fails. + chmod(unreadable, 0o700) end end @@ -1158,6 +1183,12 @@ let s, c, r withenv("PATH" => string(tempdir(), ":", dir)) do s = string("repl-completio") + @lock REPL.REPLCompletions.PATH_cache_lock begin + # force the next cache update to happen immediately + REPL.REPLCompletions.next_cache_update = 0 + end + c,r = test_scomplete(s) + wait(REPL.REPLCompletions.PATH_cache_task::Task) # wait for caching to complete c,r = test_scomplete(s) @test ["repl-completion"] == c @test s[r] == "repl-completio" @@ -1185,7 +1216,7 @@ let current_dir, forbidden catch e e isa Base.IOError && occursin("ELOOP", e.msg) end - c, r = test_complete("\""*escape_string(joinpath(path, "selfsym"))) + c, r = test_complete("\"$(escape_string(path))/selfsym") @test c == ["selfsymlink"] end end @@ -1222,20 +1253,20 @@ mktempdir() do path dir_space = replace(space_folder, " " => "\\ ") s = Sys.iswindows() ? "cd $dir_space\\\\space" : "cd $dir_space/space" c, r = test_scomplete(s) - @test s[r] == "space" - @test "space\\ .file" in c + @test s[r] == (Sys.iswindows() ? "$dir_space\\\\space" : "$dir_space/space") + @test "'$space_folder'/'space .file'" in c # Also use shell escape rules within cmd backticks s = "`$s" c, r = test_scomplete(s) - @test s[r] == "space" - @test "space\\ .file" in c + @test s[r] == (Sys.iswindows() ? "$dir_space\\\\space" : "$dir_space/space") + @test "'$space_folder'/'space .file'" in c # escape string according to Julia escaping rules - julia_esc(str) = escape_string(str, ('\"','$')) + julia_esc(str) = REPL.REPLCompletions.do_string_escape(str) # For normal strings the string should be properly escaped according to # the usual rules for Julia strings. - s = "cd(\"" * julia_esc(joinpath(path, space_folder, "space")) + s = "cd(\"" * julia_esc(joinpath(path, space_folder) * "/space") c, r = test_complete(s) @test s[r] == "space" @test "space .file\"" in c @@ -1244,7 +1275,7 @@ mktempdir() do path # which needs to be escaped in Julia strings (on unix we could do this # test with all sorts of special chars) touch(joinpath(space_folder, "needs_escape\$.file")) - escpath = julia_esc(joinpath(path, space_folder, "needs_escape\$")) + escpath = julia_esc(joinpath(path, space_folder) * "/needs_escape\$") s = "cd(\"$escpath" c, r = test_complete(s) @test s[r] == "needs_escape\\\$" @@ -1281,12 +1312,12 @@ mktempdir() do path # in shell commands the shell path completion cannot complete # paths with these characters c, r, res = test_scomplete(test_dir) - @test c[1] == test_dir*(Sys.iswindows() ? "\\\\" : "/") + @test c[1] == "'$test_dir/'" @test res end escdir = julia_esc(test_dir) c, r, res = test_complete("\""*escdir) - @test c[1] == escdir*(Sys.iswindows() ? "\\\\" : "/") + @test c[1] == escdir * "/" @test res finally rm(joinpath(path, test_dir), recursive=true) @@ -1322,27 +1353,43 @@ if Sys.iswindows() cd(path) do s = "cd ..\\\\" c,r = test_scomplete(s) - @test r == length(s)+1:length(s) - @test temp_name * "\\\\" in c + @test r == lastindex(s)-3:lastindex(s) + @test "../$temp_name/" in c + + s = "cd ../" + c,r = test_scomplete(s) + @test r == lastindex(s)+1:lastindex(s) + @test "$temp_name/" in c s = "ls $(file[1:2])" c,r = test_scomplete(s) - @test r == length(s)-1:length(s) + @test r == lastindex(s)-1:lastindex(s) @test file in c s = "cd(\"..\\\\" c,r = test_complete(s) - @test r == length(s)+1:length(s) - @test temp_name * "\\\\" in c + @test r == lastindex(s)-3:lastindex(s) + @test "../$temp_name/" in c + + s = "cd(\"../" + c,r = test_complete(s) + @test r == lastindex(s)+1:lastindex(s) + @test "$temp_name/" in c s = "cd(\"$(file[1:2])" c,r = test_complete(s) - @test r == length(s) - 1:length(s) + @test r == lastindex(s) - 1:lastindex(s) @test (length(c) > 1 && file in c) || (["$file\""] == c) end rm(tmp) end +# issue 51985 +let s = "`\\" + c,r = test_scomplete(s) + @test r == lastindex(s)+1:lastindex(s) +end + # auto completions of true and false... issue #14101 let s = "tru" c, r, res = test_complete(s) @@ -1457,22 +1504,22 @@ end c, r = test_complete("CompletionFoo.kwtest3(a;foob") @test c == ["foobar="] c, r = test_complete("CompletionFoo.kwtest3(a; le") - @test "length" ∈ c # provide this kind of completion in case the user wants to splat a variable + @test "length" ∉ c @test "length=" ∈ c @test "len2=" ∈ c @test "len2" ∉ c c, r = test_complete("CompletionFoo.kwtest3.(a;\nlength") - @test "length" ∈ c + @test "length" ∉ c @test "length=" ∈ c c, r = test_complete("CompletionFoo.kwtest3(a, length=4, l") @test "length" ∈ c @test "length=" ∉ c # since it was already used, do not suggest it again @test "len2=" ∈ c c, r = test_complete("CompletionFoo.kwtest3(a; kwargs..., fo") - @test "foreach" ∈ c # provide this kind of completion in case the user wants to splat a variable + @test "foreach" ∉ c @test "foobar=" ∈ c c, r = test_complete("CompletionFoo.kwtest3(a; another!kwarg=0, le") - @test "length" ∈ c + @test "length" ∉ c @test "length=" ∈ c # the first method could be called and `anotherkwarg` slurped @test "len2=" ∈ c c, r = test_complete("CompletionFoo.kwtest3(a; another!") @@ -1486,7 +1533,7 @@ end c, r = test_complete_foo("kwtest3(blabla; unknown=4, namedar") @test c == ["namedarg="] c, r = test_complete_foo("kwtest3(blabla; named") - @test "named" ∈ c + @test "named" ∉ c @test "namedarg=" ∈ c @test "len2" ∉ c c, r = test_complete_foo("kwtest3(blabla; named.") @@ -1494,11 +1541,11 @@ end c, r = test_complete_foo("kwtest3(blabla; named..., another!") @test c == ["another!kwarg="] c, r = test_complete_foo("kwtest3(blabla; named..., len") - @test "length" ∈ c + @test "length" ∉ c @test "length=" ∈ c @test "len2=" ∈ c c, r = test_complete_foo("kwtest3(1+3im; named") - @test "named" ∈ c + @test "named" ∉ c # TODO: @test "namedarg=" ∉ c @test "len2" ∉ c c, r = test_complete_foo("kwtest3(1+3im; named.") @@ -1980,7 +2027,7 @@ let (c, r, res) = test_complete_context("getkeyelem(mutable_const_prop).value.") end # JuliaLang/julia/#51548 -# don't return wrong result due to mutable inconsistentcy +# don't return wrong result due to mutable inconsistency function issue51548(T, a) # if we fold `xs = getindex(T)` to `xs::Const(Vector{T}())`, then we may wrongly # constant-fold `isempty(xs)::Const(true)` and return wrong result @@ -1996,6 +2043,16 @@ let inferred = REPL.REPLCompletions.repl_eval_ex( RT = Core.Compiler.widenconst(inferred) @test Val{false} <: RT end +module TestLimitAggressiveInferenceGetProp +global global_var = 1 +end +function test_limit_aggressive_inference_getprop() + return getproperty(TestLimitAggressiveInferenceGetProp, :global_var) +end +let inferred = REPL.REPLCompletions.repl_eval_ex( + :(test_limit_aggressive_inference_getprop()), @__MODULE__; limit_aggressive_inference=true) + @test inferred == Core.Const(1) +end # Test completion of var"" identifiers (#49280) let s = "var\"complicated " @@ -2085,7 +2142,121 @@ for (s, compl) in (("2*CompletionFoo.nam", "named"), @test only(c) == compl end +# allows symbol completion within incomplete :macrocall +# https://github.com/JuliaLang/julia/issues/51827 +macro issue51827(args...) + length(args) ≥ 2 || error("@issue51827: incomplete arguments") + return args +end +let s = "@issue51827 Base.ac" + c, r, res = test_complete_context(s) + @test res + @test "acquire" in c +end + let t = REPLCompletions.repl_eval_ex(:(`a b`), @__MODULE__; limit_aggressive_inference=true) @test t isa Core.Const @test t.val == `a b` end + +# issue #51823 +@test "include" in test_complete_context("inc", Main)[1] + +# REPL completions should not try to concrete-evaluate !:noub methods +function very_unsafe_method(i::Int) + xs = Any[] + @inbounds xs[i] +end +let t = REPLCompletions.repl_eval_ex(:(unsafe_method(42)), @__MODULE__) + @test isnothing(t) +end + +# https://github.com/JuliaLang/julia/issues/52099 +const issue52099 = [] +let t = REPLCompletions.repl_eval_ex(:(Base.PersistentDict(issue52099 => 3)), @__MODULE__) + if t isa Core.Const + @test length(t.val) == 1 + end +end + +# test REPLInterpreter effects for `getindex(::Dict, key)` +for (DictT, KeyT) = Any[(Dict{Symbol,Any}, Symbol), + (Dict{Int,Any}, Int), + (Dict{String,Any}, String)] + @testset let DictT=DictT, KeyT=KeyT + effects = Base.infer_effects(getindex, (DictT,KeyT); interp=REPL.REPLCompletions.REPLInterpreter()) + @test Core.Compiler.is_effect_free(effects) + @test Core.Compiler.is_terminates(effects) + @test Core.Compiler.is_noub(effects) + effects = Base.infer_effects((DictT,KeyT); interp=REPL.REPLCompletions.REPLInterpreter()) do d, key + key in keys(d) + end + @test Core.Compiler.is_effect_free(effects) + @test Core.Compiler.is_terminates(effects) + @test Core.Compiler.is_noub(effects) + end +end + +# test invalidation support +replinterp_invalidation_callee(c::Bool=rand(Bool)) = Some(c ? r"foo" : r"bar") +replinterp_invalidation_caller() = replinterp_invalidation_callee().value +@test REPLCompletions.repl_eval_ex(:(replinterp_invalidation_caller()), @__MODULE__) == Regex +replinterp_invalidation_callee(c::Bool=rand(Bool)) = Some(c ? "foo" : "bar") +@test REPLCompletions.repl_eval_ex(:(replinterp_invalidation_caller()), @__MODULE__) == String + +# JuliaLang/julia#52922 +let s = "using Base.Th" + c, r, res = test_complete_context(s) + @test res + @test "Threads" in c +end +let s = "using Base." + c, r, res = test_complete_context(s) + @test res + @test "BinaryPlatforms" in c +end +# test cases with the `.` accessor +module Issue52922 +module Inner1 +module Inner12 end +end +module Inner2 end +end +let s = "using .Iss" + c, r, res = test_complete_context(s) + @test res + @test "Issue52922" in c +end +let s = "using .Issue52922.Inn" + c, r, res = test_complete_context(s) + @test res + @test "Inner1" in c +end +let s = "using .Issue52922.Inner1." + c, r, res = test_complete_context(s) + @test res + @test "Inner12" in c +end +let s = "using .Inner1.Inn" + c, r, res = test_complete_context(s, Issue52922) + @test res + @test "Inner12" in c +end +let s = "using ..Issue52922.Inn" + c, r, res = test_complete_context(s, Issue52922.Inner1) + @test res + @test "Inner2" in c +end +let s = "using ...Issue52922.Inn" + c, r, res = test_complete_context(s, Issue52922.Inner1.Inner12) + @test res + @test "Inner2" in c +end + +struct Issue53126 end +Base.propertynames(::Issue53126) = error("this should not be called") +let s = "Issue53126()." + c, r, res = test_complete_context(s) + @test res + @test isempty(c) +end diff --git a/stdlib/REPL/test/runtests.jl b/stdlib/REPL/test/runtests.jl index e152677ccf7bb..d3eb6b9964981 100644 --- a/stdlib/REPL/test/runtests.jl +++ b/stdlib/REPL/test/runtests.jl @@ -3,6 +3,10 @@ # Make a copy of the original environment original_env = copy(ENV) +module PrecompilationTests + include("precompilation.jl") +end + module REPLTests include("repl.jl") end diff --git a/stdlib/Random/Project.toml b/stdlib/Random/Project.toml index f86f0a54f8ba4..5a9cc2dfc4cb7 100644 --- a/stdlib/Random/Project.toml +++ b/stdlib/Random/Project.toml @@ -1,5 +1,6 @@ name = "Random" uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +version = "1.11.0" [deps] SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce" diff --git a/stdlib/Random/docs/src/index.md b/stdlib/Random/docs/src/index.md index fbf0b60fc6a24..9ef86bb0d94f8 100644 --- a/stdlib/Random/docs/src/index.md +++ b/stdlib/Random/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/Random/docs/src/index.md" +``` + # Random Numbers ```@meta @@ -90,7 +94,7 @@ There are two mostly orthogonal ways to extend `Random` functionalities: The API for 1) is quite functional, but is relatively recent so it may still have to evolve in subsequent releases of the `Random` module. For example, it's typically sufficient to implement one `rand` method in order to have all other usual methods work automatically. -The API for 2) is still rudimentary, and may require more work than strictly necessary from the implementor, +The API for 2) is still rudimentary, and may require more work than strictly necessary from the implementer, in order to support usual types of generated values. ### Generating random values of custom types @@ -99,7 +103,7 @@ Generating random values for some distributions may involve various trade-offs. The `Random` module defines a customizable framework for obtaining random values that can address these issues. Each invocation of `rand` generates a *sampler* which can be customized with the above trade-offs in mind, by adding methods to `Sampler`, which in turn can dispatch on the random number generator, the object that characterizes the distribution, and a suggestion for the number of repetitions. Currently, for the latter, `Val{1}` (for a single sample) and `Val{Inf}` (for an arbitrary number) are used, with `Random.Repetition` an alias for both. -The object returned by `Sampler` is then used to generate the random values. When implementing the random generation interface for a value `X` that can be sampled from, the implementor should define the method +The object returned by `Sampler` is then used to generate the random values. When implementing the random generation interface for a value `X` that can be sampled from, the implementer should define the method ```julia rand(rng, sampler) @@ -215,7 +219,7 @@ and that we *always* want to build an alias table, regardless of the number of v Random.eltype(::Type{<:DiscreteDistribution}) = Int function Random.Sampler(::Type{<:AbstractRNG}, distribution::DiscreteDistribution, ::Repetition) - SamplerSimple(disribution, make_alias_table(distribution.probabilities)) + SamplerSimple(distribution, make_alias_table(distribution.probabilities)) end ``` should be defined to return a sampler with pre-computed data, then @@ -346,8 +350,8 @@ DocTestSetup = nothing By using an RNG parameter initialized with a given seed, you can reproduce the same pseudorandom number sequence when running your program multiple times. However, a minor release of Julia (e.g. -1.3 to 1.4) *may change* the sequence of pseudorandom numbers generated from a specific seed, in -particular if `MersenneTwister` is used. (Even if the sequence produced by a low-level function like +1.3 to 1.4) *may change* the sequence of pseudorandom numbers generated from a specific seed. +(Even if the sequence produced by a low-level function like [`rand`](@ref) does not change, the output of higher-level functions like [`randsubseq`](@ref) may change due to algorithm updates.) Rationale: guaranteeing that pseudorandom streams never change prohibits many algorithmic improvements. diff --git a/stdlib/Random/src/DSFMT.jl b/stdlib/Random/src/DSFMT.jl index e63b0b502f84e..25155b4e8575d 100644 --- a/stdlib/Random/src/DSFMT.jl +++ b/stdlib/Random/src/DSFMT.jl @@ -195,9 +195,11 @@ function dsfmt_jump(s::DSFMT_state, jp::GF2X) work = zeros(Int32, JN32) rwork = reinterpret(UInt64, work) dsfmt = Vector{UInt64}(undef, nval >> 1) - GC.@preserve dsfmt val begin - pdsfmt = Base.unsafe_convert(Ptr{Cvoid}, dsfmt) - pval = Base.unsafe_convert(Ptr{Cvoid}, val) + dsfmtref = Base.cconvert(Ptr{Cvoid}, dsfmt) + valref = Base.cconvert(Ptr{Cvoid}, val) + GC.@preserve dsfmtref valref begin + pdsfmt = Base.unsafe_convert(Ptr{Cvoid}, dsfmtref) + pval = Base.unsafe_convert(Ptr{Cvoid}, valref) Base.Libc.memcpy(pdsfmt, pval, (nval - 1) * sizeof(Int32)) end dsfmt[end] = UInt64(N*2) diff --git a/stdlib/Random/src/Random.jl b/stdlib/Random/src/Random.jl index 432e32a4de691..9ce0896d0d125 100644 --- a/stdlib/Random/src/Random.jl +++ b/stdlib/Random/src/Random.jl @@ -4,7 +4,7 @@ Random Support for generating random numbers. Provides [`rand`](@ref), [`randn`](@ref), -[`AbstractRNG`](@ref), [`MersenneTwister`](@ref), and [`RandomDevice`](@ref). +[`AbstractRNG`](@ref), [`Xoshiro`](@ref), [`MersenneTwister`](@ref), and [`RandomDevice`](@ref). """ module Random diff --git a/stdlib/Random/src/Xoshiro.jl b/stdlib/Random/src/Xoshiro.jl index b16668e99584b..5569d6d5c1da5 100644 --- a/stdlib/Random/src/Xoshiro.jl +++ b/stdlib/Random/src/Xoshiro.jl @@ -10,7 +10,7 @@ Xoshiro256++ is a fast pseudorandom number generator described by David Blackman and Sebastiano Vigna in "Scrambled Linear Pseudorandom Number Generators", ACM Trans. Math. Softw., 2021. Reference implementation is available -at http://prng.di.unimi.it +at https://prng.di.unimi.it Apart from the high speed, Xoshiro has a small memory footprint, making it suitable for applications where many different random states need to be held for long time. diff --git a/stdlib/Random/src/generation.jl b/stdlib/Random/src/generation.jl index 99017c8c76a82..d8bb48d2764d2 100644 --- a/stdlib/Random/src/generation.jl +++ b/stdlib/Random/src/generation.jl @@ -19,7 +19,7 @@ Sampler(::Type{RNG}, ::Type{T}, n::Repetition) where {RNG<:AbstractRNG,T<:AbstractFloat} = Sampler(RNG, CloseOpen01(T), n) -# generic random generation function which can be used by RNG implementors +# generic random generation function which can be used by RNG implementers # it is not defined as a fallback rand method as this could create ambiguities rand(r::AbstractRNG, ::SamplerTrivial{CloseOpen01{Float16}}) = @@ -130,7 +130,7 @@ rand(r::AbstractRNG, sp::SamplerTrivial{<:UniformBits{T}}) where {T} = #### BitInteger -# rand_generic methods are intended to help RNG implementors with common operations +# rand_generic methods are intended to help RNG implementers with common operations # we don't call them simply `rand` as this can easily contribute to create # ambiguities with user-side methods (forcing the user to resort to @eval) @@ -294,7 +294,7 @@ rem_knuth(a::T, b::T) where {T<:Unsigned} = b != 0 ? a % b : a # maximum multiple of k <= sup decremented by one, # that is 0xFFFF...FFFF if k = (typemax(T) - typemin(T)) + 1 and sup == typemax(T) - 1 # with intentional underflow -# see http://stackoverflow.com/questions/29182036/integer-arithmetic-add-1-to-uint-max-and-divide-by-n-without-overflow +# see https://stackoverflow.com/questions/29182036/integer-arithmetic-add-1-to-uint-max-and-divide-by-n-without-overflow # sup == 0 means typemax(T) + 1 maxmultiple(k::T, sup::T=zero(T)) where {T<:Unsigned} = diff --git a/stdlib/Random/src/misc.jl b/stdlib/Random/src/misc.jl index 2cedb200bc022..908776383d45f 100644 --- a/stdlib/Random/src/misc.jl +++ b/stdlib/Random/src/misc.jl @@ -220,6 +220,21 @@ function shuffle!(r::AbstractRNG, a::AbstractArray) return a end +function shuffle!(r::AbstractRNG, a::AbstractArray{Bool}) + old_count = count(a) + len = length(a) + uncommon_value = 2old_count <= len + fuel = uncommon_value ? old_count : len - old_count + fuel == 0 && return a + a .= !uncommon_value + while fuel > 0 + k = rand(r, eachindex(a)) + fuel -= a[k] != uncommon_value + a[k] = uncommon_value + end + a +end + shuffle!(a::AbstractArray) = shuffle!(default_rng(), a) """ diff --git a/stdlib/Random/src/normal.jl b/stdlib/Random/src/normal.jl index 332af1268aea0..267d9db48fee8 100644 --- a/stdlib/Random/src/normal.jl +++ b/stdlib/Random/src/normal.jl @@ -3,7 +3,7 @@ # Normally distributed random numbers using Ziggurat algorithm # The Ziggurat Method for generating random variables - Marsaglia and Tsang -# Paper and reference code: http://www.jstatsoft.org/v05/i08/ +# Paper and reference code: https://www.jstatsoft.org/v05/i08/ # randmtzig (covers also exponential variates) @@ -14,25 +14,51 @@ Generate a normally-distributed random number of type `T` with mean 0 and standard deviation 1. -Optionally generate an array of normally-distributed random numbers. -The `Base` module currently provides an implementation for the types -[`Float16`](@ref), [`Float32`](@ref), and [`Float64`](@ref) (the default), and their -[`Complex`](@ref) counterparts. When the type argument is complex, the values are drawn -from the circularly symmetric complex normal distribution of variance 1 (corresponding to real and imaginary part having independent normal distribution with mean zero and variance `1/2`). +Given the optional `dims` argument(s), generate an array of size `dims` of such numbers. +Julia's standard library supports `randn` for any floating-point type +that implements [`rand`](@ref), e.g. the `Base` types +[`Float16`](@ref), [`Float32`](@ref), [`Float64`](@ref) (the default), and [`BigFloat`](@ref), +along with their [`Complex`](@ref) counterparts. + +(When `T` is complex, the values are drawn +from the circularly symmetric complex normal distribution of variance 1, corresponding to real and imaginary parts +having independent normal distribution with mean zero and variance `1/2`). See also [`randn!`](@ref) to act in-place. # Examples + +Generating a single random number (with the default `Float64` type): + +```julia-repl +julia> randn() +-0.942481877315864 +``` + +Generating a matrix of normal random numbers (with the default `Float64` type): + +```julia-repl +julia> randn(2,3) +2×3 Matrix{Float64}: + 1.18786 -0.678616 1.49463 + -0.342792 -0.134299 -1.45005 +``` + +Setting up of the random number generator `rng` with a user-defined seed (for reproducible numbers) +and using it to generate a random `Float32` number or a matrix of `ComplexF32` random numbers: + ```jldoctest -julia> using Random; rng = Xoshiro(123); +julia> using Random + +julia> rng = Xoshiro(123); -julia> randn(rng, ComplexF64) --0.45660053706486897 - 1.0346749725929225im +julia> randn(rng, Float32) +-0.6457307f0 julia> randn(rng, ComplexF32, (2, 3)) 2×3 Matrix{ComplexF32}: - -1.14806-0.153912im 0.056538+1.0954im 0.419454-0.543347im - 0.34807+0.693657im -0.948661+0.291442im -0.0538589-0.463085im + -1.03467-1.14806im 0.693657+0.056538im 0.291442+0.419454im + -0.153912+0.34807im 1.0954-0.948661im -0.543347-0.0538589im ``` """ @inline function randn(rng::AbstractRNG=default_rng()) @@ -70,8 +96,8 @@ end @noinline function randn_unlikely(rng, idx, rabs, x) @inbounds if idx == 0 while true - xx = -ziggurat_nor_inv_r*log(rand(rng)) - yy = -log(rand(rng)) + xx = -ziggurat_nor_inv_r*log1p(-rand(rng)) + yy = -log1p(-rand(rng)) yy+yy > xx*xx && return (rabs >> 8) % Bool ? -ziggurat_nor_r-xx : ziggurat_nor_r+xx end @@ -138,7 +164,7 @@ end @noinline function randexp_unlikely(rng, idx, x) @inbounds if idx == 0 - return ziggurat_exp_r - log(rand(rng)) + return ziggurat_exp_r - log1p(-rand(rng)) elseif (fe[idx] - fe[idx+1])*rand(rng) + fe[idx+1] < exp(-x) return x # return from the triangular area else diff --git a/stdlib/Random/test/runtests.jl b/stdlib/Random/test/runtests.jl index 2ad9d38d94845..9b46951f63ff5 100644 --- a/stdlib/Random/test/runtests.jl +++ b/stdlib/Random/test/runtests.jl @@ -16,15 +16,30 @@ using Random: jump_128, jump_192, jump_128!, jump_192! import Future # randjump function test_uniform(xs::AbstractArray{T}) where {T<:AbstractFloat} - if precision(T) >= precision(Float32) # TODO: refine - @test allunique(xs) + # TODO: refine + prec = isempty(xs) ? precision(T) : precision(first(xs)) + proba_nocollision = prod((1.0 - i/2.0^prec for i=1:length(xs)-1), init=1.0) # rough estimate + xsu = Set(xs) + if (1.0 - proba_nocollision) < 2.0^-64 + @test length(xsu) == length(xs) + elseif prec > 52 && length(xs) < 3000 + # if proba of collisions is high enough, allow at most one collision; + # with the constraints on precision and length, more than one collision would happen + # with proba less than 2.0^-62 + @test length(xsu) >= length(xs)-1 end @test all(x -> zero(x) <= x < one(x), xs) end -function test_uniform(xs::AbstractArray{T}) where {T<:Integer} - if !Base.hastypemax(T) || widen(typemax(T)) - widen(typemin(T)) >= 2^30 # TODO: refine - @test allunique(xs) +function test_uniform(xs::AbstractArray{T}) where {T<:Base.BitInteger} + # TODO: refine + prec = 8*sizeof(T) + proba_nocollision = prod((1.0 - i/2.0^prec for i=1:length(xs)-1), init=1.0) + xsu = Set(xs) + if (1.0 - proba_nocollision) < 2.0^-64 + @test length(xsu) == length(xs) + elseif prec > 52 && length(xs) < 3000 + @test length(xsu) >= length(xs)-1 end end @@ -512,6 +527,7 @@ end @test shuffle!(mta,Vector(1:10)) == shuffle!(mtb,Vector(1:10)) @test shuffle(mta,Vector(2:11)) == shuffle(mtb,2:11) @test shuffle!(mta, rand(mta, 2, 3)) == shuffle!(mtb, rand(mtb, 2, 3)) + @test shuffle!(mta, rand(mta, Bool, 2, 3)) == shuffle!(mtb, rand(mtb, Bool, 2, 3)) @test shuffle(mta, rand(mta, 2, 3)) == shuffle(mtb, rand(mtb, 2, 3)) @test randperm(mta,10) == randperm(mtb,10) @@ -1231,3 +1247,7 @@ end @test xs isa Vector{Pair{Bool, Char}} @test length(xs) == 3 end + +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(Random)) +end diff --git a/stdlib/Serialization/Project.toml b/stdlib/Serialization/Project.toml index 4a2f7874e3124..97e898d731c7d 100644 --- a/stdlib/Serialization/Project.toml +++ b/stdlib/Serialization/Project.toml @@ -1,5 +1,6 @@ name = "Serialization" uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" +version = "1.11.0" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/Serialization/docs/src/index.md b/stdlib/Serialization/docs/src/index.md index 9f593a2e807d9..0d00e47ed84ce 100644 --- a/stdlib/Serialization/docs/src/index.md +++ b/stdlib/Serialization/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/Serialization/docs/src/index.md" +``` + # Serialization Provides serialization of Julia objects. diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index fbd02cc1a78dd..3f38708e8bb1e 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -7,7 +7,7 @@ Provide serialization of Julia objects via the functions """ module Serialization -import Base: GMP, Bottom, unsafe_convert, uncompressed_ast +import Base: Bottom, unsafe_convert import Core: svec, SimpleVector using Base: unaliascopy, unwrap_unionall, require_one_based_indexing, ntupleany using Core.IR @@ -80,7 +80,7 @@ const TAGS = Any[ const NTAGS = length(TAGS) @assert NTAGS == 255 -const ser_version = 25 # do not make changes without bumping the version #! +const ser_version = 27 # do not make changes without bumping the version #! format_version(::AbstractSerializer) = ser_version format_version(s::Serializer) = s.version @@ -288,6 +288,31 @@ function serialize(s::AbstractSerializer, a::SubArray{T,N,A}) where {T,N,A<:Arra serialize_any(s, b) end +serialize(s::AbstractSerializer, m::GenericMemory) = error("GenericMemory{:atomic} currently cannot be serialized") +function serialize(s::AbstractSerializer, m::Memory) + serialize_cycle_header(s, m) && return + serialize(s, length(m)) + elty = eltype(m) + if isbitstype(elty) + serialize_array_data(s.io, m) + else + sizehint!(s.table, div(length(m),4)) # prepare for lots of pointers + @inbounds for i in eachindex(m) + if isassigned(m, i) + serialize(s, m[i]) + else + writetag(s.io, UNDEFREF_TAG) + end + end + end +end + +function serialize(s::AbstractSerializer, x::GenericMemoryRef) + serialize_type(s, typeof(x)) + serialize(s, getfield(x, :mem)) + serialize(s, Base.memoryrefoffset(x)) +end + function serialize(s::AbstractSerializer, ss::String) len = sizeof(ss) if len > 7 @@ -422,7 +447,7 @@ function serialize(s::AbstractSerializer, meth::Method) serialize(s, meth.constprop) serialize(s, meth.purity) if isdefined(meth, :source) - serialize(s, Base._uncompressed_ast(meth, meth.source)) + serialize(s, Base._uncompressed_ast(meth)) else serialize(s, nothing) end @@ -511,7 +536,7 @@ function serialize_typename(s::AbstractSerializer, t::Core.TypeName) serialize(s, primary.super) serialize(s, primary.parameters) serialize(s, primary.types) - serialize(s, isdefined(primary, :instance)) + serialize(s, Base.issingletontype(primary)) serialize(s, t.flags & 0x1 == 0x1) # .abstract serialize(s, t.flags & 0x2 == 0x2) # .mutable serialize(s, Int32(length(primary.types) - t.n_uninitialized)) @@ -654,6 +679,11 @@ end serialize(s::AbstractSerializer, @nospecialize(x)) = serialize_any(s, x) +function serialize(s::AbstractSerializer, x::Core.AddrSpace) + serialize_type(s, typeof(x)) + write(s.io, Core.bitcast(UInt8, x)) +end + function serialize_any(s::AbstractSerializer, @nospecialize(x)) tag = sertag(x) if tag > 0 @@ -1028,7 +1058,8 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) isva = deserialize(s)::Bool is_for_opaque_closure = false nospecializeinfer = false - constprop = purity = 0x00 + constprop = 0x00 + purity = 0x0000 template_or_is_opaque = deserialize(s) if isa(template_or_is_opaque, Bool) is_for_opaque_closure = template_or_is_opaque @@ -1038,8 +1069,10 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) if format_version(s) >= 14 constprop = deserialize(s)::UInt8 end - if format_version(s) >= 17 - purity = deserialize(s)::UInt8 + if format_version(s) >= 26 + purity = deserialize(s)::UInt16 + elseif format_version(s) >= 17 + purity = UInt16(deserialize(s)::UInt8) end template = deserialize(s) else @@ -1052,6 +1085,7 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) end if makenew meth.module = mod + meth.debuginfo = NullDebugInfo meth.name = name meth.file = file meth.line = line @@ -1064,7 +1098,9 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) meth.purity = purity if template !== nothing # TODO: compress template - meth.source = template::CodeInfo + template = template::CodeInfo + meth.source = template + meth.debuginfo = template.debuginfo if !@isdefined(slot_syms) slot_syms = ccall(:jl_compress_argnames, Ref{String}, (Any,), meth.source.slotnames) end @@ -1078,7 +1114,7 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) end if !is_for_opaque_closure mt = ccall(:jl_method_table_for, Any, (Any,), sig) - if mt !== nothing && nothing === ccall(:jl_methtable_lookup, Any, (Any, Any, UInt), mt, sig, typemax(UInt)) + if mt !== nothing && nothing === ccall(:jl_methtable_lookup, Any, (Any, Any, UInt), mt, sig, Base.get_world_counter()) ccall(:jl_method_table_insert, Cvoid, (Any, Any, Ptr{Cvoid}), mt, meth, C_NULL) end end @@ -1118,6 +1154,7 @@ function deserialize(s::AbstractSerializer, ::Type{Core.LineInfoNode}) return Core.LineInfoNode(mod, method, deserialize(s)::Symbol, Int32(deserialize(s)::Union{Int32, Int}), Int32(deserialize(s)::Union{Int32, Int})) end + function deserialize(s::AbstractSerializer, ::Type{PhiNode}) edges = deserialize(s) if edges isa Vector{Any} @@ -1132,6 +1169,7 @@ function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) deserialize_cycle(s, ci) code = deserialize(s)::Vector{Any} ci.code = code + ci.debuginfo = NullDebugInfo # allow older-style IR with return and gotoifnot Exprs for i in 1:length(code) stmt = code[i] @@ -1144,17 +1182,27 @@ function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) end end end - ci.codelocs = deserialize(s)::Vector{Int32} + _x = deserialize(s) + have_debuginfo = _x isa Core.DebugInfo + if have_debuginfo + ci.debuginfo = _x + else + codelocs = _x::Vector{Int32} + # TODO: convert codelocs to debuginfo format? + end _x = deserialize(s) if _x isa Array || _x isa Int pre_12 = false - ci.ssavaluetypes = _x else pre_12 = true # < v1.2 ci.method_for_inference_limit_heuristics = _x - ci.ssavaluetypes = deserialize(s) - ci.linetable = deserialize(s) + _x = deserialize(s) + end + ci.ssavaluetypes = _x + if pre_12 + linetable = deserialize(s) + # TODO: convert linetable to debuginfo format? end ssaflags = deserialize(s) if length(ssaflags) ≠ length(code) @@ -1168,26 +1216,42 @@ function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) if pre_12 ci.slotflags = deserialize(s) else - ci.method_for_inference_limit_heuristics = deserialize(s) - ci.linetable = deserialize(s) + if format_version(s) <= 26 + ci.method_for_inference_limit_heuristics = deserialize(s) + end + if !have_debuginfo # pre v1.11 format + linetable = deserialize(s) + # TODO: convert linetable to debuginfo format? + end end ci.slotnames = deserialize(s) if !pre_12 ci.slotflags = deserialize(s) ci.slottypes = deserialize(s) - ci.rettype = deserialize(s) - ci.parent = deserialize(s) - world_or_edges = deserialize(s) - pre_13 = isa(world_or_edges, Integer) - if pre_13 - ci.min_world = world_or_edges + if format_version(s) <= 26 + deserialize(s) # rettype + ci.parent = deserialize(s) + world_or_edges = deserialize(s) + pre_13 = isa(world_or_edges, Union{UInt, Int}) + if pre_13 + ci.min_world = reinterpret(UInt, world_or_edges) + ci.max_world = reinterpret(UInt, deserialize(s)) + else + ci.edges = world_or_edges + ci.min_world = deserialize(s)::UInt + ci.max_world = deserialize(s)::UInt + end else - ci.edges = world_or_edges - ci.min_world = reinterpret(UInt, deserialize(s)) - ci.max_world = reinterpret(UInt, deserialize(s)) + ci.parent = deserialize(s) + ci.method_for_inference_limit_heuristics = deserialize(s) + ci.edges = deserialize(s) + ci.min_world = deserialize(s)::UInt + ci.max_world = deserialize(s)::UInt end end - ci.inferred = deserialize(s) + if format_version(s) <= 26 + deserialize(s)::Bool # inferred + end if format_version(s) < 22 inlining_cost = deserialize(s) if isa(inlining_cost, Bool) @@ -1212,15 +1276,20 @@ function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) if format_version(s) >= 14 ci.constprop = deserialize(s)::UInt8 end - if format_version(s) >= 17 + if format_version(s) >= 26 + ci.purity = deserialize(s)::UInt16 + elseif format_version(s) >= 17 ci.purity = deserialize(s)::UInt8 end if format_version(s) >= 22 ci.inlining_cost = deserialize(s)::UInt16 end + ci.debuginfo = NullDebugInfo return ci end +const NullDebugInfo = DebugInfo(:none) + if Int === Int64 const OtherInt = Int32 else @@ -1276,7 +1345,7 @@ function deserialize_array(s::AbstractSerializer) return A end -function deserialize_fillarray!(A::Array{T}, s::AbstractSerializer) where {T} +function deserialize_fillarray!(A::Union{Array{T},Memory{T}}, s::AbstractSerializer) where {T} for i = eachindex(A) tag = Int32(read(s.io, UInt8)::UInt8) if tag != UNDEFREF_TAG @@ -1286,6 +1355,48 @@ function deserialize_fillarray!(A::Array{T}, s::AbstractSerializer) where {T} return A end +function deserialize(s::AbstractSerializer, X::Type{Memory{T}} where T) + slot = pop!(s.pending_refs) # e.g. deserialize_cycle + n = deserialize(s)::Int + elty = eltype(X) + if isbitstype(elty) + A = X(undef, n) + if X === Memory{Bool} + i = 1 + while i <= n + b = read(s.io, UInt8)::UInt8 + v = (b >> 7) != 0 + count = b & 0x7f + nxt = i + count + while i < nxt + A[i] = v + i += 1 + end + end + else + A = read!(s.io, A)::X + end + s.table[slot] = A + return A + end + A = X(undef, n) + s.table[slot] = A + sizehint!(s.table, s.counter + div(n, 4)) + deserialize_fillarray!(A, s) + return A +end + +function deserialize(s::AbstractSerializer, X::Type{MemoryRef{T}} where T) + x = Core.memoryref(deserialize(s))::X + i = deserialize(s)::Int + i == 2 || (x = Core.memoryref(x, i, true)) + return x::X +end + +function deserialize(s::AbstractSerializer, X::Type{Core.AddrSpace{M}} where M) + Core.bitcast(X, read(s.io, UInt8)) +end + function deserialize_expr(s::AbstractSerializer, len) e = Expr(:temp) resolve_ref_immediately(s, e) @@ -1341,7 +1452,7 @@ function deserialize_typename(s::AbstractSerializer, number) tn.max_methods = maxm if has_instance ty = ty::DataType - if !Base.issingletontype(ty) + if !isdefined(ty, :instance) singleton = ccall(:jl_new_struct, Any, (Any, Any...), ty) # use setfield! directly to avoid `fieldtype` lowering expecting to see a Singleton object already on ty ccall(:jl_set_nth_field, Cvoid, (Any, Csize_t, Any), ty, Base.fieldindex(DataType, :instance)-1, singleton) @@ -1371,16 +1482,9 @@ function deserialize_typename(s::AbstractSerializer, number) tag = Int32(read(s.io, UInt8)::UInt8) if tag != UNDEFREF_TAG kws = handle_deserialize(s, tag) - if makenew - if kws isa Vector{Method} - for def in kws - kwmt = typeof(Core.kwcall).name.mt - ccall(:jl_method_table_insert, Cvoid, (Any, Any, Ptr{Cvoid}), mt, def, C_NULL) - end - else - # old object format -- try to forward from old to new - @eval Core.kwcall(kwargs::NamedTuple, f::$ty, args...) = $kws(kwargs, f, args...) - end + if makenew && !(kws isa Vector{Method}) + # old object format -- try to forward from old to new + @eval Core.kwcall(kwargs::NamedTuple, f::$ty, args...) = $kws(kwargs, f, args...) end end elseif makenew diff --git a/stdlib/Serialization/test/runtests.jl b/stdlib/Serialization/test/runtests.jl index 46749d4375538..a7d5023e1ec51 100644 --- a/stdlib/Serialization/test/runtests.jl +++ b/stdlib/Serialization/test/runtests.jl @@ -655,3 +655,9 @@ end @test l2 == l1 @test l2.parts === () end + +@testset "Docstrings" begin + undoc = Docs.undocumented_names(Serialization) + @test_broken isempty(undoc) + @test undoc == [:AbstractSerializer, :Serializer] +end diff --git a/stdlib/SharedArrays/Project.toml b/stdlib/SharedArrays/Project.toml index 588785347c73d..46e5332f8d89d 100644 --- a/stdlib/SharedArrays/Project.toml +++ b/stdlib/SharedArrays/Project.toml @@ -1,5 +1,6 @@ name = "SharedArrays" uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" +version = "1.11.0" [deps] Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" diff --git a/stdlib/SharedArrays/docs/src/index.md b/stdlib/SharedArrays/docs/src/index.md index 67ceabf42115a..91ef63bf18aed 100644 --- a/stdlib/SharedArrays/docs/src/index.md +++ b/stdlib/SharedArrays/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/SharedArrays/docs/src/index.md" +``` + # Shared Arrays `SharedArray` represents an array, which is shared across multiple processes, on a single machine. diff --git a/stdlib/SharedArrays/src/SharedArrays.jl b/stdlib/SharedArrays/src/SharedArrays.jl index f9f701c61fcea..93ce396277af7 100644 --- a/stdlib/SharedArrays/src/SharedArrays.jl +++ b/stdlib/SharedArrays/src/SharedArrays.jl @@ -8,7 +8,7 @@ module SharedArrays using Mmap, Distributed, Random import Base: length, size, elsize, ndims, IndexStyle, reshape, convert, deepcopy_internal, - show, getindex, setindex!, fill!, similar, reduce, map!, copyto!, unsafe_convert + show, getindex, setindex!, fill!, similar, reduce, map!, copyto!, cconvert import Random using Serialization using Serialization: serialize_cycle_header, serialize_type, writetag, UNDEFREF_TAG, serialize, deserialize @@ -358,8 +358,8 @@ for each worker process. """ localindices(S::SharedArray) = S.pidx > 0 ? range_1dim(S, S.pidx) : 1:0 -unsafe_convert(::Type{Ptr{T}}, S::SharedArray{T}) where {T} = unsafe_convert(Ptr{T}, sdata(S)) -unsafe_convert(::Type{Ptr{T}}, S::SharedArray ) where {T} = unsafe_convert(Ptr{T}, sdata(S)) +cconvert(::Type{Ptr{T}}, S::SharedArray{T}) where {T} = cconvert(Ptr{T}, sdata(S)) +cconvert(::Type{Ptr{T}}, S::SharedArray ) where {T} = cconvert(Ptr{T}, sdata(S)) function SharedArray(A::Array) S = SharedArray{eltype(A),ndims(A)}(size(A)) diff --git a/stdlib/SharedArrays/test/runtests.jl b/stdlib/SharedArrays/test/runtests.jl index 7f1bbb6891ce0..84dffafb3d92a 100644 --- a/stdlib/SharedArrays/test/runtests.jl +++ b/stdlib/SharedArrays/test/runtests.jl @@ -324,3 +324,7 @@ end @test SharedMatrix([0.1 0.2; 0.3 0.4]) == [0.1 0.2; 0.3 0.4] @test_throws MethodError SharedVector(rand(4,4)) @test_throws MethodError SharedMatrix(rand(4)) + +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(SharedArrays)) +end diff --git a/stdlib/Sockets/Project.toml b/stdlib/Sockets/Project.toml index 5afb89b29f126..6a395465722f2 100644 --- a/stdlib/Sockets/Project.toml +++ b/stdlib/Sockets/Project.toml @@ -1,5 +1,6 @@ name = "Sockets" uuid = "6462fe0b-24de-5631-8697-dd941f90decc" +version = "1.11.0" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/Sockets/docs/src/index.md b/stdlib/Sockets/docs/src/index.md index c294461151d7d..feb1744179261 100644 --- a/stdlib/Sockets/docs/src/index.md +++ b/stdlib/Sockets/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/Sockets/docs/src/index.md" +``` + # Sockets ```@docs diff --git a/stdlib/Sockets/src/IPAddr.jl b/stdlib/Sockets/src/IPAddr.jl index 04710e400fe87..d3834a8b8bf73 100644 --- a/stdlib/Sockets/src/IPAddr.jl +++ b/stdlib/Sockets/src/IPAddr.jl @@ -31,7 +31,7 @@ end """ IPv4(host::Integer) -> IPv4 -Return an IPv4 object from ip address `host` formatted as an [`Integer`](@ref). +Return an IPv4 object from IP address `host` formatted as an [`Integer`](@ref). # Examples ```jldoctest @@ -49,7 +49,17 @@ function IPv4(host::Integer) end end -# constructor: ("1.2.3.4") +""" + IPv4(str::AbstractString) -> IPv4 + +Parse an IPv4 address string into an `IPv4` object. + +# Examples +```jldoctest +julia> IPv4("127.0.0.1") +ip"127.0.0.1" +``` +""" IPv4(str::AbstractString) = parse(IPv4, str) show(io::IO,ip::IPv4) = print(io,"ip\"",ip,"\"") @@ -84,7 +94,7 @@ end """ IPv6(host::Integer) -> IPv6 -Return an IPv6 object from ip address `host` formatted as an [`Integer`](@ref). +Return an IPv6 object from IP address `host` formatted as an [`Integer`](@ref). # Examples ```jldoctest @@ -104,6 +114,17 @@ function IPv6(host::Integer) end end +""" + IPv6(str::AbstractString) -> IPv6 + +Parse an IPv6 address string into an `IPv6` object. + +# Examples +```jldoctest +julia> IPv6("::1") +ip"::1" +``` +""" IPv6(str::AbstractString) = parse(IPv6, str) # Suppress leading '0's and "0x" @@ -119,7 +140,7 @@ end show(io::IO, ip::IPv6) = print(io,"ip\"",ip,"\"") # RFC 5952 compliant show function -# http://tools.ietf.org/html/rfc5952 +# https://tools.ietf.org/html/rfc5952 function print(io::IO,ip::IPv6) i = 8 m = 0 diff --git a/stdlib/Sockets/src/Sockets.jl b/stdlib/Sockets/src/Sockets.jl index 2f6b39aa38184..5baf8826cc883 100644 --- a/stdlib/Sockets/src/Sockets.jl +++ b/stdlib/Sockets/src/Sockets.jl @@ -31,7 +31,7 @@ export IPv4, IPv6 -import Base: isless, show, print, parse, bind, convert, isreadable, iswritable, alloc_buf_hook, _uv_hook_close +import Base: isless, show, print, parse, bind, alloc_buf_hook, _uv_hook_close using Base: LibuvStream, LibuvServer, PipeEndpoint, @handle_as, uv_error, associate_julia_struct, uvfinalize, notify_error, uv_req_data, uv_req_set_data, preserve_handle, unpreserve_handle, _UVError, IOError, @@ -567,7 +567,11 @@ end """ nagle(socket::Union{TCPServer, TCPSocket}, enable::Bool) -Enables or disables Nagle's algorithm on a given TCP server or socket. +Nagle's algorithm batches multiple small TCP packets into larger +ones. This can improve throughput but worsen latency. Nagle's algorithm +is enabled by default. This function sets whether Nagle's algorithm is +active on a given TCP server or socket. The opposite option is called +`TCP_NODELAY` in other languages. !!! compat "Julia 1.3" This function requires Julia 1.3 or later. diff --git a/stdlib/Sockets/src/addrinfo.jl b/stdlib/Sockets/src/addrinfo.jl index ac4aef8737d1b..4ee9e07a58430 100644 --- a/stdlib/Sockets/src/addrinfo.jl +++ b/stdlib/Sockets/src/addrinfo.jl @@ -55,7 +55,7 @@ end Gets all of the IP addresses of the `host`. Uses the operating system's underlying `getaddrinfo` implementation, which may do a DNS lookup. -# Example +# Examples ```julia-repl julia> getalladdrinfo("google.com") 2-element Array{IPAddr,1}: @@ -122,10 +122,20 @@ end getalladdrinfo(host::AbstractString) = getalladdrinfo(String(host)) """ - getaddrinfo(host::AbstractString, IPAddr=IPv4) -> IPAddr + getaddrinfo(host::AbstractString, IPAddr) -> IPAddr Gets the first IP address of the `host` of the specified `IPAddr` type. -Uses the operating system's underlying getaddrinfo implementation, which may do a DNS lookup. +Uses the operating system's underlying getaddrinfo implementation, which may do +a DNS lookup. + +# Examples +```julia-repl +julia> getaddrinfo("localhost", IPv6) +ip"::1" + +julia> getaddrinfo("localhost", IPv4) +ip"127.0.0.1" +``` """ function getaddrinfo(host::String, T::Type{<:IPAddr}) addrs = getalladdrinfo(host) @@ -137,6 +147,14 @@ function getaddrinfo(host::String, T::Type{<:IPAddr}) throw(DNSError(host, UV_EAI_NONAME)) end getaddrinfo(host::AbstractString, T::Type{<:IPAddr}) = getaddrinfo(String(host), T) + +""" + getaddrinfo(host::AbstractString) -> IPAddr + +Gets the first available IP address of `host`, which may be either an `IPv4` or +`IPv6` address. Uses the operating system's underlying getaddrinfo +implementation, which may do a DNS lookup. +""" function getaddrinfo(host::AbstractString) addrs = getalladdrinfo(String(host)) if !isempty(addrs) @@ -344,7 +362,7 @@ are not guaranteed to be unique beyond their network segment, therefore routers do not forward them. Link-local addresses are from the address blocks `169.254.0.0/16` or `fe80::/10`. -# Example +# Examples ```julia filter(!islinklocaladdr, getipaddrs()) ``` diff --git a/stdlib/Sockets/test/runtests.jl b/stdlib/Sockets/test/runtests.jl index 02a994460afbf..2a812388d8373 100644 --- a/stdlib/Sockets/test/runtests.jl +++ b/stdlib/Sockets/test/runtests.jl @@ -682,3 +682,7 @@ end close(sockets_watchdog_timer) + +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(Sockets)) +end diff --git a/stdlib/SparseArrays.version b/stdlib/SparseArrays.version index 96024ba41da82..5eca7be5ec9b7 100644 --- a/stdlib/SparseArrays.version +++ b/stdlib/SparseArrays.version @@ -1,4 +1,4 @@ SPARSEARRAYS_BRANCH = main -SPARSEARRAYS_SHA1 = 4e6776a825f2a26c3c580f9b77ba230a6598d7dd +SPARSEARRAYS_SHA1 = cb602d7b7cf46057ddc87d23cda2bdd168a548ac SPARSEARRAYS_GIT_URL := https://github.com/JuliaSparse/SparseArrays.jl.git SPARSEARRAYS_TAR_URL = https://api.github.com/repos/JuliaSparse/SparseArrays.jl/tarball/$1 diff --git a/stdlib/Statistics.version b/stdlib/Statistics.version index 6028e12fe19f4..1449dcee29b79 100644 --- a/stdlib/Statistics.version +++ b/stdlib/Statistics.version @@ -1,4 +1,4 @@ STATISTICS_BRANCH = master -STATISTICS_SHA1 = 04e5d8916fae616f5ad328cf6a0b94cf883b8ba6 +STATISTICS_SHA1 = 68869af06e8cdeb7aba1d5259de602da7328057f STATISTICS_GIT_URL := https://github.com/JuliaStats/Statistics.jl.git STATISTICS_TAR_URL = https://api.github.com/repos/JuliaStats/Statistics.jl/tarball/$1 diff --git a/stdlib/StyledStrings.version b/stdlib/StyledStrings.version new file mode 100644 index 0000000000000..8e489a5daf289 --- /dev/null +++ b/stdlib/StyledStrings.version @@ -0,0 +1,4 @@ +STYLEDSTRINGS_BRANCH = main +STYLEDSTRINGS_SHA1 = bfdb4c3f73a93a956ad48b0f06f89eb1cd40ff6b +STYLEDSTRINGS_GIT_URL := https://github.com/JuliaLang/StyledStrings.jl.git +STYLEDSTRINGS_TAR_URL = https://api.github.com/repos/JuliaLang/StyledStrings.jl/tarball/$1 diff --git a/stdlib/SuiteSparse_jll/Project.toml b/stdlib/SuiteSparse_jll/Project.toml index 6c23952e5c6b6..ee284bc3220f7 100644 --- a/stdlib/SuiteSparse_jll/Project.toml +++ b/stdlib/SuiteSparse_jll/Project.toml @@ -1,15 +1,14 @@ name = "SuiteSparse_jll" uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" -version = "7.2.0+1" +version = "7.6.1+0" [deps] libblastrampoline_jll = "8e850b90-86db-534c-a0d3-1478176c7d93" -Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" [compat] -julia = "1.9" +julia = "1.11" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/SuiteSparse_jll/src/SuiteSparse_jll.jl b/stdlib/SuiteSparse_jll/src/SuiteSparse_jll.jl index f245759aaf383..e5edfa76997e1 100644 --- a/stdlib/SuiteSparse_jll/src/SuiteSparse_jll.jl +++ b/stdlib/SuiteSparse_jll/src/SuiteSparse_jll.jl @@ -3,7 +3,6 @@ ## dummy stub for https://github.com/JuliaBinaryWrappers/SuiteSparse_jll.jl baremodule SuiteSparse_jll using Base, Libdl, libblastrampoline_jll -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] @@ -58,7 +57,7 @@ elseif Sys.isapple() const libbtf = "@rpath/libbtf.2.dylib" const libcamd = "@rpath/libcamd.3.dylib" const libccolamd = "@rpath/libccolamd.3.dylib" - const libcholmod = "@rpath/libcholmod.4.dylib" + const libcholmod = "@rpath/libcholmod.5.dylib" const libcolamd = "@rpath/libcolamd.3.dylib" const libklu = "@rpath/libklu.2.dylib" const libldl = "@rpath/libldl.3.dylib" @@ -71,7 +70,7 @@ else const libbtf = "libbtf.so.2" const libcamd = "libcamd.so.3" const libccolamd = "libccolamd.so.3" - const libcholmod = "libcholmod.so.4" + const libcholmod = "libcholmod.so.5" const libcolamd = "libcolamd.so.3" const libklu = "libklu.so.2" const libldl = "libldl.so.3" diff --git a/stdlib/SuiteSparse_jll/test/runtests.jl b/stdlib/SuiteSparse_jll/test/runtests.jl index d6d82a73e4a57..922da55fa1881 100644 --- a/stdlib/SuiteSparse_jll/test/runtests.jl +++ b/stdlib/SuiteSparse_jll/test/runtests.jl @@ -5,7 +5,7 @@ using Test, SuiteSparse_jll # SuiteSparse only uses SUITESPARSE_MAIN_VERSION and SUITESPARSE_SUB_VERSION to compute its version # The SUITESPARSE_SUBSUB_VERSION is not used # TODO before release: update to 7020 or above when upstreamed. -# This should be safe and unecessary since we specify exact version of the BB JLL. +# This should be safe and unnecessary since we specify exact version of the BB JLL. @testset "SuiteSparse_jll" begin @test ccall((:SuiteSparse_version, libsuitesparseconfig), Cint, (Ptr{Cint},), C_NULL) > 7000 end diff --git a/stdlib/TOML/docs/src/index.md b/stdlib/TOML/docs/src/index.md index 380a4d4ee820d..c6fe514ea39f4 100644 --- a/stdlib/TOML/docs/src/index.md +++ b/stdlib/TOML/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/TOML/docs/src/index.md" +``` + # TOML TOML.jl is a Julia standard library for parsing and writing [TOML diff --git a/stdlib/TOML/src/TOML.jl b/stdlib/TOML/src/TOML.jl index a2ea1869b4079..858b75a2e0eff 100644 --- a/stdlib/TOML/src/TOML.jl +++ b/stdlib/TOML/src/TOML.jl @@ -1,5 +1,10 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +""" +TOML.jl is a Julia standard library for parsing and writing TOML v1.0 files. +This module provides functions to parse TOML strings and files into Julia data structures +and to serialize Julia data structures to TOML format. +""" module TOML module Internals @@ -105,10 +110,11 @@ const ParserError = Internals.ParserError """ - print([to_toml::Function], io::IO [=stdout], data::AbstractDict; sorted=false, by=identity) + print([to_toml::Function], io::IO [=stdout], data::AbstractDict; sorted=false, by=identity, inline_tables::IdSet{<:AbstractDict}) Write `data` as TOML syntax to the stream `io`. If the keyword argument `sorted` is set to `true`, -sort tables according to the function given by the keyword argument `by`. +sort tables according to the function given by the keyword argument `by`. If the keyword argument +`inline_tables` is given, it should be a set of tables that should be printed "inline". The following data types are supported: `AbstractDict`, `AbstractVector`, `AbstractString`, `Integer`, `AbstractFloat`, `Bool`, `Dates.DateTime`, `Dates.Time`, `Dates.Date`. Note that the integers and floats diff --git a/stdlib/TOML/src/print.jl b/stdlib/TOML/src/print.jl index 1fa9f97405504..91ea4fd392e4b 100644 --- a/stdlib/TOML/src/print.jl +++ b/stdlib/TOML/src/print.jl @@ -74,21 +74,21 @@ end ########## # Fallback -function printvalue(f::MbyFunc, io::IO, value) +function printvalue(f::MbyFunc, io::IO, value, sorted::Bool) toml_value = to_toml_value(f, value) @invokelatest printvalue(f, io, toml_value) end -function printvalue(f::MbyFunc, io::IO, value::AbstractVector) +function printvalue(f::MbyFunc, io::IO, value::AbstractVector, sorted::Bool) Base.print(io, "[") for (i, x) in enumerate(value) i != 1 && Base.print(io, ", ") - printvalue(f, io, x) + printvalue(f, io, x, sorted) end Base.print(io, "]") end -function printvalue(f::MbyFunc, io::IO, value::TOMLValue) +function printvalue(f::MbyFunc, io::IO, value::TOMLValue, sorted::Bool) value isa Dates.DateTime ? Base.print(io, Dates.format(value, Dates.dateformat"YYYY-mm-dd\THH:MM:SS.sss\Z")) : value isa Dates.Time ? Base.print(io, Dates.format(value, Dates.dateformat"HH:MM:SS.sss")) : value isa Dates.Date ? Base.print(io, Dates.format(value, Dates.dateformat"YYYY-mm-dd")) : @@ -100,7 +100,7 @@ function printvalue(f::MbyFunc, io::IO, value::TOMLValue) value isa AbstractString ? (Base.print(io, "\""); print_toml_escaped(io, value); Base.print(io, "\"")) : - value isa AbstractDict ? print_inline_table(f, io, value) : + value isa AbstractDict ? print_inline_table(f, io, value, sorted) : error("internal error in TOML printing, unhandled value") end @@ -112,13 +112,18 @@ function print_integer(io::IO, value::Integer) return end -function print_inline_table(f::MbyFunc, io::IO, value::AbstractDict) +function print_inline_table(f::MbyFunc, io::IO, value::AbstractDict, sorted::Bool) + vkeys = collect(keys(value)) + if sorted + sort!(vkeys) + end Base.print(io, "{") - for (i, (k,v)) in enumerate(value) + for (i, k) in enumerate(vkeys) + v = value[k] i != 1 && Base.print(io, ", ") printkey(io, [String(k)]) Base.print(io, " = ") - printvalue(f, io, v) + printvalue(f, io, v, sorted) end Base.print(io, "}") end @@ -141,11 +146,18 @@ function print_table(f::MbyFunc, io::IO, a::AbstractDict, indent::Int = 0, first_block::Bool = true, sorted::Bool = false, + inline_tables::IdSet, by::Function = identity, ) + + if a in inline_tables + @invokelatest print_inline_table(f, io, a) + return + end + akeys = keys(a) if sorted - akeys = sort!(collect(akeys); by=by) + akeys = sort!(collect(akeys); by) end # First print non-tabular entries @@ -154,12 +166,14 @@ function print_table(f::MbyFunc, io::IO, a::AbstractDict, if !isa(value, TOMLValue) value = to_toml_value(f, value) end - is_tabular(value) && continue + if is_tabular(value) && !(value in inline_tables) + continue + end Base.print(io, ' '^4max(0,indent-1)) printkey(io, [String(key)]) Base.print(io, " = ") # print separator - printvalue(f, io, value) + printvalue(f, io, value, sorted) Base.print(io, "\n") # new line? first_block = false end @@ -169,10 +183,10 @@ function print_table(f::MbyFunc, io::IO, a::AbstractDict, if !isa(value, TOMLValue) value = to_toml_value(f, value) end - if is_table(value) + if is_table(value) && !(value in inline_tables) push!(ks, String(key)) _values = @invokelatest values(value) - header = isempty(value) || !all(is_tabular(v) for v in _values)::Bool + header = isempty(value) || !all(is_tabular(v) for v in _values)::Bool || any(v in inline_tables for v in _values)::Bool if header # print table first_block || println(io) @@ -183,7 +197,7 @@ function print_table(f::MbyFunc, io::IO, a::AbstractDict, Base.print(io,"]\n") end # Use runtime dispatch here since the type of value seems not to be enforced other than as AbstractDict - @invokelatest print_table(f, io, value, ks; indent = indent + header, first_block = header, sorted=sorted, by=by) + @invokelatest print_table(f, io, value, ks; indent = indent + header, first_block = header, sorted, by, inline_tables) pop!(ks) elseif @invokelatest(is_array_of_tables(value)) # print array of tables @@ -197,7 +211,7 @@ function print_table(f::MbyFunc, io::IO, a::AbstractDict, Base.print(io,"]]\n") # TODO, nicer error here !isa(v, AbstractDict) && error("array should contain only tables") - @invokelatest print_table(f, io, v, ks; indent = indent + 1, sorted=sorted, by=by) + @invokelatest print_table(f, io, v, ks; indent = indent + 1, sorted, by, inline_tables) end pop!(ks) end @@ -209,7 +223,7 @@ end # API # ####### -print(f::MbyFunc, io::IO, a::AbstractDict; sorted::Bool=false, by=identity) = print_table(f, io, a; sorted=sorted, by=by) -print(f::MbyFunc, a::AbstractDict; sorted::Bool=false, by=identity) = print(f, stdout, a; sorted=sorted, by=by) -print(io::IO, a::AbstractDict; sorted::Bool=false, by=identity) = print_table(nothing, io, a; sorted=sorted, by=by) -print(a::AbstractDict; sorted::Bool=false, by=identity) = print(nothing, stdout, a; sorted=sorted, by=by) +print(f::MbyFunc, io::IO, a::AbstractDict; sorted::Bool=false, by=identity, inline_tables::IdSet{<:AbstractDict}=IdSet{Dict{String}}()) = print_table(f, io, a; sorted, by, inline_tables) +print(f::MbyFunc, a::AbstractDict; sorted::Bool=false, by=identity, inline_tables::IdSet{<:AbstractDict}=IdSet{Dict{String}}()) = print(f, stdout, a; sorted, by, inline_tables) +print(io::IO, a::AbstractDict; sorted::Bool=false, by=identity, inline_tables::IdSet{<:AbstractDict}=IdSet{Dict{String}}()) = print_table(nothing, io, a; sorted, by, inline_tables) +print( a::AbstractDict; sorted::Bool=false, by=identity, inline_tables::IdSet{<:AbstractDict}=IdSet{Dict{String}}()) = print(nothing, stdout, a; sorted, by, inline_tables) diff --git a/stdlib/TOML/test/print.jl b/stdlib/TOML/test/print.jl index 765b6feb491a5..79a87b9f0f13f 100644 --- a/stdlib/TOML/test/print.jl +++ b/stdlib/TOML/test/print.jl @@ -140,3 +140,58 @@ d = "hello" a = 2 b = 9.9 """ + + +inline_dict = Dict("a" => [1,2], "b" => Dict("a" => "b"), "c" => "foo") +d = Dict( + "x" => "y", + "y" => inline_dict, + "z" => [1,2,3], +) +inline_tables = IdSet{Dict}() +push!(inline_tables, inline_dict) +@test toml_str(d; sorted=true, inline_tables) == +""" +x = "y" +y = {a = [1, 2], b = {a = "b"}, c = "foo"} +z = [1, 2, 3] +""" + + +d = Dict("deps" => Dict( + "LocalPkg" => "fcf55292-0d03-4e8a-9e0b-701580031fc3", + "Example" => "7876af07-990d-54b4-ab0e-23690620f79a"), + "sources" => Dict( + "LocalPkg" => Dict("path" => "LocalPkg"), + "Example" => Dict("url" => "https://github.com/JuliaLang/Example.jl"))) + +inline_tables = IdSet{Dict}() +push!(inline_tables, d["sources"]["LocalPkg"]) +push!(inline_tables, d["sources"]["Example"]) + +@test toml_str(d; sorted=true, inline_tables) == +""" +[deps] +Example = "7876af07-990d-54b4-ab0e-23690620f79a" +LocalPkg = "fcf55292-0d03-4e8a-9e0b-701580031fc3" + +[sources] +Example = {url = "https://github.com/JuliaLang/Example.jl"} +LocalPkg = {path = "LocalPkg"} +""" + +inline_tables = IdSet{Dict}() +push!(inline_tables, d["sources"]["LocalPkg"]) +s = """ +[deps] +Example = "7876af07-990d-54b4-ab0e-23690620f79a" +LocalPkg = "fcf55292-0d03-4e8a-9e0b-701580031fc3" + +[sources] +LocalPkg = {path = "LocalPkg"} + + [sources.Example] + url = "https://github.com/JuliaLang/Example.jl" +""" +@test toml_str(d; sorted=true, inline_tables) == s +@test roundtrip(s) diff --git a/stdlib/TOML/test/runtests.jl b/stdlib/TOML/test/runtests.jl index 7376fab914636..47c762d054711 100644 --- a/stdlib/TOML/test/runtests.jl +++ b/stdlib/TOML/test/runtests.jl @@ -25,3 +25,7 @@ include("print.jl") include("parse.jl") @inferred TOML.parse("foo = 3") + +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(TOML)) +end diff --git a/stdlib/TOML/test/toml_test.jl b/stdlib/TOML/test/toml_test.jl index f4670058223a1..cdd8950677c75 100644 --- a/stdlib/TOML/test/toml_test.jl +++ b/stdlib/TOML/test/toml_test.jl @@ -36,7 +36,7 @@ end function check_valid(f) jsn = try jsn2data(@eval include($f * ".jl")) - # Some files cannot be reprsented with julias DateTime (timezones) + # Some files cannot be represented with julias DateTime (timezones) catch return false end @@ -72,11 +72,7 @@ for (root, dirs, files) in walkdir(valid_test_folder) rel = replace(rel, '\\' => '/') end v = check_valid(splitext(file)[1]) - if rel in failures - @test_broken v - else - @test v - end + @test v broken=rel in failures end end end @@ -145,11 +141,7 @@ for (root, dirs, files) in walkdir(invalid_test_folder) rel = replace(rel, '\\' => '/') end v = check_invalid(file) - if rel in failures - @test_broken v - else - @test v - end + @test v broken=rel in failures end end end diff --git a/stdlib/TOML/test/utils/utils.jl b/stdlib/TOML/test/utils/utils.jl index c484a61cee25a..b01acf04a72fe 100644 --- a/stdlib/TOML/test/utils/utils.jl +++ b/stdlib/TOML/test/utils/utils.jl @@ -33,7 +33,7 @@ end function get_data() tmp = mktempdir() path = joinpath(tmp, basename(url)) - Downloads.download(url, path) + retry(Downloads.download, delays=fill(10,5))(url, path) Tar.extract(`$(exe7z()) x $path -so`, joinpath(tmp, "testfiles")) return joinpath(tmp, "testfiles", "toml-test-julia-$version", "testfiles") end diff --git a/stdlib/Test/Project.toml b/stdlib/Test/Project.toml index ee1ae15fd7154..f04b4f976196f 100644 --- a/stdlib/Test/Project.toml +++ b/stdlib/Test/Project.toml @@ -1,5 +1,6 @@ name = "Test" uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +version = "1.11.0" [deps] InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" diff --git a/stdlib/Test/docs/src/index.md b/stdlib/Test/docs/src/index.md index 3d133419a1792..c1fe9e8e20c63 100644 --- a/stdlib/Test/docs/src/index.md +++ b/stdlib/Test/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/Test/docs/src/index.md" +``` + # Unit Testing ```@meta @@ -69,6 +73,8 @@ Error During Test Test threw an exception of type MethodError Expression: foo(:cat) == 1 MethodError: no method matching length(::Symbol) + The function `length` exists, but no method is defined for this combination of argument types. + Closest candidates are: length(::SimpleVector) at essentials.jl:256 length(::Base.MethodList) at reflection.jl:521 @@ -316,8 +322,12 @@ function finish(ts::CustomTestSet) # just record if we're not the top-level parent if get_testset_depth() > 0 record(get_testset(), ts) + return ts end - ts + + # so the results are printed if we are at the top level + Test.print_test_results(ts) + return ts end ``` @@ -332,6 +342,45 @@ And using that testset looks like: end ``` +In order to use a custom testset and have the recorded results printed as part of any outer default testset, +also define `Test.get_test_counts`. This might look like so: + +```julia +using Test: AbstractTestSet, Pass, Fail, Error, Broken, get_test_counts, TestCounts, format_duration + +function Test.get_test_counts(ts::CustomTestSet) + passes, fails, errors, broken = 0, 0, 0, 0 + # cumulative results + c_passes, c_fails, c_errors, c_broken = 0, 0, 0, 0 + + for t in ts.results + # count up results + isa(t, Pass) && (passes += 1) + isa(t, Fail) && (fails += 1) + isa(t, Error) && (errors += 1) + isa(t, Broken) && (broken += 1) + # handle children + if isa(t, AbstractTestSet) + tc = get_test_counts(t)::TestCounts + c_passes += tc.passes + tc.cumulative_passes + c_fails += tc.fails + tc.cumulative_fails + c_errors += tc.errors + tc.cumulative_errors + c_broken += tc.broken + tc.cumulative_broken + end + end + # get a duration, if we have one + duration = format_duration(ts) + return TestCounts(true, passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken, duration) +end +``` + +```@docs +Test.TestCounts +Test.get_test_counts +Test.format_duration +Test.print_test_results +``` + ## Test utilities ```@docs @@ -368,6 +417,8 @@ Add the following to `src/Example.jl`: ```julia module Example +export greet, simple_add, type_multiply + function greet() "Hello world!" end @@ -380,8 +431,6 @@ function type_multiply(a::Float64, b::Float64) a * b end -export greet, simple_add, type_multiply - end ``` @@ -491,3 +540,15 @@ Using `Test.jl`, more complicated tests can be added for packages but this shoul ```@meta DocTestSetup = nothing ``` + +### Code Coverage + +Code coverage tracking during tests can be enabled using the `pkg> test --coverage` flag (or at a lower level using the +[`--code-coverage`](@ref command-line-interface) julia arg). This is on by default in the +[julia-runtest](https://github.com/julia-actions/julia-runtest) GitHub action. + +To evaluate coverage either manually inspect the `.cov` files that are generated beside the source files locally, +or in CI use the [julia-processcoverage](https://github.com/julia-actions/julia-processcoverage) GitHub action. + +!!! compat "Julia 1.11" + Since Julia 1.11, coverage is not collected during the package precompilation phase. diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 0844aa9807bb1..ddd62ca9a10f8 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -1114,6 +1114,27 @@ function record(ts::DefaultTestSet, t::Union{Fail, Error}; print_result::Bool=TE return t end +""" + print_verbose(::AbstractTestSet) -> Bool + +Whether printing involving this `AbstractTestSet` should be verbose or not. + +Defaults to `false`. +""" +function print_verbose end + +""" + results(::AbstractTestSet) + +Return an iterator of results aggregated by this `AbstractTestSet`, if any were recorded. + +Defaults to the empty tuple. +""" +function results end + +print_verbose(ts::DefaultTestSet) = ts.verbose +results(ts::DefaultTestSet) = ts.results + # When a DefaultTestSet finishes, it records itself to its parent # testset, if there is one. This allows for recursive printing of # the results at the end of the tests @@ -1121,26 +1142,42 @@ record(ts::DefaultTestSet, t::AbstractTestSet) = push!(ts.results, t) @specialize -function print_test_errors(ts::DefaultTestSet) - for t in ts.results +""" + print_test_errors(::AbstractTestSet) + +Prints the errors that were recorded by this `AbstractTestSet` after it +was `finish`ed. +""" +function print_test_errors(ts::AbstractTestSet) + for t in results(ts) if isa(t, Error) || isa(t, Fail) println("Error in testset $(ts.description):") show(t) println() - elseif isa(t, DefaultTestSet) + elseif isa(t, AbstractTestSet) print_test_errors(t) end end end -function print_test_results(ts::DefaultTestSet, depth_pad=0) +""" + print_test_results(ts::AbstractTestSet, depth_pad=0) + +Print the results of an `AbstractTestSet` as a formatted table. + +`depth_pad` refers to how much padding should be added in front of all output. + +Called inside of `Test.finish`, if the `finish`ed testset is the topmost +testset. +""" +function print_test_results(ts::AbstractTestSet, depth_pad=0) # Calculate the overall number for each type so each of # the test result types are aligned - passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken, duration = get_test_counts(ts) - total_pass = passes + c_passes - total_fail = fails + c_fails - total_error = errors + c_errors - total_broken = broken + c_broken + tc = get_test_counts(ts) + total_pass = tc.passes + tc.cumulative_passes + total_fail = tc.fails + tc.cumulative_fails + total_error = tc.errors + tc.cumulative_errors + total_broken = tc.broken + tc.cumulative_broken dig_pass = total_pass > 0 ? ndigits(total_pass) : 0 dig_fail = total_fail > 0 ? ndigits(total_fail) : 0 dig_error = total_error > 0 ? ndigits(total_error) : 0 @@ -1153,14 +1190,13 @@ function print_test_results(ts::DefaultTestSet, depth_pad=0) fail_width = dig_fail > 0 ? max(length("Fail"), dig_fail) : 0 error_width = dig_error > 0 ? max(length("Error"), dig_error) : 0 broken_width = dig_broken > 0 ? max(length("Broken"), dig_broken) : 0 - total_width = dig_total > 0 ? max(length("Total"), dig_total) : 0 - duration_width = max(length("Time"), length(duration)) + total_width = max(textwidth("Total"), dig_total) + duration_width = max(textwidth("Time"), textwidth(tc.duration)) # Calculate the alignment of the test result counts by # recursively walking the tree of test sets - align = max(get_alignment(ts, 0), length("Test Summary:")) + align = max(get_alignment(ts, 0), textwidth("Test Summary:")) # Print the outer test set header once - pad = total == 0 ? "" : " " - printstyled(rpad("Test Summary:", align, " "), " |", pad; bold=true) + printstyled(rpad("Test Summary:", align, " "), " |", " "; bold=true) if pass_width > 0 printstyled(lpad("Pass", pass_width, " "), " "; bold=true, color=:green) end @@ -1173,15 +1209,16 @@ function print_test_results(ts::DefaultTestSet, depth_pad=0) if broken_width > 0 printstyled(lpad("Broken", broken_width, " "), " "; bold=true, color=Base.warn_color()) end - if total_width > 0 + if total_width > 0 || total == 0 printstyled(lpad("Total", total_width, " "), " "; bold=true, color=Base.info_color()) end - if ts.showtiming + timing = isdefined(ts, :showtiming) ? ts.showtiming : false + if timing printstyled(lpad("Time", duration_width, " "); bold=true) end println() # Recursively print a summary at every level - print_counts(ts, depth_pad, align, pass_width, fail_width, error_width, broken_width, total_width, duration_width, ts.showtiming) + print_counts(ts, depth_pad, align, pass_width, fail_width, error_width, broken_width, total_width, duration_width, timing) end @@ -1199,11 +1236,11 @@ function finish(ts::DefaultTestSet; print_results::Bool=TESTSET_PRINT_ENABLE[]) record(parent_ts, ts) return ts end - passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken, duration = get_test_counts(ts) - total_pass = passes + c_passes - total_fail = fails + c_fails - total_error = errors + c_errors - total_broken = broken + c_broken + tc = get_test_counts(ts) + total_pass = tc.passes + tc.cumulative_passes + total_fail = tc.fails + tc.cumulative_fails + total_error = tc.errors + tc.cumulative_errors + total_broken = tc.broken + tc.cumulative_broken total = total_pass + total_fail + total_error + total_broken if print_results @@ -1253,100 +1290,165 @@ function filter_errors(ts::DefaultTestSet) efs end -# Recursive function that counts the number of test results of each -# type directly in the testset, and totals across the child testsets +""" + TestCounts + +Holds the state for recursively gathering the results of a test set for display purposes. + +Fields: + + * `customized`: Whether the function `get_test_counts` was customized for the `AbstractTestSet` + this counts object is for. If a custom method was defined, always pass `true` + to the constructor. + * `passes`: The number of passing `@test` invocations. + * `fails`: The number of failing `@test` invocations. + * `errors`: The number of erroring `@test` invocations. + * `broken`: The number of broken `@test` invocations. + * `passes`: The cumulative number of passing `@test` invocations. + * `fails`: The cumulative number of failing `@test` invocations. + * `errors`: The cumulative number of erroring `@test` invocations. + * `broken`: The cumulative number of broken `@test` invocations. + * `duration`: The total duration the `AbstractTestSet` in question ran for, as a formatted `String`. +""" +struct TestCounts + customized::Bool + passes::Int + fails::Int + errors::Int + broken::Int + cumulative_passes::Int + cumulative_fails::Int + cumulative_errors::Int + cumulative_broken::Int + duration::String +end + +"""" + get_test_counts(::AbstractTestSet) -> TestCounts + +Recursive function that counts the number of test results of each +type directly in the testset, and totals across the child testsets. + +Custom `AbstractTestSet` should implement this function to get their totals +counted & displayed with `DefaultTestSet` as well. + +If this is not implemented for a custom `TestSet`, the printing falls back to +reporting `x` for failures and `?s` for the duration. +""" +function get_test_counts end + +get_test_counts(ts::AbstractTestSet) = TestCounts(false, 0,0,0,0,0,0,0,0, format_duration(ts)) + function get_test_counts(ts::DefaultTestSet) passes, fails, errors, broken = ts.n_passed, 0, 0, 0 + # cumulative results c_passes, c_fails, c_errors, c_broken = 0, 0, 0, 0 for t in ts.results isa(t, Fail) && (fails += 1) isa(t, Error) && (errors += 1) isa(t, Broken) && (broken += 1) - if isa(t, DefaultTestSet) - np, nf, ne, nb, ncp, ncf, nce , ncb, duration = get_test_counts(t) - c_passes += np + ncp - c_fails += nf + ncf - c_errors += ne + nce - c_broken += nb + ncb + if isa(t, AbstractTestSet) + tc = get_test_counts(t)::TestCounts + c_passes += tc.passes + tc.cumulative_passes + c_fails += tc.fails + tc.cumulative_fails + c_errors += tc.errors + tc.cumulative_errors + c_broken += tc.broken + tc.cumulative_broken end end + duration = format_duration(ts) ts.anynonpass = (fails + errors + c_fails + c_errors > 0) + return TestCounts(true, passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken, duration) +end + +""" + format_duration(::AbstractTestSet) + +Return a formatted string for printing the duration the testset ran for. + +If not defined, falls back to `"?s"`. +""" +format_duration(::AbstractTestSet) = "?s" + +function format_duration(ts::DefaultTestSet) (; time_start, time_end) = ts - duration = if isnothing(time_end) - "" + isnothing(time_end) && return "" + + dur_s = time_end - time_start + if dur_s < 60 + string(round(dur_s, digits = 1), "s") else - dur_s = time_end - time_start - if dur_s < 60 - string(round(dur_s, digits = 1), "s") - else - m, s = divrem(dur_s, 60) - s = lpad(string(round(s, digits = 1)), 4, "0") - string(round(Int, m), "m", s, "s") - end + m, s = divrem(dur_s, 60) + s = lpad(string(round(s, digits = 1)), 4, "0") + string(round(Int, m), "m", s, "s") end - return passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken, duration end +print_verbose(::AbstractTestSet) = false +results(::AbstractTestSet) = () + # Recursive function that prints out the results at each level of # the tree of test sets -function print_counts(ts::DefaultTestSet, depth, align, +function print_counts(ts::AbstractTestSet, depth, align, pass_width, fail_width, error_width, broken_width, total_width, duration_width, showtiming) # Count results by each type at this level, and recursively # through any child test sets - passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken, duration = get_test_counts(ts) - subtotal = passes + fails + errors + broken + c_passes + c_fails + c_errors + c_broken + tc = get_test_counts(ts) + fallbackstr = tc.customized ? " " : "x" + subtotal = tc.passes + tc.fails + tc.errors + tc.broken + + tc.cumulative_passes + tc.cumulative_fails + tc.cumulative_errors + tc.cumulative_broken # Print test set header, with an alignment that ensures all # the test results appear above each other print(rpad(string(" "^depth, ts.description), align, " "), " | ") - np = passes + c_passes - if np > 0 - printstyled(lpad(string(np), pass_width, " "), " ", color=:green) + n_passes = tc.passes + tc.cumulative_passes + if n_passes > 0 + printstyled(lpad(string(n_passes), pass_width, " "), " ", color=:green) elseif pass_width > 0 # No passes at this level, but some at another level - print(lpad(" ", pass_width), " ") + printstyled(lpad(fallbackstr, pass_width, " "), " ", color=:green) end - nf = fails + c_fails - if nf > 0 - printstyled(lpad(string(nf), fail_width, " "), " ", color=Base.error_color()) + n_fails = tc.fails + tc.cumulative_fails + if n_fails > 0 + printstyled(lpad(string(n_fails), fail_width, " "), " ", color=Base.error_color()) elseif fail_width > 0 # No fails at this level, but some at another level - print(lpad(" ", fail_width), " ") + printstyled(lpad(fallbackstr, fail_width, " "), " ", color=Base.error_color()) end - ne = errors + c_errors - if ne > 0 - printstyled(lpad(string(ne), error_width, " "), " ", color=Base.error_color()) + n_errors = tc.errors + tc.cumulative_errors + if n_errors > 0 + printstyled(lpad(string(n_errors), error_width, " "), " ", color=Base.error_color()) elseif error_width > 0 # No errors at this level, but some at another level - print(lpad(" ", error_width), " ") + printstyled(lpad(fallbackstr, error_width, " "), " ", color=Base.error_color()) end - nb = broken + c_broken - if nb > 0 - printstyled(lpad(string(nb), broken_width, " "), " ", color=Base.warn_color()) + n_broken = tc.broken + tc.cumulative_broken + if n_broken > 0 + printstyled(lpad(string(n_broken), broken_width, " "), " ", color=Base.warn_color()) elseif broken_width > 0 # None broken at this level, but some at another level - print(lpad(" ", broken_width), " ") + printstyled(lpad(fallbackstr, broken_width, " "), " ", color=Base.warn_color()) end - if np == 0 && nf == 0 && ne == 0 && nb == 0 - printstyled(lpad("None", total_width, " "), " ", color=Base.info_color()) + if n_passes == 0 && n_fails == 0 && n_errors == 0 && n_broken == 0 + total_str = tc.customized ? string(subtotal) : "?" + printstyled(lpad(total_str, total_width, " "), " ", color=Base.info_color()) else printstyled(lpad(string(subtotal), total_width, " "), " ", color=Base.info_color()) end if showtiming - printstyled(lpad(string(duration), duration_width, " ")) + printstyled(lpad(tc.duration, duration_width, " ")) end println() # Only print results at lower levels if we had failures or if the user - # wants. - if (np + nb != subtotal) || (ts.verbose) - for t in ts.results - if isa(t, DefaultTestSet) + # wants. Requires the given `AbstractTestSet` to have a vector of results + if ((n_passes + n_broken != subtotal) || print_verbose(ts)) + for t in results(ts) + if isa(t, AbstractTestSet) print_counts(t, depth + 1, align, pass_width, fail_width, error_width, broken_width, total_width, duration_width, ts.showtiming) end @@ -1459,7 +1561,7 @@ parent test set (with the context object appended to any failing tests.) `@testset let` requires at least Julia 1.9. !!! compat "Julia 1.10" - Multiple `let` assignements are supported since Julia 1.10. + Multiple `let` assignments are supported since Julia 1.10. ## Examples ```jldoctest @@ -1731,7 +1833,8 @@ function parse_testset_args(args) options = :(Dict{Symbol, Any}()) for arg in args # a standalone symbol is assumed to be the test set we should use - if isa(arg, Symbol) + # the same is true for a symbol that's not exported from a module + if isa(arg, Symbol) || Base.isexpr(arg, :.) testsettype = esc(arg) # a string is the description elseif isa(arg, AbstractString) || (isa(arg, Expr) && arg.head === :string) @@ -1814,7 +1917,7 @@ matches the inferred type modulo `AllowedType`, or when the return type is a sub `AllowedType`. This is useful when testing type stability of functions returning a small union such as `Union{Nothing, T}` or `Union{Missing, T}`. -```jldoctest; setup = :(using InteractiveUtils), filter = r"begin\\n(.|\\n)*end" +```jldoctest; setup = :(using InteractiveUtils; using Base: >), filter = r"begin\\n(.|\\n)*end" julia> f(a) = a > 1 ? 1 : 1.0 f (generic function with 1 method) diff --git a/stdlib/Test/src/logging.jl b/stdlib/Test/src/logging.jl index 7b3838903ce10..42d3eaf8eaa48 100644 --- a/stdlib/Test/src/logging.jl +++ b/stdlib/Test/src/logging.jl @@ -55,7 +55,7 @@ most `n` times. See also: [`LogRecord`](@ref). -## Example +## Examples ```jldoctest julia> using Test, Logging diff --git a/stdlib/Test/src/precompile.jl b/stdlib/Test/src/precompile.jl index 2cb2fb7f3f0c6..1e53033a09143 100644 --- a/stdlib/Test/src/precompile.jl +++ b/stdlib/Test/src/precompile.jl @@ -1,9 +1,11 @@ -redirect_stdout(devnull) do - @testset "example" begin - @test 1 == 1 - @test_throws ErrorException error() - @test_logs (:info, "Doing foo with n=2") @info "Doing foo with n=2" - @test_broken 1 == 2 - @test 1 ≈ 1.0000000000000001 +if Base.generating_output() + redirect_stdout(devnull) do + @testset "example" begin + @test 1 == 1 + @test_throws ErrorException error() + @test_logs (:info, "Doing foo with n=2") @info "Doing foo with n=2" + @test_broken 1 == 2 + @test 1 ≈ 1.0000000000000001 + end end end diff --git a/stdlib/Test/test/runtests.jl b/stdlib/Test/test/runtests.jl index 7c4edc4b9d93b..fc4838caf4ef4 100644 --- a/stdlib/Test/test/runtests.jl +++ b/stdlib/Test/test/runtests.jl @@ -77,7 +77,7 @@ end @test 1234 === @test_nowarn(1234) @test 5678 === @test_warn("WARNING: foo", begin println(stderr, "WARNING: foo"); 5678; end) let a - @test_throws UndefVarError(:a) a + @test_throws UndefVarError(:a, :local) a @test_nowarn a = 1 @test a === 1 end @@ -162,7 +162,7 @@ let fails = @testset NoThrowTestSet begin @test_throws "A test" error("a test") @test_throws r"sqrt\([Cc]omplx" sqrt(-1) @test_throws str->occursin("a T", str) error("a test") - @test_throws ["BoundsError", "aquire", "1-element", "at index [2]"] [1][2] + @test_throws ["BoundsError", "acquire", "1-element", "at index [2]"] [1][2] end for fail in fails @test fail isa Test.Fail @@ -294,7 +294,7 @@ let fails = @testset NoThrowTestSet begin end let str = sprint(show, fails[26]) - @test occursin("Expected: [\"BoundsError\", \"aquire\", \"1-element\", \"at index [2]\"]", str) + @test occursin("Expected: [\"BoundsError\", \"acquire\", \"1-element\", \"at index [2]\"]", str) @test occursin(r"Message: \"BoundsError.* 1-element.*at index \[2\]", str) end @@ -468,11 +468,11 @@ end end @testset "ts results" begin @test isa(ts, Test.DefaultTestSet) - passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken = Test.get_test_counts(ts) - total_pass = passes + c_passes - total_fail = fails + c_fails - total_error = errors + c_errors - total_broken = broken + c_broken + tc = Test.get_test_counts(ts) + total_pass = tc.passes + tc.cumulative_passes + total_fail = tc.fails + tc.cumulative_fails + total_error = tc.errors + tc.cumulative_errors + total_broken = tc.broken + tc.cumulative_broken @test total_pass == 24 @test total_fail == 6 @test total_error == 6 @@ -659,15 +659,15 @@ end @test tss.foo == 3 # test @inferred -uninferrable_function(i) = (1, "1")[i] -uninferrable_small_union(i) = (1, nothing)[i] -@test_throws ErrorException @inferred(uninferrable_function(1)) +uninferable_function(i) = (1, "1")[i] +uninferable_small_union(i) = (1, nothing)[i] +@test_throws ErrorException @inferred(uninferable_function(1)) @test @inferred(identity(1)) == 1 -@test @inferred(Nothing, uninferrable_small_union(1)) === 1 -@test @inferred(Nothing, uninferrable_small_union(2)) === nothing -@test_throws ErrorException @inferred(Missing, uninferrable_small_union(1)) -@test_throws ErrorException @inferred(Missing, uninferrable_small_union(2)) -@test_throws ArgumentError @inferred(nothing, uninferrable_small_union(1)) +@test @inferred(Nothing, uninferable_small_union(1)) === 1 +@test @inferred(Nothing, uninferable_small_union(2)) === nothing +@test_throws ErrorException @inferred(Missing, uninferable_small_union(1)) +@test_throws ErrorException @inferred(Missing, uninferable_small_union(2)) +@test_throws ArgumentError @inferred(nothing, uninferable_small_union(1)) # Ensure @inferred only evaluates the arguments once inferred_test_global = 0 @@ -692,12 +692,12 @@ end # Issue #17105 # @inferred with kwargs -inferrable_kwtest(x; y=1) = 2x -uninferrable_kwtest(x; y=1) = 2x+y -@test (@inferred inferrable_kwtest(1)) == 2 -@test (@inferred inferrable_kwtest(1; y=1)) == 2 -@test (@inferred uninferrable_kwtest(1)) == 3 -@test (@inferred uninferrable_kwtest(1; y=2)) == 4 +inferable_kwtest(x; y=1) = 2x +uninferable_kwtest(x; y=1) = 2x+y +@test (@inferred inferable_kwtest(1)) == 2 +@test (@inferred inferable_kwtest(1; y=1)) == 2 +@test (@inferred uninferable_kwtest(1)) == 3 +@test (@inferred uninferable_kwtest(1; y=2)) == 4 @test_throws ErrorException @testset "$(error())" for i in 1:10 end @@ -1193,7 +1193,7 @@ h25835(;x=1,y=1) = x isa Int ? x*y : (rand(Bool) ? 1.0 : 1) @test @inferred(f25835(x=nothing)) == () @test @inferred(f25835(x=1)) == (1,) - # A global argument should make this uninferrable + # A global argument should make this uninferable global y25835 = 1 @test f25835(x=y25835) == (1,) @test_throws ErrorException @inferred((()->f25835(x=y25835))()) == (1,) @@ -1582,3 +1582,131 @@ let end end end + +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(Test)) +end + +module CustomTestSetModule + using Test + struct CustomTestSet <: Test.AbstractTestSet + description::String + end + Test.record(::CustomTestSet, result) = result + Test.finish(cts::CustomTestSet) = cts +end + +@testset "Unexported custom TestSet" begin + using .CustomTestSetModule + let res = @testset CustomTestSetModule.CustomTestSet begin + @test true + end + @test res isa CustomTestSetModule.CustomTestSet + end +end + +struct CustomPrintingTestSet <: AbstractTestSet + description::String + passes::Int + errors::Int + fails::Int + broken::Int +end + +function Test.finish(cpts::CustomPrintingTestSet) + if Test.get_testset_depth() != 0 + push!(Test.get_current_testset(), cpts) + # printing is handled by the parent + return cpts + end + + Test.print_testset_results(cpts) + cpts +end + +@testset "Custom testsets participate in printing" begin + mktemp() do f, _ + write(f, + """ + using Test + + mutable struct CustomPrintingTestSet <: Test.AbstractTestSet + description::String + passes::Int + fails::Int + errors::Int + broken::Int + end + CustomPrintingTestSet(desc::String) = CustomPrintingTestSet(desc, 0,0,0,0) + + Test.record(cpts::CustomPrintingTestSet, ::Test.Pass) = cpts.passes += 1 + Test.record(cpts::CustomPrintingTestSet, ::Test.Error) = cpts.errors += 1 + Test.record(cpts::CustomPrintingTestSet, ::Test.Fail) = cpts.fails += 1 + Test.record(cpts::CustomPrintingTestSet, ::Test.Broken) = cpts.broken += 1 + Test.get_test_counts(ts::CustomPrintingTestSet) = Test.TestCounts( + true, + ts.passes, + ts.fails, + ts.errors, + ts.broken, + 0, + 0, + 0, + 0, + Test.format_duration(ts)) + + function Test.finish(cpts::CustomPrintingTestSet) + if Test.get_testset_depth() != 0 + Test.record(Test.get_testset(), cpts) + # printing is handled by the parent + return cpts + end + + Test.print_test_results(cpts) + cpts + end + + struct NonRecordingTestSet <: Test.AbstractTestSet + description::String + end + Test.record(nrts::NonRecordingTestSet, ::Test.Result) = nrts + Test.finish(nrts::NonRecordingTestSet) = Test.record(Test.get_testset(), nrts) + + @testset "outer" begin + @testset "a" begin + @test true + end + @testset CustomPrintingTestSet "custom" begin + @test false + @test true + @test_broken false + @test error() + end + @testset NonRecordingTestSet "no-record" begin + @test false + @test true + @test_broken false + @test error() + end + @testset "b" begin + @test true + end + end + """) + + # this tests both the `TestCounts` parts as well as the fallback `x`s + expected = r""" + Test Summary: | Pass Fail Error Broken Total Time + outer | 3 1 1 1 6 \s*\d*.\ds + a | 1 1 \s*\d*.\ds + custom | 1 1 1 1 4 \s*?s + no-record | x x x x ? \s*?s + b | 1 1 \s*\d*.\ds + ERROR: Some tests did not pass: 3 passed, 1 failed, 1 errored, 1 broken. + """ + + cmd = `$(Base.julia_cmd()) --startup-file=no --color=no $f` + result = read(pipeline(ignorestatus(cmd), stderr=devnull), String) + @test occursin(expected, result) + end +end diff --git a/stdlib/UUIDs/Project.toml b/stdlib/UUIDs/Project.toml index 11dbcda5c4944..4eb31dc9572c0 100644 --- a/stdlib/UUIDs/Project.toml +++ b/stdlib/UUIDs/Project.toml @@ -1,5 +1,6 @@ name = "UUIDs" uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" +version = "1.11.0" [deps] Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" diff --git a/stdlib/UUIDs/docs/src/index.md b/stdlib/UUIDs/docs/src/index.md index 1e6c950dd8999..c9529a4a38170 100644 --- a/stdlib/UUIDs/docs/src/index.md +++ b/stdlib/UUIDs/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/UUIDs/docs/src/index.md" +``` + # UUIDs ```@docs diff --git a/stdlib/UUIDs/test/runtests.jl b/stdlib/UUIDs/test/runtests.jl index 1770b24b3abae..d2b9ee17f2e38 100644 --- a/stdlib/UUIDs/test/runtests.jl +++ b/stdlib/UUIDs/test/runtests.jl @@ -73,3 +73,7 @@ str = "22b4a8a1-e548-4eeb-9270-60426d66a48e" for r in rand(UInt128, 10^3) @test UUID(r) == UUID(string(UUID(r))) end + +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(UUIDs)) +end diff --git a/stdlib/Unicode/Project.toml b/stdlib/Unicode/Project.toml index 5e3040ce9e3db..a01833870644e 100644 --- a/stdlib/Unicode/Project.toml +++ b/stdlib/Unicode/Project.toml @@ -1,6 +1,6 @@ name = "Unicode" uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" - +version = "1.11.0" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/Unicode/docs/src/index.md b/stdlib/Unicode/docs/src/index.md index 2771c8a9f01cc..fdf07685a4492 100644 --- a/stdlib/Unicode/docs/src/index.md +++ b/stdlib/Unicode/docs/src/index.md @@ -1,6 +1,14 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/Unicode/docs/src/index.md" +``` + # Unicode +The `Unicode` module provides essential functionality for managing Unicode characters and strings. +It includes validation, category determination, normalization, case transformation, and grapheme segmentation, enabling effective Unicode data handling. + ```@docs +Unicode Unicode.julia_chartransform Unicode.isassigned Unicode.isequal_normalized diff --git a/stdlib/Unicode/src/Unicode.jl b/stdlib/Unicode/src/Unicode.jl index bd54037ec9e6f..b9822d0073c73 100644 --- a/stdlib/Unicode/src/Unicode.jl +++ b/stdlib/Unicode/src/Unicode.jl @@ -1,5 +1,9 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license - +""" +The `Unicode` module provides essential functionality for managing Unicode characters and strings. +It includes validation, category determination, normalization, case transformation, and grapheme segmentation, +enabling effective Unicode data handling. +""" module Unicode export graphemes, isequal_normalized @@ -208,12 +212,19 @@ end using Base.Unicode: utf8proc_error, UTF8PROC_DECOMPOSE, UTF8PROC_CASEFOLD, UTF8PROC_STRIPMARK -function _decompose_char!(codepoint::Union{Integer,Char}, dest::Vector{UInt32}, options::Integer) - ret = @ccall utf8proc_decompose_char(codepoint::UInt32, dest::Ptr{UInt32}, length(dest)::Int, options::Cint, C_NULL::Ptr{Cint})::Int +function _decompose_char!(codepoint::Union{Integer,Char}, dest::Vector{UInt32}, offset::Integer, options::Integer) + ret = GC.@preserve dest @ccall utf8proc_decompose_char(codepoint::UInt32, pointer(dest, 1+offset)::Ptr{UInt32}, (length(dest)-offset)::Int, options::Cint, C_NULL::Ptr{Cint})::Int ret < 0 && utf8proc_error(ret) return ret end +# would be good to have higher-level accessor functions in utf8proc. alternatively, +# we could mirror the whole utf8proc_property_t struct in Julia, but that is annoying +# because of the bitfields. +combining_class(uc::Integer) = + 0x000301 ≤ uc ≤ 0x10ffff ? unsafe_load(ccall(:utf8proc_get_property, Ptr{UInt16}, (UInt32,), uc), 2) : 0x0000 +combining_class(c::AbstractChar) = ismalformed(c) ? 0x0000 : combining_class(UInt32(c)) + """ isequal_normalized(s1::AbstractString, s2::AbstractString; casefold=false, stripmark=false, chartransform=identity) @@ -225,6 +236,9 @@ As with [`Unicode.normalize`](@ref), you can also pass an arbitrary function via the `chartransform` keyword (mapping `Integer` codepoints to codepoints) to perform custom normalizations, such as [`Unicode.julia_chartransform`](@ref). +!!! compat "Julia 1.8" + The `isequal_normalized` function was added in Julia 1.8. + # Examples For example, the string `"noël"` can be constructed in two canonically equivalent ways @@ -251,29 +265,78 @@ julia> isequal_normalized(s1, "NOËL", casefold=true) true ``` """ -function isequal_normalized(s1::AbstractString, s2::AbstractString; casefold::Bool=false, stripmark::Bool=false, chartransform=identity) - function decompose_next_char!(c, state, d, options, s) - n = _decompose_char!(c, d, options) - if n > length(d) # may be possible in future Unicode versions? - n = _decompose_char!(c, resize!(d, n), options) +isequal_normalized(s1::AbstractString, s2::AbstractString; casefold::Bool=false, stripmark::Bool=false, chartransform=identity) = + _isequal_normalized!(s1, s2, Vector{UInt32}(undef, 4), Vector{UInt32}(undef, 4), chartransform; casefold, stripmark) + +# like isequal_normalized, but takes pre-allocated codepoint buffers as arguments, and chartransform is a positional argument +function _isequal_normalized!(s1::AbstractString, s2::AbstractString, + d1::Vector{UInt32}, d2::Vector{UInt32}, chartransform::F=identity; + casefold::Bool=false, stripmark::Bool=false) where {F} + function decompose_next_chars!(state, d, options, s) + local n + offset = 0 + @inbounds while true + # read a char and decompose it to d + c = chartransform(UInt32(state[1])) + state = iterate(s, state[2]) + if c < 0x80 # fast path for common ASCII case + n = 1 + offset + n > length(d) && resize!(d, 2n) + d[n] = casefold ? (0x41 ≤ c ≤ 0x5A ? c+0x20 : c) : c + break # ASCII characters are all zero combining class + else + while true + n = _decompose_char!(c, d, offset, options) + offset + if n > length(d) + resize!(d, 2n) + continue + end + break + end + end + + # decomposed chars must be sorted in ascending order of combining class, + # which means we need to keep fetching chars until we get to non-combining + (iszero(combining_class(d[n])) || isnothing(state)) && break # non-combining + offset = n + end + + # sort by combining class + if n < 32 # almost always true + for j1 = 2:n # insertion sort + cc = combining_class(d[j1]) + iszero(cc) && continue # don't re-order non-combiners + for j2 = j1:-1:2 + combining_class(d[j2-1]) ≤ cc && break + d[j2-1], d[j2] = d[j2], d[j2-1] + end + end + else # avoid n^2 complexity in crazy large-n case + j = 1 + @views while j < n + j₀ = j + something(findnext(iszero ∘ combining_class, d[j+1:n], 1), n+1-j) + sort!(d[j:j₀-1], by=combining_class) + j = j₀ + end end - return 1, n, iterate(s, state) + + # split return statement to help type inference: + return state === nothing ? (1, n, nothing) : (1, n, state) end options = UTF8PROC_DECOMPOSE casefold && (options |= UTF8PROC_CASEFOLD) stripmark && (options |= UTF8PROC_STRIPMARK) i1,i2 = iterate(s1),iterate(s2) - d1,d2 = Vector{UInt32}(undef, 4), Vector{UInt32}(undef, 4) # codepoint buffers n1 = n2 = 0 # lengths of codepoint buffers j1 = j2 = 1 # indices in d1, d2 while true if j1 > n1 i1 === nothing && return i2 === nothing && j2 > n2 - j1, n1, i1 = decompose_next_char!(chartransform(UInt32(i1[1])), i1[2], d1, options, s1) + j1, n1, i1 = decompose_next_chars!(i1, d1, options, s1) end if j2 > n2 i2 === nothing && return false - j2, n2, i2 = decompose_next_char!(chartransform(UInt32(i2[1])), i2[2], d2, options, s2) + j2, n2, i2 = decompose_next_chars!(i2, d2, options, s2) end d1[j1] == d2[j2] || return false j1 += 1; j2 += 1 diff --git a/stdlib/Unicode/test/runtests.jl b/stdlib/Unicode/test/runtests.jl index 5c5a75b33e363..7fa57508cffbf 100644 --- a/stdlib/Unicode/test/runtests.jl +++ b/stdlib/Unicode/test/runtests.jl @@ -3,6 +3,9 @@ using Test using Unicode using Unicode: normalize, isassigned, julia_chartransform +import Random + +Random.seed!(12345) @testset "string normalization" begin # normalize (Unicode normalization etc.): @@ -27,14 +30,14 @@ using Unicode: normalize, isassigned, julia_chartransform @test normalize("\u0072\u0307\u0323", :NFC) == "\u1E5B\u0307" #26917 # julia_chartransform identifier normalization - @test normalize("julia\u025B\u00B5\u00B7\u0387\u2212", chartransform=julia_chartransform) == - "julia\u03B5\u03BC\u22C5\u22C5\u002D" + @test normalize("julia\u025B\u00B5\u00B7\u0387\u2212\u210F", chartransform=julia_chartransform) == + "julia\u03B5\u03BC\u22C5\u22C5\u002D\u0127" @test julia_chartransform('\u00B5') === '\u03BC' end @testset "unicode sa#15" begin #Tests from Unicode SA#15, "Unicode normalization forms" - #http://www.unicode.org/reports/tr15/ + #https://www.unicode.org/reports/tr15/ @testset "canonical equivalence" begin let ==(a::Array{Char},b::Array{Char}) = normalize(string(a...), :NFC)==normalize(string(b...), :NFC) @@ -455,6 +458,9 @@ end @test !Base.Unicode.isvalid(Char, overlong_char) end +# the obvious, but suboptimal, algorithm: +isequal_normalized_naive(s1, s2; kws...) = normalize(s1; kws...) == normalize(s2; kws...) + @testset "Unicode equivalence" begin @test isequal_normalized("no\u00EBl", "noe\u0308l") @test !isequal_normalized("no\u00EBl", "noe\u0308l ") @@ -466,4 +472,69 @@ end @test isequal_normalized("no\u00EBl", "noel", stripmark=true) @test isequal_normalized("no\u00EBl", "NOEL", stripmark=true, casefold=true) @test isequal_normalized("\u00B5\u0302m", "\u03BC\u0302m", chartransform=julia_chartransform) + + # issue #52408 + @testset "Sorting combining characters" begin + for str in ("\u5bc\u5b0", "j\u5ae\u5bf\u5b2\u5b4") # julia#52408 examples + @test isequal_normalized(str, normalize(str)) + end + + # first codepoint in every possible Unicode combining class + let cc_chars = UInt32[0x00000334, 0x00016ff0, 0x0000093c, 0x00003099, 0x0000094d, 0x000005b0, 0x000005b1, 0x000005b2, 0x000005b3, 0x000005b4, 0x000005b5, 0x000005b6, 0x000005b7, 0x000005b8, 0x000005b9, 0x000005bb, 0x000005bc, 0x000005bd, 0x000005bf, 0x000005c1, 0x000005c2, 0x0000fb1e, 0x0000064b, 0x0000064c, 0x0000064d, 0x00000618, 0x00000619, 0x0000061a, 0x00000651, 0x00000652, 0x00000670, 0x00000711, 0x00000c55, 0x00000c56, 0x00000e38, 0x00000e48, 0x00000eb8, 0x00000ec8, 0x00000f71, 0x00000f72, 0x00000f74, 0x00000321, 0x00001dce, 0x0000031b, 0x00001dfa, 0x00000316, 0x0000059a, 0x0000302e, 0x0001d16d, 0x000005ae, 0x00000301, 0x00000315, 0x0000035c, 0x0000035d, 0x00000345], + vowels = ['a', 'e', 'i', 'o', 'u', 'å', 'é', 'î', 'ö', 'ü'], Vowels = [vowels; uppercase.(vowels)] + function randcc(n, n_cc) # random string with lots of combining chars + buf = IOBuffer() + for _ = 1:n + print.(buf, rand(Vowels, rand(1:5))) + print.(buf, Char.(rand(cc_chars, rand(0:n_cc)))) + end + return String(take!(buf)) + end + for _ = 1:100 + s = randcc(10,10) + ns = normalize(s) + cs = normalize(s, casefold=true) + @test isequal_normalized(s, s) + if !isequal_normalized(s, ns) + @show s + end + @test isequal_normalized(s, ns) + @test isequal_normalized(cs, ns) == isequal_normalized_naive(cs, ns) + @test isequal_normalized(cs, ns, casefold=true) == + isequal_normalized_naive(cs, ns, casefold=true) + end + for _ = 1:3 + s = randcc(5,1000) # exercise sort!-based fallback + @test isequal_normalized(s, normalize(s)) + end + function randcc2(n, n_cc) # 2 strings with equivalent reordered combiners + buf1 = IOBuffer() + buf2 = IOBuffer() + p = n_cc / length(cc_chars) + for _ = 1:n + a = join(rand(Vowels, rand(1:5))) + print(buf1, a) + print(buf2, a) + + # chars from distinct combining classes + # are canonically equivalent when re-ordered + c = Random.randsubseq(cc_chars, p) + print.(buf1, Char.(Random.shuffle!(c))) + print.(buf2, Char.(Random.shuffle!(c))) + end + return String(take!(buf1)), String(take!(buf2)) + end + for _ = 1:100 + s1, s2 = randcc2(10,10) + @test isequal_normalized(s1, s2) + end + end + + # combining characters in the same class are inequivalent if re-ordered: + @test !isequal_normalized("x\u0334\u0335", "x\u0335\u0334") + end +end + +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(Unicode)) end diff --git a/stdlib/Zlib_jll/Project.toml b/stdlib/Zlib_jll/Project.toml index b1fa9576af3ec..bb5771654430b 100644 --- a/stdlib/Zlib_jll/Project.toml +++ b/stdlib/Zlib_jll/Project.toml @@ -1,6 +1,6 @@ name = "Zlib_jll" uuid = "83775a58-1f1d-513f-b197-d71354ab007a" -version = "1.2.13+1" +version = "1.3.1+0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/stdlib/Zlib_jll/src/Zlib_jll.jl b/stdlib/Zlib_jll/src/Zlib_jll.jl index ea381b8b0683c..fb043c7143789 100644 --- a/stdlib/Zlib_jll/src/Zlib_jll.jl +++ b/stdlib/Zlib_jll/src/Zlib_jll.jl @@ -3,7 +3,6 @@ ## dummy stub for https://github.com/JuliaBinaryWrappers/Zlib_jll.jl baremodule Zlib_jll using Base, Libdl -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] diff --git a/stdlib/Zlib_jll/test/runtests.jl b/stdlib/Zlib_jll/test/runtests.jl index f04f9c70a7054..81eb742a172fe 100644 --- a/stdlib/Zlib_jll/test/runtests.jl +++ b/stdlib/Zlib_jll/test/runtests.jl @@ -3,5 +3,5 @@ using Test, Zlib_jll @testset "Zlib_jll" begin - @test VersionNumber(unsafe_string(ccall((:zlibVersion, libz), Cstring, ()))) == v"1.2.13" + @test VersionNumber(unsafe_string(ccall((:zlibVersion, libz), Cstring, ()))) == v"1.3.1" end diff --git a/stdlib/dSFMT_jll/Project.toml b/stdlib/dSFMT_jll/Project.toml index a83775f625987..0db19e602f67b 100644 --- a/stdlib/dSFMT_jll/Project.toml +++ b/stdlib/dSFMT_jll/Project.toml @@ -1,6 +1,6 @@ name = "dSFMT_jll" uuid = "05ff407c-b0c1-5878-9df8-858cc2e60c36" -version = "2.2.4+4" +version = "2.2.5+0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/stdlib/dSFMT_jll/src/dSFMT_jll.jl b/stdlib/dSFMT_jll/src/dSFMT_jll.jl index 35ada23778a94..b84bf0d8204ae 100644 --- a/stdlib/dSFMT_jll/src/dSFMT_jll.jl +++ b/stdlib/dSFMT_jll/src/dSFMT_jll.jl @@ -4,7 +4,6 @@ baremodule dSFMT_jll using Base, Libdl -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] diff --git a/stdlib/libLLVM_jll/Project.toml b/stdlib/libLLVM_jll/Project.toml index b0bd480fc99fc..3decbf5f81512 100644 --- a/stdlib/libLLVM_jll/Project.toml +++ b/stdlib/libLLVM_jll/Project.toml @@ -1,13 +1,13 @@ name = "libLLVM_jll" uuid = "8f36deef-c2a5-5394-99ed-8e07531fb29a" -version = "15.0.7+8" +version = "16.0.6+4" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" [compat] -julia = "1.8" +julia = "1.11" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/libLLVM_jll/src/libLLVM_jll.jl b/stdlib/libLLVM_jll/src/libLLVM_jll.jl index 3140dc3989a72..be2acb34faa65 100644 --- a/stdlib/libLLVM_jll/src/libLLVM_jll.jl +++ b/stdlib/libLLVM_jll/src/libLLVM_jll.jl @@ -4,7 +4,6 @@ baremodule libLLVM_jll using Base, Libdl -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] diff --git a/stdlib/libblastrampoline_jll/src/libblastrampoline_jll.jl b/stdlib/libblastrampoline_jll/src/libblastrampoline_jll.jl index 49e7932a6b701..bbdad252be14a 100644 --- a/stdlib/libblastrampoline_jll/src/libblastrampoline_jll.jl +++ b/stdlib/libblastrampoline_jll/src/libblastrampoline_jll.jl @@ -4,7 +4,6 @@ baremodule libblastrampoline_jll using Base, Libdl -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] diff --git a/stdlib/nghttp2_jll/Project.toml b/stdlib/nghttp2_jll/Project.toml index d75e0780bd01a..88e60941f65ee 100644 --- a/stdlib/nghttp2_jll/Project.toml +++ b/stdlib/nghttp2_jll/Project.toml @@ -1,13 +1,13 @@ name = "nghttp2_jll" uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" -version = "1.52.0+1" +version = "1.60.0+0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" [compat] -julia = "1.6" +julia = "1.11" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/nghttp2_jll/src/nghttp2_jll.jl b/stdlib/nghttp2_jll/src/nghttp2_jll.jl index 76e8d3582c402..5057299614aa5 100644 --- a/stdlib/nghttp2_jll/src/nghttp2_jll.jl +++ b/stdlib/nghttp2_jll/src/nghttp2_jll.jl @@ -3,7 +3,6 @@ ## dummy stub for https://github.com/JuliaBinaryWrappers/nghttp2_jll.jl baremodule nghttp2_jll using Base, Libdl -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] diff --git a/stdlib/nghttp2_jll/test/runtests.jl b/stdlib/nghttp2_jll/test/runtests.jl index 2f9af6d6a3338..b6ddefb8222cd 100644 --- a/stdlib/nghttp2_jll/test/runtests.jl +++ b/stdlib/nghttp2_jll/test/runtests.jl @@ -11,5 +11,5 @@ end @testset "nghttp2_jll" begin info = unsafe_load(ccall((:nghttp2_version,libnghttp2), Ptr{nghttp2_info}, (Cint,), 0)) - @test VersionNumber(unsafe_string(info.version_str)) == v"1.52.0" + @test VersionNumber(unsafe_string(info.version_str)) == v"1.60.0" end diff --git a/stdlib/p7zip_jll/Project.toml b/stdlib/p7zip_jll/Project.toml index b1bd4bc9e0a1a..6bca9d1d0545b 100644 --- a/stdlib/p7zip_jll/Project.toml +++ b/stdlib/p7zip_jll/Project.toml @@ -1,6 +1,6 @@ name = "p7zip_jll" uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" -version = "17.4.0+2" +version = "17.5.0+0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/stdlib/p7zip_jll/src/p7zip_jll.jl b/stdlib/p7zip_jll/src/p7zip_jll.jl index 01f26de936e78..a2a90a2450ea6 100644 --- a/stdlib/p7zip_jll/src/p7zip_jll.jl +++ b/stdlib/p7zip_jll/src/p7zip_jll.jl @@ -3,7 +3,6 @@ ## dummy stub for https://github.com/JuliaBinaryWrappers/p7zip_jll.jl baremodule p7zip_jll using Base -Base.Experimental.@compiler_options compile=min optimize=0 infer=false const PATH_list = String[] const LIBPATH_list = String[] diff --git a/stdlib/stdlib.mk b/stdlib/stdlib.mk index 99bdefc66fa90..b79059d3368b1 100644 --- a/stdlib/stdlib.mk +++ b/stdlib/stdlib.mk @@ -4,23 +4,26 @@ STDLIBS_WITHIN_SYSIMG := \ INDEPENDENT_STDLIBS := \ ArgTools Base64 CRC32c Dates DelimitedFiles Distributed Downloads Future \ - InteractiveUtils LazyArtifacts LibGit2 LibCURL Logging Markdown Mmap \ - NetworkOptions Profile Printf Pkg REPL Serialization SharedArrays SparseArrays \ - Statistics Tar Test TOML Unicode UUIDs \ + InteractiveUtils JuliaSyntaxHighlighting LazyArtifacts LibGit2 LibCURL Logging \ + Markdown Mmap NetworkOptions Profile Printf Pkg REPL Serialization SharedArrays \ + SparseArrays Statistics StyledStrings SuiteSparse_jll Tar Test TOML Unicode UUIDs \ dSFMT_jll GMP_jll libLLVM_jll LLD_jll LLVMLibUnwind_jll LibUnwind_jll LibUV_jll \ LibCURL_jll LibSSH2_jll LibGit2_jll nghttp2_jll MozillaCACerts_jll MbedTLS_jll \ MPFR_jll OpenLibm_jll PCRE2_jll p7zip_jll Zlib_jll - STDLIBS := $(STDLIBS_WITHIN_SYSIMG) $(INDEPENDENT_STDLIBS) VERSDIR := v$(shell cut -d. -f1-2 < $(JULIAHOME)/VERSION) -SYSIMG_STDLIB_SRCS = +SYSIMG_STDLIBS_SRCS = +INDEPENDENT_STDLIBS_SRCS = define STDLIB_srcs $1_SRCS := $$(shell find $$(build_datarootdir)/julia/stdlib/$$(VERSDIR)/$1/src -name \*.jl) \ - $$(wildcard $$(build_prefix)/manifest/$$(VERSDIR)/$1) $$(build_datarootdir)/julia/stdlib/$$(VERSDIR)/$1/Project.toml +$$(wildcard $$(build_prefix)/manifest/$$(VERSDIR)/$1) $$(build_datarootdir)/julia/stdlib/$$(VERSDIR)/$1/Project.toml + ifneq ($(filter $(1),$(STDLIBS_WITHIN_SYSIMG)),) - SYSIMG_STDLIB_SRCS += $$($1_SRCS) + SYSIMG_STDLIBS_SRCS += $$($1_SRCS) +else + INDEPENDENT_STDLIBS_SRCS += $$($1_SRCS) endif endef diff --git a/sysimage.mk b/sysimage.mk index 44c5762ec8b81..b5f47be0160a4 100644 --- a/sysimage.mk +++ b/sysimage.mk @@ -26,6 +26,9 @@ COMPILER_SRCS := $(addprefix $(JULIAHOME)/, \ base/docs/core.jl \ base/abstractarray.jl \ base/abstractdict.jl \ + base/abstractset.jl \ + base/iddict.jl \ + base/idset.jl \ base/array.jl \ base/bitarray.jl \ base/bitset.jl \ @@ -54,7 +57,7 @@ COMPILER_SRCS += $(shell find $(JULIAHOME)/base/compiler -name \*.jl) # sort these to remove duplicates BASE_SRCS := $(sort $(shell find $(JULIAHOME)/base -name \*.jl -and -not -name sysimg.jl) \ $(shell find $(BUILDROOT)/base -name \*.jl -and -not -name sysimg.jl)) -STDLIB_SRCS := $(JULIAHOME)/base/sysimg.jl $(SYSIMG_STDLIB_SRCS) +STDLIB_SRCS := $(JULIAHOME)/base/sysimg.jl $(SYSIMG_STDLIBS_SRCS) RELBUILDROOT := $(call rel_path,$(JULIAHOME)/base,$(BUILDROOT)/base)/ # <-- make sure this always has a trailing slash $(build_private_libdir)/corecompiler.ji: $(COMPILER_SRCS) diff --git a/test/.gitignore b/test/.gitignore index a1af9ae3d44bf..20bf199b87c74 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -2,3 +2,6 @@ /ccalltest /ccalltest.s /libccalltest.* +/relocatedepot +/RelocationTestPkg2/src/foo.txt +/RelocationTestPkg*/Manifest.toml diff --git a/test/Makefile b/test/Makefile index 88dbe5b2b4ed6..1b9cb377c943d 100644 --- a/test/Makefile +++ b/test/Makefile @@ -7,14 +7,14 @@ STDLIBDIR := $(build_datarootdir)/julia/stdlib/$(VERSDIR) # TODO: this Makefile ignores BUILDDIR, except for computing JULIA_EXECUTABLE export JULIA_DEPOT_PATH := $(build_prefix)/share/julia -export JULIA_LOAD_PATH := @stdlib +export JULIA_LOAD_PATH := @$(PATHSEP)@stdlib unexport JULIA_PROJECT := unexport JULIA_BINDIR := TESTGROUPS = unicode strings compiler TESTS = all default stdlib $(TESTGROUPS) \ $(patsubst $(STDLIBDIR)/%/,%,$(dir $(wildcard $(STDLIBDIR)/*/.))) \ - $(filter-out runtests testdefs, \ + $(filter-out runtests testdefs relocatedepot, \ $(patsubst $(SRCDIR)/%.jl,%,$(wildcard $(SRCDIR)/*.jl))) \ $(foreach group,$(TESTGROUPS), \ $(patsubst $(SRCDIR)/%.jl,%,$(wildcard $(SRCDIR)/$(group)/*.jl))) @@ -34,6 +34,32 @@ $(addprefix revise-, $(TESTS)): revise-% : @cd $(SRCDIR) && \ $(call PRINT_JULIA, $(call spawn,$(JULIA_EXECUTABLE)) --check-bounds=yes --startup-file=no --depwarn=error ./runtests.jl --revise $*) +relocatedepot: + @rm -rf $(SRCDIR)/relocatedepot + @cd $(SRCDIR) && \ + $(call PRINT_JULIA, $(call spawn,$(JULIA_EXECUTABLE)) --check-bounds=yes --startup-file=no --depwarn=error ./runtests.jl $@) + @mkdir $(SRCDIR)/relocatedepot + @cp -R $(build_datarootdir)/julia $(SRCDIR)/relocatedepot + @cp -R $(SRCDIR)/RelocationTestPkg1 $(SRCDIR)/relocatedepot + @cp -R $(SRCDIR)/RelocationTestPkg2 $(SRCDIR)/relocatedepot + @cp -R $(SRCDIR)/RelocationTestPkg3 $(SRCDIR)/relocatedepot + @cp -R $(SRCDIR)/RelocationTestPkg4 $(SRCDIR)/relocatedepot + @cd $(SRCDIR) && \ + $(call PRINT_JULIA, $(call spawn,RELOCATEDEPOT="" $(JULIA_EXECUTABLE)) --check-bounds=yes --startup-file=no --depwarn=error ./runtests.jl $@) + +revise-relocatedepot: revise-% : + @rm -rf $(SRCDIR)/relocatedepot + @cd $(SRCDIR) && \ + $(call PRINT_JULIA, $(call spawn,$(JULIA_EXECUTABLE)) --check-bounds=yes --startup-file=no --depwarn=error ./runtests.jl --revise $*) + @mkdir $(SRCDIR)/relocatedepot + @cp -R $(build_datarootdir)/julia $(SRCDIR)/relocatedepot + @cp -R $(SRCDIR)/RelocationTestPkg1 $(SRCDIR)/relocatedepot + @cp -R $(SRCDIR)/RelocationTestPkg2 $(SRCDIR)/relocatedepot + @cp -R $(SRCDIR)/RelocationTestPkg3 $(SRCDIR)/relocatedepot + @cp -R $(SRCDIR)/RelocationTestPkg4 $(SRCDIR)/relocatedepot + @cd $(SRCDIR) && \ + $(call PRINT_JULIA, $(call spawn,RELOCATEDEPOT="" $(JULIA_EXECUTABLE)) --check-bounds=yes --startup-file=no --depwarn=error ./runtests.jl --revise $*) + embedding: @$(MAKE) -C $(SRCDIR)/$@ check $(EMBEDDING_ARGS) @@ -46,5 +72,6 @@ clangsa: clean: @$(MAKE) -C embedding $@ $(EMBEDDING_ARGS) @$(MAKE) -C gcext $@ $(GCEXT_ARGS) + @$(MAKE) -C llvmpasses $@ -.PHONY: $(TESTS) $(addprefix revise-, $(TESTS)) embedding gcext clangsa clean +.PHONY: $(TESTS) $(addprefix revise-, $(TESTS)) relocatedepot revise-relocatedepot embedding gcext clangsa clean diff --git a/test/RelocationTestPkg1/Project.toml b/test/RelocationTestPkg1/Project.toml new file mode 100644 index 0000000000000..4b5b67c3aef2d --- /dev/null +++ b/test/RelocationTestPkg1/Project.toml @@ -0,0 +1,3 @@ +name = "RelocationTestPkg1" +uuid = "854e1adb-5a97-46bf-a391-1cfe05ac726d" +version = "0.1.0" diff --git a/test/RelocationTestPkg1/src/RelocationTestPkg1.jl b/test/RelocationTestPkg1/src/RelocationTestPkg1.jl new file mode 100644 index 0000000000000..a86543a61b3f8 --- /dev/null +++ b/test/RelocationTestPkg1/src/RelocationTestPkg1.jl @@ -0,0 +1,5 @@ +module RelocationTestPkg1 + +greet() = print("Hello World!") + +end # module RelocationTestPkg1 diff --git a/test/RelocationTestPkg1/src/foo.txt b/test/RelocationTestPkg1/src/foo.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/test/RelocationTestPkg2/Project.toml b/test/RelocationTestPkg2/Project.toml new file mode 100644 index 0000000000000..b909269a0894c --- /dev/null +++ b/test/RelocationTestPkg2/Project.toml @@ -0,0 +1,3 @@ +name = "RelocationTestPkg2" +uuid = "8d933983-b090-4b0b-a37e-c34793f459d1" +version = "0.1.0" diff --git a/test/RelocationTestPkg2/src/RelocationTestPkg2.jl b/test/RelocationTestPkg2/src/RelocationTestPkg2.jl new file mode 100644 index 0000000000000..dc36a06cb48d4 --- /dev/null +++ b/test/RelocationTestPkg2/src/RelocationTestPkg2.jl @@ -0,0 +1,7 @@ +module RelocationTestPkg2 + +include_dependency("foo.txt") +include_dependency("foodir") +greet() = print("Hello World!") + +end # module RelocationTestPkg2 diff --git a/test/RelocationTestPkg2/src/foo.txt b/test/RelocationTestPkg2/src/foo.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/test/RelocationTestPkg3/Project.toml b/test/RelocationTestPkg3/Project.toml new file mode 100644 index 0000000000000..61882cb5cda65 --- /dev/null +++ b/test/RelocationTestPkg3/Project.toml @@ -0,0 +1,3 @@ +name = "RelocationTestPkg3" +uuid = "1ba4f954-9da9-4cd2-9ca7-6250235df52c" +version = "0.1.0" diff --git a/test/RelocationTestPkg3/src/RelocationTestPkg3.jl b/test/RelocationTestPkg3/src/RelocationTestPkg3.jl new file mode 100644 index 0000000000000..6ed8e1e560a99 --- /dev/null +++ b/test/RelocationTestPkg3/src/RelocationTestPkg3.jl @@ -0,0 +1,7 @@ +module RelocationTestPkg3 + +include_dependency("bar.txt", track_content=true) +include_dependency("bardir", track_content=true) +greet() = print("Hello World!") + +end # module RelocationTestPkg3 diff --git a/test/RelocationTestPkg3/src/bar.txt b/test/RelocationTestPkg3/src/bar.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/test/RelocationTestPkg4/Project.toml b/test/RelocationTestPkg4/Project.toml new file mode 100644 index 0000000000000..8334a684f064e --- /dev/null +++ b/test/RelocationTestPkg4/Project.toml @@ -0,0 +1,6 @@ +name = "RelocationTestPkg4" +uuid = "d423d817-d7e9-49ac-b245-9d9d6db0b429" +version = "0.1.0" + +[deps] +RelocationTestPkg1 = "854e1adb-5a97-46bf-a391-1cfe05ac726d" diff --git a/test/RelocationTestPkg4/src/RelocationTestPkg4.jl b/test/RelocationTestPkg4/src/RelocationTestPkg4.jl new file mode 100644 index 0000000000000..d24a51d19a918 --- /dev/null +++ b/test/RelocationTestPkg4/src/RelocationTestPkg4.jl @@ -0,0 +1,5 @@ +module RelocationTestPkg4 + +greet() = print("Hello World!") + +end # module RelocationTestPkg4 diff --git a/test/abstractarray.jl b/test/abstractarray.jl index 1bf61c856cb76..04d3ccc6805d2 100644 --- a/test/abstractarray.jl +++ b/test/abstractarray.jl @@ -5,6 +5,15 @@ using Random, LinearAlgebra isdefined(Main, :InfiniteArrays) || @eval Main include("testhelpers/InfiniteArrays.jl") using .Main.InfiniteArrays +isdefined(Main, :StructArrays) || @eval Main include("testhelpers/StructArrays.jl") +using .Main.StructArrays + +isdefined(Main, :FillArrays) || @eval Main include("testhelpers/FillArrays.jl") +using .Main.FillArrays + +isdefined(Main, :SizedArrays) || @eval Main include("testhelpers/SizedArrays.jl") +using .Main.SizedArrays + A = rand(5,4,3) @testset "Bounds checking" begin @test checkbounds(Bool, A, 1, 1, 1) == true @@ -84,6 +93,7 @@ end @test checkbounds(Bool, A, 1:60) == true @test checkbounds(Bool, A, 1:61) == false @test checkbounds(Bool, A, 2, 2, 2, 1:1) == true # extra indices + @test checkbounds(Bool, A, 2, 2, 2, 10:9) == true @test checkbounds(Bool, A, 2, 2, 2, 1:2) == false @test checkbounds(Bool, A, 1:5, 1:4) == false @test checkbounds(Bool, A, 1:5, 1:12) == false @@ -104,6 +114,7 @@ end @test checkbounds(Bool, A, trues(5), trues(13)) == false @test checkbounds(Bool, A, trues(6), trues(12)) == false @test checkbounds(Bool, A, trues(5, 4, 3)) == true + @test checkbounds(Bool, A, trues(5, 4, 3, 1)) == true # issue 45867 @test checkbounds(Bool, A, trues(5, 4, 2)) == false @test checkbounds(Bool, A, trues(5, 12)) == false @test checkbounds(Bool, A, trues(1, 5), trues(1, 4, 1), trues(1, 1, 3)) == false @@ -111,7 +122,9 @@ end @test checkbounds(Bool, A, trues(1, 5), trues(1, 5, 1), trues(1, 1, 3)) == false @test checkbounds(Bool, A, trues(1, 5), :, 2) == false @test checkbounds(Bool, A, trues(5, 4), trues(3)) == true - @test checkbounds(Bool, A, trues(4, 4), trues(3)) == true + @test checkbounds(Bool, A, trues(5), trues(4, 3, 1)) == true + @test checkbounds(Bool, A, trues(5, 4), trues(3, 2)) == false + @test checkbounds(Bool, A, trues(4, 4), trues(3)) == false @test checkbounds(Bool, A, trues(5, 4), trues(2)) == false @test checkbounds(Bool, A, trues(6, 4), trues(3)) == false @test checkbounds(Bool, A, trues(5, 4), trues(4)) == false @@ -134,6 +147,10 @@ end @test checkbounds(Bool, A, [CartesianIndex((6, 4))], 3) == false @test checkbounds(Bool, A, [CartesianIndex((5, 5))], 3) == false @test checkbounds(Bool, A, [CartesianIndex((5, 4))], 4) == false + @test checkbounds(Bool, A, 5, [CartesianIndex((4, 3, 1))]) == true + @test checkbounds(Bool, A, 5, [CartesianIndex((4, 3, 2))]) == false + @test_throws ArgumentError checkbounds(Bool, A, [CartesianIndex((4, 3)), CartesianIndex((4,))]) + @test_throws ArgumentError checkbounds(Bool, A, [CartesianIndex((1,)), 1]) end @testset "index conversion" begin @@ -318,6 +335,13 @@ end end end +@testset "copy for LinearIndices/CartesianIndices" begin + C = CartesianIndices((1:2, 1:4)) + @test copy(C) === C + L = LinearIndices((1:2, 1:4)) + @test copy(L) === L +end + # token type on which to dispatch testing methods in order to avoid potential # name conflicts elsewhere in the base test suite mutable struct TestAbstractArray end @@ -487,6 +511,13 @@ function test_vector_indexing(::Type{T}, shape, ::Type{TestAbstractArray}) where mask = bitrand(shape) @testset "test logical indexing" begin + let + masks1 = (mask,) + @test only(@inferred(to_indices(A, masks1))) isa Base.LogicalIndex{Int} + if IndexStyle(B) isa IndexCartesian + @test only(@inferred(to_indices(B, masks1))) === Base.LogicalIndex(mask) + end + end @test B[mask] == A[mask] == B[findall(mask)] == A[findall(mask)] == LinearIndices(mask)[findall(mask)] @test B[vec(mask)] == A[vec(mask)] == LinearIndices(mask)[findall(mask)] mask1 = bitrand(size(A, 1)) @@ -496,10 +527,15 @@ function test_vector_indexing(::Type{T}, shape, ::Type{TestAbstractArray}) where @test B[mask1, 1, trailing2] == A[mask1, 1, trailing2] == LinearIndices(mask)[findall(mask1)] if ndims(B) > 1 + slice = ntuple(Returns(:), ndims(B)-1) maskfront = bitrand(shape[1:end-1]) - Bslice = B[ntuple(i->(:), ndims(B)-1)..., 1] - @test B[maskfront,1] == Bslice[maskfront] + Bslicefront = B[slice..., 1] + @test B[maskfront, 1] == Bslicefront[maskfront] @test size(B[maskfront, 1:1]) == (sum(maskfront), 1) + maskend = bitrand(shape[2:end]) + Bsliceend = B[1, slice...] + @test B[1 ,maskend] == Bsliceend[maskend] + @test size(B[1:1, maskend]) == (1, sum(maskend)) end end end @@ -522,12 +558,24 @@ function test_primitives(::Type{T}, shape, ::Type{TestAbstractArray}) where T @test firstindex(B, 1) == firstindex(A, 1) == first(axes(B, 1)) @test firstindex(B, 2) == firstindex(A, 2) == first(axes(B, 2)) - # isassigned(a::AbstractArray, i::Int...) + @test !isassigned(B) + # isassigned(a::AbstractArray, i::Integer...) j = rand(1:length(B)) @test isassigned(B, j) if T == T24Linear @test !isassigned(B, length(B) + 1) end + # isassigned(a::AbstractArray, i::CartesianIndex) + @test isassigned(B, first(CartesianIndices(B))) + ind = last(CartesianIndices(B)) + @test !isassigned(B, ind + oneunit(ind)) + # isassigned(a::AbstractArray, i::Union{Integer,CartesianIndex}...) + @test isassigned(B, Int16.(first.(axes(B)))..., CartesianIndex(1,1)) + # Bool isn't a valid index + @test_throws ArgumentError isassigned(B, Bool.(first.(axes(B)))..., CartesianIndex(1,1)) + @test_throws ArgumentError isassigned(B, Bool.(first.(axes(B)))...) + @test_throws ArgumentError isassigned(B, true) + @test_throws ArgumentError isassigned(B, false) # reshape(a::AbstractArray, dims::Dims) @test_throws DimensionMismatch reshape(B, (0, 1)) @@ -799,9 +847,8 @@ Base.getindex(A::TSlowNIndexes{T,2}, i::Int, j::Int) where {T} = A.data[i,j] @test isa(map(Set, Array[[1,2],[3,4]]), Vector{Set{Int}}) end -@testset "mapping over scalars and empty arguments:" begin +@testset "mapping over scalars" begin @test map(sin, 1) === sin(1) - @test map(()->1234) === 1234 end function test_UInt_indexing(::Type{TestAbstractArray}) @@ -999,6 +1046,16 @@ end @test isempty(v) @test isempty(v2::Vector{Int}) @test isempty(v3::Vector{Float64}) + + S = StructArrays.StructArray{Complex{Int}}((v, v)) + for T in (Complex{Int}, ComplexF64) + S0 = empty(S, T) + @test S0 isa StructArrays.StructArray{T} + @test length(S0) == 0 + end + S0 = empty(S, String) + @test S0 isa Vector{String} + @test length(S0) == 0 end @testset "CartesianIndices" begin @@ -1079,6 +1136,7 @@ end @testset "IndexStyle for various types" begin @test Base.IndexStyle(UpperTriangular) == IndexCartesian() # subtype of AbstractArray, not of Array @test Base.IndexStyle(Vector) == IndexLinear() + @test Base.IndexStyle(Memory) == IndexLinear() @test Base.IndexStyle(UnitRange) == IndexLinear() @test Base.IndexStyle(UpperTriangular(rand(3, 3)), [1; 2; 3]) == IndexCartesian() @test Base.IndexStyle(UpperTriangular(rand(3, 3)), rand(3, 3), [1; 2; 3]) == IndexCartesian() @@ -1108,23 +1166,23 @@ end @testset "sizeof" begin let arrUInt8 = zeros(UInt8, 10) @test sizeof(arrUInt8) == 10 - @test Core.sizeof(arrUInt8) == 10 + @test Core.sizeof(arrUInt8) == 3 * sizeof(Int) end let arrUInt32 = zeros(UInt32, 10) @test sizeof(arrUInt32) == 40 - @test Core.sizeof(arrUInt32) == 40 + @test Core.sizeof(arrUInt32) == 3 * sizeof(Int) end let arrFloat64 = zeros(Float64, 10, 10) @test sizeof(arrFloat64) == 800 - @test Core.sizeof(arrFloat64) == 800 + @test Core.sizeof(arrFloat64) == 4 * sizeof(Int) end # Test union arrays (Issue #23321) let arrUnion = Union{Int64, Cvoid}[rand(Bool) ? k : nothing for k = 1:10] @test sizeof(arrUnion) == 80 - @test Core.sizeof(arrUnion) == 80 + @test Core.sizeof(arrUnion) == 3 * sizeof(Int) end # Test non-power of 2 types (Issue #35884) @@ -1138,7 +1196,7 @@ end let arrayOfUInt48 = [a, b, c] f35884(x) = sizeof(x) @test f35884(arrayOfUInt48) == 24 - @test Core.sizeof(arrayOfUInt48) == 24 + @test Core.sizeof(arrayOfUInt48) == 3 * sizeof(Int) end end @@ -1164,7 +1222,7 @@ function Base.getindex(S::Strider{<:Any,N}, I::Vararg{Int,N}) where {N} end Base.strides(S::Strider) = S.strides Base.elsize(::Type{<:Strider{T}}) where {T} = Base.elsize(Vector{T}) -Base.unsafe_convert(::Type{Ptr{T}}, S::Strider{T}) where {T} = pointer(S.data, S.offset) +Base.cconvert(::Type{Ptr{T}}, S::Strider{T}) where {T} = MemoryRef(S.data.ref, S.offset) @testset "Simple 3d strided views and permutes" for sz in ((5, 3, 2), (7, 11, 13)) A = collect(reshape(1:prod(sz), sz)) @@ -1353,6 +1411,28 @@ Base.pushfirst!(tpa::TestPushArray{T}, a::T) where T = pushfirst!(tpa.data, a) @test tpa.data == reverse(collect(1:6)) end +mutable struct SimpleArray{T} <: AbstractVector{T} + els::Vector{T} +end +Base.size(sa::SimpleArray) = size(sa.els) +Base.getindex(sa::SimpleArray, idx...) = getindex(sa.els, idx...) +Base.setindex!(sa::SimpleArray, v, idx...) = setindex!(sa.els, v, idx...) +Base.resize!(sa::SimpleArray, n) = resize!(sa.els, n) +Base.copy(sa::SimpleArray) = SimpleArray(copy(sa.els)) + +isdefined(Main, :OffsetArrays) || @eval Main include("testhelpers/OffsetArrays.jl") +using .Main.OffsetArrays + +@testset "Failing `$f` should not grow the array $a" for + f in (push!, append!, pushfirst!, prepend!), + a in (["foo", "Bar"], SimpleArray(["foo", "Bar"]), OffsetVector(["foo", "Bar"], 0:1)) + for args in ((1,), (1,2), ([1], [2]), [1]) + orig = copy(a) + @test_throws Exception f(a, args...) + @test a == orig + end +end + @testset "splatting into hvcat" begin t = (1, 2) @test [t...; 3 4] == [1 2; 3 4] @@ -1759,47 +1839,50 @@ module IRUtils include("compiler/irutils.jl") end -@testset "strides for ReshapedArray" begin - function check_strides(A::AbstractArray) - # Make sure stride(A, i) is equivalent with strides(A)[i] (if 1 <= i <= ndims(A)) - dims = ntuple(identity, ndims(A)) - map(i -> stride(A, i), dims) == @inferred(strides(A)) || return false - # Test strides via value check. - for i in eachindex(IndexLinear(), A) - A[i] === Base.unsafe_load(pointer(A, i)) || return false - end - return true +function check_pointer_strides(A::AbstractArray) + # Make sure stride(A, i) is equivalent with strides(A)[i] (if 1 <= i <= ndims(A)) + dims = ntuple(identity, ndims(A)) + map(i -> stride(A, i), dims) == @inferred(strides(A)) || return false + # Test pointer via value check. + first(A) === Base.unsafe_load(pointer(A)) || return false + # Test strides via value check. + for i in eachindex(IndexLinear(), A) + A[i] === Base.unsafe_load(pointer(A, i)) || return false end + return true +end + +@testset "strides for ReshapedArray" begin # Type-based contiguous Check a = vec(reinterpret(reshape, Int16, reshape(view(reinterpret(Int32, randn(10)), 2:11), 5, :))) f(a) = only(strides(a)); @test IRUtils.fully_eliminated(f, Base.typesof(a)) && f(a) == 1 # General contiguous check a = view(rand(10,10), 1:10, 1:10) - @test check_strides(vec(a)) + @test check_pointer_strides(vec(a)) b = view(parent(a), 1:9, 1:10) @test_throws "Input is not strided." strides(vec(b)) # StridedVector parent for n in 1:3 a = view(collect(1:60n), 1:n:60n) - @test check_strides(reshape(a, 3, 4, 5)) - @test check_strides(reshape(a, 5, 6, 2)) + @test check_pointer_strides(reshape(a, 3, 4, 5)) + @test check_pointer_strides(reshape(a, 5, 6, 2)) b = view(parent(a), 60n:-n:1) - @test check_strides(reshape(b, 3, 4, 5)) - @test check_strides(reshape(b, 5, 6, 2)) + @test check_pointer_strides(reshape(b, 3, 4, 5)) + @test check_pointer_strides(reshape(b, 5, 6, 2)) end # StridedVector like parent a = randn(10, 10, 10) b = view(a, 1:10, 1:1, 5:5) - @test check_strides(reshape(b, 2, 5)) + @test check_pointer_strides(reshape(b, 2, 5)) # Other StridedArray parent a = view(randn(10,10), 1:9, 1:10) - @test check_strides(reshape(a,3,3,2,5)) - @test check_strides(reshape(a,3,3,5,2)) - @test check_strides(reshape(a,9,5,2)) - @test check_strides(reshape(a,3,3,10)) - @test check_strides(reshape(a,1,3,1,3,1,5,1,2)) - @test check_strides(reshape(a,3,3,5,1,1,2,1,1)) + @test check_pointer_strides(reshape(a,3,3,2,5)) + @test check_pointer_strides(reshape(a,3,3,5,2)) + @test check_pointer_strides(reshape(a,9,5,2)) + @test check_pointer_strides(reshape(a,3,3,10)) + @test check_pointer_strides(reshape(a,1,3,1,3,1,5,1,2)) + @test check_pointer_strides(reshape(a,3,3,5,1,1,2,1,1)) @test_throws "Input is not strided." strides(reshape(a,3,6,5)) @test_throws "Input is not strided." strides(reshape(a,3,2,3,5)) @test_throws "Input is not strided." strides(reshape(a,3,5,3,2)) @@ -1812,7 +1895,14 @@ end @test @inferred(strides(a)) == (1, 1, 1) # Dense parent (but not StridedArray) A = reinterpret(Int8, reinterpret(reshape, Int16, rand(Int8, 2, 3, 3))) - @test check_strides(reshape(A, 3, 2, 3)) + @test check_pointer_strides(reshape(A, 3, 2, 3)) +end + +@testset "pointer for SubArray with none-dense parent." begin + a = view(Matrix(reshape(0x01:0xc8, 20, :)), 1:2:20, :) + b = reshape(a, 20, :) + @test check_pointer_strides(view(b, 2:11, 1:5)) + @test check_pointer_strides(view(b, reshape(2:11, 2, :), 1:5)) end @testset "stride for 0 dims array #44087" begin @@ -1842,13 +1932,17 @@ end @testset "type-based offset axes check" begin a = randn(ComplexF64, 10) + b = randn(ComplexF64, 4, 4, 4, 4) ta = reinterpret(Float64, a) tb = reinterpret(Float64, view(a, 1:2:10)) tc = reinterpret(Float64, reshape(view(a, 1:3:10), 2, 2, 1)) + td = view(b, :, :, 1, 1) # Issue #44040 @test IRUtils.fully_eliminated(Base.require_one_based_indexing, Base.typesof(ta, tc)) @test IRUtils.fully_eliminated(Base.require_one_based_indexing, Base.typesof(tc, tc)) @test IRUtils.fully_eliminated(Base.require_one_based_indexing, Base.typesof(ta, tc, tb)) + # Issue #49332 + @test IRUtils.fully_eliminated(Base.require_one_based_indexing, Base.typesof(td, td, td)) # Ranges && CartesianIndices @test IRUtils.fully_eliminated(Base.require_one_based_indexing, Base.typesof(1:10, Base.OneTo(10), 1.0:2.0, LinRange(1.0, 2.0, 2), 1:2:10, CartesianIndices((1:2:10, 1:2:10)))) # Remind us to call `any` in `Base.has_offset_axes` once our compiler is ready. @@ -1867,3 +1961,178 @@ f45952(x) = [x;;] @test_throws "invalid index: true of type Bool" isassigned(A, 1, true) @test_throws "invalid index: true of type Bool" isassigned(A, true) end + +@testset "repeat for FillArrays" begin + f = FillArrays.Fill(3, (4,)) + @test repeat(f, 2) === FillArrays.Fill(3, (8,)) + @test repeat(f, 2, 3) === FillArrays.Fill(3, (8, 3)) + @test repeat(f, inner=(1,2), outer=(3,1)) === repeat(f, 3, 2) === FillArrays.Fill(3, (12,2)) + f = FillArrays.Fill(3, (4, 2)) + @test repeat(f, 2, 3) === FillArrays.Fill(3, (8, 6)) + @test repeat(f, 2, 3, 4) === FillArrays.Fill(3, (8, 6, 4)) + @test repeat(f, inner=(1,2), outer=(3,1)) === FillArrays.Fill(3, (12, 4)) +end + +@testset "zero" begin + @test zero([1 2; 3 4]) isa Matrix{Int} + @test zero([1 2; 3 4]) == [0 0; 0 0] + + @test zero([1.0]) isa Vector{Float64} + @test zero([1.0]) == [0.0] + + @test zero([[2,2], [3,3,3]]) isa Vector{Vector{Int}} + @test zero([[2,2], [3,3,3]]) == [[0,0], [0, 0, 0]] + + + @test zero(Union{Float64, Missing}[missing]) == [0.0] + struct CustomNumber <: Number + val::Float64 + end + Base.zero(::Type{CustomNumber}) = CustomNumber(0.0) + @test zero([CustomNumber(5.0)]) == [CustomNumber(0.0)] + @test zero(Union{CustomNumber, Missing}[missing]) == [CustomNumber(0.0)] + @test zero(Vector{Union{CustomNumber, Missing}}(undef, 1)) == [CustomNumber(0.0)] +end + +@testset "`_prechecked_iterate` optimization" begin + function test_prechecked_iterate(iter) + Js = Base._prechecked_iterate(iter) + for I in iter + J, s = Js::NTuple{2,Any} + @test J === I + Js = Base._prechecked_iterate(iter, s) + end + end + test_prechecked_iterate(1:10) + test_prechecked_iterate(Base.OneTo(10)) + test_prechecked_iterate(CartesianIndices((3, 3))) + test_prechecked_iterate(CartesianIndices(())) + test_prechecked_iterate(LinearIndices((3, 3))) + test_prechecked_iterate(LinearIndices(())) + test_prechecked_iterate(Base.SCartesianIndices2{3}(1:3)) +end + +@testset "IndexStyles in copyto!" begin + A = rand(3,2) + B = zeros(size(A)) + colons = ntuple(_->:, ndims(B)) + # Ensure that the AbstractArray methods are hit + # by using views instead of Arrays + @testset "IndexLinear - IndexLinear" begin + B .= 0 + copyto!(view(B, colons...), A) + @test B == A + end + @testset "IndexLinear - IndexCartesian" begin + B .= 0 + copyto!(view(B, colons...), view(A, axes(A)...)) + @test B == A + end + @testset "IndexCartesian - IndexLinear" begin + B .= 0 + copyto!(view(B, axes(B)...), A) + @test B == A + end + @testset "IndexCartesian - IndexCartesian" begin + B .= 0 + copyto!(view(B, axes(B)...), view(A, axes(A)...)) + @test B == A + end +end + +@testset "_unsetindex!" begin + struct MyMatrixUnsetIndexCartInds{T,A<:AbstractMatrix{T}} <: AbstractMatrix{T} + data :: A + end + Base.size(A::MyMatrixUnsetIndexCartInds) = size(A.data) + Base.getindex(M::MyMatrixUnsetIndexCartInds, i::Int, j::Int) = M.data[i,j] + Base.setindex!(M::MyMatrixUnsetIndexCartInds, v, i::Int, j::Int) = setindex!(M.data, v, i, j) + struct MyMatrixUnsetIndexLinInds{T,A<:AbstractMatrix{T}} <: AbstractMatrix{T} + data :: A + end + Base.size(A::MyMatrixUnsetIndexLinInds) = size(A.data) + Base.getindex(M::MyMatrixUnsetIndexLinInds, i::Int) = M.data[i] + Base.setindex!(M::MyMatrixUnsetIndexLinInds, v, i::Int) = setindex!(M.data, v, i) + Base.IndexStyle(::Type{<:MyMatrixUnsetIndexLinInds}) = IndexLinear() + + function test_unsetindex(MT) + M = MT(ones(2,2)) + M2 = MT(Matrix{BigFloat}(undef, 2,2)) + copyto!(M, M2) + @test all(==(1), M) + M3 = MT(Matrix{BigFloat}(undef, 2,2)) + for i in eachindex(M3) + @test !isassigned(M3, i) + end + M3 .= 1 + @test_throws MethodError copyto!(M3, M2) + end + test_unsetindex(MyMatrixUnsetIndexCartInds) + test_unsetindex(MyMatrixUnsetIndexLinInds) +end + +@testset "reshape for offset arrays" begin + p = Base.IdentityUnitRange(3:4) + r = reshape(p, :, 1) + @test r[eachindex(r)] == UnitRange(p) + @test collect(r) == r + + struct ZeroBasedArray{T,N,A<:AbstractArray{T,N}} <: AbstractArray{T,N} + a :: A + function ZeroBasedArray(a::AbstractArray) + Base.require_one_based_indexing(a) + new{eltype(a), ndims(a), typeof(a)}(a) + end + end + Base.parent(z::ZeroBasedArray) = z.a + Base.size(z::ZeroBasedArray) = size(parent(z)) + Base.axes(z::ZeroBasedArray) = map(x -> Base.IdentityUnitRange(0:x - 1), size(parent(z))) + Base.getindex(z::ZeroBasedArray{<:Any, N}, i::Vararg{Int,N}) where {N} = parent(z)[map(x -> x + 1, i)...] + Base.setindex!(z::ZeroBasedArray{<:Any, N}, val, i::Vararg{Int,N}) where {N} = parent(z)[map(x -> x + 1, i)...] = val + + z = ZeroBasedArray(collect(1:4)) + r2 = reshape(z, :, 1) + @test r2[CartesianIndices(r2)] == r2[LinearIndices(r2)] + r2[firstindex(r2)] = 34 + @test z[0] == 34 + r2[eachindex(r2)] = r2 .* 2 + for (i, j) in zip(eachindex(r2), eachindex(z)) + @test r2[i] == z[j] + end +end + +@testset "zero for arbitrary axes" begin + r = SizedArrays.SOneTo(2) + s = Base.OneTo(2) + _to_oneto(x::Integer) = Base.OneTo(2) + _to_oneto(x::Union{Base.OneTo, SizedArrays.SOneTo}) = x + for (f, v) in ((zeros, 0), (ones, 1), ((x...)->fill(3,x...),3)) + for ax in ((r,r), (s, r), (2, r)) + A = f(ax...) + @test axes(A) == map(_to_oneto, ax) + if all(x -> x isa SizedArrays.SOneTo, ax) + @test A isa SizedArrays.SizedArray && parent(A) isa Array + else + @test A isa Array + end + @test all(==(v), A) + end + end +end + +@testset "one" begin + @test one([1 2; 3 4]) == [1 0; 0 1] + @test one([1 2; 3 4]) isa Matrix{Int} + + struct Mat <: AbstractMatrix{Int} + p::Matrix{Int} + end + Base.size(m::Mat) = size(m.p) + Base.IndexStyle(::Type{<:Mat}) = IndexLinear() + Base.getindex(m::Mat, i::Int) = m.p[i] + Base.setindex!(m::Mat, v, i::Int) = m.p[i] = v + Base.similar(::Mat, ::Type{Int}, size::NTuple{2,Int}) = Mat(Matrix{Int}(undef, size)) + + @test one(Mat([1 2; 3 4])) == Mat([1 0; 0 1]) + @test one(Mat([1 2; 3 4])) isa Mat +end diff --git a/test/ambiguous.jl b/test/ambiguous.jl index bbbe509439083..748660cc9c981 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -75,7 +75,7 @@ let io = IOBuffer() 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)) - @test_throws(ErrorException("no unique matching method found for the specified argument types"), + @test_throws("Calling invoke(f, t, args...) would throw:\nMethodError: no method matching ambig", which(ambig, (UInt8, Int))) @test length(code_typed(ambig, (UInt8, Int))) == 0 end @@ -332,23 +332,22 @@ end @test length(detect_unbound_args(M25341; recursive=true)) == 1 # Test that Core and Base are free of UndefVarErrors -# not using isempty so this prints more information when it fails @testset "detect_unbound_args in Base and Core" begin # TODO: review this list and remove everything between test_broken and test let need_to_handle_undef_sparam = Set{Method}(detect_unbound_args(Core; recursive=true)) pop!(need_to_handle_undef_sparam, which(Core.Compiler.eltype, Tuple{Type{Tuple{Any}}})) - @test_broken need_to_handle_undef_sparam == Set() + @test_broken isempty(need_to_handle_undef_sparam) pop!(need_to_handle_undef_sparam, which(Core.Compiler._cat, Tuple{Any, AbstractArray})) pop!(need_to_handle_undef_sparam, first(methods(Core.Compiler.same_names))) - @test need_to_handle_undef_sparam == Set() + @test isempty(need_to_handle_undef_sparam) end let need_to_handle_undef_sparam = Set{Method}(detect_unbound_args(Base; recursive=true, allowed_undefineds)) pop!(need_to_handle_undef_sparam, which(Base._totuple, (Type{Tuple{Vararg{E}}} where E, Any, Any))) pop!(need_to_handle_undef_sparam, which(Base.eltype, Tuple{Type{Tuple{Any}}})) pop!(need_to_handle_undef_sparam, first(methods(Base.same_names))) - @test_broken need_to_handle_undef_sparam == Set() + @test_broken isempty(need_to_handle_undef_sparam) pop!(need_to_handle_undef_sparam, which(Base._cat, Tuple{Any, AbstractArray})) pop!(need_to_handle_undef_sparam, which(Base.byteenv, (Union{AbstractArray{Pair{T,V}, 1}, Tuple{Vararg{Pair{T,V}}}} where {T<:AbstractString,V},))) pop!(need_to_handle_undef_sparam, which(Base.float, Tuple{AbstractArray{Union{Missing, T},N} where {T, N}})) @@ -357,7 +356,7 @@ end pop!(need_to_handle_undef_sparam, which(Base.zero, Tuple{Type{Union{Missing, T}} where T})) pop!(need_to_handle_undef_sparam, which(Base.one, Tuple{Type{Union{Missing, T}} where T})) pop!(need_to_handle_undef_sparam, which(Base.oneunit, Tuple{Type{Union{Missing, T}} where T})) - @test need_to_handle_undef_sparam == Set() + @test isempty(need_to_handle_undef_sparam) end end diff --git a/test/arrayops.jl b/test/arrayops.jl index ba02847094b0e..b64d08264e2d1 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -115,7 +115,10 @@ end @test convert(Array{Int,1}, r) == [2,3,4] @test_throws MethodError convert(Array{Int,2}, r) @test convert(Array{Int}, r) == [2,3,4] - @test Base.unsafe_convert(Ptr{Int}, r) == Base.unsafe_convert(Ptr{Int}, s) + let rc = Base.cconvert(Ptr{Int}, r), rs = Base.cconvert(Ptr{Int}, s) + @test rc == rs + @test Base.unsafe_convert(Ptr{Int}, rc) == Base.unsafe_convert(Ptr{Int}, rs) + end @test isa(r, StridedArray) # issue #22411 end @testset "linearslow" begin @@ -131,6 +134,7 @@ end @test convert(Array{Int,1}, r) == [2,3,5] @test_throws MethodError convert(Array{Int,2}, r) @test convert(Array{Int}, r) == [2,3,5] + # @test_throws ErrorException Base.cconvert(Ptr{Int}, r) broken=true @test_throws ErrorException Base.unsafe_convert(Ptr{Int}, r) r[2] = -1 @test a[3] == -1 @@ -737,6 +741,9 @@ end v = [1,2,3] @test permutedims(v) == [1 2 3] + zd = fill(0) + @test permutedims(zd, ()) == zd + x = PermutedDimsArray([1 2; 3 4], (2, 1)) @test size(x) == (2, 2) @test copy(x) == [1 3; 2 4] @@ -790,6 +797,14 @@ end oa = OffsetVector(copy(a), -1) @test circshift!(oa, 1) === oa @test oa == circshift(OffsetVector(a, -1), 1) + + # 1d circshift! (#53554) + a = [] + @test circshift!(a, 1) === a + @test circshift!(a, 1) == [] + a = [1:5;] + @test circshift!(a, 10) === a + @test circshift!(a, 10) == 1:5 end @testset "circcopy" begin @@ -1442,6 +1457,15 @@ end @test sortslices(B, dims=(1,3)) == B end +@testset "sortslices inference (#52019)" begin + x = rand(3, 2) + @inferred sortslices(x, dims=1) + @inferred sortslices(x, dims=(2,)) + x = rand(1, 2, 3) + @inferred sortslices(x, dims=(1,2)) + @inferred sortslices(x, dims=3, by=sum) +end + @testset "fill" begin @test fill!(Float64[1.0], -0.0)[1] === -0.0 A = fill(1.,3,3) @@ -1782,6 +1806,32 @@ end # offset array @test append!([1,2], OffsetArray([9,8], (-3,))) == [1,2,9,8] @test prepend!([1,2], OffsetArray([9,8], (-3,))) == [9,8,1,2] + + # Error recovery + A = [1, 2] + @test_throws MethodError append!(A, [1, 2, "hi"]) + @test A == [1, 2, 1, 2] + + oA = OffsetVector(A, 0:3) + @test_throws InexactError append!(oA, [1, 2, 3.01]) + @test oA == OffsetVector([1, 2, 1, 2, 1, 2], 0:5) + + @test_throws InexactError append!(A, (x for x in [1, 2, 3.1])) + @test A == [1, 2, 1, 2, 1, 2, 1, 2] + + @test_throws InexactError append!(A, (x for x in [1, 2, 3.1] if isfinite(x))) + @test A == [1, 2, 1, 2, 1, 2, 1, 2, 1, 2] + + @test_throws MethodError prepend!(A, [1, 2, "hi"]) + @test A == [2, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2] + + A = [1, 2] + @test_throws InexactError prepend!(A, (x for x in [1, 2, 3.1])) + @test A == [2, 1, 1, 2] + + A = [1, 2] + @test_throws InexactError prepend!(A, (x for x in [1, 2, 3.1] if isfinite(x))) + @test A == [2, 1, 1, 2] end let A = [1,2] @@ -2124,6 +2174,8 @@ R = CartesianIndices((3,0)) @test @inferred(eachindex(Base.IndexLinear(), a, b)) == 1:4 @test @inferred(eachindex(a, b)) == CartesianIndices((2,2)) @test @inferred(eachindex(a, a)) == 1:4 + @test @inferred(eachindex(a, a, a)) == 1:4 + @test @inferred(eachindex(a, a, b)) == CartesianIndices((2,2)) @test_throws DimensionMismatch eachindex(a, rand(3,3)) @test_throws DimensionMismatch eachindex(b, rand(3,3)) end @@ -2568,7 +2620,7 @@ end end @testset "sign, conj[!], ~" begin - local A, B, C + local A, B, C, D, E A = [-10,0,3] B = [-10.0,0.0,3.0] C = [1,im,0] @@ -2585,6 +2637,11 @@ end @test typeof(conj(A)) == Vector{Int} @test typeof(conj(B)) == Vector{Float64} @test typeof(conj(C)) == Vector{Complex{Int}} + D = [C copy(C); copy(C) copy(C)] + @test conj(D) == conj!(copy(D)) + E = [D, copy(D)] + @test conj(E) == conj!(copy(E)) + @test (@allocations conj!(E)) == 0 @test .~A == [9,-1,-4] @test typeof(.~A) == Vector{Int} @@ -2724,7 +2781,7 @@ end end @testset "accumulate, accumulate!" begin - @test accumulate(+, [1,2,3]) == [1, 3, 6] + @test accumulate(+, [1, 2, 3]) == [1, 3, 6] @test accumulate(min, [1 2; 3 4], dims=1) == [1 2; 1 2] @test accumulate(max, [1 2; 3 0], dims=2) == [1 2; 3 3] @test accumulate(+, Bool[]) == Int[] @@ -2741,12 +2798,15 @@ end @test accumulate(min, [1 0; 0 1], dims=1) == [1 0; 0 0] @test accumulate(min, [1 0; 0 1], dims=2) == [1 0; 0 0] + @test accumulate(+, [1, 2, 3], dims=1, init=1) == [2, 4, 7] + @test accumulate(*, [1, 4, 2], dims=1, init=2) == [2, 8, 16] + @test accumulate(min, [3 2 1; 3 2 1], dims=2) == [3 2 1; 3 2 1] @test accumulate(min, [3 2 1; 3 2 1], dims=2, init=2) == [2 2 1; 2 2 1] @test isa(accumulate(+, Int[]), Vector{Int}) @test isa(accumulate(+, Int[]; init=1.), Vector{Float64}) - @test accumulate(+, [1,2]; init=1) == [2, 4] + @test accumulate(+, [1, 2]; init=1) == [2, 4] arr = randn(4) @test accumulate(*, arr; init=1) ≈ accumulate(*, arr) @@ -2790,7 +2850,7 @@ end # asymmetric operation op(x,y) = 2x+y - @test accumulate(op, [10,20, 30]) == [10, op(10, 20), op(op(10, 20), 30)] == [10, 40, 110] + @test accumulate(op, [10, 20, 30]) == [10, op(10, 20), op(op(10, 20), 30)] == [10, 40, 110] @test accumulate(op, [10 20 30], dims=2) == [10 op(10, 20) op(op(10, 20), 30)] == [10 40 110] #25506 @@ -2799,6 +2859,33 @@ end @inferred accumulate(*, String[]) @test accumulate(*, ['a' 'b'; 'c' 'd'], dims=1) == ["a" "b"; "ac" "bd"] @test accumulate(*, ['a' 'b'; 'c' 'd'], dims=2) == ["a" "ab"; "c" "cd"] + + # #53438 + v = [(1, 2), (3, 4)] + @test_throws MethodError accumulate(+, v) + @test_throws MethodError cumsum(v) + @test_throws MethodError cumprod(v) + @test_throws MethodError accumulate(+, v; init=(0, 0)) + @test_throws MethodError accumulate(+, v; dims=1, init=(0, 0)) + + # Some checks to ensure we're identifying the widest needed eltype + # as identified in PR 53461 + @testset "Base._accumulate_promote_op" begin + # A somewhat contrived example where each call to `foo` + # will return a different type + foo(x::Bool, y::Int)::Int = x + y + foo(x::Int, y::Int)::Float64 = x + y + foo(x::Float64, y::Int)::ComplexF64 = x + y * im + foo(x::ComplexF64, y::Int)::String = string(x, "+", y) + + v = collect(1:5) + @test Base._accumulate_promote_op(foo, v; init=true) === Base._accumulate_promote_op(foo, v) == Union{Float64, String, ComplexF64} + @test Base._accumulate_promote_op(/, v) === Base._accumulate_promote_op(/, v; init=0) == Float64 + @test Base._accumulate_promote_op(+, v) === Base._accumulate_promote_op(+, v; init=0) === Int + @test Base._accumulate_promote_op(+, v; init=0.0) === Float64 + @test Base._accumulate_promote_op(+, Union{Int, Missing}[v...]) === Union{Int, Missing} + @test Base._accumulate_promote_op(+, Union{Int, Nothing}[v...]) === Union{Int, Nothing} + end end struct F21666{T <: Base.ArithmeticStyle} @@ -3126,3 +3213,50 @@ end @test c + zero(c) == c end end + +@testset "Wrapping Memory into Arrays with view and reshape" begin + mem::Memory{Int} = Memory{Int}(undef, 10) .= 11:20 + + @test_throws DimensionMismatch reshape(mem, 10, 10) + @test_throws DimensionMismatch reshape(mem, 5) + @test_throws BoundsError view(mem, 1:10, 1:10) + @test_throws BoundsError view(mem, 1:11) + @test_throws BoundsError view(mem, 3:11) + @test_throws BoundsError view(mem, 0:4) + + @test @inferred(view(mem, 1:5))::Vector{Int} == 11:15 + @test @inferred(view(mem, 1:2))::Vector{Int} == 11:12 + @test @inferred(view(mem, 1:10))::Vector{Int} == 11:20 + @test @inferred(view(mem, 3:8))::Vector{Int} == 13:18 + @test @inferred(view(mem, 20:19))::Vector{Int} == [] + @test @inferred(view(mem, -5:-7))::Vector{Int} == [] + @test @inferred(view(mem, :))::Vector{Int} == mem + @test @inferred(reshape(mem, 5, 2))::Matrix{Int} == reshape(11:20, 5, 2) + + # 53990 + @test @inferred(view(mem, unsigned(1):10))::Vector{Int} == 11:20 + + empty_mem = Memory{Module}(undef, 0) + @test_throws BoundsError view(empty_mem, 0:1) + @test_throws BoundsError view(empty_mem, 1:2) + @test_throws DimensionMismatch reshape(empty_mem, 1) + @test_throws DimensionMismatch reshape(empty_mem, 1, 2, 3) + @test_throws ArgumentError reshape(empty_mem, 2^16, 2^16, 2^16, 2^16) + + @test @inferred(view(empty_mem, 1:0))::Vector{Module} == [] + @test @inferred(view(empty_mem, 10:3))::Vector{Module} == [] + @test @inferred(view(empty_mem, :))::Vector{Module} == empty_mem + @test isempty(@inferred(reshape(empty_mem, 0, 7, 1))::Array{Module, 3}) + + offset_inds = OffsetArrays.IdOffsetRange(values=3:6, indices=53:56) + @test @inferred(view(collect(mem), offset_inds)) == view(mem, offset_inds) +end + +@testset "Memory size" begin + len = 5 + mem = Memory{Int}(undef, len) + @test size(mem, 1) == len + @test size(mem, 0x1) == len + @test size(mem, 2) == 1 + @test size(mem, 0x2) == 1 +end diff --git a/test/atexit.jl b/test/atexit.jl index 64b56e32466df..4a37d465f250b 100644 --- a/test/atexit.jl +++ b/test/atexit.jl @@ -214,12 +214,13 @@ using Test # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # 3. attempting to register a hook after all hooks have finished (disallowed) """ - const atexit_has_finished = Threads.Atomic{Bool}(false) + const atexit_has_finished = Threads.Atomic{Int}(0) atexit() do Threads.@spawn begin # Block until the atexit hooks have all finished. We use a manual "spin # lock" because task switch is disallowed inside the finalizer, below. - while !atexit_has_finished[] end + atexit_has_finished[] = 1 + while atexit_has_finished[] == 1 end try # By the time this runs, all the atexit hooks will be done. # So this will throw. @@ -231,15 +232,16 @@ using Test exit(22) end end + while atexit_has_finished[] == 0 end end # Finalizers run after the atexit hooks, so this blocks exit until the spawned # task above gets a chance to run. x = [] finalizer(x) do x # Allow the spawned task to finish - atexit_has_finished[] = true + atexit_has_finished[] = 2 # Then spin forever to prevent exit. - while atexit_has_finished[] end + while atexit_has_finished[] == 2 end end exit(0) """ => 22, diff --git a/test/atomics.jl b/test/atomics.jl index dd50fb96be49f..0d6c841073ad5 100644 --- a/test/atomics.jl +++ b/test/atomics.jl @@ -44,6 +44,13 @@ copy(r::Union{Refxy,ARefxy}) = typeof(r)(r.x, r.y) function add(x::T, y)::T where {T}; x + y; end swap(x, y) = y +struct UndefComplex{T} + re::T + im::T + UndefComplex{T}() where {T} = new{T}() +end +Base.convert(T::Type{<:UndefComplex}, S) = T() + let T1 = Refxy{NTuple{3,UInt8}}, T2 = ARefxy{NTuple{3,UInt8}} @test sizeof(T1) == 6 @@ -60,10 +67,13 @@ end # check that very large types are getting locks let (x, y) = (Complex{Int128}(10, 30), Complex{Int128}(20, 40)) - ar = ARefxy(x, y) r = Refxy(x, y) + ar = ARefxy(x, y) + mr = AtomicMemory{Pair{typeof(x),typeof(y)}}(undef, 20) @test 64 == sizeof(r) < sizeof(ar) - @test sizeof(r) == sizeof(ar) - Int(fieldoffset(typeof(ar), 1)) + @test sizeof(ar) == sizeof(r) + Int(fieldoffset(typeof(ar), 1)) + @test_broken Base.elsize(mr) == sizeof(ar) + @test sizeof(mr) == length(mr) * (sizeof(r) + 16) end struct PadIntA <: Number # internal padding @@ -87,6 +97,8 @@ Base.show(io::IO, x::PadIntA) = print(io, "PadIntA(", x.b, ")") Base.show(io::IO, x::PadIntB) = print(io, "PadIntB(", Int(x), ")") Base.show(io::IO, x::Int24) = print(io, "Int24(", Core.Intrinsics.zext_int(Int, x), ")") +## Fields + @noinline function _test_field_operators(r) r = r[] TT = fieldtype(typeof(r), :x) @@ -264,6 +276,26 @@ test_field_operators(ARefxy{Float64}(123_10, 123_20)) @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)) + + @test_throws ConcurrencyViolationError("invalid atomic ordering") setfieldonce!(r, :x, x, :u, :not_atomic) + @test_throws ConcurrencyViolationError("setfieldonce!: atomic field cannot be written non-atomically") setfieldonce!(r, :x, x) + @test_throws ConcurrencyViolationError("setfieldonce!: atomic field cannot be written non-atomically") setfieldonce!(r, :x, y, :not_atomic, :not_atomic) + @test_throws ConcurrencyViolationError("invalid atomic ordering") setfieldonce!(r, :x, x, :unordered, :not_atomic) + @test_throws ConcurrencyViolationError("setfieldonce!: atomic field cannot be accessed non-atomically") setfieldonce!(r, :x, x, :monotonic, :not_atomic) + @test_throws ConcurrencyViolationError("setfieldonce!: atomic field cannot be accessed non-atomically") setfieldonce!(r, :x, x, :acquire, :not_atomic) + @test_throws ConcurrencyViolationError("setfieldonce!: atomic field cannot be accessed non-atomically") setfieldonce!(r, :x, x, :release, :not_atomic) + @test_throws ConcurrencyViolationError("setfieldonce!: atomic field cannot be accessed non-atomically") setfieldonce!(r, :x, x, :acquire_release, :not_atomic) + @test_throws ConcurrencyViolationError("setfieldonce!: atomic field cannot be accessed non-atomically") setfieldonce!(r, :x, x, :sequentially_consistent, :not_atomic) + @test_throws ConcurrencyViolationError("invalid atomic ordering") setfieldonce!(r, :x, x, :not_atomic, :u) + @test_throws ConcurrencyViolationError("invalid atomic ordering") setfieldonce!(r, :x, x, :not_atomic, :unordered) + @test_throws ConcurrencyViolationError("invalid atomic ordering") setfieldonce!(r, :x, x, :not_atomic, :monotonic) + @test_throws ConcurrencyViolationError("invalid atomic ordering") setfieldonce!(r, :x, x, :not_atomic, :acquire) + @test_throws ConcurrencyViolationError("invalid atomic ordering") setfieldonce!(r, :x, x, :not_atomic, :release) + @test_throws ConcurrencyViolationError("invalid atomic ordering") setfieldonce!(r, :x, x, :not_atomic, :acquire_release) + @test_throws ConcurrencyViolationError("invalid atomic ordering") setfieldonce!(r, :x, x, :not_atomic, :sequentially_consistent) + @test setfieldonce!(r, :x, y, :sequentially_consistent, :sequentially_consistent) === false + @test setfieldonce!(r, :x, y, :sequentially_consistent, :sequentially_consistent) === false + @test setfieldonce!(r, :x, x, :sequentially_consistent) === false nothing end @noinline function test_field_orderings(r, x, y) @@ -287,12 +319,6 @@ test_field_orderings(Complex{Int128}(10, 30), Complex{Int128}(20, 40)) test_field_orderings(10.0, 20.0) test_field_orderings(NaN, Inf) -struct UndefComplex{T} - re::T - im::T - UndefComplex{T}() where {T} = new{T}() -end -Base.convert(T::Type{<:UndefComplex}, S) = T() @noinline function _test_field_undef(r) r = r[] TT = fieldtype(typeof(r), :x) @@ -318,6 +344,29 @@ test_field_undef(ARefxy{Union{Nothing,Integer}}) test_field_undef(ARefxy{UndefComplex{Any}}) test_field_undef(ARefxy{UndefComplex{UndefComplex{Any}}}) +@noinline function _test_once_undef(r) + r = r[] + TT = fieldtype(typeof(r), :x) + x = convert(TT, 12345_10) + @test_throws UndefRefError getfield(r, :x) + @test setfieldonce!(r, :x, x, :sequentially_consistent) === true + @test getfield(r, :x, :sequentially_consistent) === x + @test setfieldonce!(r, :x, convert(TT, 12345_20), :sequentially_consistent) === false + nothing +end + +@noinline function test_once_undef(TT) + _test_once_undef(Ref(TT())) + _test_once_undef(Ref{Any}(TT())) + nothing +end + +test_once_undef(ARefxy{BigInt}) +test_once_undef(ARefxy{Any}) +test_once_undef(ARefxy{Union{Nothing,Integer}}) +test_once_undef(ARefxy{UndefComplex{Any}}) +test_once_undef(ARefxy{UndefComplex{UndefComplex{Any}}}) + @test_throws ErrorException @macroexpand @atomic foo() @test_throws ErrorException @macroexpand @atomic foo += bar @test_throws ErrorException @macroexpand @atomic foo += bar @@ -374,6 +423,13 @@ let a = ARefxy(1, -1) @test_throws ConcurrencyViolationError @atomicreplace :monotonic :acquire a.x xchg end +let a = ARefxy{Union{Nothing,Integer}}() + @test_throws ConcurrencyViolationError @atomiconce :not_atomic a.x = 2 + @test true === @atomiconce a.x = 1 + @test 1 === @atomic a.x + @test false === @atomiconce a.x = 2 +end + # atomic getfield with boundcheck # via codegen getx(a, boundcheck) = getfield(a, :x, :sequentially_consistent, boundcheck) @@ -384,3 +440,558 @@ ans = getfield(ARefxy{Any}(42, 42), :x, :sequentially_consistent, true) @test ans == 42 ans = getfield(ARefxy{Any}(42, 42), :x, :sequentially_consistent, false) @test ans == 42 + + +## Globals + +# the optimizer is terrible at handling PhiC nodes, so this must be a function +# generator with a custom inlining here of r, instead of being able to assume +# the inlining pass can inline a constant value correctly +function gen_test_global_operators(@nospecialize r) + M = @__MODULE__ + return quote + TT = Core.get_binding_type($M, $r) + T = typeof(getglobal($M, $r)) + @test getglobal($M, $r, :sequentially_consistent) === T(123_10) + @test setglobal!($M, $r, T(123_1), :sequentially_consistent) === T(123_1) + @test getglobal($M, $r, :sequentially_consistent) === T(123_1) + @test replaceglobal!($M, $r, 123_1 % UInt, T(123_30), :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((T(123_1), false)) + @test replaceglobal!($M, $r, T(123_1), T(123_30), :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((T(123_1), true)) + @test getglobal($M, $r, :sequentially_consistent) === T(123_30) + @test replaceglobal!($M, $r, T(123_1), T(123_1), :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((T(123_30), false)) + @test getglobal($M, $r, :sequentially_consistent) === T(123_30) + @test modifyglobal!($M, $r, add, 1, :sequentially_consistent) === Pair{TT,TT}(T(123_30), T(123_31)) + @test modifyglobal!($M, $r, add, 1, :sequentially_consistent) === Pair{TT,TT}(T(123_31), T(123_32)) + @test getglobal($M, $r, :sequentially_consistent) === T(123_32) + @test swapglobal!($M, $r, T(123_1), :sequentially_consistent) === T(123_32) + @test getglobal($M, $r, :sequentially_consistent) === T(123_1) + end +end +@noinline function test_global_operators(T::Type) + r = Symbol("g1_$T") + @eval global $r::$T = 123_10 + invokelatest(@eval(() -> $(gen_test_global_operators(QuoteNode(r))))) + r = Symbol("g2_$T") + @eval global $r::$T = 123_10 + invokelatest(@eval(r -> $(gen_test_global_operators(:r))), r) + nothing +end +test_global_operators(Int) +test_global_operators(Any) +test_global_operators(Union{Nothing,Int}) +test_global_operators(Complex{Int32}) +test_global_operators(Complex{Int128}) +test_global_operators(PadIntA) +test_global_operators(PadIntB) +#FIXME: test_global_operators(Int24) +test_global_operators(Float64) + +function gen_test_global_orderings(@nospecialize r) + M = @__MODULE__ + return quote + @nospecialize x y + TT = Core.get_binding_type($M, $r) + + @test getglobal($M, $r) === x + @test_throws ConcurrencyViolationError("invalid atomic ordering") getglobal($M, $r, :u) + @test_throws ConcurrencyViolationError("getglobal: module binding cannot be read non-atomically") getglobal($M, $r, :not_atomic) + @test getglobal($M, $r, :unordered) === x + @test getglobal($M, $r, :monotonic) === x + @test getglobal($M, $r, :acquire) === x + @test_throws ConcurrencyViolationError("invalid atomic ordering") getglobal($M, $r, :release) === x + @test_throws ConcurrencyViolationError("invalid atomic ordering") getglobal($M, $r, :acquire_release) === x + @test getglobal($M, $r, :sequentially_consistent) === x + @test isdefined($M, $r) + @test_throws ConcurrencyViolationError("invalid atomic ordering") isdefined($M, $r, :u) + @test_throws ConcurrencyViolationError("isdefined: module binding cannot be accessed non-atomically") isdefined($M, $r, :not_atomic) + @test isdefined($M, $r, :unordered) + @test isdefined($M, $r, :monotonic) + @test isdefined($M, $r, :acquire) + @test_throws ConcurrencyViolationError("invalid atomic ordering") isdefined($M, $r, :release) + @test_throws ConcurrencyViolationError("invalid atomic ordering") isdefined($M, $r, :acquire_release) + @test isdefined($M, $r, :sequentially_consistent) + + @test_throws ConcurrencyViolationError("invalid atomic ordering") setglobal!($M, $r, y, :u) + @test_throws ConcurrencyViolationError("setglobal!: module binding cannot be written non-atomically") setglobal!($M, $r, y, :not_atomic) + @test getglobal($M, $r) === x + @test setglobal!($M, $r, y) === y + @test setglobal!($M, $r, y, :unordered) === y + @test setglobal!($M, $r, y, :monotonic) === y + @test_throws ConcurrencyViolationError("invalid atomic ordering") setglobal!($M, $r, y, :acquire) === y + @test setglobal!($M, $r, y, :release) === y + @test_throws ConcurrencyViolationError("invalid atomic ordering") setglobal!($M, $r, y, :acquire_release) === y + @test setglobal!($M, $r, y, :sequentially_consistent) === y + @test getglobal($M, $r) === y + + @test_throws ConcurrencyViolationError("invalid atomic ordering") swapglobal!($M, $r, x, :u) + @test_throws ConcurrencyViolationError("swapglobal!: module binding cannot be written non-atomically") swapglobal!($M, $r, x, :not_atomic) + @test_throws ConcurrencyViolationError("invalid atomic ordering") swapglobal!($M, $r, x, :unordered) === y + @test swapglobal!($M, $r, x, :monotonic) === y + @test swapglobal!($M, $r, x, :acquire) === x + @test swapglobal!($M, $r, x, :release) === x + @test swapglobal!($M, $r, x, :acquire_release) === x + @test swapglobal!($M, $r, x, :sequentially_consistent) === x + @test swapglobal!($M, $r, x) === x + + @test_throws ConcurrencyViolationError("invalid atomic ordering") modifyglobal!($M, $r, swap, x, :u) + @test_throws ConcurrencyViolationError("modifyglobal!: module binding cannot be written non-atomically") modifyglobal!($M, $r, swap, x, :not_atomic) + @test_throws ConcurrencyViolationError("invalid atomic ordering") modifyglobal!($M, $r, swap, x, :unordered) + @test modifyglobal!($M, $r, swap, x, :monotonic) === Pair{TT,TT}(x, x) + @test modifyglobal!($M, $r, swap, x, :acquire) === Pair{TT,TT}(x, x) + @test modifyglobal!($M, $r, swap, x, :release) === Pair{TT,TT}(x, x) + @test modifyglobal!($M, $r, swap, x, :acquire_release) === Pair{TT,TT}(x, x) + @test modifyglobal!($M, $r, swap, x, :sequentially_consistent) === Pair{TT,TT}(x, x) + @test modifyglobal!($M, $r, swap, x) === Pair{TT,TT}(x, x) + + @test_throws ConcurrencyViolationError("invalid atomic ordering") replaceglobal!($M, $r, x, x, :u, :not_atomic) + @test_throws ConcurrencyViolationError("replaceglobal!: module binding cannot be written non-atomically") replaceglobal!($M, $r, y, x, :not_atomic, :not_atomic) + @test_throws ConcurrencyViolationError("invalid atomic ordering") replaceglobal!($M, $r, x, x, :unordered, :not_atomic) + @test_throws ConcurrencyViolationError("replaceglobal!: module binding cannot be accessed non-atomically") replaceglobal!($M, $r, x, x, :monotonic, :not_atomic) + @test_throws ConcurrencyViolationError("replaceglobal!: module binding cannot be accessed non-atomically") replaceglobal!($M, $r, x, x, :acquire, :not_atomic) + @test_throws ConcurrencyViolationError("replaceglobal!: module binding cannot be accessed non-atomically") replaceglobal!($M, $r, x, x, :release, :not_atomic) + @test_throws ConcurrencyViolationError("replaceglobal!: module binding cannot be accessed non-atomically") replaceglobal!($M, $r, x, x, :acquire_release, :not_atomic) + @test_throws ConcurrencyViolationError("replaceglobal!: module binding cannot be accessed non-atomically") replaceglobal!($M, $r, x, x, :sequentially_consistent, :not_atomic) + @test_throws ConcurrencyViolationError("invalid atomic ordering") replaceglobal!($M, $r, x, x, :not_atomic, :u) + @test_throws ConcurrencyViolationError("invalid atomic ordering") replaceglobal!($M, $r, x, x, :not_atomic, :unordered) + @test_throws ConcurrencyViolationError("invalid atomic ordering") replaceglobal!($M, $r, x, x, :not_atomic, :monotonic) + @test_throws ConcurrencyViolationError("invalid atomic ordering") replaceglobal!($M, $r, x, x, :not_atomic, :acquire) + @test_throws ConcurrencyViolationError("invalid atomic ordering") replaceglobal!($M, $r, x, x, :not_atomic, :release) + @test_throws ConcurrencyViolationError("invalid atomic ordering") replaceglobal!($M, $r, x, x, :not_atomic, :acquire_release) + @test_throws ConcurrencyViolationError("invalid atomic ordering") replaceglobal!($M, $r, x, x, :not_atomic, :sequentially_consistent) + @test replaceglobal!($M, $r, x, y, :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((x, true)) + @test replaceglobal!($M, $r, x, y, :sequentially_consistent, :sequentially_consistent) === ReplaceType{TT}((y, x === y)) + @test replaceglobal!($M, $r, y, x, :sequentially_consistent) === ReplaceType{TT}((y, true)) + @test replaceglobal!($M, $r, x, x) === ReplaceType{TT}((x, true)) + + @test_throws ConcurrencyViolationError("invalid atomic ordering") setglobalonce!($M, $r, x, :u, :not_atomic) + @test_throws ConcurrencyViolationError("setglobalonce!: module binding cannot be written non-atomically") setglobalonce!($M, $r, y, :not_atomic, :not_atomic) + @test_throws ConcurrencyViolationError("invalid atomic ordering") setglobalonce!($M, $r, x, :unordered, :not_atomic) + @test_throws ConcurrencyViolationError("setglobalonce!: module binding cannot be accessed non-atomically") setglobalonce!($M, $r, x, :monotonic, :not_atomic) + @test_throws ConcurrencyViolationError("setglobalonce!: module binding cannot be accessed non-atomically") setglobalonce!($M, $r, x, :acquire, :not_atomic) + @test_throws ConcurrencyViolationError("setglobalonce!: module binding cannot be accessed non-atomically") setglobalonce!($M, $r, x, :release, :not_atomic) + @test_throws ConcurrencyViolationError("setglobalonce!: module binding cannot be accessed non-atomically") setglobalonce!($M, $r, x, :acquire_release, :not_atomic) + @test_throws ConcurrencyViolationError("setglobalonce!: module binding cannot be accessed non-atomically") setglobalonce!($M, $r, x, :sequentially_consistent, :not_atomic) + @test_throws ConcurrencyViolationError("invalid atomic ordering") setglobalonce!($M, $r, x, :not_atomic, :u) + @test_throws ConcurrencyViolationError("invalid atomic ordering") setglobalonce!($M, $r, x, :not_atomic, :unordered) + @test_throws ConcurrencyViolationError("invalid atomic ordering") setglobalonce!($M, $r, x, :not_atomic, :monotonic) + @test_throws ConcurrencyViolationError("invalid atomic ordering") setglobalonce!($M, $r, x, :not_atomic, :acquire) + @test_throws ConcurrencyViolationError("invalid atomic ordering") setglobalonce!($M, $r, x, :not_atomic, :release) + @test_throws ConcurrencyViolationError("invalid atomic ordering") setglobalonce!($M, $r, x, :not_atomic, :acquire_release) + @test_throws ConcurrencyViolationError("invalid atomic ordering") setglobalonce!($M, $r, x, :not_atomic, :sequentially_consistent) + @test setglobalonce!($M, $r, x) === false + @test setglobalonce!($M, $r, y, :sequentially_consistent, :sequentially_consistent) === false + @test setglobalonce!($M, $r, y, :sequentially_consistent, :sequentially_consistent) === false + @test setglobalonce!($M, $r, x, :sequentially_consistent) === false + end +end +@noinline function test_global_orderings(T::Type, x, y) + @nospecialize + r = Symbol("h1_$T") + @eval global $r::$T = $(QuoteNode(x)) + invokelatest(@eval((x, y) -> $(gen_test_global_orderings(QuoteNode(r)))), x, y) + r = Symbol("h2_$T") + @eval global $r::$T = $(QuoteNode(x)) + invokelatest(@eval((r, x, y) -> $(gen_test_global_orderings(:r))), r, x, y) + nothing +end +test_global_orderings(Int, 10, 20) +test_global_orderings(Bool, true, false) +test_global_orderings(String, "hi", "bye") +test_global_orderings(Symbol, :hi, :bye) +test_global_orderings(Nothing, nothing, nothing) +test_global_orderings(Any, 123_10, 123_20) +test_global_orderings(Any, true, false) +test_global_orderings(Union{Nothing,Missing}, nothing, missing) +test_global_orderings(Union{Nothing,Int}, nothing, 123_1) +test_global_orderings(Complex{Int128}, Complex{Int128}(10, 30), Complex{Int128}(20, 40)) +test_global_orderings(Float64, 10.0, 20.0) +test_global_orderings(Float64, NaN, Inf) + +function gen_test_global_undef(@nospecialize r) + M = @__MODULE__ + return quote + TT = Core.get_binding_type($M, $r) + x = convert(TT, 12345_10) + @test_throws UndefVarError getglobal($M, $r) + @test_throws UndefVarError getglobal($M, $r, :sequentially_consistent) + @test_throws UndefVarError modifyglobal!($M, $r, add, 1, :sequentially_consistent) + @test_throws (TT === Any ? UndefVarError : Union{TypeError,ErrorException}) replaceglobal!($M, $r, 1, 1.0, :sequentially_consistent) # TODO: should this be TypeError or ErrorException + @test_throws UndefVarError replaceglobal!($M, $r, 1, x, :sequentially_consistent) + @test_throws UndefVarError getglobal($M, $r, :sequentially_consistent) + @test_throws UndefVarError swapglobal!($M, $r, x, :sequentially_consistent) + @test getglobal($M, $r, :sequentially_consistent) === x === getglobal($M, $r) + end +end +@noinline function test_global_undef(T) + r = Symbol("u1_$T") + @eval global $r::$T + invokelatest(@eval(() -> $(gen_test_global_undef(QuoteNode(r))))) + r = Symbol("u2_$T") + @eval global $r::$T + invokelatest(@eval(r -> $(gen_test_global_undef(:r))), r) + nothing +end +test_global_undef(BigInt) +test_global_undef(Any) +test_global_undef(Union{Nothing,Integer}) +test_global_undef(UndefComplex{Any}) +test_global_undef(UndefComplex{UndefComplex{Any}}) +test_global_undef(Int) + +function gen_test_globalonce(@nospecialize r) + M = @__MODULE__ + return quote + TT = Core.get_binding_type($M, $r) + x = convert(TT, 12345_10) + @test_throws UndefVarError getglobal($M, $r) + @test setglobalonce!($M, $r, x, :sequentially_consistent) === true + @test getglobal($M, $r, :sequentially_consistent) === x + @test setglobalonce!($M, $r, convert(TT, 12345_20), :sequentially_consistent) === false + end +end +@noinline function test_globalonce(T) + r = Symbol("o1_$T") + @eval global $r::$T + invokelatest(@eval(() -> $(gen_test_globalonce(QuoteNode(r))))) + r = Symbol("o2_$T") + @eval global $r::$T + invokelatest(@eval(r -> $(gen_test_globalonce(:r))), r) + nothing +end +test_globalonce(BigInt) +test_globalonce(Any) +test_globalonce(Union{Nothing,Integer}) +test_globalonce(UndefComplex{Any}) +test_globalonce(UndefComplex{UndefComplex{Any}}) +test_globalonce(Int) + +# test macroexpansions +global x::Int +let a = @__MODULE__ + @test_throws ConcurrencyViolationError @atomiconce :not_atomic a.x = 2 + @test true === @atomiconce a.x = 1 + @test 1 === @atomic a.x + @test false === @atomiconce a.x = 2 +end +let a = @__MODULE__ + @test 1 === @atomic a.x + @test 2 === @atomic :sequentially_consistent a.x = 2 + @test 3 === @atomic :monotonic a.x = 3 + local four = 4 + @test 4 === @atomic :monotonic a.x = four + @test 3 === @atomic :monotonic a.x = four - 1 + @test_throws ConcurrencyViolationError @atomic :not_atomic a.x = 2 + @test_throws ConcurrencyViolationError @atomic :not_atomic a.x + @test_throws ConcurrencyViolationError @atomic :not_atomic a.x += 1 + + @test 3 === @atomic :monotonic a.x + @test 5 === @atomic a.x += 2 + @test 4 === @atomic :monotonic a.x -= 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_throws ConcurrencyViolationError @atomic :not_atomic a.x + 1 + @test_throws ConcurrencyViolationError @atomic :not_atomic a.x max 30 + + @test 20 === @atomic a.x + @test 20 === @atomicswap a.x = 1 + @test 1 === @atomicswap :monotonic a.x = 2 + @test_throws ConcurrencyViolationError @atomicswap :not_atomic a.x = 1 + + @test 2 === @atomic a.x + @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 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 + +## Memory + +using InteractiveUtils +using Core: memoryrefget, memoryrefset!, memoryrefswap!, memoryrefreplace!, memoryrefmodify!, memoryrefsetonce!, memoryref_isassigned + +@noinline function _test_memory_operators(r) + r = r[] + TT = eltype(r) + T = typeof(r[]) + @test memoryrefget(r, :sequentially_consistent, true) === T(123_10) + @test memoryrefset!(r, T(123_1), :sequentially_consistent, true) === T(123_1) + @test memoryrefget(r, :sequentially_consistent, true) === T(123_1) + @test memoryrefreplace!(r, 123_1 % UInt, T(123_30), :sequentially_consistent, :sequentially_consistent, true) === ReplaceType{TT}((T(123_1), false)) + @test memoryrefreplace!(r, T(123_1), T(123_30), :sequentially_consistent, :sequentially_consistent, true) === ReplaceType{TT}((T(123_1), true)) + @test memoryrefget(r, :sequentially_consistent, true) === T(123_30) + @test memoryrefreplace!(r, T(123_1), T(123_1), :sequentially_consistent, :sequentially_consistent, true) === ReplaceType{TT}((T(123_30), false)) + @test memoryrefget(r, :sequentially_consistent, true) === T(123_30) + @test memoryrefmodify!(r, add, 1, :sequentially_consistent, true) === Pair{TT,TT}(T(123_30), T(123_31)) + @test memoryrefmodify!(r, add, 1, :sequentially_consistent, true) === Pair{TT,TT}(T(123_31), T(123_32)) + @test memoryrefget(r, :sequentially_consistent, true) === T(123_32) + @test memoryrefswap!(r, T(123_1), :sequentially_consistent, true) === T(123_32) + @test memoryrefget(r, :sequentially_consistent, true) === T(123_1) + nothing +end +@noinline function test_memory_operators(T::Type) + x = convert(T, 123_10) + r = GenericMemoryRef(AtomicMemory{T}(undef, 1)) + memoryrefset!(r, x, :unordered, true) # @atomic r[] = x + _test_memory_operators(Ref(r)) + r = GenericMemoryRef(AtomicMemory{T}(undef, 1)) + memoryrefset!(r, x, :unordered, true) # @atomic r[] = x + _test_memory_operators(Ref{Any}(r)) + nothing +end +test_memory_operators(Int) +test_memory_operators(Any) +test_memory_operators(Union{Nothing,Int}) +test_memory_operators(Complex{Int32}) +test_memory_operators(Complex{Int128}) +test_memory_operators(PadIntA) +test_memory_operators(PadIntB) +#FIXME: test_memory_operators(Int24) +test_memory_operators(Float64) + +@noinline function _test_memory_orderings(xr, yr, x, y) + @nospecialize x y + xr = xr[] + yr = yr[] + TT = eltype(yr) + @test TT == eltype(xr) + + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefget(xr, :u, true) + @test_throws ConcurrencyViolationError("memoryrefget: atomic memory cannot be accessed non-atomically") memoryrefget(xr, :not_atomic, true) + @test memoryrefget(xr, :unordered, true) === x + @test memoryrefget(xr, :monotonic, true) === x + @test memoryrefget(xr, :acquire, true) === x + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefget(xr, :release, true) === x + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefget(xr, :acquire_release, true) === x + @test memoryrefget(xr, :sequentially_consistent, true) === x + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryref_isassigned(xr, :u, true) + @test_throws ConcurrencyViolationError("memoryref_isassigned: atomic memory cannot be accessed non-atomically") memoryref_isassigned(xr, :not_atomic, true) + @test memoryref_isassigned(xr, :unordered, true) + @test memoryref_isassigned(xr, :monotonic, true) + @test memoryref_isassigned(xr, :acquire, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryref_isassigned(xr, :release, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryref_isassigned(xr, :acquire_release, true) + @test memoryref_isassigned(xr, :sequentially_consistent, true) + + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefget(yr, :u, true) + @test memoryrefget(yr, :not_atomic, true) === y + @test_throws ConcurrencyViolationError("memoryrefget: non-atomic memory cannot be accessed atomically") memoryrefget(yr, :unordered, true) + @test_throws ConcurrencyViolationError("memoryrefget: non-atomic memory cannot be accessed atomically") memoryrefget(yr, :monotonic, true) + @test_throws ConcurrencyViolationError("memoryrefget: non-atomic memory cannot be accessed atomically") memoryrefget(yr, :acquire, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefget(yr, :release, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefget(yr, :acquire_release, true) + @test_throws ConcurrencyViolationError("memoryrefget: non-atomic memory cannot be accessed atomically") memoryrefget(yr, :sequentially_consistent, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryref_isassigned(yr, :u, true) + @test memoryref_isassigned(yr, :not_atomic, true) + @test_throws ConcurrencyViolationError("memoryref_isassigned: non-atomic memory cannot be accessed atomically") memoryref_isassigned(yr, :unordered, true) + @test_throws ConcurrencyViolationError("memoryref_isassigned: non-atomic memory cannot be accessed atomically") memoryref_isassigned(yr, :monotonic, true) + @test_throws ConcurrencyViolationError("memoryref_isassigned: non-atomic memory cannot be accessed atomically") memoryref_isassigned(yr, :acquire, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryref_isassigned(yr, :release, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryref_isassigned(yr, :acquire_release, true) + @test_throws ConcurrencyViolationError("memoryref_isassigned: non-atomic memory cannot be accessed atomically") memoryref_isassigned(yr, :sequentially_consistent, true) + + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefset!(xr, y, :u, true) + @test_throws ConcurrencyViolationError("memoryrefset!: atomic memory cannot be written non-atomically") memoryrefset!(xr, y, :not_atomic, true) + @test memoryrefget(xr, :unordered, true) === x + @test memoryrefset!(xr, y, :unordered, true) === y + @test memoryrefset!(xr, y, :monotonic, true) === y + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefset!(xr, y, :acquire, true) === y + @test memoryrefset!(xr, y, :release, true) === y + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefset!(xr, y, :acquire_release, true) === y + @test memoryrefset!(xr, y, :sequentially_consistent, true) === y + @test memoryrefget(xr, :unordered, true) === y + + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefset!(yr, x, :u, true) + @test_throws ConcurrencyViolationError("memoryrefset!: non-atomic memory cannot be written atomically") memoryrefset!(yr, x, :unordered, true) + @test_throws ConcurrencyViolationError("memoryrefset!: non-atomic memory cannot be written atomically") memoryrefset!(yr, x, :monotonic, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefset!(yr, x, :acquire, true) + @test_throws ConcurrencyViolationError("memoryrefset!: non-atomic memory cannot be written atomically") memoryrefset!(yr, x, :release, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefset!(yr, x, :acquire_release, true) + @test_throws ConcurrencyViolationError("memoryrefset!: non-atomic memory cannot be written atomically") memoryrefset!(yr, x, :sequentially_consistent, true) + @test memoryrefget(yr, :not_atomic, true) === y + @test memoryrefset!(yr, x, :not_atomic, true) === x + @test memoryrefset!(yr, x, :not_atomic, true) === x + @test memoryrefget(yr, :not_atomic, true) === x + + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefswap!(yr, y, :u, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefswap!(yr, y, :unordered, true) + @test_throws ConcurrencyViolationError("memoryrefswap!: non-atomic memory cannot be written atomically") memoryrefswap!(yr, y, :monotonic, true) + @test_throws ConcurrencyViolationError("memoryrefswap!: non-atomic memory cannot be written atomically") memoryrefswap!(yr, y, :acquire, true) + @test_throws ConcurrencyViolationError("memoryrefswap!: non-atomic memory cannot be written atomically") memoryrefswap!(yr, y, :release, true) + @test_throws ConcurrencyViolationError("memoryrefswap!: non-atomic memory cannot be written atomically") memoryrefswap!(yr, y, :acquire_release, true) + @test_throws ConcurrencyViolationError("memoryrefswap!: non-atomic memory cannot be written atomically") memoryrefswap!(yr, y, :sequentially_consistent, true) + @test memoryrefswap!(yr, y, :not_atomic, true) === x + + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefmodify!(yr, swap, y, :u, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefmodify!(yr, swap, y, :unordered, true) + @test_throws ConcurrencyViolationError("memoryrefmodify!: non-atomic memory cannot be written atomically") memoryrefmodify!(yr, swap, y, :monotonic, true) + @test_throws ConcurrencyViolationError("memoryrefmodify!: non-atomic memory cannot be written atomically") memoryrefmodify!(yr, swap, y, :acquire, true) + @test_throws ConcurrencyViolationError("memoryrefmodify!: non-atomic memory cannot be written atomically") memoryrefmodify!(yr, swap, y, :release, true) + @test_throws ConcurrencyViolationError("memoryrefmodify!: non-atomic memory cannot be written atomically") memoryrefmodify!(yr, swap, y, :acquire_release, true) + @test_throws ConcurrencyViolationError("memoryrefmodify!: non-atomic memory cannot be written atomically") memoryrefmodify!(yr, swap, y, :sequentially_consistent, true) + @test memoryrefmodify!(yr, swap, x, :not_atomic, true) === Pair{TT,TT}(y, x) + + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefreplace!(yr, y, y, :u, :not_atomic, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefreplace!(yr, y, y, :unordered, :not_atomic, true) + @test_throws ConcurrencyViolationError("memoryrefreplace!: non-atomic memory cannot be written atomically") memoryrefreplace!(yr, y, y, :monotonic, :not_atomic, true) + @test_throws ConcurrencyViolationError("memoryrefreplace!: non-atomic memory cannot be written atomically") memoryrefreplace!(yr, y, y, :acquire, :not_atomic, true) + @test_throws ConcurrencyViolationError("memoryrefreplace!: non-atomic memory cannot be written atomically") memoryrefreplace!(yr, y, y, :release, :not_atomic, true) + @test_throws ConcurrencyViolationError("memoryrefreplace!: non-atomic memory cannot be written atomically") memoryrefreplace!(yr, y, y, :acquire_release, :not_atomic, true) + @test_throws ConcurrencyViolationError("memoryrefreplace!: non-atomic memory cannot be written atomically") memoryrefreplace!(yr, y, y, :sequentially_consistent, :not_atomic, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefreplace!(yr, y, y, :not_atomic, :u, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefreplace!(yr, y, y, :not_atomic, :unordered, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefreplace!(yr, y, y, :not_atomic, :monotonic, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefreplace!(yr, y, y, :not_atomic, :acquire, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefreplace!(yr, y, y, :not_atomic, :release, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefreplace!(yr, y, y, :not_atomic, :acquire_release, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefreplace!(yr, y, y, :not_atomic, :sequentially_consistent, true) + @test memoryrefreplace!(yr, x, y, :not_atomic, :not_atomic, true) === ReplaceType{TT}((x, true)) + @test memoryrefreplace!(yr, x, y, :not_atomic, :not_atomic, true) === ReplaceType{TT}((y, x === y)) + @test memoryrefreplace!(yr, y, y, :not_atomic, :not_atomic, true) === ReplaceType{TT}((y, true)) + + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefswap!(xr, x, :u, true) + @test_throws ConcurrencyViolationError("memoryrefswap!: atomic memory cannot be written non-atomically") memoryrefswap!(xr, x, :not_atomic, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefswap!(xr, x, :unordered, true) === y + @test memoryrefswap!(xr, x, :monotonic, true) === y + @test memoryrefswap!(xr, x, :acquire, true) === x + @test memoryrefswap!(xr, x, :release, true) === x + @test memoryrefswap!(xr, x, :acquire_release, true) === x + @test memoryrefswap!(xr, x, :sequentially_consistent, true) === x + + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefmodify!(xr, swap, x, :u, true) + @test_throws ConcurrencyViolationError("memoryrefmodify!: atomic memory cannot be written non-atomically") memoryrefmodify!(xr, swap, x, :not_atomic, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefmodify!(xr, swap, x, :unordered, true) + @test memoryrefmodify!(xr, swap, x, :monotonic, true) === Pair{TT,TT}(x, x) + @test memoryrefmodify!(xr, swap, x, :acquire, true) === Pair{TT,TT}(x, x) + @test memoryrefmodify!(xr, swap, x, :release, true) === Pair{TT,TT}(x, x) + @test memoryrefmodify!(xr, swap, x, :acquire_release, true) === Pair{TT,TT}(x, x) + @test memoryrefmodify!(xr, swap, x, :sequentially_consistent, true) === Pair{TT,TT}(x, x) + + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefreplace!(xr, x, x, :u, :not_atomic, true) + @test_throws ConcurrencyViolationError("memoryrefreplace!: atomic memory cannot be written non-atomically") memoryrefreplace!(xr, y, x, :not_atomic, :not_atomic, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefreplace!(xr, x, x, :unordered, :not_atomic, true) + @test_throws ConcurrencyViolationError("memoryrefreplace!: atomic memory cannot be accessed non-atomically") memoryrefreplace!(xr, x, x, :monotonic, :not_atomic, true) + @test_throws ConcurrencyViolationError("memoryrefreplace!: atomic memory cannot be accessed non-atomically") memoryrefreplace!(xr, x, x, :acquire, :not_atomic, true) + @test_throws ConcurrencyViolationError("memoryrefreplace!: atomic memory cannot be accessed non-atomically") memoryrefreplace!(xr, x, x, :release, :not_atomic, true) + @test_throws ConcurrencyViolationError("memoryrefreplace!: atomic memory cannot be accessed non-atomically") memoryrefreplace!(xr, x, x, :acquire_release, :not_atomic, true) + @test_throws ConcurrencyViolationError("memoryrefreplace!: atomic memory cannot be accessed non-atomically") memoryrefreplace!(xr, x, x, :sequentially_consistent, :not_atomic, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefreplace!(xr, x, x, :not_atomic, :u, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefreplace!(xr, x, x, :not_atomic, :unordered, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefreplace!(xr, x, x, :not_atomic, :monotonic, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefreplace!(xr, x, x, :not_atomic, :acquire, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefreplace!(xr, x, x, :not_atomic, :release, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefreplace!(xr, x, x, :not_atomic, :acquire_release, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefreplace!(xr, x, x, :not_atomic, :sequentially_consistent, true) + @test memoryrefreplace!(xr, x, y, :sequentially_consistent, :sequentially_consistent, true) === ReplaceType{TT}((x, true)) + @test memoryrefreplace!(xr, x, y, :sequentially_consistent, :sequentially_consistent, true) === ReplaceType{TT}((y, x === y)) + @test memoryrefreplace!(xr, y, x, :sequentially_consistent, :sequentially_consistent, true) === ReplaceType{TT}((y, true)) + + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefsetonce!(xr, x, :u, :not_atomic, true) + @test_throws ConcurrencyViolationError("memoryrefsetonce!: atomic memory cannot be written non-atomically") memoryrefsetonce!(xr, y, :not_atomic, :not_atomic, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefsetonce!(xr, x, :unordered, :not_atomic, true) + @test_throws ConcurrencyViolationError("memoryrefsetonce!: atomic memory cannot be accessed non-atomically") memoryrefsetonce!(xr, x, :monotonic, :not_atomic, true) + @test_throws ConcurrencyViolationError("memoryrefsetonce!: atomic memory cannot be accessed non-atomically") memoryrefsetonce!(xr, x, :acquire, :not_atomic, true) + @test_throws ConcurrencyViolationError("memoryrefsetonce!: atomic memory cannot be accessed non-atomically") memoryrefsetonce!(xr, x, :release, :not_atomic, true) + @test_throws ConcurrencyViolationError("memoryrefsetonce!: atomic memory cannot be accessed non-atomically") memoryrefsetonce!(xr, x, :acquire_release, :not_atomic, true) + @test_throws ConcurrencyViolationError("memoryrefsetonce!: atomic memory cannot be accessed non-atomically") memoryrefsetonce!(xr, x, :sequentially_consistent, :not_atomic, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefsetonce!(xr, x, :not_atomic, :u, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefsetonce!(xr, x, :not_atomic, :unordered, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefsetonce!(xr, x, :not_atomic, :monotonic, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefsetonce!(xr, x, :not_atomic, :acquire, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefsetonce!(xr, x, :not_atomic, :release, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefsetonce!(xr, x, :not_atomic, :acquire_release, true) + @test_throws ConcurrencyViolationError("invalid atomic ordering") memoryrefsetonce!(xr, x, :not_atomic, :sequentially_consistent, true) + @test memoryrefsetonce!(xr, y, :sequentially_consistent, :sequentially_consistent, true) === false + @test memoryrefsetonce!(xr, y, :sequentially_consistent, :sequentially_consistent, true) === false + @test memoryrefsetonce!(xr, x, :sequentially_consistent, :sequentially_consistent, true) === false + nothing +end +@noinline function test_memory_orderings(T::Type, x, y) + @nospecialize + xr = GenericMemoryRef(AtomicMemory{T}(undef, 1)) + memoryrefset!(xr, x, :unordered, true) # @atomic xr[] = x + yr = GenericMemoryRef(Memory{T}(undef, 1)) + yr[] = y + _test_memory_orderings(Ref(xr), Ref(yr), x, y) + xr = GenericMemoryRef(AtomicMemory{T}(undef, 1)) + memoryrefset!(xr, x, :unordered, true) # @atomic xr[] = x + yr = GenericMemoryRef(Memory{T}(undef, 1)) + yr[] = y + _test_memory_orderings(Ref{Any}(xr), Ref{Any}(yr), x, y) + nothing +end +@noinline test_memory_orderings(x, y) = (@nospecialize; test_memory_orderings(typeof(x), x, y)) +test_memory_orderings(10, 20) +test_memory_orderings(true, false) +test_memory_orderings("hi", "bye") +test_memory_orderings(:hi, :bye) +test_memory_orderings(nothing, nothing) +test_memory_orderings(Any, 123_10, 123_20) +test_memory_orderings(Any, true, false) +test_memory_orderings(Union{Nothing,Missing}, nothing, missing) +test_memory_orderings(Union{Nothing,Int}, nothing, 123_1) +test_memory_orderings(Complex{Int128}(10, 30), Complex{Int128}(20, 40)) +test_memory_orderings(10.0, 20.0) +test_memory_orderings(NaN, Inf) + +@noinline function _test_memory_undef(r) + r = r[] + TT = eltype(r) + x = convert(TT, 12345_10) + @test_throws UndefRefError memoryrefget(r, :sequentially_consistent, true) + @test_throws UndefRefError memoryrefmodify!(r, add, 1, :sequentially_consistent, true) + @test_throws (TT === Any ? UndefRefError : TypeError) memoryrefreplace!(r, 1, 1.0, :sequentially_consistent, :sequentially_consistent, true) + @test_throws UndefRefError memoryrefreplace!(r, 1, x, :sequentially_consistent, :sequentially_consistent, true) + @test_throws UndefRefError memoryrefget(r, :sequentially_consistent, true) + @test_throws UndefRefError memoryrefswap!(r, x, :sequentially_consistent, true) + @test memoryrefget(r, :sequentially_consistent, true) === x + nothing +end +@noinline function test_memory_undef(T) + r = GenericMemoryRef(AtomicMemory{T}(undef, 1)) + _test_memory_undef(Ref(r)) + r = GenericMemoryRef(AtomicMemory{T}(undef, 1)) + _test_memory_undef(Ref{Any}(r)) + nothing +end +test_memory_undef(BigInt) +test_memory_undef(Any) +test_memory_undef(Union{Nothing,Integer}) +test_memory_undef(UndefComplex{Any}) +test_memory_undef(UndefComplex{UndefComplex{Any}}) + +@noinline function _test_once_undef(r) + r = r[] + TT = eltype(r) + x = convert(TT, 12345_10) + @test_throws UndefRefError memoryrefget(r, :sequentially_consistent, true) + @test memoryrefsetonce!(r, x, :sequentially_consistent, :sequentially_consistent, true) === true + @test memoryrefget(r, :sequentially_consistent, true) === x + @test memoryrefsetonce!(r, convert(TT, 12345_20), :sequentially_consistent, :sequentially_consistent, true) === false + nothing +end +@noinline function test_once_undef(T) + r = GenericMemoryRef(AtomicMemory{T}(undef, 1)) + _test_once_undef(Ref(r)) + r = GenericMemoryRef(AtomicMemory{T}(undef, 1)) + _test_once_undef(Ref{Any}(r)) + nothing +end +test_once_undef(BigInt) +test_once_undef(Any) +test_once_undef(Union{Nothing,Integer}) +test_once_undef(UndefComplex{Any}) +test_once_undef(UndefComplex{UndefComplex{Any}}) diff --git a/test/backtrace.jl b/test/backtrace.jl index 50a50100488c4..68873678df57b 100644 --- a/test/backtrace.jl +++ b/test/backtrace.jl @@ -202,6 +202,15 @@ let trace = try end @test trace[1].func === Symbol("top-level scope") end +let trace = try + eval(Expr(:toplevel, LineNumberNode(3, :a_filename), Expr(:error, 1))) + catch + stacktrace(catch_backtrace()) + end + @test trace[1].func === Symbol("top-level scope") + @test trace[1].file === :a_filename + @test trace[1].line == 3 +end let trace = try include_string(@__MODULE__, """ @@ -253,10 +262,14 @@ let code = """ if ip isa Base.InterpreterIP && ip.code isa Core.MethodInstance] num_fs = sum(meth_names .== :f29695) num_gs = sum(meth_names .== :g29695) - print(num_fs, ' ', num_gs) + if num_fs != 1000 || num_gs != 1000 + Base.show_backtrace(stderr, bt) + error("Expected 1000 frames each, got \$num_fs, \$num_fs") + end + exit() """ - @test read(`$(Base.julia_cmd()) --startup-file=no --compile=min -e $code`, String) == "1000 1000" + @test success(pipeline(`$(Base.julia_cmd()) --startup-file=no --compile=min -e $code`; stderr)) end # Test that modules make it into InterpreterIP for top-level code diff --git a/test/bitarray.jl b/test/bitarray.jl index 056a201bd4f6f..2cf285370441e 100644 --- a/test/bitarray.jl +++ b/test/bitarray.jl @@ -3,6 +3,9 @@ using Base: findprevnot, findnextnot using Random, LinearAlgebra, Test +isdefined(Main, :SizedArrays) || @eval Main include("testhelpers/SizedArrays.jl") +using .Main.SizedArrays + tc(r1::NTuple{N,Any}, r2::NTuple{N,Any}) where {N} = all(x->tc(x...), [zip(r1,r2)...]) tc(r1::BitArray{N}, r2::Union{BitArray{N},Array{Bool,N}}) where {N} = true tc(r1::SubArray{Bool,N1,BitArray{N2}}, r2::SubArray{Bool,N1,<:Union{BitArray{N2},Array{Bool,N2}}}) where {N1,N2} = true @@ -82,6 +85,25 @@ allsizes = [((), BitArray{0}), ((v1,), BitVector), @test !isassigned(b, length(b) + 1) end +@testset "trues and falses with custom axes" begin + for ax in ((SizedArrays.SOneTo(2),), (SizedArrays.SOneTo(2), Base.OneTo(2))) + t = trues(ax) + if all(x -> x isa SizedArrays.SOneTo, ax) + @test t isa SizedArrays.SizedArray && parent(t) isa BitArray + else + @test t isa BitArray + end + @test all(t) + + f = falses(ax) + if all(x -> x isa SizedArrays.SOneTo, ax) + @test t isa SizedArrays.SizedArray && parent(t) isa BitArray + else + @test t isa BitArray + end + @test !any(f) + end +end @testset "Conversions for size $sz" for (sz, T) in allsizes b1 = rand!(falses(sz...)) diff --git a/test/broadcast.jl b/test/broadcast.jl index 269adc9f7276d..2ae3d96b2a709 100644 --- a/test/broadcast.jl +++ b/test/broadcast.jl @@ -49,9 +49,9 @@ ci(x) = CartesianIndex(x) @test @inferred(newindex(ci((2,2)), (true, false), (-1,-1))) == ci((2,-1)) @test @inferred(newindex(ci((2,2)), (false, true), (-1,-1))) == ci((-1,2)) @test @inferred(newindex(ci((2,2)), (false, false), (-1,-1))) == ci((-1,-1)) -@test @inferred(newindex(ci((2,2)), (true,), (-1,-1))) == ci((2,)) -@test @inferred(newindex(ci((2,2)), (true,), (-1,))) == ci((2,)) -@test @inferred(newindex(ci((2,2)), (false,), (-1,))) == ci((-1,)) +@test @inferred(newindex(ci((2,2)), (true,), (-1,-1))) == 2 +@test @inferred(newindex(ci((2,2)), (true,), (-1,))) == 2 +@test @inferred(newindex(ci((2,2)), (false,), (-1,))) == -1 @test @inferred(newindex(ci((2,2)), (), ())) == ci(()) end @@ -592,6 +592,16 @@ end end end +@testset "convert behavior of logical broadcast" begin + a = mod.(1:4, 2) + @test !isa(a, BitArray) + for T in (Array{Bool}, BitArray) + la = T(a) + la .= mod.(0:3, 2) + @test la == [false; true; false; true] + end +end + # Test that broadcast treats type arguments as scalars, i.e. containertype yields Any, # even for subtypes of abstract array. (https://github.com/JuliaStats/DataArrays.jl/issues/229) @testset "treat type arguments as scalars, DataArrays issue 229" begin @@ -1153,6 +1163,9 @@ Base.BroadcastStyle(a::MyBroadcastStyleWithField, b::MyBroadcastStyleWithField) MyBroadcastStyleWithField(1) @test_throws ErrorException Broadcast.result_style(MyBroadcastStyleWithField(1), MyBroadcastStyleWithField(2)) + dest = [0, 0] + dest .= Broadcast.Broadcasted(MyBroadcastStyleWithField(1), +, (1:2, 2:3)) + @test dest == [3, 5] end # test that `Broadcast` definition is defined as total and eligible for concrete evaluation @@ -1162,3 +1175,22 @@ import Base.Broadcast: BroadcastStyle, DefaultArrayStyle f51129(v, x) = (1 .- (v ./ x) .^ 2) @test @inferred(f51129([13.0], 6.5)) == [-3.0] + +@testset "Docstrings" begin + undoc = Docs.undocumented_names(Broadcast) + @test_broken isempty(undoc) + @test undoc == [:dotview] +end + +@testset "broadcast for `AbstractArray` without `CartesianIndex` support" begin + struct BVec52775 <: AbstractVector{Int} + a::Vector{Int} + end + Base.size(a::BVec52775) = size(a.a) + Base.getindex(a::BVec52775, i::Real) = a.a[i] + Base.getindex(a::BVec52775, i) = error("unsupported index!") + a = BVec52775([1,2,3]) + bc = Base.broadcasted(identity, a) + @test bc[1] == bc[CartesianIndex(1)] == bc[1, CartesianIndex()] + @test a .+ [1 2] == a.a .+ [1 2] +end diff --git a/test/buildkitetestjson.jl b/test/buildkitetestjson.jl new file mode 100644 index 0000000000000..49c47e0d8f151 --- /dev/null +++ b/test/buildkitetestjson.jl @@ -0,0 +1,167 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# Convert test(set) results to a Buildkite-compatible JSON representation. +# Based on . + +module BuildkiteTestJSON + +using Test +using Dates + +export write_testset_json_files + +# Bootleg JSON writer + +""" + json_repr(io::IO, value; kwargs...) -> Nothing + +Obtain a JSON representation of `value`, and print it to `io`. + +This may not be the best, most feature-complete, or fastest implementation. +However, it works for its intended purpose. +""" +function json_repr end + +function json_repr(io::IO, val::String; indent::Int=0) + print(io, '"') + escape_string(io, val, ('"',)) + print(io, '"') +end +json_repr(io::IO, val::Integer; indent::Int=0) = print(io, val) +json_repr(io::IO, val::Float64; indent::Int=0) = print(io, val) +function json_repr(io::IO, val::AbstractVector; indent::Int=0) + print(io, '[') + for elt in val + print(io, '\n', ' '^(indent + 2)) + json_repr(io, elt; indent=indent+2) + elt === last(val) || print(io, ',') + end + print(io, '\n', ' '^indent, ']') +end +function json_repr(io::IO, val::Dict; indent::Int=0) + print(io, '{') + for (i, (k, v)) in enumerate(pairs(val)) + print(io, '\n', ' '^(indent + 2)) + json_repr(io, string(k)) + print(io, ": ") + json_repr(io, v; indent=indent+2) + i === length(val) || print(io, ',') + end + print(io, '\n', ' '^indent, '}') +end +json_repr(io::IO, val::Any; indent::Int=0) = json_repr(io, string(val)) + +# Test result processing + +function result_dict(testset::Test.DefaultTestSet, prefix::String="") + Dict{String, Any}( + "id" => Base.UUID(rand(UInt128)), + "scope" => join((prefix, testset.description), '/'), + "history" => if !isnothing(testset.time_end) + Dict{String, Any}( + "start_at" => testset.time_start, + "end_at" => testset.time_end, + "duration" => testset.time_end - testset.time_start) + else + Dict{String, Any}("start_at" => testset.time_start, "duration" => 0.0) + end) +end + +function result_dict(result::Test.Result) + file, line = if !hasproperty(result, :source) || isnothing(result.source) + "unknown", 0 + else + something(result.source.file, "unknown"), result.source.line + end + status = if result isa Test.Pass && result.test_type === :skipped + "skipped" + elseif result isa Test.Pass + "passed" + elseif result isa Test.Fail || result isa Test.Error + "failed" + else + "unknown" + end + data = Dict{String, Any}( + "name" => "$(result.test_type): $(result.orig_expr)", + "location" => string(file, ':', line), + "file_name" => file, + "result" => status) + add_failure_info!(data, result) +end + +function add_failure_info!(data::Dict{String, Any}, result::Test.Result) + if result isa Test.Fail + data["failure_reason"] = if result.test_type === :test && !isnothing(result.data) + "Evaluated: $(result.data)" + elseif result.test_type === :test_throws_nothing + "No exception thrown" + elseif result.test_type === :test_throws_wrong + "Wrong exception type thrown" + else + "unknown" + end + elseif result isa Test.Error + data["failure_reason"] = if result.test_type === :test_error + if occursin("\nStacktrace:\n", result.backtrace) + err, trace = split(result.backtrace, "\nStacktrace:\n", limit=2) + data["failure_expanded"] = + [Dict{String,Any}("expanded" => split(err, '\n'), + "backtrace" => split(trace, '\n'))] + end + "Exception (unexpectedly) thrown during test" + elseif result.test_type === :test_nonbool + "Expected the expression to evaluate to a Bool, not a $(typeof(result.data))" + elseif result.test_type === :test_unbroken + "Expected this test to be broken, but it passed" + else + "unknown" + end + end + data +end + +function collect_results!(results::Vector{Dict{String, Any}}, testset::Test.DefaultTestSet, prefix::String="") + common_data = result_dict(testset, prefix) + result_offset = length(results) + 1 + result_counts = Dict{Tuple{String, String}, Int}() + for (i, result) in enumerate(testset.results) + if result isa Test.Result + rdata = result_dict(result) + rid = (rdata["location"], rdata["result"]) + if haskey(result_counts, rid) + result_counts[rid] += 1 + else + result_counts[rid] = 1 + push!(results, merge(common_data, rdata)) + end + elseif result isa Test.DefaultTestSet + collect_results!(results, result, common_data["scope"]) + end + end + # Modify names to hold `result_counts` + for i in result_offset:length(results) + result = results[i] + rid = (result["location"], result["result"]) + if get(result_counts, rid, 0) > 1 + result["name"] = replace(result["name"], r"^([^:]):" => + SubstitutionString("\\1 (x$(result_counts[rid])):")) + end + end + results +end + +function write_testset_json_files(dir::String, testset::Test.DefaultTestSet) + data = Dict{String, Any}[] + collect_results!(data, testset) + files = String[] + # Buildkite is limited to 5000 results per file https://buildkite.com/docs/test-analytics/importing-json + for (i, chunk) in enumerate(Iterators.partition(data, 5000)) + res_file = joinpath(dir, "results_$i.json") + open(io -> json_repr(io, chunk), res_file, "w") + push!(files, res_file) + end + return files +end + +end diff --git a/test/cartesian.jl b/test/cartesian.jl index df16dfc281d4f..9643da72642ec 100644 --- a/test/cartesian.jl +++ b/test/cartesian.jl @@ -533,3 +533,35 @@ end inds2 = (1, CI(1, 2), 1, CI(1, 2), 1, CI(1, 2), 1) @test (@inferred CI(inds2)) == CI(1, 1, 2, 1, 1, 2, 1, 1, 2, 1) end + +@testset "@ncallkw" begin + f(x...; a, b = 1, c = 2, d = 3) = +(x..., a, b, c, d) + x_1, x_2 = (-1, -2) + kw = (a = 0, c = 0, d = 0) + @test x_1 + x_2 + 1 + 4 == Base.Cartesian.@ncallkw 2 f kw 4 x + b = 0 + kw = (c = 0, d = 0) + @test x_1 + x_2 + 4 == Base.Cartesian.@ncallkw 2 f (; a = 0, b, kw...) 4 x +end + +@testset "if with and without else branch" begin + t1 = Base.Cartesian.@ntuple 3 i -> i == 1 ? 1 : 0 + t2 = Base.Cartesian.@ntuple 3 i -> begin + m = 0 + if i == 1 + m = 1 + end + m + end + @test t1 == t2 + t3 = Base.Cartesian.@ntuple 3 i -> begin + m = 0 + if i == 1 + m = 1 + elseif i == 2 + m = 2 + end + m + end + @test t3 == (1, 2, 0) +end diff --git a/test/ccall.jl b/test/ccall.jl index 6e8269a36225d..8b1fefdfc66e4 100644 --- a/test/ccall.jl +++ b/test/ccall.jl @@ -1477,7 +1477,7 @@ end # issue #20835 @test_throws(ErrorException("could not evaluate ccall argument type (it might depend on a local variable)"), eval(:(f20835(x) = ccall(:fn, Cvoid, (Ptr{typeof(x)},), x)))) -@test_throws(UndefVarError(:Something_not_defined_20835), +@test_throws(UndefVarError(:Something_not_defined_20835, @__MODULE__), eval(:(f20835(x) = ccall(:fn, Something_not_defined_20835, (Ptr{typeof(x)},), x)))) @test isempty(methods(f20835)) @@ -1838,7 +1838,7 @@ ccall_lazy_lib_name(x) = ccall((:testUcharX, compute_lib_name()), Int32, (UInt8, @test ccall_lazy_lib_name(0) == 0 @test ccall_lazy_lib_name(3) == 1 ccall_with_undefined_lib() = ccall((:time, xx_nOt_DeFiNeD_xx), Cint, (Ptr{Cvoid},), C_NULL) -@test_throws UndefVarError(:xx_nOt_DeFiNeD_xx) ccall_with_undefined_lib() +@test_throws UndefVarError(:xx_nOt_DeFiNeD_xx, @__MODULE__) ccall_with_undefined_lib() @testset "transcode for UInt8 and UInt16" begin a = [UInt8(1), UInt8(2), UInt8(3)] @@ -1934,3 +1934,6 @@ end end @test_throws "could not load symbol \"test\"" somefunction_not_found_libc() end + +# issue #52025 +@test Base.unsafe_convert(Ptr{Ptr{Cchar}}, Base.cconvert(Ptr{Ptr{Cchar}}, map(pointer, ["ab"]))) isa Ptr{Ptr{Cchar}} diff --git a/test/channels.jl b/test/channels.jl index 36fec7b842de1..5e81f477126cd 100644 --- a/test/channels.jl +++ b/test/channels.jl @@ -40,6 +40,8 @@ end c = Channel() @test eltype(c) == Any @test c.sz_max == 0 + @test isempty(c) == true # Nothing in it + @test isfull(c) == true # But no more room c = Channel(1) @test eltype(c) == Any @@ -49,6 +51,11 @@ end @test isready(c) == false @test eltype(Channel(1.0)) == Any + c = Channel(1) + @test isfull(c) == false + put!(c, 1) + @test isfull(c) == true + c = Channel{Int}(1) @test eltype(c) == Int @test_throws MethodError put!(c, "Hello") @@ -457,8 +464,8 @@ end Sys.iswindows() && Base.process_events() # schedule event (windows?) close(async) # and close @test !isopen(async) - @test tc[] == 2 - @test tc[] == 2 + @test tc[] == 3 + @test tc[] == 3 yield() # consume event & then close @test tc[] == 3 sleep(0.1) # no further events @@ -479,7 +486,7 @@ end close(async) @test !isopen(async) Base.process_events() # and close - @test tc[] == 0 + @test tc[] == 1 yield() # consume event & then close @test tc[] == 1 sleep(0.1) # no further events @@ -559,7 +566,7 @@ end e = @elapsed for i = 1:5 wait(t) end - @test 1.5 > e >= 0.4 + @test e >= 0.4 @test a[] == 0 nothing end @@ -631,3 +638,10 @@ end @test n_avail(c) == 0 end end + +@testset "Task properties" begin + f() = rand(2,2) + t = Task(f) + @test_throws ErrorException("Querying `scope` is disallowed. Use `current_scope` instead.") t.scope + @test t.state == :runnable +end diff --git a/test/checked.jl b/test/checked.jl index bacda3db75dec..7ee28186f9783 100644 --- a/test/checked.jl +++ b/test/checked.jl @@ -3,7 +3,7 @@ # Checked integer arithmetic import Base: checked_abs, checked_neg, checked_add, checked_sub, checked_mul, - checked_div, checked_rem, checked_fld, checked_mod, checked_cld, + checked_div, checked_rem, checked_fld, checked_mod, checked_cld, checked_pow, add_with_overflow, sub_with_overflow, mul_with_overflow # checked operations @@ -166,6 +166,19 @@ import Base: checked_abs, checked_neg, checked_add, checked_sub, checked_mul, @test checked_cld(typemin(T), T(1)) === typemin(T) @test_throws DivideError checked_cld(typemin(T), T(0)) @test_throws DivideError checked_cld(typemin(T), T(-1)) + + @test checked_pow(T(1), T(0)) === T(1) + @test checked_pow(typemax(T), T(0)) === T(1) + @test checked_pow(typemin(T), T(0)) === T(1) + @test checked_pow(T(1), T(1)) === T(1) + @test checked_pow(T(1), typemax(T)) === T(1) + @test checked_pow(T(2), T(2)) === T(4) + @test_throws OverflowError checked_pow(T(2), typemax(T)) + @test_throws OverflowError checked_pow(T(-2), typemax(T)) + @test_throws OverflowError checked_pow(typemax(T), T(2)) + @test_throws OverflowError checked_pow(typemin(T), T(2)) + @test_throws DomainError checked_pow(T(2), -T(1)) + @test_throws DomainError checked_pow(-T(2), -T(1)) end @testset for T in (UInt8, UInt16, UInt32, UInt64, UInt128) @@ -296,6 +309,10 @@ end @test checked_cld(true, true) === true @test checked_cld(false, true) === false @test_throws DivideError checked_cld(true, false) + + @test checked_pow(true, 1) === true + @test checked_pow(true, 1000000) === true + @test checked_pow(false, 1000000) === false end @testset "BigInt" begin @test checked_abs(BigInt(-1)) == BigInt(1) @@ -310,6 +327,9 @@ end @test checked_fld(BigInt(10), BigInt(3)) == BigInt(3) @test checked_mod(BigInt(9), BigInt(4)) == BigInt(1) @test checked_cld(BigInt(10), BigInt(3)) == BigInt(4) + + @test checked_pow(BigInt(2), 2) == BigInt(4) + @test checked_pow(BigInt(2), 100) == BigInt(1267650600228229401496703205376) end @testset "Additional tests" begin @@ -358,3 +378,7 @@ end @test checked_mul(1, 2, 3, 4, 5, 6, 7) === 5040 @test checked_mul(1, 2, 3, 4, 5, 6, 7, 8) === 40320 end + +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(Base.Checked)) +end diff --git a/test/choosetests.jl b/test/choosetests.jl index 2f77b11767dee..5f89233f892c8 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -24,12 +24,12 @@ const TESTNAMES = [ "some", "meta", "stacktraces", "docs", "gc", "misc", "threads", "stress", "binaryplatforms", "atexit", "enums", "cmdlineargs", "int", "interpreter", - "checked", "bitset", "floatfuncs", "precompile", + "checked", "bitset", "floatfuncs", "precompile", "relocatedepot", "boundscheck", "error", "ambiguous", "cartesian", "osutils", "channels", "iostream", "secretbuffer", "specificity", "reinterpretarray", "syntax", "corelogging", "missing", "asyncmap", "smallarrayshrink", "opaque_closure", "filesystem", "download", - "scopedvalues", + "scopedvalues", "compileall" ] const INTERNET_REQUIRED_LIST = [ @@ -151,15 +151,14 @@ function choosetests(choices = []) filtertests!(tests, "unicode", ["unicode/utf8"]) filtertests!(tests, "strings", ["strings/basic", "strings/search", "strings/util", - "strings/io", "strings/types"]) + "strings/io", "strings/types", "strings/annotated"]) # do subarray before sparse but after linalg filtertests!(tests, "subarray") filtertests!(tests, "compiler", [ "compiler/datastructures", "compiler/inference", "compiler/effects", - "compiler/validation", "compiler/ssair", "compiler/irpasses", - "compiler/codegen", "compiler/inline", "compiler/contextual", - "compiler/invalidation", "compiler/AbstractInterpreter", - "compiler/EscapeAnalysis/EscapeAnalysis"]) + "compiler/validation", "compiler/ssair", "compiler/irpasses", "compiler/tarjan", + "compiler/codegen", "compiler/inline", "compiler/contextual", "compiler/invalidation", + "compiler/AbstractInterpreter", "compiler/EscapeAnalysis/EscapeAnalysis"]) filtertests!(tests, "compiler/EscapeAnalysis", [ "compiler/EscapeAnalysis/EscapeAnalysis"]) filtertests!(tests, "stdlib", STDLIBS) diff --git a/test/clangsa/GCPushPop.cpp b/test/clangsa/GCPushPop.cpp index a62c1501bf323..72e0494a7d936 100644 --- a/test/clangsa/GCPushPop.cpp +++ b/test/clangsa/GCPushPop.cpp @@ -18,7 +18,7 @@ void missingPop2() { } // expected-warning{{Non-popped GC frame present at end of function}} // expected-note@-1{{Non-popped GC frame present at end of function}} -void superflousPop() { +void superfluousPop() { JL_GC_POP(); // expected-warning{{JL_GC_POP without corresponding push}} } // expected-note@-1{{JL_GC_POP without corresponding push}} diff --git a/test/clangsa/MissingRoots.c b/test/clangsa/MissingRoots.c index 0ff5e633622ce..b71b2571e8a3e 100644 --- a/test/clangsa/MissingRoots.c +++ b/test/clangsa/MissingRoots.c @@ -415,7 +415,7 @@ void stack_rooted(jl_value_t *lb JL_MAYBE_UNROOTED, jl_value_t *ub JL_MAYBE_UNRO JL_DLLEXPORT jl_value_t *jl_totally_used_function(int i) { jl_value_t *v = jl_box_int32(i); // expected-note{{Started tracking value here}} - jl_safepoint(); // expected-note{{Value may have been GCed here}} + jl_gc_safepoint(); // expected-note{{Value may have been GCed here}} return v; // expected-warning{{Return value may have been GCed}} // expected-note@-1{{Return value may have been GCed}} } diff --git a/test/client.jl b/test/client.jl index 0649ab3241d62..61fe7d5093474 100644 --- a/test/client.jl +++ b/test/client.jl @@ -12,14 +12,14 @@ nested_error_pattern = r""" ERROR: DivideError: integer division error Stacktrace:.* - caused by: UndefVarError: `__not_a_binding__` not defined + caused by: UndefVarError: `__not_a_binding__` not defined in `Main` Stacktrace:.* """s @testset "display_error" begin # Display of errors which cause more than one entry on the exception stack excs = try - eval(nested_error_expr) + Core.eval(Main, nested_error_expr) catch Base.current_exceptions() end @@ -31,7 +31,7 @@ nested_error_pattern = r""" DivideError: integer division error Stacktrace:.* - caused by: UndefVarError: `__not_a_binding__` not defined + caused by: UndefVarError: `__not_a_binding__` not defined in `Main` Stacktrace:.* """s, sprint(show, excs)) end @@ -52,3 +52,8 @@ end ERROR: ErrorException """s, err_str) end + +@testset "defining `ans` and `err`" begin + @test eval(:(ans = 1)) == 1 + @test eval(:(err = 1)) == 1 +end diff --git a/test/cmdlineargs.jl b/test/cmdlineargs.jl index 2e3598d0a1597..3fbfdd9bc1257 100644 --- a/test/cmdlineargs.jl +++ b/test/cmdlineargs.jl @@ -4,8 +4,8 @@ import Libdl # helper function for passing input to stdin # and returning the stdout result -function writereadpipeline(input, exename) - p = open(exename, "w+") +function writereadpipeline(input, exename; stderr=nothing) + p = open(pipeline(exename; stderr), "w+") @async begin write(p.in, input) close(p.in) @@ -62,11 +62,25 @@ end @testset "julia_cmd" begin julia_basic = Base.julia_cmd() + function get_julia_cmd(arg) + io = Base.BufferStream() + cmd = `$julia_basic $arg -e 'print(repr(Base.julia_cmd()))'` + try + run(pipeline(cmd, stdout=io, stderr=io)) + catch + @error "cmd failed" cmd read(io, String) + rethrow() + end + closewrite(io) + return read(io, String) + end + opts = Base.JLOptions() - get_julia_cmd(arg) = strip(read(`$julia_basic $arg -e 'print(repr(Base.julia_cmd()))'`, String), ['`']) for (arg, default) in ( - ("-C$(unsafe_string(opts.cpu_target))", false), + # Use a Cmd to handle space nicely when + # interpolating inside another Cmd. + (`-C $(unsafe_string(opts.cpu_target))`, false), ("-J$(unsafe_string(opts.image_file))", false), @@ -123,32 +137,45 @@ end ("--pkgimages=no", false), ) @testset "$arg" begin + str = arg isa Cmd ? join(arg.exec, ' ') : arg if default - @test !occursin(arg, get_julia_cmd(arg)) + @test !occursin(str, get_julia_cmd(arg)) else - @test occursin(arg, get_julia_cmd(arg)) + @test occursin(str, get_julia_cmd(arg)) end end end + + # Test empty `cpu_target` gives a helpful error message, issue #52209. + io = IOBuffer() + p = run(pipeline(`$(Base.julia_cmd(; cpu_target="")) --startup-file=no -e ''`; stderr=io); wait=false) + wait(p) + @test p.exitcode == 1 + @test occursin("empty CPU name", String(take!(io))) end let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` # tests for handling of ENV errors - let v = writereadpipeline( + let + io = IOBuffer() + v = writereadpipeline( "println(\"REPL: \", @which(less), @isdefined(InteractiveUtils))", setenv(`$exename -i -E '@assert isempty(LOAD_PATH); push!(LOAD_PATH, "@stdlib"); @isdefined InteractiveUtils'`, "JULIA_LOAD_PATH" => "", "JULIA_DEPOT_PATH" => ";:", - "HOME" => homedir())) + "HOME" => homedir()); + stderr=io) # @which is undefined @test_broken v == ("false\nREPL: InteractiveUtilstrue\n", true) + stderr = String(take!(io)) + @test_broken isempty(stderr) end let v = writereadpipeline("println(\"REPL: \", InteractiveUtils)", setenv(`$exename -i -e 'const InteractiveUtils = 3'`, "JULIA_LOAD_PATH" => ";;;:::", "JULIA_DEPOT_PATH" => ";;;:::", "HOME" => homedir())) - # TODO: ideally, `@which`, etc. would still work, but Julia can't handle `using $InterativeUtils` + # TODO: ideally, `@which`, etc. would still work, but Julia can't handle `using $InteractiveUtils` @test v == ("REPL: 3\n", true) end @testset let v = readchomperrors(`$exename -i -e ' @@ -361,7 +388,7 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` # --gcthreads code = "print(Threads.ngcthreads())" cpu_threads = ccall(:jl_effective_threads, Int32, ()) - @test (cpu_threads == 1 ? "1" : string(div(cpu_threads, 2))) == + @test string(cpu_threads) == read(`$exename --threads auto -e $code`, String) == read(`$exename --threads=auto -e $code`, String) == read(`$exename -tauto -e $code`, String) == @@ -417,9 +444,7 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` mktempdir() do dir helperdir = joinpath(@__DIR__, "testhelpers") inputfile = joinpath(helperdir, "coverage_file.jl") - expected = replace(read(joinpath(helperdir, "coverage_file.info.bad"), String), - "" => realpath(inputfile)) - expected_good = replace(read(joinpath(helperdir, "coverage_file.info"), String), + expected = replace(read(joinpath(helperdir, "coverage_file.info"), String), "" => realpath(inputfile)) covfile = replace(joinpath(dir, "coverage.info"), "%" => "%%") @test !isfile(covfile) @@ -437,21 +462,18 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` got = read(covfile, String) rm(covfile) @test occursin(expected, got) || (expected, got) - @test_broken occursin(expected_good, got) @test readchomp(`$exename -E "Base.JLOptions().code_coverage" -L $inputfile --code-coverage=$covfile --code-coverage=user`) == "1" @test isfile(covfile) got = read(covfile, String) rm(covfile) @test occursin(expected, got) || (expected, got) - @test_broken occursin(expected_good, got) @test readchomp(`$exename -E "Base.JLOptions().code_coverage" -L $inputfile --code-coverage=$covfile --code-coverage=all`) == "2" @test isfile(covfile) got = read(covfile, String) rm(covfile) @test occursin(expected, got) || (expected, got) - @test_broken occursin(expected_good, got) # Ask for coverage in specific file tfile = realpath(inputfile) @@ -461,7 +483,6 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` got = read(covfile, String) rm(covfile) @test occursin(expected, got) || (expected, got) - @test_broken occursin(expected_good, got) # Ask for coverage in directory tdir = dirname(realpath(inputfile)) @@ -471,16 +492,111 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` got = read(covfile, String) rm(covfile) @test occursin(expected, got) || (expected, got) - @test_broken occursin(expected_good, got) + + # Ask for coverage in current directory + tdir = dirname(realpath(inputfile)) + cd(tdir) do + # there may be atrailing separator here so use rstrip + @test readchomp(`$exename -E "(Base.JLOptions().code_coverage, rstrip(unsafe_string(Base.JLOptions().tracked_path), '/'))" -L $inputfile + --code-coverage=$covfile --code-coverage=@`) == "(3, $(repr(tdir)))" + end + @test isfile(covfile) + got = read(covfile, String) + rm(covfile) + @test occursin(expected, got) || (expected, got) + + # Ask for coverage in relative directory + tdir = dirname(realpath(inputfile)) + cd(dirname(tdir)) do + @test readchomp(`$exename -E "(Base.JLOptions().code_coverage, unsafe_string(Base.JLOptions().tracked_path))" -L $inputfile + --code-coverage=$covfile --code-coverage=@testhelpers`) == "(3, $(repr(tdir)))" + end + @test isfile(covfile) + got = read(covfile, String) + rm(covfile) + @test occursin(expected, got) || (expected, got) + + # Ask for coverage in relative directory with dot-dot notation + tdir = dirname(realpath(inputfile)) + cd(tdir) do + @test readchomp(`$exename -E "(Base.JLOptions().code_coverage, unsafe_string(Base.JLOptions().tracked_path))" -L $inputfile + --code-coverage=$covfile --code-coverage=@../testhelpers`) == "(3, $(repr(tdir)))" + end + @test isfile(covfile) + got = read(covfile, String) + rm(covfile) + @test occursin(expected, got) || (expected, got) # Ask for coverage in a different directory tdir = mktempdir() # a dir that contains no code @test readchomp(`$exename -E "(Base.JLOptions().code_coverage, unsafe_string(Base.JLOptions().tracked_path))" -L $inputfile - --code-coverage=$covfile --code-coverage=@$tdir`) == "(3, $(repr(tdir)))" + --code-coverage=$covfile --code-coverage=@$tdir`) == "(3, $(repr(realpath(tdir))))" @test isfile(covfile) got = read(covfile, String) @test isempty(got) rm(covfile) + + function coverage_info_for(src::String) + mktemp(dir) do srcfile, io + write(io, src); close(io) + outfile = tempname(dir, cleanup=false)*".info" + run(`$exename --code-coverage=$outfile $srcfile`) + result = read(outfile, String) + rm(outfile, force=true) + result + end + end + @test contains(coverage_info_for(""" + function cov_bug(x, p) + if p > 2 + print("") # runs + end + if Base.compilerbarrier(:const, false) + println("Does not run") + end + end + function do_test() + cov_bug(5, 3) + end + do_test() + """), """ + DA:2,1 + DA:3,1 + DA:5,1 + DA:6,0 + DA:9,1 + DA:10,1 + LH:5 + LF:6 + """) + @test contains(coverage_info_for(""" + function cov_bug() + if Base.compilerbarrier(:const, true) + if Base.compilerbarrier(:const, true) + if Base.compilerbarrier(:const, false) + println("Does not run") + end + else + print("Does not run either") + end + else + print("") + end + return nothing + end + cov_bug() + """), """ + DA:1,1 + DA:2,1 + DA:3,1 + DA:4,1 + DA:5,0 + DA:8,0 + DA:11,0 + DA:13,1 + LH:5 + LF:8 + """) end # --track-allocation @@ -513,9 +629,9 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` @test popfirst!(got) == " 32 Base.invokelatest(g, x)" end if Sys.WORD_SIZE == 64 - @test popfirst!(got) == " 48 []" - else @test popfirst!(got) == " 32 []" + else + @test popfirst!(got) == " 16 []" end @test popfirst!(got) == " - end" @test popfirst!(got) == " - f(1.23)" @@ -543,23 +659,27 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` @test occursin("llvm.module.flags", code) @test !occursin("llvm.dbg.cu", code) @test !occursin("int.jl", code) - @test !occursin("\"Int64\"", code) + @test !occursin("name: \"Int64\"", code) end let code = readchomperrors(`$exename -g1 -E "@eval Int64(1)+Int64(1)"`) @test code[1] code = code[3] @test occursin("llvm.module.flags", code) @test occursin("llvm.dbg.cu", code) - @test occursin("int.jl", code) - @test !occursin("\"Int64\"", code) + # TODO: consider moving test to llvmpasses as this fails on some platforms + # without clear reason + @test_skip occursin("int.jl", code) + @test !occursin("name: \"Int64\"", code) end let code = readchomperrors(`$exename -g2 -E "@eval Int64(1)+Int64(1)"`) @test code[1] code = code[3] @test occursin("llvm.module.flags", code) @test occursin("llvm.dbg.cu", code) - @test occursin("int.jl", code) - @test occursin("\"Int64\"", code) + # TODO: consider moving test to llvmpasses as this fails on some platforms + # without clear reason + @test_skip occursin("int.jl", code) + @test occursin("name: \"Int64\"", code) end end end @@ -979,8 +1099,26 @@ end @test lines[3] == "foo" @test lines[4] == "bar" end -#heap-size-hint, we reserve 250 MB for non GC memory (llvm, etc.) -@test readchomp(`$(Base.julia_cmd()) --startup-file=no --heap-size-hint=500M -e "println(@ccall jl_gc_get_max_memory()::UInt64)"`) == "$((500-250)*1024*1024)" +end + +@testset "heap size hint" begin + #heap-size-hint, we reserve 250 MB for non GC memory (llvm, etc.) + @test readchomp(`$(Base.julia_cmd()) --startup-file=no --heap-size-hint=500M -e "println(@ccall jl_gc_get_max_memory()::UInt64)"`) == "$((500-250)*1024*1024)" + + mem = ccall(:uv_get_total_memory, UInt64, ()) + cmem = ccall(:uv_get_constrained_memory, UInt64, ()) + if cmem > 0 && cmem < mem + mem = cmem + end + maxmem = parse(UInt64, readchomp(`$(Base.julia_cmd()) --startup-file=no --heap-size-hint=25% -e "println(@ccall jl_gc_get_max_memory()::UInt64)"`)) + hint = max(mem÷4, 251*1024*1024) - 250*1024*1024 + MAX32HEAP = 1536 * 1024 * 1024 + if Int === Int32 && hint > MAX32HEAP + hint = MAX32HEAP + end + @test abs(Float64(maxmem) - hint)/maxmem < 0.05 + + @test readchomp(`$(Base.julia_cmd()) --startup-file=no --heap-size-hint=10M -e "println(@ccall jl_gc_get_max_memory()::UInt64)"`) == "$(1*1024*1024)" end ## `Main.main` entrypoint @@ -994,3 +1132,28 @@ end # Test import from module @test readchomp(`$(Base.julia_cmd()) -e 'module Hello; export main; (@main)(ARGS) = println("hello"); end; using .Hello'`) == "hello" @test readchomp(`$(Base.julia_cmd()) -e 'module Hello; export main; (@main)(ARGS) = println("hello"); end; import .Hello'`) == "" + +# test --bug-report=rr +if Sys.islinux() && Sys.ARCH in (:i686, :x86_64) # rr is only available on these platforms + mktempdir() do temp_trace_dir + @test success(pipeline(setenv(`$(Base.julia_cmd()) --bug-report=rr-local -e 'exit()'`, + "JULIA_RR_RECORD_ARGS" => "-n --nested=ignore", + "_RR_TRACE_DIR" => temp_trace_dir); #=stderr, stdout=#)) + end +end + +@testset "--heap-size-hint" begin + exename = `$(Base.julia_cmd())` + @test errors_not_signals(`$exename --heap-size-hint -e "exit(0)"`) + @testset "--heap-size-hint=$str" for str in ["asdf","","0","1.2vb","b","GB","2.5GB̂","1.2gb2","42gigabytes","5gig","2GiB","NaNt"] + @test errors_not_signals(`$exename --heap-size-hint=$str -e "exit(0)"`) + end + k = 1024 + m = 1024k + g = 1024m + t = 1024g + @testset "--heap-size-hint=$str" for (str, val) in [("1", 1), ("1e7", 1e7), ("2.5e7", 2.5e7), ("1MB", 1m), ("2.5g", 2.5g), ("1e4kB", 1e4k), + ("1e100", typemax(UInt64)), ("1e500g", typemax(UInt64)), ("1e-12t", 1), ("500000000b", 500000000)] + @test parse(UInt64,read(`$exename --heap-size-hint=$str -E "Base.JLOptions().heap_size_hint"`, String)) == val + end +end diff --git a/test/combinatorics.jl b/test/combinatorics.jl index f8fe4e0bd0829..862e3bfa37e1e 100644 --- a/test/combinatorics.jl +++ b/test/combinatorics.jl @@ -2,6 +2,9 @@ using Random: randcycle +isdefined(Main, :ImmutableArrays) || @eval Main include("testhelpers/ImmutableArrays.jl") +using .Main.ImmutableArrays + @testset "binomial" begin @test binomial(5,-1) == 0 @test binomial(5,10) == 0 @@ -67,6 +70,10 @@ end @test isperm(T) == true @test isperm(K) == false end + + # issue #47847 + p = ImmutableArrays.ImmutableArray([2,3,1]) + @test invperm(p) == invperm([2,3,1]) end @testset "factorial" begin @@ -122,3 +129,24 @@ end end end end + +@testset "permute!" begin + #simple array + @test permute!([1,2,3,4,5],[3,2,1,5,4]) == [3,2,1,5,4] + #empty array + @test permute!([],[]) == [] + #single-element array + @test permute!([5],[1]) == [5] + #repeated elements in array + @test permute!([1,2,2,3,3,3],[2,1,3,5,4,6]) == [2,1,2,3,3,3] + #permutation vector contains zero + @test_throws BoundsError permute!([1,2,3],[0,1,2]) + #permutation vector contains negative indices + @test_throws BoundsError permute!([1,2,3],[2,-1,1]) + #permutation vector contains indices larger than array size + @test_throws BoundsError permute!([1,2,3],[2,4,1]) + #permutation vector is empty + @test_throws DimensionMismatch permute!([1,2,3],[]) + #array is empty + @test_throws BoundsError permute!([],[2,1]) +end diff --git a/test/compileall.jl b/test/compileall.jl new file mode 100644 index 0000000000000..914b0fc8a4d9e --- /dev/null +++ b/test/compileall.jl @@ -0,0 +1,13 @@ +# This test builds a full system image, so it can take a little while. +# We make it a separate test target here, so that it can run in parallel +# with the rest of the tests. + +mktempdir() do dir + @test success(pipeline(`$(Base.julia_cmd()) --compile=all --strip-ir --output-o $(dir)/sys.o.a -e 'exit()'`, stderr=stderr)) broken=(Sys.iswindows() && Sys.WORD_SIZE == 32) + if isfile(joinpath(dir, "sys.o.a")) + Base.Linking.link_image(joinpath(dir, "sys.o.a"), joinpath(dir, "sys.so")) + # TODO: Broken on Windows due to + # https://github.com/llvm/llvm-project/issues/84424 + @test success(`$(Base.julia_cmd()) -J $(dir)/sys.so -e 'Base.scrub_repl_backtrace(nothing); exit()'`) broken=Sys.iswindows() + end +end diff --git a/test/compiler/AbstractInterpreter.jl b/test/compiler/AbstractInterpreter.jl index 2d77c148275b9..bd7adc4be7639 100644 --- a/test/compiler/AbstractInterpreter.jl +++ b/test/compiler/AbstractInterpreter.jl @@ -6,6 +6,17 @@ const CC = Core.Compiler include("irutils.jl") include("newinterp.jl") +# interpreter that performs abstract interpretation only +# (semi-concrete interpretation should be disabled automatically) +@newinterp AbsIntOnlyInterp1 +CC.may_optimize(::AbsIntOnlyInterp1) = false +@test Base.infer_return_type(Base.init_stdio, (Ptr{Cvoid},); interp=AbsIntOnlyInterp1()) >: IO + +# it should work even if the interpreter discards inferred source entirely +@newinterp AbsIntOnlyInterp2 +CC.may_optimize(::AbsIntOnlyInterp2) = false +CC.transform_result_for_cache(::AbsIntOnlyInterp2, ::Core.MethodInstance, ::CC.WorldRange, ::CC.InferenceResult) = nothing +@test Base.infer_return_type(Base.init_stdio, (Ptr{Cvoid},); interp=AbsIntOnlyInterp2()) >: IO # OverlayMethodTable # ================== @@ -21,7 +32,7 @@ end @newinterp MTOverlayInterp @MethodTable OverlayedMT -CC.method_table(interp::MTOverlayInterp) = CC.OverlayMethodTable(CC.get_world_counter(interp), OverlayedMT) +CC.method_table(interp::MTOverlayInterp) = CC.OverlayMethodTable(CC.get_inference_world(interp), OverlayedMT) function CC.add_remark!(interp::MTOverlayInterp, ::CC.InferenceState, remark) if interp.meta !== nothing @@ -120,7 +131,7 @@ end |> only === Nothing # https://github.com/JuliaLang/julia/issues/48097 @newinterp Issue48097Interp @MethodTable Issue48097MT -CC.method_table(interp::Issue48097Interp) = CC.OverlayMethodTable(CC.get_world_counter(interp), Issue48097MT) +CC.method_table(interp::Issue48097Interp) = CC.OverlayMethodTable(CC.get_inference_world(interp), Issue48097MT) CC.InferenceParams(::Issue48097Interp) = CC.InferenceParams(; unoptimize_throw_blocks=false) function CC.concrete_eval_eligible(interp::Issue48097Interp, @nospecialize(f), result::CC.MethodCallResult, arginfo::CC.ArgInfo, sv::CC.AbsIntState) @@ -141,7 +152,7 @@ end # Should not concrete-eval overlayed methods in semi-concrete interpretation @newinterp OverlaySinInterp @MethodTable OverlaySinMT -CC.method_table(interp::OverlaySinInterp) = CC.OverlayMethodTable(CC.get_world_counter(interp), OverlaySinMT) +CC.method_table(interp::OverlaySinInterp) = CC.OverlayMethodTable(CC.get_inference_world(interp), OverlaySinMT) overlay_sin1(x) = error("Not supposed to be called.") @overlay OverlaySinMT overlay_sin1(x) = cos(x) @overlay OverlaySinMT Base.sin(x::Union{Float32,Float64}) = overlay_sin1(x) @@ -330,19 +341,17 @@ function CC.abstract_call(interp::NoinlineInterpreter, ret = @invoke CC.abstract_call(interp::CC.AbstractInterpreter, arginfo::CC.ArgInfo, si::CC.StmtInfo, sv::CC.InferenceState, max_methods::Int) if sv.mod in noinline_modules(interp) - return CC.CallMeta(ret.rt, ret.effects, NoinlineCallInfo(ret.info)) + return CC.CallMeta(ret.rt, ret.exct, ret.effects, NoinlineCallInfo(ret.info)) end return ret end -function CC.inlining_policy(interp::NoinlineInterpreter, - @nospecialize(src), @nospecialize(info::CallInfo), stmt_flag::UInt32, mi::MethodInstance, - argtypes::Vector{Any}) +function CC.src_inlining_policy(interp::NoinlineInterpreter, + @nospecialize(src), @nospecialize(info::CallInfo), stmt_flag::UInt32) if isa(info, NoinlineCallInfo) - return nothing + return false end - return @invoke CC.inlining_policy(interp::CC.AbstractInterpreter, - src::Any, info::CallInfo, stmt_flag::UInt32, mi::MethodInstance, - argtypes::Vector{Any}) + return @invoke CC.src_inlining_policy(interp::CC.AbstractInterpreter, + src::Any, info::CallInfo, stmt_flag::UInt32) end @inline function inlined_usually(x, y, z) @@ -370,7 +379,7 @@ let NoinlineModule = Module() # it should work for cached results method = only(methods(inlined_usually, (Float64,Float64,Float64,))) mi = CC.specialize_method(method, Tuple{typeof(inlined_usually),Float64,Float64,Float64}, Core.svec()) - @test haskey(interp.code_cache.dict, mi) + @test CC.haskey(CC.code_cache(interp), mi) let src = code_typed1((Float64,Float64,Float64); interp) do x, y, z inlined_usually(x, y, z) end @@ -401,7 +410,6 @@ end Core.eval(Core.Compiler, quote f(;a=1) = a end) @test_throws MethodError Core.Compiler.f(;b=2) - # Custom lookup function # ====================== @@ -437,18 +445,15 @@ custom_lookup_context(x::Int) = custom_lookup_target(true, x) const CONST_INVOKE_INTERP_WORLD = Base.get_world_counter() const CONST_INVOKE_INTERP = ConstInvokeInterp(; world=CONST_INVOKE_INTERP_WORLD) function custom_lookup(mi::MethodInstance, min_world::UInt, max_world::UInt) - local matched_mi = nothing for inf_result in CONST_INVOKE_INTERP.inf_cache if inf_result.linfo === mi if CC.any(inf_result.overridden_by_const) - return CodeInstance(CONST_INVOKE_INTERP, inf_result, inf_result.valid_worlds) - elseif matched_mi === nothing - matched_mi = inf_result.linfo + return CodeInstance(CONST_INVOKE_INTERP, inf_result) end end end - matched_mi === nothing && return nothing - return CONST_INVOKE_INTERP.code_cache.dict[matched_mi] + # XXX: This seems buggy, custom_lookup should probably construct the absint on demand. + return CC.getindex(CC.code_cache(CONST_INVOKE_INTERP), mi) end let # generate cache @@ -458,7 +463,6 @@ let # generate cache target_mi = CC.specialize_method(only(methods(custom_lookup_target)), Tuple{typeof(custom_lookup_target),Bool,Int}, Core.svec()) target_ci = custom_lookup(target_mi, CONST_INVOKE_INTERP_WORLD, CONST_INVOKE_INTERP_WORLD) @test target_ci.rettype == Tuple{Float64,Nothing} # constprop'ed source - # display(@ccall jl_uncompress_ir(target_ci.def.def::Any, C_NULL::Ptr{Cvoid}, target_ci.inferred::Any)::Any) raw = false lookup = @cfunction(custom_lookup, Any, (Any,Csize_t,Csize_t)) @@ -470,6 +474,60 @@ let # generate cache lookup) io = IOBuffer() code_llvm(io, custom_lookup_target, (Bool,Int,); params) - @test occursin("j_sin_", String(take!(io))) - @test !occursin("j_cos_", String(take!(io))) + s = String(take!(io)) + @test occursin("j_sin_", s) + @test !occursin("j_cos_", s) +end + +# custom inferred data +# ==================== + +@newinterp CustomDataInterp +struct CustomDataInterpToken end +CC.cache_owner(::CustomDataInterp) = CustomDataInterpToken() +struct CustomData + inferred + CustomData(@nospecialize inferred) = new(inferred) +end +function CC.transform_result_for_cache(interp::CustomDataInterp, + mi::Core.MethodInstance, valid_worlds::CC.WorldRange, result::CC.InferenceResult) + inferred_result = @invoke CC.transform_result_for_cache(interp::CC.AbstractInterpreter, + mi::Core.MethodInstance, valid_worlds::CC.WorldRange, result::CC.InferenceResult) + return CustomData(inferred_result) +end +function CC.src_inlining_policy(interp::CustomDataInterp, @nospecialize(src), + @nospecialize(info::CC.CallInfo), stmt_flag::UInt32) + if src isa CustomData + src = src.inferred + end + return @invoke CC.src_inlining_policy(interp::CC.AbstractInterpreter, src::Any, + info::CC.CallInfo, stmt_flag::UInt32) +end +CC.retrieve_ir_for_inlining(cached_result::CodeInstance, src::CustomData) = + CC.retrieve_ir_for_inlining(cached_result, src.inferred) +CC.retrieve_ir_for_inlining(mi::MethodInstance, src::CustomData, preserve_local_sources::Bool) = + CC.retrieve_ir_for_inlining(mi, src.inferred, preserve_local_sources) +let src = code_typed((Int,); interp=CustomDataInterp()) do x + return sin(x) + cos(x) + end |> only |> first + @test count(isinvoke(:sin), src.code) == 1 + @test count(isinvoke(:cos), src.code) == 1 + @test count(isinvoke(:+), src.code) == 0 +end + +# ephemeral cache mode +@newinterp DebugInterp #=ephemeral_cache=#true +func_ext_cache1(a) = func_ext_cache2(a) * cos(a) +func_ext_cache2(a) = sin(a) +let interp = DebugInterp() + @test Base.infer_return_type(func_ext_cache1, (Float64,); interp) === Float64 + @test isdefined(interp, :code_cache) + found = false + for (mi, codeinst) in interp.code_cache.dict + if mi.def.name === :func_ext_cache2 + found = true + break + end + end + @test found end diff --git a/test/compiler/EscapeAnalysis/EAUtils.jl b/test/compiler/EscapeAnalysis/EAUtils.jl index 02038cf8e5540..87228aaf2858a 100644 --- a/test/compiler/EscapeAnalysis/EAUtils.jl +++ b/test/compiler/EscapeAnalysis/EAUtils.jl @@ -62,21 +62,16 @@ __clear_cache!() = empty!(GLOBAL_EA_CODE_CACHE) # imports import .CC: AbstractInterpreter, NativeInterpreter, WorldView, WorldRange, - InferenceParams, OptimizationParams, get_world_counter, get_inference_cache, code_cache + InferenceParams, OptimizationParams, get_world_counter, get_inference_cache, + ipo_dataflow_analysis!, cache_result! # usings using Core: CodeInstance, MethodInstance, CodeInfo using .CC: - InferenceResult, OptimizationState, IRCode, copy as cccopy, - @timeit, convert_to_ircode, slot2reg, compact!, ssa_inlining_pass!, sroa_pass!, - adce_pass!, JLOptions, verify_ir, verify_linetable + InferenceResult, OptimizationState, IRCode using .EA: analyze_escapes, ArgEscapeCache, EscapeInfo, EscapeState -struct CodeCache - cache::IdDict{MethodInstance,CodeInstance} -end -CodeCache() = CodeCache(IdDict{MethodInstance,CodeInstance}()) -const GLOBAL_CODE_CACHE = CodeCache() +struct EAToken end # when working outside of Core.Compiler, # cache entire escape state for later inspection and debugging @@ -87,7 +82,7 @@ struct EscapeCacheInfo end struct EscapeCache - cache::IdDict{MethodInstance,EscapeCacheInfo} + cache::IdDict{MethodInstance,EscapeCacheInfo} # TODO(aviatesk) Should this be CodeInstance to EscapeCacheInfo? end EscapeCache() = EscapeCache(IdDict{MethodInstance,EscapeCacheInfo}()) const GLOBAL_ESCAPE_CACHE = EscapeCache() @@ -103,84 +98,49 @@ mutable struct EscapeAnalyzer <: AbstractInterpreter const inf_params::InferenceParams const opt_params::OptimizationParams const inf_cache::Vector{InferenceResult} - const code_cache::CodeCache const escape_cache::EscapeCache const entry_mi::MethodInstance result::EscapeResultForEntry function EscapeAnalyzer(world::UInt, entry_mi::MethodInstance, - code_cache::CodeCache=GLOBAL_CODE_CACHE, escape_cache::EscapeCache=GLOBAL_ESCAPE_CACHE) inf_params = InferenceParams() opt_params = OptimizationParams() inf_cache = InferenceResult[] - return new(world, inf_params, opt_params, inf_cache, code_cache, escape_cache, entry_mi) + return new(world, inf_params, opt_params, inf_cache, escape_cache, entry_mi) end end CC.InferenceParams(interp::EscapeAnalyzer) = interp.inf_params CC.OptimizationParams(interp::EscapeAnalyzer) = interp.opt_params -CC.get_world_counter(interp::EscapeAnalyzer) = interp.world +CC.get_inference_world(interp::EscapeAnalyzer) = interp.world CC.get_inference_cache(interp::EscapeAnalyzer) = interp.inf_cache +CC.cache_owner(::EscapeAnalyzer) = EAToken() -struct EscapeAnalyzerCacheView - code_cache::CodeCache - escape_cache::EscapeCache -end - -function CC.code_cache(interp::EscapeAnalyzer) - worlds = WorldRange(get_world_counter(interp)) - return WorldView(EscapeAnalyzerCacheView(interp.code_cache, interp.escape_cache), worlds) -end -CC.haskey(wvc::WorldView{EscapeAnalyzerCacheView}, mi::MethodInstance) = haskey(wvc.cache.code_cache.cache, mi) -CC.get(wvc::WorldView{EscapeAnalyzerCacheView}, mi::MethodInstance, default) = get(wvc.cache.code_cache.cache, mi, default) -CC.getindex(wvc::WorldView{EscapeAnalyzerCacheView}, mi::MethodInstance) = getindex(wvc.cache.code_cache.cache, mi) -function CC.setindex!(wvc::WorldView{EscapeAnalyzerCacheView}, ci::CodeInstance, mi::MethodInstance) - wvc.cache.code_cache.cache[mi] = ci - add_invalidation_callback!(wvc.cache.code_cache, wvc.cache.escape_cache, mi) # register the callback on invalidation - return nothing -end -function add_invalidation_callback!(code_cache::CodeCache, escape_cache::EscapeCache, mi) - callback = InvalidationCallback(code_cache, escape_cache) - if !isdefined(mi, :callbacks) - mi.callbacks = Any[callback] - else - if !any(@nospecialize(cb)->cb===callback, mi.callbacks) - push!(mi.callbacks, callback) - end +function CC.ipo_dataflow_analysis!(interp::EscapeAnalyzer, ir::IRCode, caller::InferenceResult) + # run EA on all frames that have been optimized + nargs = let def = caller.linfo.def; isa(def, Method) ? Int(def.nargs) : 0; end + get_escape_cache = GetEscapeCache(interp) + estate = try + analyze_escapes(ir, nargs, CC.optimizer_lattice(interp), get_escape_cache) + catch err + @error "error happened within EA, inspect `Main.failed_escapeanalysis`" + Main.failed_escapeanalysis = FailedAnalysis(ir, nargs, get_escape_cache) + rethrow(err) end - return nothing -end -struct InvalidationCallback - code_cache::CodeCache - escape_cache::EscapeCache -end -function (callback::InvalidationCallback)(replaced::MethodInstance, max_world, - seen::IdSet{MethodInstance}=IdSet{MethodInstance}()) - (; code_cache, escape_cache) = callback - push!(seen, replaced) - delete!(code_cache.cache, replaced) - delete!(escape_cache.cache, replaced) - if isdefined(replaced, :backedges) - for mi in replaced.backedges - isa(mi, MethodInstance) || continue # might be `Type` object representing an `invoke` signature - mi in seen && continue # otherwise fall into infinite loop - callback(mi, max_world, seen) - end + if caller.linfo === interp.entry_mi + # return back the result + interp.result = EscapeResultForEntry(CC.copy(ir), estate, caller.linfo) end - return nothing -end + record_escapes!(interp, caller, estate, ir) -function CC.optimize(interp::EscapeAnalyzer, opt::OptimizationState, caller::InferenceResult) - ir = run_passes_ipo_safe_with_ea(interp, opt.src, opt, caller) - CC.ipo_dataflow_analysis!(interp, ir, caller) - return CC.finish(interp, opt, ir, caller) + @invoke CC.ipo_dataflow_analysis!(interp::AbstractInterpreter, ir::IRCode, caller::InferenceResult) end function record_escapes!(interp::EscapeAnalyzer, - caller::InferenceResult, estate::EscapeState, cacheir::IRCode) - cache = ArgEscapeCache(estate) - ecache = EscapeCacheInfo(cache, estate, cacheir) - return caller.argescapes = ecache + caller::InferenceResult, estate::EscapeState, ir::IRCode) + argescapes = ArgEscapeCache(estate) + ecacheinfo = EscapeCacheInfo(argescapes, estate, ir) + return CC.stack_analysis_result!(caller, ecacheinfo) end struct GetEscapeCache @@ -188,8 +148,8 @@ struct GetEscapeCache GetEscapeCache(interp::EscapeAnalyzer) = new(interp.escape_cache) end function ((; escape_cache)::GetEscapeCache)(mi::MethodInstance) - cached = get(escape_cache.cache, mi, nothing) - return cached === nothing ? nothing : cached.argescapes + ecacheinfo = get(escape_cache.cache, mi, nothing) + return ecacheinfo === nothing ? false : ecacheinfo.argescapes end struct FailedAnalysis @@ -198,45 +158,12 @@ struct FailedAnalysis get_escape_cache::GetEscapeCache end -function run_passes_ipo_safe_with_ea(interp::EscapeAnalyzer, - ci::CodeInfo, sv::OptimizationState, caller::InferenceResult) - @timeit "convert" ir = convert_to_ircode(ci, sv) - @timeit "slot2reg" ir = slot2reg(ir, ci, sv) - # TODO: Domsorting can produce an updated domtree - no need to recompute here - @timeit "compact 1" ir = compact!(ir) - @timeit "Inlining" ir = ssa_inlining_pass!(ir, sv.inlining, ci.propagate_inbounds) - # @timeit "verify 2" verify_ir(ir) - @timeit "compact 2" ir = compact!(ir) - @timeit "SROA" ir = sroa_pass!(ir, sv.inlining) - @timeit "ADCE" ir = adce_pass!(ir, sv.inlining) - @timeit "compact 3" ir = compact!(ir, true) - if JLOptions().debug_level == 2 - @timeit "verify 3" (verify_ir(ir); verify_linetable(ir.linetable)) - end - nargs = let def = sv.linfo.def; isa(def, Method) ? Int(def.nargs) : 0; end - get_escape_cache = GetEscapeCache(interp) - local estate::EscapeState - try - @timeit "EA" estate = analyze_escapes(ir, nargs, get_escape_cache) - catch err - @error "error happened within EA, inspect `Main.failed_escapeanalysis`" - Main.failed_escapeanalysis = FailedAnalysis(ir, nargs, get_escape_cache) - rethrow(err) - end - if caller.linfo === interp.entry_mi - # return back the result - interp.result = EscapeResultForEntry(cccopy(ir), estate, sv.linfo) - end - record_escapes!(interp, caller, estate, ir) - return ir -end - -function CC.cache_result!(interp::EscapeAnalyzer, result::InferenceResult) - argescapes = result.argescapes - if argescapes isa EscapeCacheInfo - interp.escape_cache.cache[result.linfo] = argescapes +function CC.cache_result!(interp::EscapeAnalyzer, inf_result::InferenceResult) + ecacheinfo = CC.traverse_analysis_results(inf_result) do @nospecialize result + return result isa EscapeCacheInfo ? result : nothing end - return @invoke CC.cache_result!(interp::AbstractInterpreter, result::InferenceResult) + ecacheinfo isa EscapeCacheInfo && (interp.escape_cache.cache[inf_result.linfo] = ecacheinfo) + return @invoke CC.cache_result!(interp::AbstractInterpreter, inf_result::InferenceResult) end # printing @@ -304,7 +231,7 @@ Base.show(io::IO, result::EscapeResult) = print_with_info(io, result) @eval Base.iterate(res::EscapeResult, state=1) = return state > $(fieldcount(EscapeResult)) ? nothing : (getfield(res, state), state+1) -Base.show(io::IO, cached::EscapeCacheInfo) = show(io, EscapeResult(cached.ir, cached.state)) +Base.show(io::IO, ecacheinfo::EscapeCacheInfo) = show(io, EscapeResult(ecacheinfo.ir, ecacheinfo.state)) # adapted from https://github.com/JuliaDebug/LoweredCodeUtils.jl/blob/4612349432447e868cf9285f647108f43bd0a11c/src/codeedges.jl#L881-L897 function print_with_info(io::IO, result::EscapeResult) diff --git a/test/compiler/EscapeAnalysis/EscapeAnalysis.jl b/test/compiler/EscapeAnalysis/EscapeAnalysis.jl index b598388a3d138..d8ea8be21fe07 100644 --- a/test/compiler/EscapeAnalysis/EscapeAnalysis.jl +++ b/test/compiler/EscapeAnalysis/EscapeAnalysis.jl @@ -1,6 +1,6 @@ module test_EA -const use_core_compiler = false +const use_core_compiler = true if use_core_compiler const EscapeAnalysis = Core.Compiler.EscapeAnalysis @@ -39,10 +39,8 @@ let utils_ex = quote Core.eval(@__MODULE__, utils_ex) end -using Core.Compiler: alloc_array_ndims using .EscapeAnalysis: - EscapeInfo, IndexableElements, IndexableFields, - array_resize_info, is_array_copy, normalize + EscapeInfo, IndexableElements, IndexableFields, normalize isϕ(@nospecialize x) = isa(x, Core.PhiNode) function with_normalized_name(@nospecialize(f), @nospecialize(x)) @@ -54,11 +52,11 @@ function with_normalized_name(@nospecialize(f), @nospecialize(x)) return false end isarrayalloc(@nospecialize x) = - with_normalized_name(nn::Symbol->!isnothing(alloc_array_ndims(nn)), x) + with_normalized_name(nn::Symbol->false, x) isarrayresize(@nospecialize x) = - with_normalized_name(nn::Symbol->!isnothing(array_resize_info(nn)), x) + with_normalized_name(nn::Symbol->false, x) isarraycopy(@nospecialize x) = - with_normalized_name(nn::Symbol->is_array_copy(nn), x) + with_normalized_name(nn::Symbol->false, x) """ is_load_forwardable(x::EscapeInfo) -> Bool @@ -203,6 +201,7 @@ end let # try/catch result = code_escapes((Any,)) do a try + println("prevent ConstABI") nothing catch err return a # return escape @@ -212,6 +211,7 @@ end end let result = code_escapes((Any,)) do a try + println("prevent ConstABI") nothing finally return a # return escape @@ -1508,7 +1508,7 @@ end @testset "array primitives" begin # arrayref - let result = code_escapes((Vector{String},Int)) do xs, i + @test_skip let result = code_escapes((Vector{String},Int)) do xs, i s = Base.arrayref(true, xs, i) return s end @@ -1517,7 +1517,7 @@ end @test has_thrown_escape(result.state[Argument(2)]) # xs @test !has_return_escape(result.state[Argument(3)], r) # i end - let result = code_escapes((Vector{String},Int)) do xs, i + @test_skip let result = code_escapes((Vector{String},Int)) do xs, i s = Base.arrayref(false, xs, i) return s end @@ -1526,28 +1526,28 @@ end @test !has_thrown_escape(result.state[Argument(2)]) # xs @test !has_return_escape(result.state[Argument(3)], r) # i end - let result = code_escapes((Vector{String},Bool)) do xs, i + @test_skip let result = code_escapes((Vector{String},Bool)) do xs, i c = Base.arrayref(true, xs, i) # TypeError will happen here return c end t = only(findall(iscall((result.ir, Base.arrayref)), result.ir.stmts.stmt)) @test has_thrown_escape(result.state[Argument(2)], t) # xs end - let result = code_escapes((String,Int)) do xs, i + @test_skip let result = code_escapes((String,Int)) do xs, i c = Base.arrayref(true, xs, i) # TypeError will happen here return c end t = only(findall(iscall((result.ir, Base.arrayref)), result.ir.stmts.stmt)) @test has_thrown_escape(result.state[Argument(2)], t) # xs end - let result = code_escapes((AbstractVector{String},Int)) do xs, i + @test_skip let result = code_escapes((AbstractVector{String},Int)) do xs, i c = Base.arrayref(true, xs, i) # TypeError may happen here return c end t = only(findall(iscall((result.ir, Base.arrayref)), result.ir.stmts.stmt)) @test has_thrown_escape(result.state[Argument(2)], t) # xs end - let result = code_escapes((Vector{String},Any)) do xs, i + @test_skip let result = code_escapes((Vector{String},Any)) do xs, i c = Base.arrayref(true, xs, i) # TypeError may happen here return c end @@ -1556,7 +1556,7 @@ end end # arrayset - let result = code_escapes((Vector{String},String,Int,)) do xs, x, i + @test_skip let result = code_escapes((Vector{String},String,Int,)) do xs, x, i Base.arrayset(true, xs, x, i) return xs end @@ -1565,7 +1565,7 @@ end @test has_thrown_escape(result.state[Argument(2)]) # xs @test has_return_escape(result.state[Argument(3)], r) # x end - let result = code_escapes((Vector{String},String,Int,)) do xs, x, i + @test_skip let result = code_escapes((Vector{String},String,Int,)) do xs, x, i Base.arrayset(false, xs, x, i) return xs end @@ -1574,7 +1574,7 @@ end @test !has_thrown_escape(result.state[Argument(2)]) # xs @test has_return_escape(result.state[Argument(3)], r) # x end - let result = code_escapes((String,String,String,)) do s, t, u + @test_skip let result = code_escapes((String,String,String,)) do s, t, u xs = Vector{String}(undef, 3) Base.arrayset(true, xs, s, 1) Base.arrayset(true, xs, t, 2) @@ -1588,7 +1588,7 @@ end @test has_return_escape(result.state[Argument(i)], r) end end - let result = code_escapes((Vector{String},String,Bool,)) do xs, x, i + @test_skip let result = code_escapes((Vector{String},String,Bool,)) do xs, x, i Base.arrayset(true, xs, x, i) # TypeError will happen here return xs end @@ -1596,7 +1596,7 @@ end @test has_thrown_escape(result.state[Argument(2)], t) # xs @test has_thrown_escape(result.state[Argument(3)], t) # x end - let result = code_escapes((String,String,Int,)) do xs, x, i + @test_skip let result = code_escapes((String,String,Int,)) do xs, x, i Base.arrayset(true, xs, x, i) # TypeError will happen here return xs end @@ -1604,7 +1604,7 @@ end @test has_thrown_escape(result.state[Argument(2)], t) # xs::String @test has_thrown_escape(result.state[Argument(3)], t) # x::String end - let result = code_escapes((AbstractVector{String},String,Int,)) do xs, x, i + @test_skip let result = code_escapes((AbstractVector{String},String,Int,)) do xs, x, i Base.arrayset(true, xs, x, i) # TypeError may happen here return xs end @@ -1612,7 +1612,7 @@ end @test has_thrown_escape(result.state[Argument(2)], t) # xs @test has_thrown_escape(result.state[Argument(3)], t) # x end - let result = code_escapes((Vector{String},AbstractString,Int,)) do xs, x, i + @test_skip let result = code_escapes((Vector{String},AbstractString,Int,)) do xs, x, i Base.arrayset(true, xs, x, i) # TypeError may happen here return xs end @@ -1622,7 +1622,7 @@ end end # arrayref and arrayset - let result = code_escapes() do + @test_skip let result = code_escapes() do a = Vector{Vector{Any}}(undef, 1) b = Any[] a[1] = b @@ -1638,7 +1638,7 @@ end @test !has_return_escape(result.state[SSAValue(ai)], r) @test has_return_escape(result.state[SSAValue(bi)], r) end - let result = code_escapes() do + @test_skip let result = code_escapes() do a = Vector{Vector{Any}}(undef, 1) b = Any[] a[1] = b @@ -1654,7 +1654,7 @@ end @test has_return_escape(result.state[SSAValue(ai)], r) @test has_return_escape(result.state[SSAValue(bi)], r) end - let result = code_escapes((Vector{Any},String,Int,Int)) do xs, s, i, j + @test_skip let result = code_escapes((Vector{Any},String,Int,Int)) do xs, s, i, j x = SafeRef(s) xs[i] = x xs[j] # potential error @@ -1666,19 +1666,19 @@ end end # arraysize - let result = code_escapes((Vector{Any},)) do xs + @test_skip let result = code_escapes((Vector{Any},)) do xs Core.arraysize(xs, 1) end t = only(findall(iscall((result.ir, Core.arraysize)), result.ir.stmts.stmt)) @test !has_thrown_escape(result.state[Argument(2)], t) end - let result = code_escapes((Vector{Any},Int,)) do xs, dim + @test_skip let result = code_escapes((Vector{Any},Int,)) do xs, dim Core.arraysize(xs, dim) end t = only(findall(iscall((result.ir, Core.arraysize)), result.ir.stmts.stmt)) @test !has_thrown_escape(result.state[Argument(2)], t) end - let result = code_escapes((Any,)) do xs + @test_skip let result = code_escapes((Any,)) do xs Core.arraysize(xs, 1) end t = only(findall(iscall((result.ir, Core.arraysize)), result.ir.stmts.stmt)) @@ -1686,19 +1686,19 @@ end end # arraylen - let result = code_escapes((Vector{Any},)) do xs + @test_skip let result = code_escapes((Vector{Any},)) do xs Base.arraylen(xs) end t = only(findall(iscall((result.ir, Base.arraylen)), result.ir.stmts.stmt)) @test !has_thrown_escape(result.state[Argument(2)], t) # xs end - let result = code_escapes((String,)) do xs + @test_skip let result = code_escapes((String,)) do xs Base.arraylen(xs) end t = only(findall(iscall((result.ir, Base.arraylen)), result.ir.stmts.stmt)) @test has_thrown_escape(result.state[Argument(2)], t) # xs end - let result = code_escapes((Vector{Any},)) do xs + @test_skip let result = code_escapes((Vector{Any},)) do xs Base.arraylen(xs, 1) end t = only(findall(iscall((result.ir, Base.arraylen)), result.ir.stmts.stmt)) @@ -1707,7 +1707,7 @@ end # array resizing # without BoundsErrors - let result = code_escapes((Vector{Any},String)) do xs, x + @test_skip let result = code_escapes((Vector{Any},String)) do xs, x @ccall jl_array_grow_beg(xs::Any, 2::UInt)::Cvoid xs[1] = x xs @@ -1716,7 +1716,7 @@ end @test !has_thrown_escape(result.state[Argument(2)], t) # xs @test !has_thrown_escape(result.state[Argument(3)], t) # x end - let result = code_escapes((Vector{Any},String)) do xs, x + @test_skip let result = code_escapes((Vector{Any},String)) do xs, x @ccall jl_array_grow_end(xs::Any, 2::UInt)::Cvoid xs[1] = x xs @@ -1726,7 +1726,7 @@ end @test !has_thrown_escape(result.state[Argument(3)], t) # x end # with possible BoundsErrors - let result = code_escapes((String,)) do x + @test_skip let result = code_escapes((String,)) do x xs = Any[1,2,3] xs[3] = x @ccall jl_array_del_beg(xs::Any, 2::UInt)::Cvoid # can potentially throw @@ -1737,7 +1737,7 @@ end @test has_thrown_escape(result.state[SSAValue(i)], t) # xs @test has_thrown_escape(result.state[Argument(2)], t) # x end - let result = code_escapes((String,)) do x + @test_skip let result = code_escapes((String,)) do x xs = Any[1,2,3] xs[1] = x @ccall jl_array_del_end(xs::Any, 2::UInt)::Cvoid # can potentially throw @@ -1748,7 +1748,7 @@ end @test has_thrown_escape(result.state[SSAValue(i)], t) # xs @test has_thrown_escape(result.state[Argument(2)], t) # x end - let result = code_escapes((String,)) do x + @test_skip let result = code_escapes((String,)) do x xs = Any[x] @ccall jl_array_grow_at(xs::Any, 1::UInt, 2::UInt)::Cvoid # can potentially throw end @@ -1757,7 +1757,7 @@ end @test has_thrown_escape(result.state[SSAValue(i)], t) # xs @test has_thrown_escape(result.state[Argument(2)], t) # x end - let result = code_escapes((String,)) do x + @test_skip let result = code_escapes((String,)) do x xs = Any[x] @ccall jl_array_del_at(xs::Any, 1::UInt, 2::UInt)::Cvoid # can potentially throw end @@ -1768,15 +1768,15 @@ end end # array copy - let result = code_escapes((Vector{Any},)) do xs + @test_skip let result = code_escapes((Vector{Any},)) do xs return copy(xs) end i = only(findall(isarraycopy, result.ir.stmts.stmt)) r = only(findall(isreturn, result.ir.stmts.stmt)) @test has_return_escape(result.state[SSAValue(i)], r) - @test_broken !has_return_escape(result.state[Argument(2)], r) + @test !has_return_escape(result.state[Argument(2)], r) end - let result = code_escapes((String,)) do s + @test_skip let result = code_escapes((String,)) do s xs = String[s] xs′ = copy(xs) return xs′[1] @@ -1788,7 +1788,7 @@ end @test !has_return_escape(result.state[SSAValue(i2)]) @test has_return_escape(result.state[Argument(2)], r) # s end - let result = code_escapes((Vector{Any},)) do xs + @test_skip let result = code_escapes((Vector{Any},)) do xs xs′ = copy(xs) return xs′[1] # may potentially throw BoundsError, should escape `xs` conservatively (i.e. escape its elements) end @@ -1800,7 +1800,7 @@ end @test has_thrown_escape(result.state[Argument(2)], ref) @test has_return_escape(result.state[Argument(2)], ret) end - let result = code_escapes((String,)) do s + @test_skip let result = code_escapes((String,)) do s xs = Vector{String}(undef, 1) xs[1] = s xs′ = copy(xs) @@ -1824,15 +1824,15 @@ end return isassigned(xs, i) end r = only(findall(isreturn, result.ir.stmts.stmt)) - @test !has_return_escape(result.state[Argument(2)], r) - @test !has_thrown_escape(result.state[Argument(2)]) + @test_broken !has_return_escape(result.state[Argument(2)], r) + @test_broken !has_thrown_escape(result.state[Argument(2)]) end # indexing analysis # ----------------- # safe case - let result = code_escapes((String,String)) do s, t + @test_skip let result = code_escapes((String,String)) do s, t a = Vector{Any}(undef, 2) a[1] = s a[2] = t @@ -1845,7 +1845,7 @@ end @test has_return_escape(result.state[Argument(2)], r) # s @test !has_return_escape(result.state[Argument(3)], r) # t end - let result = code_escapes((String,String)) do s, t + @test_skip let result = code_escapes((String,String)) do s, t a = Matrix{Any}(undef, 1, 2) a[1, 1] = s a[1, 2] = t @@ -1858,7 +1858,7 @@ end @test has_return_escape(result.state[Argument(2)], r) # s @test !has_return_escape(result.state[Argument(3)], r) # t end - let result = code_escapes((Bool,String,String,String)) do c, s, t, u + @test_skip let result = code_escapes((Bool,String,String,String)) do c, s, t, u a = Vector{Any}(undef, 2) if c a[1] = s @@ -1877,7 +1877,7 @@ end @test has_return_escape(result.state[Argument(4)], r) # t @test !has_return_escape(result.state[Argument(5)], r) # u end - let result = code_escapes((Bool,String,String,String)) do c, s, t, u + @test_skip let result = code_escapes((Bool,String,String,String)) do c, s, t, u a = Any[nothing, nothing] # TODO how to deal with loop indexing? if c a[1] = s @@ -1896,7 +1896,7 @@ end @test has_return_escape(result.state[Argument(4)], r) # t @test_broken !has_return_escape(result.state[Argument(5)], r) # u end - let result = code_escapes((String,)) do s + @test_skip let result = code_escapes((String,)) do s a = Vector{Vector{Any}}(undef, 1) b = Any[s] a[1] = b @@ -1912,7 +1912,7 @@ end @test_broken is_load_forwardable(result.state[SSAValue(ib)]) @test has_return_escape(result.state[Argument(2)], r) # s end - let result = code_escapes((Bool,String,String,Regex,Regex,)) do c, s1, s2, t1, t2 + @test_skip let result = code_escapes((Bool,String,String,Regex,Regex,)) do c, s1, s2, t1, t2 if c a = Vector{String}(undef, 2) a[1] = s1 @@ -1934,7 +1934,7 @@ end @test has_return_escape(result.state[Argument(5)], r) # t1 @test !has_return_escape(result.state[Argument(6)], r) # t2 end - let result = code_escapes((String,String,Int)) do s, t, i + @test_skip let result = code_escapes((String,String,Int)) do s, t, i a = Any[s] push!(a, t) return a[2] @@ -1947,7 +1947,7 @@ end @test has_return_escape(result.state[Argument(3)], r) # t end # unsafe cases - let result = code_escapes((String,String,Int)) do s, t, i + @test_skip let result = code_escapes((String,String,Int)) do s, t, i a = Vector{Any}(undef, 2) a[1] = s a[2] = t @@ -1960,7 +1960,7 @@ end @test has_return_escape(result.state[Argument(2)], r) # s @test has_return_escape(result.state[Argument(3)], r) # t end - let result = code_escapes((String,String,Int)) do s, t, i + @test_skip let result = code_escapes((String,String,Int)) do s, t, i a = Vector{Any}(undef, 2) a[1] = s a[i] = t @@ -1973,7 +1973,7 @@ end @test has_return_escape(result.state[Argument(2)], r) # s @test has_return_escape(result.state[Argument(3)], r) # t end - let result = code_escapes((String,String,Int,Int,Int)) do s, t, i, j, k + @test_skip let result = code_escapes((String,String,Int,Int,Int)) do s, t, i, j, k a = Vector{Any}(undef, 2) a[3] = s # BoundsError a[1] = t @@ -1984,7 +1984,7 @@ end @test !has_return_escape(result.state[SSAValue(i)], r) @test !is_load_forwardable(result.state[SSAValue(i)]) end - let result = @eval Module() begin + @test_skip let result = @eval Module() begin @noinline some_resize!(a) = pushfirst!(a, nothing) $code_escapes((String,String,Int)) do s, t, i a = Vector{Any}(undef, 2) @@ -2000,7 +2000,7 @@ end end # circular reference - let result = code_escapes() do + @test_skip let result = code_escapes() do xs = Vector{Any}(undef, 1) xs[1] = xs return xs[1] @@ -2009,7 +2009,7 @@ end r = only(findall(isreturn, result.ir.stmts.stmt)) @test has_return_escape(result.state[SSAValue(i)], r) end - let result = @eval Module() begin + @test_skip let result = @eval Module() begin const Ax = Vector{Any}(undef, 1) Ax[1] = Ax $code_escapes() do @@ -2040,7 +2040,7 @@ end end # demonstrate array primitive support with a realistic end to end example -let result = code_escapes((Int,String,)) do n,s +@test_skip let result = code_escapes((Int,String,)) do n,s xs = String[] for i in 1:n push!(xs, s) @@ -2054,7 +2054,7 @@ let result = code_escapes((Int,String,)) do n,s @test has_return_escape(result.state[Argument(3)], r) # s @test !has_thrown_escape(result.state[Argument(3)]) # s end -let result = code_escapes((Int,String,)) do n,s +@test_skip let result = code_escapes((Int,String,)) do n,s xs = String[] for i in 1:n pushfirst!(xs, s) @@ -2068,7 +2068,7 @@ let result = code_escapes((Int,String,)) do n,s @test has_return_escape(result.state[Argument(3)], r) # s @test !has_thrown_escape(result.state[Argument(3)]) # s end -let result = code_escapes((String,String,String)) do s, t, u +@test_skip let result = code_escapes((String,String,String)) do s, t, u xs = String[] resize!(xs, 3) xs[1] = s @@ -2085,129 +2085,6 @@ let result = code_escapes((String,String,String)) do s, t, u @test has_return_escape(result.state[Argument(4)], r) # u end -@static if isdefined(Core, :ImmutableArray) - -import Core: ImmutableArray, arrayfreeze, mutating_arrayfreeze, arraythaw - -@testset "ImmutableArray" begin - # arrayfreeze - let result = code_escapes((Vector{Any},)) do xs - arrayfreeze(xs) - end - @test !has_thrown_escape(result.state[Argument(2)]) - end - let result = code_escapes((Vector,)) do xs - arrayfreeze(xs) - end - @test !has_thrown_escape(result.state[Argument(2)]) - end - let result = code_escapes((Any,)) do xs - arrayfreeze(xs) - end - @test has_thrown_escape(result.state[Argument(2)]) - end - let result = code_escapes((ImmutableArray{Any,1},)) do xs - arrayfreeze(xs) - end - @test has_thrown_escape(result.state[Argument(2)]) - end - let result = code_escapes() do - xs = Any[] - arrayfreeze(xs) - end - i = only(findall(isarrayalloc, result.ir.stmts.stmt)) - @test has_no_escape(result.state[SSAValue(1)]) - end - - # mutating_arrayfreeze - let result = code_escapes((Vector{Any},)) do xs - mutating_arrayfreeze(xs) - end - @test !has_thrown_escape(result.state[Argument(2)]) - end - let result = code_escapes((Vector,)) do xs - mutating_arrayfreeze(xs) - end - @test !has_thrown_escape(result.state[Argument(2)]) - end - let result = code_escapes((Any,)) do xs - mutating_arrayfreeze(xs) - end - @test has_thrown_escape(result.state[Argument(2)]) - end - let result = code_escapes((ImmutableArray{Any,1},)) do xs - mutating_arrayfreeze(xs) - end - @test has_thrown_escape(result.state[Argument(2)]) - end - let result = code_escapes() do - xs = Any[] - mutating_arrayfreeze(xs) - end - i = only(findall(isarrayalloc, result.ir.stmts.stmt)) - @test has_no_escape(result.state[SSAValue(1)]) - end - - # arraythaw - let result = code_escapes((ImmutableArray{Any,1},)) do xs - arraythaw(xs) - end - @test !has_thrown_escape(result.state[Argument(2)]) - end - let result = code_escapes((ImmutableArray,)) do xs - arraythaw(xs) - end - @test !has_thrown_escape(result.state[Argument(2)]) - end - let result = code_escapes((Any,)) do xs - arraythaw(xs) - end - @test has_thrown_escape(result.state[Argument(2)]) - end - let result = code_escapes((Vector{Any},)) do xs - arraythaw(xs) - end - @test has_thrown_escape(result.state[Argument(2)]) - end - let result = code_escapes() do - xs = ImmutableArray(Any[]) - arraythaw(xs) - end - i = only(findall(isarrayalloc, result.ir.stmts.stmt)) - @test has_no_escape(result.state[SSAValue(1)]) - end -end - -# demonstrate some arrayfreeze optimizations -# !has_return_escape(ary) means ary is eligible for arrayfreeze to mutating_arrayfreeze optimization -let result = code_escapes((Int,)) do n - xs = collect(1:n) - ImmutableArray(xs) - end - i = only(findall(isarrayalloc, result.ir.stmts.stmt)) - @test !has_return_escape(result.state[SSAValue(i)]) -end -let result = code_escapes((Vector{Float64},)) do xs - ys = sin.(xs) - ImmutableArray(ys) - end - i = only(findall(isarrayalloc, result.ir.stmts.stmt)) - @test !has_return_escape(result.state[SSAValue(i)]) -end -let result = code_escapes((Vector{Pair{Int,String}},)) do xs - n = maximum(first, xs) - ys = Vector{String}(undef, n) - for (i, s) in xs - ys[i] = s - end - ImmutableArray(xs) - end - i = only(findall(isarrayalloc, result.ir.stmts.stmt)) - @test !has_return_escape(result.state[SSAValue(i)]) -end - -end # @static if isdefined(Core, :ImmutableArray) - # demonstrate a simple type level analysis can sometimes improve the analysis accuracy # by compensating the lack of yet unimplemented analyses @testset "special-casing bitstype" begin diff --git a/test/compiler/codegen.jl b/test/compiler/codegen.jl index bb0592c86c654..3ad7c9bbb71d4 100644 --- a/test/compiler/codegen.jl +++ b/test/compiler/codegen.jl @@ -128,11 +128,16 @@ if !is_debug_build && opt_level > 0 # String test_loads_no_call(strip_debug_calls(get_llvm(core_sizeof, Tuple{Core.SimpleVector})), [Iptr]) # Array - test_loads_no_call(strip_debug_calls(get_llvm(core_sizeof, Tuple{Vector{Int}})), [Iptr]) + test_loads_no_call(strip_debug_calls(get_llvm(sizeof, Tuple{Vector{Int}})), [Iptr]) + # As long as the eltype is known we don't need to load the elsize, but do need to check isvector + @test_skip test_loads_no_call(strip_debug_calls(get_llvm(sizeof, Tuple{Array{Any}})), ["atomic $Iptr", "ptr", "ptr", Iptr, Iptr, "ptr", Iptr]) + # Memory + test_loads_no_call(strip_debug_calls(get_llvm(core_sizeof, Tuple{Memory{Int}})), [Iptr]) # As long as the eltype is known we don't need to load the elsize - test_loads_no_call(strip_debug_calls(get_llvm(core_sizeof, Tuple{Array{Any}})), [Iptr]) - # Check that we load the elsize - test_loads_no_call(strip_debug_calls(get_llvm(core_sizeof, Tuple{Vector})), [Iptr, "i16"]) + test_loads_no_call(strip_debug_calls(get_llvm(core_sizeof, Tuple{Memory{Any}})), [Iptr]) + # Check that we load the elsize and isunion from the typeof layout + test_loads_no_call(strip_debug_calls(get_llvm(core_sizeof, Tuple{Memory})), [Iptr, "atomic $Iptr", "ptr", "i32", "i16"]) + test_loads_no_call(strip_debug_calls(get_llvm(core_sizeof, Tuple{Memory})), [Iptr, "atomic $Iptr", "ptr", "i32", "i16"]) # Primitive Type size should be folded to a constant test_loads_no_call(strip_debug_calls(get_llvm(core_sizeof, Tuple{Ptr})), String[]) @@ -309,8 +314,8 @@ end # PR #23595 @generated f23595(g, args...) = Expr(:call, :g, Expr(:(...), :args)) -x23595 = rand(1) -@test f23595(Core.arrayref, true, x23595, 1) == x23595[] +x23595 = rand(1).ref +@test f23595(Core.memoryrefget, x23595, :not_atomic, true) == x23595[] # Issue #22421 @noinline f22421_1(x) = x[] + 1 @@ -367,26 +372,10 @@ mktemp() do f_22330, _ end # Alias scope -macro aliasscope(body) - sym = gensym() - esc(quote - $(Expr(:aliasscope)) - $sym = $body - $(Expr(:popaliasscope)) - $sym - end) -end - -struct ConstAliasScope{T<:Array} - a::T -end - -@eval Base.getindex(A::ConstAliasScope, i1::Int) = Core.const_arrayref($(Expr(:boundscheck)), A.a, i1) -@eval Base.getindex(A::ConstAliasScope, i1::Int, i2::Int, I::Int...) = (@inline; Core.const_arrayref($(Expr(:boundscheck)), A.a, i1, i2, I...)) - +using Base.Experimental: @aliasscope, Const function foo31018!(a, b) @aliasscope for i in eachindex(a, b) - a[i] = ConstAliasScope(b)[i] + a[i] = Const(b)[i] end end io = IOBuffer() @@ -498,12 +487,16 @@ function f37262(x) catch GC.safepoint() end + local a try GC.gc() - return g37262(x) + a = g37262(x) + Base.inferencebarrier(false) && error() + return a catch ex GC.gc() finally + @isdefined(a) && Base.donotdelete(a) GC.gc() end end @@ -581,13 +574,13 @@ end end @test occursin("llvm.julia.gc_preserve_begin", get_llvm(f3, Tuple{Bool}, true, false, false)) - # unions of immutables (JuliaLang/julia#39501) + # PhiNode of unions of immutables (JuliaLang/julia#39501) function f2(cond) - val = cond ? 1 : 1f0 + val = cond ? 1 : "" GC.@preserve val begin end return cond end - @test !occursin("llvm.julia.gc_preserve_begin", get_llvm(f2, Tuple{Bool}, true, false, false)) + @test occursin("llvm.julia.gc_preserve_begin", get_llvm(f2, Tuple{Bool}, true, false, false)) # make sure the fix for the above doesn't regress #34241 function f4(cond) val = cond ? ([1],) : ([1f0],) @@ -867,3 +860,16 @@ foo50964(1) # Shouldn't assert! # https://github.com/JuliaLang/julia/issues/51233 obj51233 = (1,) @test_throws ErrorException obj51233.x + +# Very specific test for multiversioning +if Sys.ARCH === :x86_64 + foo52079() = Core.Intrinsics.have_fma(Float64) + if foo52079() == true + let io = IOBuffer() + code_native(io,Base.Math.exp_impl,(Float64,Float64,Val{:ℯ}), dump_module=false) + str = String(take!(io)) + @test !occursin("fma_emulated", str) + @test occursin("vfmadd", str) + end + end +end diff --git a/test/compiler/contextual.jl b/test/compiler/contextual.jl index bbcf7b0dfb959..17919c6a9f6ad 100644 --- a/test/compiler/contextual.jl +++ b/test/compiler/contextual.jl @@ -43,14 +43,16 @@ module MiniCassette end end - function transform!(ci::CodeInfo, nargs::Int, sparams::Core.SimpleVector) + function transform!(mi::MethodInstance, ci::CodeInfo, nargs::Int, sparams::Core.SimpleVector) code = ci.code + di = Core.Compiler.DebugInfoStream(mi, ci.debuginfo, length(code)) ci.slotnames = Symbol[Symbol("#self#"), :ctx, :f, :args, ci.slotnames[nargs+1:end]...] ci.slotflags = UInt8[(0x00 for i = 1:4)..., ci.slotflags[nargs+1:end]...] # Insert one SSAValue for every argument statement prepend!(code, Any[Expr(:call, getfield, SlotNumber(4), i) for i = 1:nargs]) - prepend!(ci.codelocs, fill(0, nargs)) + prepend!(di.codelocs, fill(Int32(0), 3nargs)) prepend!(ci.ssaflags, fill(0x00, nargs)) + ci.debuginfo = Core.DebugInfo(di, length(code)) ci.ssavaluetypes += nargs function map_slot_number(slot::Int) if slot == 1 @@ -88,7 +90,7 @@ module MiniCassette code_info = copy(code_info) @assert code_info.edges === nothing code_info.edges = MethodInstance[mi] - transform!(code_info, length(args), match.sparams) + transform!(mi, code_info, length(args), match.sparams) # TODO: this is mandatory: code_info.min_world = max(code_info.min_world, min_world[]) # TODO: this is mandatory: code_info.max_world = min(code_info.max_world, max_world[]) return code_info diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index 6200e40f7510d..d3884bf2c73e2 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -249,52 +249,52 @@ struct SyntacticallyDefined{T} x::T end -import Core.Compiler: Const, getfield_notundefined +import Core.Compiler: Const, getfield_notuninit for T = (Base.RefValue, Maybe) # both mutable and immutable for name = (Const(1), Const(:x)) - @test getfield_notundefined(T{String}, name) - @test getfield_notundefined(T{Integer}, name) - @test getfield_notundefined(T{Union{String,Integer}}, name) - @test getfield_notundefined(Union{T{String},T{Integer}}, name) - @test !getfield_notundefined(T{Int}, name) - @test !getfield_notundefined(T{<:Integer}, name) - @test !getfield_notundefined(T{Union{Int32,Int64}}, name) - @test !getfield_notundefined(T, name) + @test getfield_notuninit(T{String}, name) + @test getfield_notuninit(T{Integer}, name) + @test getfield_notuninit(T{Union{String,Integer}}, name) + @test getfield_notuninit(Union{T{String},T{Integer}}, name) + @test !getfield_notuninit(T{Int}, name) + @test !getfield_notuninit(T{<:Integer}, name) + @test !getfield_notuninit(T{Union{Int32,Int64}}, name) + @test !getfield_notuninit(T, name) end # throw doesn't account for undefined behavior for name = (Const(0), Const(2), Const(1.0), Const(:y), Const("x"), Float64, String, Nothing) - @test getfield_notundefined(T{String}, name) - @test getfield_notundefined(T{Int}, name) - @test getfield_notundefined(T{Integer}, name) - @test getfield_notundefined(T{<:Integer}, name) - @test getfield_notundefined(T{Union{Int32,Int64}}, name) - @test getfield_notundefined(T, name) + @test getfield_notuninit(T{String}, name) + @test getfield_notuninit(T{Int}, name) + @test getfield_notuninit(T{Integer}, name) + @test getfield_notuninit(T{<:Integer}, name) + @test getfield_notuninit(T{Union{Int32,Int64}}, name) + @test getfield_notuninit(T, name) end # should not be too conservative when field isn't known very well but object information is accurate - @test getfield_notundefined(T{String}, Int) - @test getfield_notundefined(T{String}, Symbol) - @test getfield_notundefined(T{Integer}, Int) - @test getfield_notundefined(T{Integer}, Symbol) - @test !getfield_notundefined(T{Int}, Int) - @test !getfield_notundefined(T{Int}, Symbol) - @test !getfield_notundefined(T{<:Integer}, Int) - @test !getfield_notundefined(T{<:Integer}, Symbol) + @test getfield_notuninit(T{String}, Int) + @test getfield_notuninit(T{String}, Symbol) + @test getfield_notuninit(T{Integer}, Int) + @test getfield_notuninit(T{Integer}, Symbol) + @test !getfield_notuninit(T{Int}, Int) + @test !getfield_notuninit(T{Int}, Symbol) + @test !getfield_notuninit(T{<:Integer}, Int) + @test !getfield_notuninit(T{<:Integer}, Symbol) end # should be conservative when object information isn't accurate -@test !getfield_notundefined(Any, Const(1)) -@test !getfield_notundefined(Any, Const(:x)) +@test !getfield_notuninit(Any, Const(1)) +@test !getfield_notuninit(Any, Const(:x)) # tuples and namedtuples should be okay if not given accurate information for TupleType = Any[Tuple{Int,Int,Int}, Tuple{Int,Vararg{Int}}, Tuple{Any}, Tuple, NamedTuple{(:a, :b), Tuple{Int,Int}}, NamedTuple{(:x,),Tuple{Any}}, NamedTuple], FieldType = Any[Int, Symbol, Any] - @test getfield_notundefined(TupleType, FieldType) + @test getfield_notuninit(TupleType, FieldType) end # skip analysis on fields that are known to be defined syntactically -@test Core.Compiler.getfield_notundefined(SyntacticallyDefined{Float64}, Symbol) -@test Core.Compiler.getfield_notundefined(Const(Main), Const(:var)) -@test Core.Compiler.getfield_notundefined(Const(Main), Const(42)) -# high-level tests for `getfield_notundefined` +@test Core.Compiler.getfield_notuninit(SyntacticallyDefined{Float64}, Symbol) +@test Core.Compiler.getfield_notuninit(Const(Main), Const(:var)) +@test Core.Compiler.getfield_notuninit(Const(Main), Const(42)) +# high-level tests for `getfield_notuninit` @test Base.infer_effects() do Maybe{Int}() end |> !Core.Compiler.is_consistent @@ -446,7 +446,7 @@ let effects = Base.infer_effects() do end @test !Core.Compiler.is_nothrow(effects) end -@test_throws ErrorException setglobal!_nothrow_undefinedyet() +@test_throws Union{ErrorException,TypeError} setglobal!_nothrow_undefinedyet() # TODO: what kind of error should this be? # Nothrow for setfield! mutable struct SetfieldNothrow @@ -472,14 +472,15 @@ end |> Core.Compiler.is_consistent # issue 46122: @assume_effects for @ccall @test Base.infer_effects((Vector{Int},)) do a - Base.@assume_effects :effect_free @ccall jl_array_ptr(a::Any)::Ptr{Int} + Base.@assume_effects :effect_free @ccall this_call_does_not_really_exist(a::Any)::Ptr{Int} end |> Core.Compiler.is_effect_free # `getfield_effects` handles access to union object nicely let 𝕃 = Core.Compiler.fallback_lattice - @test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(𝕃, Core.Compiler.ArgInfo(nothing, Any[Core.Const(getfield), Some{String}, Core.Const(:value)]), String)) - @test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(𝕃, Core.Compiler.ArgInfo(nothing, Any[Core.Const(getfield), Some{Symbol}, Core.Const(:value)]), Symbol)) - @test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(𝕃, Core.Compiler.ArgInfo(nothing, Any[Core.Const(getfield), Union{Some{Symbol},Some{String}}, Core.Const(:value)]), Union{Symbol,String})) + getfield_effects = Core.Compiler.getfield_effects + @test Core.Compiler.is_consistent(getfield_effects(𝕃, Any[Some{String}, Core.Const(:value)], String)) + @test Core.Compiler.is_consistent(getfield_effects(𝕃, Any[Some{Symbol}, Core.Const(:value)], Symbol)) + @test Core.Compiler.is_consistent(getfield_effects(𝕃, Any[Union{Some{Symbol},Some{String}}, Core.Const(:value)], Union{Symbol,String})) end @test Base.infer_effects((Bool,)) do c obj = c ? Some{String}("foo") : Some{Symbol}(:bar) @@ -497,6 +498,25 @@ end |> Core.Compiler.is_nothrow getfield(t, i, false) # invalid name type end |> !Core.Compiler.is_nothrow +@test Base.infer_effects((Some{Any},)) do some + getfield(some, 1, :not_atomic) +end |> Core.Compiler.is_nothrow +@test Base.infer_effects((Some{Any},)) do some + getfield(some, 1, :invalid_atomic_spec) +end |> !Core.Compiler.is_nothrow +@test Base.infer_effects((Some{Any},Bool)) do some, boundscheck + getfield(some, 1, boundscheck) +end |> Core.Compiler.is_nothrow +@test Base.infer_effects((Some{Any},Bool)) do some, boundscheck + getfield(some, 1, :not_atomic, boundscheck) +end |> Core.Compiler.is_nothrow +@test Base.infer_effects((Some{Any},Bool)) do some, boundscheck + getfield(some, 1, :invalid_atomic_spec, boundscheck) +end |> !Core.Compiler.is_nothrow +@test Base.infer_effects((Some{Any},Any)) do some, boundscheck + getfield(some, 1, :not_atomic, boundscheck) +end |> !Core.Compiler.is_nothrow + @test Core.Compiler.is_consistent(Base.infer_effects(setindex!, (Base.RefValue{Int}, Int))) # :inaccessiblememonly effect @@ -696,31 +716,34 @@ end # low-level constructor @noinline construct_array(@nospecialize(T), args...) = Array{T}(undef, args...) # should eliminate safe but dead allocations -let good_dims = @static Int === Int64 ? (1:10) : (1:8) - Ns = @static Int === Int64 ? (1:10) : (1:8) +let good_dims = [1, 2, 3, 4, 10] + Ns = [1, 2, 3, 4, 10] for dim = good_dims, N = Ns + Int64(dim)^N > typemax(Int) && continue dims = ntuple(i->dim, N) @test @eval Base.infer_effects() do - $construct_array(Int, $(dims...)) + construct_array(Int, $(dims...)) end |> Core.Compiler.is_removable_if_unused @test @eval fully_eliminated() do - $construct_array(Int, $(dims...)) + construct_array(Int, $(dims...)) nothing end end end # should analyze throwness correctly let bad_dims = [-1, typemax(Int)] - for dim in bad_dims, N in 1:10 - dims = ntuple(i->dim, N) - @test @eval Base.infer_effects() do - $construct_array(Int, $(dims...)) - end |> !Core.Compiler.is_removable_if_unused - @test @eval !fully_eliminated() do - $construct_array(Int, $(dims...)) - nothing + for dim in bad_dims, N in [1, 2, 3, 4, 10] + for T in Any[Int, Union{Missing,Nothing}, Missing, Any] + dims = ntuple(i->dim, N) + @test @eval Base.infer_effects() do + construct_array($T, $(dims...)) + end |> !Core.Compiler.is_removable_if_unused + @test @eval !fully_eliminated() do + construct_array($T, $(dims...)) + nothing + end + @test_throws "invalid " @eval construct_array($T, $(dims...)) end - @test_throws "invalid Array" @eval $construct_array(Int, $(dims...)) end end @@ -764,12 +787,9 @@ for safesig = Any[ end end -# arrayref -# -------- - -for tt = Any[(Bool,Vector{Any},Int), - (Bool,Matrix{Any},Int,Int)] - @testset let effects = Base.infer_effects(Base.arrayref, tt) +# array getindex +let tt = (MemoryRef{Any},Symbol,Bool) + @testset let effects = Base.infer_effects(Core.memoryrefget, tt) @test Core.Compiler.is_consistent_if_inaccessiblememonly(effects) @test Core.Compiler.is_effect_free(effects) @test !Core.Compiler.is_nothrow(effects) @@ -777,12 +797,9 @@ for tt = Any[(Bool,Vector{Any},Int), end end -# arrayset -# -------- - -for tt = Any[(Bool,Vector{Any},Any,Int), - (Bool,Matrix{Any},Any,Int,Int)] - @testset let effects = Base.infer_effects(Base.arrayset, tt) +# array setindex! +let tt = (MemoryRef{Any},Any,Symbol,Bool) + @testset let effects = Base.infer_effects(Core.memoryrefset!, tt) @test Core.Compiler.is_consistent_if_inaccessiblememonly(effects) @test Core.Compiler.is_effect_free_if_inaccessiblememonly(effects) @test !Core.Compiler.is_nothrow(effects) @@ -790,24 +807,24 @@ for tt = Any[(Bool,Vector{Any},Any,Int), end end # nothrow for arrayset -@test Base.infer_effects((Vector{Int},Int,Int)) do a, v, i - Base.arrayset(true, a, v, i) +@test Base.infer_effects((MemoryRef{Int},Int)) do a, v + Core.memoryrefset!(a, v, :not_atomic, true) end |> !Core.Compiler.is_nothrow -@test Base.infer_effects((Vector{Int},Int,Int)) do a, v, i - a[i] = v # may throw +@test Base.infer_effects((MemoryRef{Int},Int)) do a, v + a[] = v # may throw end |> !Core.Compiler.is_nothrow # when bounds checking is turned off, it should be safe -@test Base.infer_effects((Vector{Int},Int,Int)) do a, v, i - Base.arrayset(false, a, v, i) +@test Base.infer_effects((MemoryRef{Int},Int)) do a, v + Core.memoryrefset!(a, v, :not_atomic, false) end |> Core.Compiler.is_nothrow -@test Base.infer_effects((Vector{Number},Number,Int)) do a, v, i - Base.arrayset(false, a, v, i) +@test Base.infer_effects((MemoryRef{Number},Number)) do a, v + Core.memoryrefset!(a, v, :not_atomic, false) end |> Core.Compiler.is_nothrow # arraysize # --------- -let effects = Base.infer_effects(Base.arraysize, (Array,Int)) +let effects = Base.infer_effects(size, (Array,Int)) @test Core.Compiler.is_consistent_if_inaccessiblememonly(effects) @test Core.Compiler.is_effect_free(effects) @test !Core.Compiler.is_nothrow(effects) @@ -819,7 +836,7 @@ end # arraylen # -------- -let effects = Base.infer_effects(Base.arraylen, (Vector{Any},)) +let effects = Base.infer_effects(length, (Vector{Any},)) @test Core.Compiler.is_consistent_if_inaccessiblememonly(effects) @test Core.Compiler.is_effect_free(effects) @test Core.Compiler.is_nothrow(effects) @@ -829,19 +846,19 @@ end # resize # ------ -for op = Any[ - Base._growbeg!, - Base._growend!, - Base._deletebeg!, - Base._deleteend!, - ] - let effects = Base.infer_effects(op, (Vector, Int)) - @test Core.Compiler.is_effect_free_if_inaccessiblememonly(effects) - @test Core.Compiler.is_terminates(effects) - @test !Core.Compiler.is_nothrow(effects) - end -end - +#for op = Any[ +# Base._growbeg!, +# Base._growend!, +# Base._deletebeg!, +# Base._deleteend!, +# ] +# let effects = Base.infer_effects(op, (Vector, Int)) +# @test Core.Compiler.is_effect_free_if_inaccessiblememonly(effects) +# @test Core.Compiler.is_terminates(effects) +# @test !Core.Compiler.is_nothrow(effects) +# end +#end +# # tuple indexing # -------------- @@ -850,21 +867,21 @@ end # end to end # ---------- -function simple_vec_ops(T, op!, op, xs...) - a = T[] - op!(a, xs...) - return op(a) -end -for T = Any[Int,Any], op! = Any[push!,pushfirst!], op = Any[length,size], - xs = Any[(Int,), (Int,Int,)] - let effects = Base.infer_effects(simple_vec_ops, (Type{T},typeof(op!),typeof(op),xs...)) - @test Core.Compiler.is_foldable(effects) - end -end +#function simple_vec_ops(T, op!, op, xs...) +# a = T[] +# op!(a, xs...) +# return op(a) +#end +#for T = Any[Int,Any], op! = Any[push!,pushfirst!], op = Any[length,size], +# xs = Any[(Int,), (Int,Int,)] +# let effects = Base.infer_effects(simple_vec_ops, (Type{T},typeof(op!),typeof(op),xs...)) +# @test Core.Compiler.is_foldable(effects) +# end +#end # Test that builtin_effects handles vararg correctly @test !Core.Compiler.is_nothrow(Core.Compiler.builtin_effects(Core.Compiler.fallback_lattice, Core.isdefined, - Core.Compiler.ArgInfo(nothing, Any[Core.Compiler.Const(Core.isdefined), String, Vararg{Any}]), Bool)) + Any[String, Vararg{Any}], Bool)) # Test that :new can be eliminated even if an sparam is unknown struct SparamUnused{T} @@ -887,7 +904,7 @@ end |> Core.Compiler.is_foldable_nothrow @test Base.infer_effects(Tuple{WrapperOneField{Float64}, Symbol}) do w, s getfield(w, s) end |> Core.Compiler.is_foldable -@test Core.Compiler.getfield_notundefined(WrapperOneField{Float64}, Symbol) +@test Core.Compiler.getfield_notuninit(WrapperOneField{Float64}, Symbol) @test Base.infer_effects(Tuple{WrapperOneField{Symbol}, Symbol}) do w, s getfield(w, s) end |> Core.Compiler.is_foldable @@ -979,7 +996,7 @@ end let effects = Base.infer_effects() do isdefined(defined_ref, :x) end - @test Core.Compiler.is_consistent(effects) + @test !Core.Compiler.is_consistent(effects) @test Core.Compiler.is_nothrow(effects) end let effects = Base.infer_effects() do @@ -1116,17 +1133,267 @@ end post_opt_refine_effect_free(y, true) end |> Core.Compiler.is_effect_free -# effects for Cmd construction -for f in (() -> `a b c`, () -> `a a$("bb")a $("c")`) - effects = Base.infer_effects(f) +# Check EA-based refinement of :effect_free +Base.@assume_effects :nothrow @noinline _noinline_set!(x) = (x[] = 1; nothing) + +function set_ref_with_unused_arg_1(_) + x = Ref(0) + _noinline_set!(x) + return nothing +end +function set_ref_with_unused_arg_2(_) + x = @noinline Ref(0) + _noinline_set!(x) + return nothing +end +function set_arg_ref!(x) + _noinline_set!(x) + y = Ref(false) + y[] && (Main.x = x) + return nothing +end + +function set_arr_with_unused_arg_1(_) + x = Int[0] + _noinline_set!(x) + return nothing +end +function set_arr_with_unused_arg_2(_) + x = @noinline Int[0] + _noinline_set!(x) + return nothing +end +function set_arg_arr!(x) + _noinline_set!(x) + y = Bool[false] + y[] && (Main.x = x) + return nothing +end + +# This is inferable by type analysis only since the arguments have no mutable memory +@test Core.Compiler.is_effect_free_if_inaccessiblememonly(Base.infer_effects(_noinline_set!, (Base.RefValue{Int},))) +@test Core.Compiler.is_effect_free_if_inaccessiblememonly(Base.infer_effects(_noinline_set!, (Vector{Int},))) +for func in (set_ref_with_unused_arg_1, set_ref_with_unused_arg_2, + set_arr_with_unused_arg_1, set_arr_with_unused_arg_2) + effects = Base.infer_effects(func, (Nothing,)) + @test Core.Compiler.is_inaccessiblememonly(effects) @test Core.Compiler.is_effect_free(effects) - @test Core.Compiler.is_terminates(effects) - @test Core.Compiler.is_noub(effects) - @test !Core.Compiler.is_consistent(effects) end -let effects = Base.infer_effects(x -> `a $x`, (Any,)) - @test !Core.Compiler.is_effect_free(effects) - @test !Core.Compiler.is_terminates(effects) - @test !Core.Compiler.is_noub(effects) - @test !Core.Compiler.is_consistent(effects) + +# These need EA +@test Core.Compiler.is_effect_free(Base.infer_effects(set_ref_with_unused_arg_1, (Base.RefValue{Int},))) +@test Core.Compiler.is_effect_free(Base.infer_effects(set_ref_with_unused_arg_2, (Base.RefValue{Int},))) +@test Core.Compiler.is_effect_free_if_inaccessiblememonly(Base.infer_effects(set_arg_ref!, (Base.RefValue{Int},))) +@test_broken Core.Compiler.is_effect_free(Base.infer_effects(set_arr_with_unused_arg_1, (Vector{Int},))) +@test_broken Core.Compiler.is_effect_free(Base.infer_effects(set_arr_with_unused_arg_2, (Vector{Int},))) +@test_broken Core.Compiler.is_effect_free_if_inaccessiblememonly(Base.infer_effects(set_arg_arr!, (Vector{Int},))) + +function issue51837(; openquotechar::Char, newlinechar::Char) + ncodeunits(openquotechar) == 1 || throw(ArgumentError("`openquotechar` must be a single-byte character")) + if !isnothing(newlinechar) + ncodeunits(newlinechar) > 1 && throw(ArgumentError("`newlinechar` must be a single-byte character.")) + end + return nothing +end +@test Base.infer_effects() do openquotechar::Char, newlinechar::Char + issue51837(; openquotechar, newlinechar) +end |> !Core.Compiler.is_nothrow +@test_throws ArgumentError issue51837(; openquotechar='α', newlinechar='\n') + +# idempotency of effects derived by post-opt analysis +callgetfield(x, f) = getfield(x, f, Base.@_boundscheck) +@test Base.infer_effects(callgetfield, (Some{Any},Symbol)).noub === Core.Compiler.NOUB_IF_NOINBOUNDS +callgetfield1(x, f) = getfield(x, f, Base.@_boundscheck) +callgetfield_simple(x, f) = callgetfield1(x, f) +@test Base.infer_effects(callgetfield_simple, (Some{Any},Symbol)).noub === + Base.infer_effects(callgetfield_simple, (Some{Any},Symbol)).noub === + Core.Compiler.ALWAYS_TRUE +callgetfield2(x, f) = getfield(x, f, Base.@_boundscheck) +callgetfield_inbounds(x, f) = @inbounds callgetfield2(x, f) +@test Base.infer_effects(callgetfield_inbounds, (Some{Any},Symbol)).noub === + Base.infer_effects(callgetfield_inbounds, (Some{Any},Symbol)).noub === + Core.Compiler.ALWAYS_FALSE + +# noub modeling for memory ops +let (memoryref, memoryrefget, memoryref_isassigned, memoryrefset!) = + (Core.memoryref, Core.memoryrefget, Core.memoryref_isassigned, Core.memoryrefset!) + function builtin_effects(@nospecialize xs...) + interp = Core.Compiler.NativeInterpreter() + 𝕃 = Core.Compiler.typeinf_lattice(interp) + rt = Core.Compiler.builtin_tfunction(interp, xs..., nothing) + return Core.Compiler.builtin_effects(𝕃, xs..., rt) + end + @test Core.Compiler.is_noub(builtin_effects(memoryref, Any[Memory,])) + @test Core.Compiler.is_noub(builtin_effects(memoryref, Any[MemoryRef,Int])) + @test Core.Compiler.is_noub(builtin_effects(memoryref, Any[MemoryRef,Int,Core.Const(true)])) + @test !Core.Compiler.is_noub(builtin_effects(memoryref, Any[MemoryRef,Int,Core.Const(false)])) + @test !Core.Compiler.is_noub(builtin_effects(memoryref, Any[MemoryRef,Int,Bool])) + @test Core.Compiler.is_noub(builtin_effects(memoryref, Any[MemoryRef,Int,Int])) + @test !Core.Compiler.is_noub(builtin_effects(memoryref, Any[MemoryRef,Int,Vararg{Bool}])) + @test !Core.Compiler.is_noub(builtin_effects(memoryref, Any[MemoryRef,Vararg{Any}])) + @test Core.Compiler.is_noub(builtin_effects(memoryrefget, Any[MemoryRef,Symbol,Core.Const(true)])) + @test !Core.Compiler.is_noub(builtin_effects(memoryrefget, Any[MemoryRef,Symbol,Core.Const(false)])) + @test !Core.Compiler.is_noub(builtin_effects(memoryrefget, Any[MemoryRef,Symbol,Bool])) + @test Core.Compiler.is_noub(builtin_effects(memoryrefget, Any[MemoryRef,Symbol,Int])) + @test !Core.Compiler.is_noub(builtin_effects(memoryrefget, Any[MemoryRef,Symbol,Vararg{Bool}])) + @test !Core.Compiler.is_noub(builtin_effects(memoryrefget, Any[MemoryRef,Vararg{Any}])) + @test Core.Compiler.is_noub(builtin_effects(memoryref_isassigned, Any[MemoryRef,Symbol,Core.Const(true)])) + @test !Core.Compiler.is_noub(builtin_effects(memoryref_isassigned, Any[MemoryRef,Symbol,Core.Const(false)])) + @test !Core.Compiler.is_noub(builtin_effects(memoryref_isassigned, Any[MemoryRef,Symbol,Bool])) + @test Core.Compiler.is_noub(builtin_effects(memoryref_isassigned, Any[MemoryRef,Symbol,Int])) + @test !Core.Compiler.is_noub(builtin_effects(memoryref_isassigned, Any[MemoryRef,Symbol,Vararg{Bool}])) + @test !Core.Compiler.is_noub(builtin_effects(memoryref_isassigned, Any[MemoryRef,Vararg{Any}])) + @test Core.Compiler.is_noub(builtin_effects(memoryrefset!, Any[MemoryRef,Any,Symbol,Core.Const(true)])) + @test !Core.Compiler.is_noub(builtin_effects(memoryrefset!, Any[MemoryRef,Any,Symbol,Core.Const(false)])) + @test !Core.Compiler.is_noub(builtin_effects(memoryrefset!, Any[MemoryRef,Any,Symbol,Bool])) + @test Core.Compiler.is_noub(builtin_effects(memoryrefset!, Any[MemoryRef,Any,Symbol,Int])) + @test !Core.Compiler.is_noub(builtin_effects(memoryrefset!, Any[MemoryRef,Any,Symbol,Vararg{Bool}])) + @test !Core.Compiler.is_noub(builtin_effects(memoryrefset!, Any[MemoryRef,Vararg{Any}])) + # `:boundscheck` taint should be refined by post-opt analysis + @test Base.infer_effects() do xs::Vector{Any}, i::Int + memoryrefget(memoryref(getfield(xs, :ref), i, Base.@_boundscheck), :not_atomic, Base.@_boundscheck) + end |> Core.Compiler.is_noub_if_noinbounds +end + +# high level tests +@test Core.Compiler.is_noub_if_noinbounds(Base.infer_effects(getindex, (Vector{Int},Int))) +@test Core.Compiler.is_noub_if_noinbounds(Base.infer_effects(getindex, (Vector{Any},Int))) +@test Core.Compiler.is_noub_if_noinbounds(Base.infer_effects(setindex!, (Vector{Int},Int,Int))) +@test Core.Compiler.is_noub_if_noinbounds(Base.infer_effects(setindex!, (Vector{Any},Any,Int))) +@test Core.Compiler.is_noub_if_noinbounds(Base.infer_effects(isassigned, (Vector{Int},Int))) +@test Core.Compiler.is_noub_if_noinbounds(Base.infer_effects(isassigned, (Vector{Any},Int))) +@test Base.infer_effects((Vector{Int},Int)) do xs, i + xs[i] +end |> Core.Compiler.is_noub +@test Base.infer_effects((Vector{Any},Int)) do xs, i + xs[i] +end |> Core.Compiler.is_noub +@test Base.infer_effects((Vector{Int},Int,Int)) do xs, x, i + xs[i] = x +end |> Core.Compiler.is_noub +@test Base.infer_effects((Vector{Any},Any,Int)) do xs, x, i + xs[i] = x +end |> Core.Compiler.is_noub +@test Base.infer_effects((Vector{Int},Int)) do xs, i + @inbounds xs[i] +end |> !Core.Compiler.is_noub +@test Base.infer_effects((Vector{Any},Int)) do xs, i + @inbounds xs[i] +end |> !Core.Compiler.is_noub +Base.@propagate_inbounds getindex_propagate(xs, i) = xs[i] +getindex_dont_propagate(xs, i) = xs[i] +@test Core.Compiler.is_noub_if_noinbounds(Base.infer_effects(getindex_propagate, (Vector{Any},Int))) +@test Core.Compiler.is_noub(Base.infer_effects(getindex_dont_propagate, (Vector{Any},Int))) +@test Base.infer_effects((Vector{Any},Int)) do xs, i + @inbounds getindex_propagate(xs, i) +end |> !Core.Compiler.is_noub +@test Base.infer_effects((Vector{Any},Int)) do xs, i + @inbounds getindex_dont_propagate(xs, i) +end |> Core.Compiler.is_noub + +# refine `:nothrow` when `exct` is known to be `Bottom` +@test Base.infer_exception_type(getindex, (Vector{Int},Int)) == BoundsError +function getindex_nothrow(xs::Vector{Int}, i::Int) + try + return xs[i] + catch err + err isa BoundsError && return nothing + rethrow(err) + end +end +@test Core.Compiler.is_nothrow(Base.infer_effects(getindex_nothrow, (Vector{Int}, Int))) + +# callsite `@assume_effects` annotation +let ast = code_lowered((Int,)) do x + Base.@assume_effects :total identity(x) + end |> only + ssaflag = ast.ssaflags[findfirst(!iszero, ast.ssaflags)::Int] + override = Core.Compiler.decode_statement_effects_override(ssaflag) + # if this gets broken, check if this is synced with expr.jl + @test override.consistent && override.effect_free && override.nothrow && + override.terminates_globally && !override.terminates_locally && + override.notaskstate && override.inaccessiblememonly && + override.noub && !override.noub_if_noinbounds +end +@test Base.infer_effects((Float64,)) do x + isinf(x) && return 0.0 + return Base.@assume_effects :nothrow sin(x) +end |> Core.Compiler.is_nothrow +let effects = Base.infer_effects((Vector{Float64},)) do xs + isempty(xs) && return 0.0 + Base.@assume_effects :nothrow begin + x = Base.@assume_effects :noub @inbounds xs[1] + isinf(x) && return 0.0 + return sin(x) + end + end + # all nested overrides should be applied + @test Core.Compiler.is_nothrow(effects) + @test Core.Compiler.is_noub(effects) end +@test Base.infer_effects((Int,)) do x + res = 1 + 0 ≤ x < 20 || error("bad fact") + Base.@assume_effects :terminates_locally while x > 1 + res *= x + x -= 1 + end + return res +end |> Core.Compiler.is_terminates + +# https://github.com/JuliaLang/julia/issues/52531 +const a52531 = Core.Ref(1) +@eval getref52531() = $(QuoteNode(a52531)).x +@test !Core.Compiler.is_consistent(Base.infer_effects(getref52531)) +let + global set_a52531!, get_a52531 + _a::Int = -1 + set_a52531!(a::Int) = (_a = a; return get_a52531()) + get_a52531() = _a +end +@test !Core.Compiler.is_consistent(Base.infer_effects(set_a52531!, (Int,))) +@test !Core.Compiler.is_consistent(Base.infer_effects(get_a52531, ())) +@test get_a52531() == -1 +@test set_a52531!(1) == 1 +@test get_a52531() == 1 + +let + global is_initialized52531, set_initialized52531! + _is_initialized = false + set_initialized52531!(flag::Bool) = (_is_initialized = flag) + is_initialized52531() = _is_initialized +end +top_52531(_) = (set_initialized52531!(true); nothing) +@test !Core.Compiler.is_consistent(Base.infer_effects(is_initialized52531)) +@test !Core.Compiler.is_removable_if_unused(Base.infer_effects(set_initialized52531!, (Bool,))) +@test !is_initialized52531() +top_52531(0) +@test is_initialized52531() + +const ref52843 = Ref{Int}() +@eval func52843() = ($ref52843[] = 1; nothing) +@test !Core.Compiler.is_foldable(Base.infer_effects(func52843)) +let; Base.Experimental.@force_compile; func52843(); end +@test ref52843[] == 1 + +@test Core.Compiler.is_inaccessiblememonly(Base.infer_effects(identity∘identity, Tuple{Any})) +@test Core.Compiler.is_inaccessiblememonly(Base.infer_effects(()->Vararg, Tuple{})) + +# pointerref nothrow for invalid pointer +@test !Core.Compiler.intrinsic_nothrow(Core.Intrinsics.pointerref, Any[Type{Ptr{Vector{Int64}}}, Int, Int]) +@test !Core.Compiler.intrinsic_nothrow(Core.Intrinsics.pointerref, Any[Type{Ptr{T}} where T, Int, Int]) + +# post-opt :consistent-cy analysis correctness +# https://github.com/JuliaLang/julia/issues/53508 +@test !Core.Compiler.is_consistent(Base.infer_effects(getindex, (UnitRange{Int},Int))) +@test !Core.Compiler.is_consistent(Base.infer_effects(getindex, (Base.OneTo{Int},Int))) + +@noinline f53613() = @assert isdefined(@__MODULE__, :v53613) +g53613() = f53613() +@test !Core.Compiler.is_consistent(Base.infer_effects(f53613)) +@test_broken !Core.Compiler.is_consistent(Base.infer_effects(g53613)) +@test_throws AssertionError f53613() +@test_throws AssertionError g53613() +global v53613 = nothing +@test f53613() === nothing +@test g53613() === nothing diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 619e6f6f87a53..358b165f1d7e8 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -98,6 +98,16 @@ end @test !Core.Compiler.type_more_complex(Tuple{Vararg{Tuple{}}}, Tuple{Vararg{Tuple}}, Core.svec(), 0, 0, 0) @test Core.Compiler.type_more_complex(Tuple{Vararg{Tuple}}, Tuple{Vararg{Tuple{}}}, Core.svec(), 0, 0, 0) +# issue #51694 +@test Core.Compiler.type_more_complex( + Base.Generator{Base.Iterators.Flatten{Array{Bool, 1}}, typeof(identity)}, + Base.Generator{Array{Bool, 1}, typeof(identity)}, + Core.svec(), 0, 0, 0) +@test Core.Compiler.type_more_complex( + Base.Generator{Base.Iterators.Flatten{Base.Generator{Array{Bool, 1}, typeof(identity)}}, typeof(identity)}, + Base.Generator{Array{Bool, 1}, typeof(identity)}, + Core.svec(), 0, 0, 0) + let # 40336 t = Type{Type{Type{Int}}} c = Type{Type{Int}} @@ -128,7 +138,7 @@ end Core.Compiler.limit_type_size(Type{Any}, Type, Union{}, 0, 0) == Type{Any} -# issue #43296 #43296 +# issue #43296 struct C43296{t,I} end r43296(b) = r43296(typeof(b)) r43296(::Type) = nothing @@ -139,7 +149,8 @@ f43296(g, :) = h k43296(b, j, :) = l k43296(b, j, ::Nothing) = b i43296(b, j) = k43296(b, j, r43296(j)) -@test only(Base.return_types(i43296, (Int, C43296{C43296{C43296{Val, Tuple}, Tuple}}))) == Int +@test only(Base.return_types(i43296, (Int, C43296{C43296{C43296{Val, Tuple}}}))) <: Int +@test only(Base.return_types(i43296, (Int, C43296{C43296{C43296{Val, <:Tuple}}}))) <: Int abstract type e43296{a, j} <: AbstractArray{a, j} end abstract type b43296{a, j, c, d} <: e43296{a, j} end @@ -173,11 +184,7 @@ Base.ndims(g::e43296) = ndims(typeof(g)) # PR 22120 function tuplemerge_test(a, b, r, commutative=true) @test r == Core.Compiler.tuplemerge(a, b) - if commutative - @test r == Core.Compiler.tuplemerge(b, a) - else - @test_broken r == Core.Compiler.tuplemerge(b, a) - end + @test r == Core.Compiler.tuplemerge(b, a) broken=!commutative end tuplemerge_test(Tuple{Int}, Tuple{String}, Tuple{Union{Int, String}}) tuplemerge_test(Tuple{Int}, Tuple{String, String}, Tuple) @@ -227,16 +234,21 @@ tuplemerge_test(Tuple{}, Tuple{Complex, Vararg{Union{ComplexF32, ComplexF64}}}, @test Core.Compiler.tmerge(Union{Nothing, Tuple{Char, Int}}, Tuple{Union{Char, String, SubString{String}, Symbol}, Int}) == Union{Nothing, Tuple{Union{Char, String, SubString{String}, Symbol}, Int}} @test Core.Compiler.tmerge(Nothing, Tuple{Integer, Int}) == Union{Nothing, Tuple{Integer, Int}} @test Core.Compiler.tmerge(Union{Nothing, Tuple{Int, Int}}, Tuple{Integer, Int}) == Union{Nothing, Tuple{Integer, Int}} -@test Core.Compiler.tmerge(Union{Nothing, AbstractVector{Int}}, Vector) == Union{Nothing, AbstractVector} -@test Core.Compiler.tmerge(Union{Nothing, AbstractVector{Int}}, Matrix) == Union{Nothing, AbstractArray} -@test Core.Compiler.tmerge(Union{Nothing, AbstractVector{Int}}, Matrix{Int}) == Union{Nothing, AbstractArray{Int}} -@test Core.Compiler.tmerge(Union{Nothing, AbstractVector{Int}}, Array) == Union{Nothing, AbstractArray} -@test Core.Compiler.tmerge(Union{Nothing, AbstractArray{Int}}, Vector) == Union{Nothing, AbstractArray} -@test Core.Compiler.tmerge(Union{Nothing, AbstractVector}, Matrix{Int}) == Union{Nothing, AbstractArray} +@test Core.Compiler.tmerge(Union{Nothing, Int, AbstractVector{Int}}, Vector) == Union{Nothing, Int, AbstractVector} +@test Core.Compiler.tmerge(Union{Nothing, Int, AbstractVector{Int}}, Matrix) == Union{Nothing, Int, AbstractArray} +@test Core.Compiler.tmerge(Union{Nothing, Int, AbstractVector{Int}}, Matrix{Int}) == Union{Nothing, Int, AbstractArray{Int}} +@test Core.Compiler.tmerge(Union{Nothing, Int, AbstractVector{Int}}, Array) == Union{Nothing, Int, AbstractArray} +@test Core.Compiler.tmerge(Union{Nothing, Int, AbstractArray{Int}}, Vector) == Union{Nothing, Int, AbstractArray} +@test Core.Compiler.tmerge(Union{Nothing, Int, AbstractVector}, Matrix{Int}) == Union{Nothing, Int, AbstractArray} @test Core.Compiler.tmerge(Union{Nothing, AbstractFloat}, Integer) == Union{Nothing, AbstractFloat, Integer} +@test Core.Compiler.tmerge(AbstractVector, AbstractMatrix) == Union{AbstractVector, AbstractMatrix} +@test Core.Compiler.tmerge(Union{AbstractVector, Nothing}, AbstractMatrix) == Union{Nothing, AbstractVector, AbstractMatrix} +@test Core.Compiler.tmerge(Union{AbstractVector, Int}, AbstractMatrix) == Union{Int, AbstractVector, AbstractMatrix} +@test Core.Compiler.tmerge(Union{AbstractVector, Integer}, AbstractMatrix) == Union{Integer, AbstractArray} +@test Core.Compiler.tmerge(Union{AbstractVector, Nothing, Int}, AbstractMatrix) == Union{Nothing, Int, AbstractArray} # test that recursively more complicated types don't widen all the way to Any when there is a useful valid type upper bound -# Specificially test with base types of a trivial type, a simple union, a complicated union, and a tuple. +# Specifically test with base types of a trivial type, a simple union, a complicated union, and a tuple. for T in (Nothing, Base.BitInteger, Union{Int, Int32, Int16, Int8}, Tuple{Int, Int}) Ta, Tb = T, T for i in 1:10 @@ -356,7 +368,7 @@ barTuple2() = fooTuple{tuple(:y)}() # issue #6050 @test Core.Compiler.getfield_tfunc(Core.Compiler.fallback_lattice, Dict{Int64,Tuple{UnitRange{Int64},UnitRange{Int64}}}, - Core.Compiler.Const(:vals)) == Array{Tuple{UnitRange{Int64},UnitRange{Int64}},1} + Core.Compiler.Const(:vals)) == Memory{Tuple{UnitRange{Int64},UnitRange{Int64}}} # assert robustness of `getfield_tfunc` struct GetfieldRobustness @@ -416,8 +428,7 @@ code_llvm(devnull, f14009, (Int,)) mutable struct B14009{T}; end g14009(a) = g14009(B14009{a}) code_typed(g14009, (Type{Int},)) -code_llvm(devnull, f14009, (Int,)) - +code_llvm(devnull, g14009, (Type{Int},)) # issue #9232 arithtype9232(::Type{T},::Type{T}) where {T<:Real} = arithtype9232(T) @@ -665,7 +676,6 @@ end function test_inferred_static(arrow::Pair, all_ssa) code, rt = arrow @test isdispatchelem(rt) - @test code.inferred for i = 1:length(code.code) e = code.code[i] test_inferred_static(e) @@ -721,7 +731,7 @@ for (codetype, all_ssa) in Any[ test_inferred_static(codetype, all_ssa) end @test f18679() === () -@test_throws UndefVarError(:any_undef_global) g18679() +@test_throws UndefVarError(:any_undef_global, @__MODULE__) g18679() @test h18679() === nothing @@ -1180,8 +1190,6 @@ let isdefined_tfunc(@nospecialize xs...) = @test isdefined_tfunc(Core.SimpleVector, Const(1)) === Const(false) @test Const(false) ⊑ isdefined_tfunc(Const(:x), Symbol) @test Const(false) ⊑ isdefined_tfunc(Const(:x), Const(:y)) - @test isdefined_tfunc(Vector{Int}, Const(1)) == Const(false) - @test isdefined_tfunc(Vector{Any}, Const(1)) == Const(false) @test isdefined_tfunc(Module, Int) === Union{} @test isdefined_tfunc(Tuple{Any,Vararg{Any}}, Const(0)) === Const(false) @test isdefined_tfunc(Tuple{Any,Vararg{Any}}, Const(1)) === Const(true) @@ -1321,30 +1329,33 @@ test_const_return(()->sizeof(1), Tuple{}, sizeof(Int)) test_const_return(()->sizeof(DataType), Tuple{}, sizeof(DataType)) test_const_return(()->sizeof(1 < 2), Tuple{}, 1) test_const_return(()->fieldtype(Dict{Int64,Nothing}, :age), Tuple{}, UInt) -test_const_return(@eval(()->Core.sizeof($(Array{Int,0}(undef)))), Tuple{}, sizeof(Int)) -test_const_return(@eval(()->Core.sizeof($(Matrix{Float32}(undef, 2, 2)))), Tuple{}, 4 * 2 * 2) +test_const_return(@eval(()->Core.sizeof($(Array{Int,0}(undef)))), Tuple{}, 2 * sizeof(Int)) +test_const_return(@eval(()->Core.sizeof($(Matrix{Float32}(undef, 2, 2)))), Tuple{}, 4 * sizeof(Int)) +# TODO: do we want to implement these? +# test_const_return(@eval(()->sizeof($(Array{Int,0}(undef)))), Tuple{}, sizeof(Int)) +# test_const_return(@eval(()->sizeof($(Matrix{Float32}(undef, 2, 2)))), Tuple{}, 4 * 2 * 2) +# test_const_return(@eval(()->Core.sizeof($(Memory{Int}(undef, 0)))), Tuple{}, 0) # Make sure Core.sizeof with a ::DataType as inferred input type is inferred but not constant. function sizeof_typeref(typeref) return Core.sizeof(typeref[]) end @test @inferred(sizeof_typeref(Ref{DataType}(Int))) == sizeof(Int) -@test find_call(first(code_typed(sizeof_typeref, (Ref{DataType},))[1]), Core.sizeof, 2) +@test find_call(only(code_typed(sizeof_typeref, (Ref{DataType},)))[1], Core.sizeof, 2) # Constant `Vector` can be resized and shouldn't be optimized to a constant. const constvec = [1, 2, 3] @eval function sizeof_constvec() - return Core.sizeof($constvec) + return sizeof($constvec) end @test @inferred(sizeof_constvec()) == sizeof(Int) * 3 -@test find_call(first(code_typed(sizeof_constvec, ())[1]), Core.sizeof, 2) push!(constvec, 10) -@test @inferred(sizeof_constvec()) == sizeof(Int) * 4 +@test sizeof_constvec() == sizeof(Int) * 4 test_const_return(x->isdefined(x, :re), Tuple{ComplexF64}, true) isdefined_f3(x) = isdefined(x, 3) @test @inferred(isdefined_f3(())) == false -@test find_call(first(code_typed(isdefined_f3, Tuple{Tuple{Vararg{Int}}})[1]), isdefined, 3) +@test find_call(only(code_typed(isdefined_f3, Tuple{Tuple{Vararg{Int}}}))[1], isdefined, 3) let isa_tfunc(@nospecialize xs...) = Core.Compiler.isa_tfunc(Core.Compiler.fallback_lattice, xs...) @@ -1522,7 +1533,7 @@ let nfields_tfunc(@nospecialize xs...) = @test sizeof_nothrow(Type{Ptr}) @test sizeof_nothrow(Type{Union{Ptr{Int}, Int}}) @test !sizeof_nothrow(Const(Tuple)) - @test !sizeof_nothrow(Type{Vector{Int}}) + @test sizeof_nothrow(Type{Vector{Int}}) @test !sizeof_nothrow(Type{Union{Int, String}}) @test sizeof_nothrow(String) @test !sizeof_nothrow(Type{String}) @@ -1568,36 +1579,72 @@ end f_typeof_tfunc(x) = typeof(x) @test Base.return_types(f_typeof_tfunc, (Union{<:T, Int} where T<:Complex,)) == Any[Union{Type{Int}, Type{Complex{T}} where T<:Real}] -# arrayref / arrayset / arraysize -import Core.Compiler: Const -let arrayref_tfunc(@nospecialize xs...) = Core.Compiler.arrayref_tfunc(Core.Compiler.fallback_lattice, xs...) - arrayset_tfunc(@nospecialize xs...) = Core.Compiler.arrayset_tfunc(Core.Compiler.fallback_lattice, xs...) - arraysize_tfunc(@nospecialize xs...) = Core.Compiler.arraysize_tfunc(Core.Compiler.fallback_lattice, xs...) - @test arrayref_tfunc(Const(true), Vector{Int}, Int) === Int - @test arrayref_tfunc(Const(true), Vector{<:Integer}, Int) === Integer - @test arrayref_tfunc(Const(true), Vector, Int) === Any - @test arrayref_tfunc(Const(true), Vector{Int}, Int, Vararg{Int}) === Int - @test arrayref_tfunc(Const(true), Vector{Int}, Vararg{Int}) === Int - @test arrayref_tfunc(Const(true), Vector{Int}) === Union{} - @test arrayref_tfunc(Const(true), String, Int) === Union{} - @test arrayref_tfunc(Const(true), Vector{Int}, Float64) === Union{} - @test arrayref_tfunc(Int, Vector{Int}, Int) === Union{} - @test arrayset_tfunc(Const(true), Vector{Int}, Int, Int) === Vector{Int} - let ua = Vector{<:Integer} - @test arrayset_tfunc(Const(true), ua, Int, Int) === ua - end - @test arrayset_tfunc(Const(true), Vector, Int, Int) === Vector - @test arrayset_tfunc(Const(true), Any, Int, Int) === Any - @test arrayset_tfunc(Const(true), Vector{String}, String, Int, Vararg{Int}) === Vector{String} - @test arrayset_tfunc(Const(true), Vector{String}, String, Vararg{Int}) === Vector{String} - @test arrayset_tfunc(Const(true), Vector{String}, String) === Union{} - @test arrayset_tfunc(Const(true), String, Char, Int) === Union{} - @test arrayset_tfunc(Const(true), Vector{Int}, Int, Float64) === Union{} - @test arrayset_tfunc(Int, Vector{Int}, Int, Int) === Union{} - @test arrayset_tfunc(Const(true), Vector{Int}, Float64, Int) === Union{} - @test arraysize_tfunc(Vector, Int) === Int - @test arraysize_tfunc(Vector, Float64) === Union{} - @test arraysize_tfunc(String, Int) === Union{} +# memoryref_tfunc, memoryrefget_tfunc, memoryrefset!_tfunc, memoryref_isassigned, memoryrefoffset_tfunc +let memoryref_tfunc(@nospecialize xs...) = Core.Compiler.memoryref_tfunc(Core.Compiler.fallback_lattice, xs...) + memoryrefget_tfunc(@nospecialize xs...) = Core.Compiler.memoryrefget_tfunc(Core.Compiler.fallback_lattice, xs...) + memoryref_isassigned_tfunc(@nospecialize xs...) = Core.Compiler.memoryref_isassigned_tfunc(Core.Compiler.fallback_lattice, xs...) + memoryrefset!_tfunc(@nospecialize xs...) = Core.Compiler.memoryrefset!_tfunc(Core.Compiler.fallback_lattice, xs...) + memoryrefoffset_tfunc(@nospecialize xs...) = Core.Compiler.memoryrefoffset_tfunc(Core.Compiler.fallback_lattice, xs...) + interp = Core.Compiler.NativeInterpreter() + builtin_tfunction(@nospecialize xs...) = Core.Compiler.builtin_tfunction(interp, xs..., nothing) + @test memoryref_tfunc(Memory{Int}) == MemoryRef{Int} + @test memoryref_tfunc(Memory{Integer}) == MemoryRef{Integer} + @test memoryref_tfunc(MemoryRef{Int}, Int) == MemoryRef{Int} + @test memoryref_tfunc(MemoryRef{Int}, Vararg{Int}) == MemoryRef{Int} + @test memoryref_tfunc(MemoryRef{Int}, Int, Symbol) == Union{} + @test memoryref_tfunc(MemoryRef{Int}, Int, Bool) == MemoryRef{Int} + @test memoryref_tfunc(MemoryRef{Int}, Int, Vararg{Bool}) == MemoryRef{Int} + @test memoryref_tfunc(Memory{Int}, Int) == Union{} + @test memoryref_tfunc(Any, Any, Any) == Any # also probably could be GenericMemoryRef + @test memoryref_tfunc(Any, Any) == Any # also probably could be GenericMemoryRef + @test memoryref_tfunc(Any) == GenericMemoryRef + @test memoryrefget_tfunc(MemoryRef{Int}, Symbol, Bool) === Int + @test memoryrefget_tfunc(MemoryRef{Int}, Any, Any) === Int + @test memoryrefget_tfunc(MemoryRef{<:Integer}, Symbol, Bool) === Integer + @test memoryrefget_tfunc(GenericMemoryRef, Symbol, Bool) === Any + @test memoryrefget_tfunc(GenericMemoryRef{:not_atomic}, Symbol, Bool) === Any + @test memoryrefget_tfunc(Vector{Int}, Symbol, Bool) === Union{} + @test memoryrefget_tfunc(String, Symbol, Bool) === Union{} + @test memoryrefget_tfunc(MemoryRef{Int}, String, Bool) === Union{} + @test memoryrefget_tfunc(MemoryRef{Int}, Symbol, String) === Union{} + @test memoryrefget_tfunc(Any, Any, Any) === Any + @test builtin_tfunction(Core.memoryrefget, Any[MemoryRef{Int}, Vararg{Any}]) == Int + @test builtin_tfunction(Core.memoryrefget, Any[MemoryRef{Int}, Symbol, Bool, Vararg{Bool}]) == Int + @test memoryref_isassigned_tfunc(MemoryRef{Any}, Symbol, Bool) === Bool + @test memoryref_isassigned_tfunc(MemoryRef{Any}, Any, Any) === Bool + @test memoryref_isassigned_tfunc(MemoryRef{<:Integer}, Symbol, Bool) === Bool + @test memoryref_isassigned_tfunc(GenericMemoryRef, Symbol, Bool) === Bool + @test memoryref_isassigned_tfunc(GenericMemoryRef{:not_atomic}, Symbol, Bool) === Bool + @test memoryref_isassigned_tfunc(Vector{Int}, Symbol, Bool) === Union{} + @test memoryref_isassigned_tfunc(String, Symbol, Bool) === Union{} + @test memoryref_isassigned_tfunc(MemoryRef{Int}, String, Bool) === Union{} + @test memoryref_isassigned_tfunc(MemoryRef{Int}, Symbol, String) === Union{} + @test memoryref_isassigned_tfunc(Any, Any, Any) === Bool + @test builtin_tfunction(Core.memoryref_isassigned, Any[MemoryRef{Int}, Vararg{Any}]) == Bool + @test builtin_tfunction(Core.memoryref_isassigned, Any[MemoryRef{Int}, Symbol, Bool, Vararg{Bool}]) == Bool + @test memoryrefset!_tfunc(MemoryRef{Int}, Int, Symbol, Bool) === Int + let ua = MemoryRef{<:Integer} + @test memoryrefset!_tfunc(ua, Int, Symbol, Bool) === Int + end + @test memoryrefset!_tfunc(GenericMemoryRef, Int, Symbol, Bool) === Int + @test memoryrefset!_tfunc(GenericMemoryRef{:not_atomic}, Int, Symbol, Bool) === Int + @test memoryrefset!_tfunc(Any, Int, Symbol, Bool) === Int + @test memoryrefset!_tfunc(MemoryRef{String}, Int, Symbol, Bool) === Union{} + @test memoryrefset!_tfunc(String, Char, Symbol, Bool) === Union{} + @test memoryrefset!_tfunc(MemoryRef{Int}, Any, Symbol, Bool) === Any # could improve this to Int + @test memoryrefset!_tfunc(MemoryRef{Int}, Any, Any, Any) === Any # could improve this to Int + @test memoryrefset!_tfunc(GenericMemoryRef{:not_atomic}, Any, Any, Any) === Any + @test memoryrefset!_tfunc(GenericMemoryRef, Any, Any, Any) === Any + @test memoryrefset!_tfunc(Any, Any, Any, Any) === Any + @test builtin_tfunction(Core.memoryrefset!, Any[MemoryRef{Int}, Vararg{Any}]) == Any + @test builtin_tfunction(Core.memoryrefset!, Any[MemoryRef{Int}, Vararg{Symbol}]) == Union{} + @test builtin_tfunction(Core.memoryrefset!, Any[MemoryRef{Int}, Any, Symbol, Vararg{Bool}]) === Any # could improve this to Int + @test builtin_tfunction(Core.memoryrefset!, Any[MemoryRef{Int}, Any, Symbol, Bool, Vararg{Any}]) === Any # could improve this to Int + @test memoryrefoffset_tfunc(MemoryRef) == memoryrefoffset_tfunc(GenericMemoryRef) == Int + @test memoryrefoffset_tfunc(Memory) == memoryrefoffset_tfunc(GenericMemory) == Union{} + @test builtin_tfunction(Core.memoryrefoffset, Any[Vararg{MemoryRef}]) == Int + @test builtin_tfunction(Core.memoryrefoffset, Any[Vararg{Any}]) == Int + @test builtin_tfunction(Core.memoryrefoffset, Any[Vararg{Memory}]) == Union{} end let tuple_tfunc(@nospecialize xs...) = @@ -1651,13 +1698,12 @@ let linfo = get_linfo(Base.convert, Tuple{Type{Int64}, Int32}), @test opt.src !== linfo.def.source @test length(opt.src.slotflags) == linfo.def.nargs <= length(opt.src.slotnames) @test opt.src.ssavaluetypes isa Vector{Any} - @test !opt.src.inferred @test opt.mod === Base end # approximate static parameters due to unions let T1 = Array{Float64}, T2 = Array{_1,2} where _1 - inference_test_copy(a::T) where {T<:Array} = ccall(:jl_array_copy, Ref{T}, (Any,), a) + inference_test_copy(a::T) where {T<:Array} = ccall(:array_copy_like, Ref{T}, (Any,), a) rt = Base.return_types(inference_test_copy, (Union{T1,T2},))[1] @test rt >: T1 && rt >: T2 @@ -2403,7 +2449,7 @@ isaint(a) = isa(a, Int) end return 0 end |> only === Int -# handle multiple call-site refinment targets +# handle multiple call-site refinement targets isasome(_) = true isasome(::Nothing) = false @test_broken Base.return_types((AliasableField{Union{Int,Nothing}},); interp=MustAliasInterpreter()) do a @@ -2543,7 +2589,7 @@ function h25579(g) return t ? typeof(h) : typeof(h) end @test Base.return_types(h25579, (Base.RefValue{Union{Nothing, Int}},)) == - Any[Union{Type{Float64}, Type{Int}, Type{Nothing}}] + Any[Type{Float64}] f26172(v) = Val{length(Base.tail(ntuple(identity, v)))}() # Val(M-1) g26172(::Val{0}) = () @@ -2980,13 +3026,16 @@ end @test ig27907(Int, Int, 1, 0) == 0 # issue #28279 +# ensure that lowering doesn't move these into statement position, which would require renumbering +using Base: +, - function f28279(b::Bool) - i = 1 - while i > b - i -= 1 + let i = 1 + while i > b + i -= 1 + end + if b end + return i + 1 end - if b end - return i + 1 end code28279 = code_lowered(f28279, (Bool,))[1].code oldcode28279 = deepcopy(code28279) @@ -4366,30 +4415,28 @@ g41908() = f41908(Any[1][1]) # issue #42022 let x = Tuple{Int,Any}[ #= 1=# (0, Expr(:(=), Core.SlotNumber(3), 1)) - #= 2=# (0, Expr(:enter, 18)) + #= 2=# (0, EnterNode(17)) #= 3=# (2, Expr(:(=), Core.SlotNumber(3), 2.0)) - #= 4=# (2, Expr(:enter, 12)) + #= 4=# (2, EnterNode(12)) #= 5=# (4, Expr(:(=), Core.SlotNumber(3), '3')) #= 6=# (4, Core.GotoIfNot(Core.SlotNumber(2), 9)) #= 7=# (4, Expr(:leave, Core.SSAValue(4), Core.SSAValue(2))) #= 8=# (0, Core.ReturnNode(1)) #= 9=# (4, Expr(:call, GlobalRef(Main, :throw))) #=10=# (4, Expr(:leave, Core.SSAValue(4))) - #=11=# (2, Core.GotoNode(16)) - #=12=# (4, Expr(:leave, Core.SSAValue(4))) - #=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, Core.SSAValue(2))) - #=17=# (0, Core.GotoNode(22)) - #=18=# (2, Expr(:leave, Core.SSAValue(2))) - #=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))) + #=11=# (2, Core.GotoNode(15)) + #=12=# (2, Expr(:(=), Core.SlotNumber(4), Expr(:the_exception))) + #=13=# (2, Expr(:call, GlobalRef(Main, :rethrow))) + #=14=# (2, Expr(:pop_exception, Core.SSAValue(4))) + #=15=# (2, Expr(:leave, Core.SSAValue(2))) + #=16=# (0, Core.GotoNode(20)) + #=17=# (0, Expr(:(=), Core.SlotNumber(5), Expr(:the_exception))) + #=18=# (0, nothing) + #=19=# (0, Expr(:pop_exception, Core.SSAValue(2))) + #=20=# (0, Core.ReturnNode(Core.SlotNumber(3))) ] - handler_at = Core.Compiler.compute_trycatch(last.(x), Core.Compiler.BitSet()) - @test handler_at == first.(x) + handler_at, handlers = Core.Compiler.compute_trycatch(last.(x), Core.Compiler.BitSet()) + @test map(x->x[1] == 0 ? 0 : handlers[x[1]].enter_idx, handler_at) == first.(x) end @test only(Base.return_types((Bool,)) do y @@ -4406,7 +4453,7 @@ end nothing end return x - end) === Union{Int, Float64, Char} + end) === Union{Int, Char} # issue #42097 struct Foo42097{F} end @@ -4438,7 +4485,7 @@ let # Vararg #=va=# Bound, unbound, # => Tuple{Integer,Integer} (invalid `TypeVar` widened beforehand) } where Bound<:Integer - argtypes = Core.Compiler.most_general_argtypes(method, specTypes, true) + argtypes = Core.Compiler.most_general_argtypes(method, specTypes) popfirst!(argtypes) @test argtypes[1] == Integer @test argtypes[2] == Integer @@ -4632,11 +4679,11 @@ end a = Tuple{Vararg{Tuple{}}} a = Core.Compiler.tmerge(Core.Compiler.JLTypeLattice(), Tuple{a}, a) - @test a == Tuple{Vararg{Tuple{Vararg{Tuple{}}}}} + @test a == Union{Tuple{Tuple{Vararg{Tuple{}}}}, Tuple{Vararg{Tuple{}}}} a = Core.Compiler.tmerge(Core.Compiler.JLTypeLattice(), Tuple{a}, a) - @test a == Tuple{Vararg{Tuple{Vararg{Tuple{Vararg{Tuple{}}}}}}} + @test a == Tuple{Vararg{Union{Tuple{Tuple{Vararg{Tuple{}}}}, Tuple{Vararg{Tuple{}}}}}} a = Core.Compiler.tmerge(Core.Compiler.JLTypeLattice(), Tuple{a}, a) - @test a == Tuple{Vararg{Tuple{Vararg{Tuple{Vararg{Tuple{Vararg{Tuple{}}}}}}}}} + @test a == Tuple a = Core.Compiler.tmerge(Core.Compiler.JLTypeLattice(), Tuple{a}, a) @test a == Tuple end @@ -4889,10 +4936,21 @@ g() = empty_nt_values(Base.inferencebarrier(Tuple{})) # This is somewhat sensitive to the exact recursion level that inference is willing to do, but the intention # is to test the case where inference limited a recursion, but then a forced constprop nevertheless managed # to terminate the call. +@newinterp RecurseInterpreter +let CC = Core.Compiler + function CC.const_prop_entry_heuristic(interp::RecurseInterpreter, result::CC.MethodCallResult, + si::CC.StmtInfo, sv::CC.AbsIntState, force::Bool) + if result.rt isa CC.LimitedAccuracy + return force # allow forced constprop to recurse into unresolved cycles + end + return @invoke CC.const_prop_entry_heuristic(interp::CC.AbstractInterpreter, result::CC.MethodCallResult, + si::CC.StmtInfo, sv::CC.AbsIntState, force::Bool) + end +end Base.@constprop :aggressive type_level_recurse1(x...) = x[1] == 2 ? 1 : (length(x) > 100 ? x : type_level_recurse2(x[1] + 1, x..., x...)) Base.@constprop :aggressive type_level_recurse2(x...) = type_level_recurse1(x...) type_level_recurse_entry() = Val{type_level_recurse1(1)}() -@test Base.return_types(type_level_recurse_entry, ()) |> only == Val{1} +@test Base.infer_return_type(type_level_recurse_entry, (); interp=RecurseInterpreter()) == Val{1} # Test that inference doesn't give up if it can potentially refine effects, # even if the return type is Any. @@ -5129,7 +5187,7 @@ let 𝕃 = Core.Compiler.SimpleInferenceLattice.instance end isa Vector end -# `getindex(::SimpleVector, ::Int)` shuold be concrete-evaluated +# `getindex(::SimpleVector, ::Int)` should be concrete-evaluated @eval Base.return_types() do $(Core.svec(1,Int,nothing))[2] end |> only == Type{Int} @@ -5255,14 +5313,14 @@ let TV = TypeVar(:T) end # use `Vararg` type constraints -use_vararg_constrant1(args::Vararg{T,N}) where {T,N} = Val(T), Val(N) -@test only(Base.return_types(use_vararg_constrant1, Tuple{Int,Int})) == Tuple{Val{Int},Val{2}} -use_vararg_constrant2(args::Vararg{T,N}) where {T,N} = Val(T), N -@test only(Base.return_types(use_vararg_constrant2, Tuple{Vararg{Int}})) == Tuple{Val{Int},Int} -use_vararg_constrant3(args::NTuple{N,T}) where {T,N} = Val(T), Val(N) -@test only(Base.return_types(use_vararg_constrant3, Tuple{Tuple{Int,Int}})) == Tuple{Val{Int},Val{2}} -use_vararg_constrant4(args::NTuple{N,T}) where {T,N} = Val(T), N -@test only(Base.return_types(use_vararg_constrant4, Tuple{NTuple{N,Int}} where N)) == Tuple{Val{Int},Int} +use_vararg_constraint1(args::Vararg{T,N}) where {T,N} = Val(T), Val(N) +@test only(Base.return_types(use_vararg_constraint1, Tuple{Int,Int})) == Tuple{Val{Int},Val{2}} +use_vararg_constraint2(args::Vararg{T,N}) where {T,N} = Val(T), N +@test only(Base.return_types(use_vararg_constraint2, Tuple{Vararg{Int}})) == Tuple{Val{Int},Int} +use_vararg_constraint3(args::NTuple{N,T}) where {T,N} = Val(T), Val(N) +@test only(Base.return_types(use_vararg_constraint3, Tuple{Tuple{Int,Int}})) == Tuple{Val{Int},Val{2}} +use_vararg_constraint4(args::NTuple{N,T}) where {T,N} = Val(T), N +@test only(Base.return_types(use_vararg_constraint4, Tuple{NTuple{N,Int}} where N)) == Tuple{Val{Int},Int} # issue 51228 global whatever_unknown_value51228 @@ -5284,3 +5342,386 @@ end @test only(Base.return_types((x,f) -> getfield(x, f), (An51317, Symbol))) === Int @test only(Base.return_types(x -> getfield(x, :b), (A51317,))) === Union{} @test only(Base.return_types(x -> getfield(x, :b), (An51317,))) === Union{} + +# Don't visit the catch block for empty try/catch +function completely_dead_try_catch() + try + catch + return 2.0 + end + return 1 +end +@test Base.return_types(completely_dead_try_catch) |> only === Int +@test fully_eliminated(completely_dead_try_catch) + +function nothrow_try_catch() + try + 1+1 + catch + return 2.0 + end + return 1 +end +@test Base.return_types(nothrow_try_catch) |> only === Int +@test fully_eliminated(nothrow_try_catch) + +may_error(b) = Base.inferencebarrier(b) && error() +function phic_type1() + a = 1 + try + may_error(false) + a = 1.0 + catch + return a + end + return 2 +end +@test Base.return_types(phic_type1) |> only === Int +@test phic_type1() === 2 + +function phic_type2() + a = 1 + try + may_error(false) + a = 1.0 + may_error(false) + catch + return a + end + return 2 +end +@test Base.return_types(phic_type2) |> only === Union{Int, Float64} +@test phic_type2() === 2 + +function phic_type3() + a = 1 + try + may_error(false) + a = 1.0 + may_error(false) + if Base.inferencebarrier(false) + a = Ref(1) + elseif Base.inferencebarrier(false) + a = nothing + end + catch + return a + end + return 2 +end +@test Base.return_types(phic_type3) |> only === Union{Int, Float64} +@test phic_type3() === 2 + +# Issue #51852 +function phic_type4() + a = (;progress = "a") + try + may_error(false) + let b = Base.inferencebarrier(true) ? (;progress = 1.0) : a + a = b + end + catch + end + GC.gc() + return a +end +@test Base.return_types(phic_type4) |> only === Union{@NamedTuple{progress::Float64}, @NamedTuple{progress::String}} +@test phic_type4() === (;progress = 1.0) + +function phic_type5() + a = (;progress = "a") + try + vals = (a, (progress=1.0,)) + may_error(false) + a = vals[Base.inferencebarrier(false) ? 1 : 2] + catch + end + GC.gc() + return a +end +@test Base.return_types(phic_type5) |> only === Union{@NamedTuple{progress::Float64}, @NamedTuple{progress::String}} +@test phic_type5() === (;progress = 1.0) + +function phic_type6() + a = Base.inferencebarrier(true) ? (;progress = "a") : (;progress = Ref{Any}(0)) + try + may_error(false) + let b = Base.inferencebarrier(true) ? (;progress = 1.0) : a + a = b + end + catch + end + GC.gc() + return a +end +@test Base.return_types(phic_type6) |> only === Union{@NamedTuple{progress::Float64}, @NamedTuple{progress::Base.RefValue{Any}}, @NamedTuple{progress::String}} +@test phic_type6() === (;progress = 1.0) + +function phic_type7() + a = Base.inferencebarrier(true) ? (;progress = "a") : (;progress = Ref{Any}(0)) + try + vals = (a, (progress=1.0,)) + may_error(false) + a = vals[Base.inferencebarrier(false) ? 1 : 2] + catch + end + GC.gc() + return a +end +@test Base.return_types(phic_type7) |> only === Union{@NamedTuple{progress::Float64}, @NamedTuple{progress::Base.RefValue{Any}}, @NamedTuple{progress::String}} +@test phic_type7() === (;progress = 1.0) + +function phic_type8() + local a + try + may_error(true) + a = Base.inferencebarrier(1) + catch + end + + try + a = 2 + may_error(true) + catch + end + GC.gc() + return a +end +@test Base.return_types(phic_type8) |> only === Int +@test phic_type8() === 2 + +function phic_type9() + local a + try + may_error(false) + a = Base.inferencebarrier(false) ? 1 : nothing + catch + end + + try + a = 2 + may_error(true) + catch + end + GC.gc() + return a +end +@test Base.return_types(phic_type9) |> only === Int +@test phic_type9() === 2 + +function phic_type10() + local a + try + may_error(false) + a = Base.inferencebarrier(true) ? missing : nothing + catch + end + + try + Base.inferencebarrier(true) && (a = 2) + may_error(true) + catch + end + GC.gc() + return a::Int +end +@test Base.return_types(phic_type10) |> only === Int +@test phic_type10() === 2 + +undef_trycatch() = try (a_undef_trycatch = a_undef_trycatch, b = 2); return 1 catch end +# `global a_undef_trycatch` could be defined dynamically, so both paths must be allowed +@test Base.return_types(undef_trycatch) |> only === Union{Nothing, Int} +@test undef_trycatch() === nothing + +# Test that `exit` returns `Union{}` (issue #51856) +function test_exit_bottom(s) + n = tryparse(Int, s) + isnothing(n) && exit() + n +end +@test only(Base.return_types(test_exit_bottom, Tuple{String})) == Int + +function foo_typed_throw_error() + try + error() + catch e + if isa(e, ErrorException) + return 1.0 + end + end + return 1 +end +@test Base.return_types(foo_typed_throw_error) |> only === Float64 + +will_throw_no_method(x::Int) = 1 +function foo_typed_throw_metherr() + try + will_throw_no_method(1.0) + catch e + if isa(e, MethodError) + return 1.0 + end + end + return 1 +end +@test Base.return_types(foo_typed_throw_metherr) |> only === Float64 + +# refine `exct` when `:nothrow` is proven +Base.@assume_effects :nothrow function sin_nothrow(x::Float64) + x == Inf && return zero(x) + return sin(x) +end +@test Base.infer_exception_type(sin_nothrow, (Float64,)) == Union{} +@test Base.return_types((Float64,)) do x + try + return sin_nothrow(x) + catch err + return err + end +end |> only === Float64 +# for semi-concrete interpretation result too +Base.@constprop :aggressive function sin_maythrow(x::Float64, maythrow::Bool) + if maythrow + return sin(x) + else + return @noinline sin_nothrow(x) + end +end +@test Base.return_types((Float64,)) do x + try + return sin_maythrow(x, false) + catch err + return err + end +end |> only === Float64 + +# exception type from GotoIfNot +@test Base.infer_exception_type(c::Bool -> c ? 1 : 2) == Union{} +@test Base.infer_exception_type(c::Missing -> c ? 1 : 2) == TypeError +@test Base.infer_exception_type(c::Any -> c ? 1 : 2) == TypeError + +# exception type inference for `:new` +struct NewExctInference + a::Int + @eval NewExctInference(a) = $(Expr(:new, :NewExctInference, :a)) +end +@test Base.infer_exception_type(NewExctInference, (Float64,)) == TypeError + +# semi-concrete interpretation accuracy +# https://github.com/JuliaLang/julia/issues/50037 +@inline countvars50037(bitflags::Int, var::Int) = bitflags >> 0 +@test Base.infer_return_type() do var::Int + Val(countvars50037(1, var)) +end == Val{1} + +# Issue #52168 +f52168(x, t::Type) = x::NTuple{2, Base.inferencebarrier(t)::Type} +@test f52168((1, 2.), Any) === (1, 2.) + +# Issue #27031 +let x = 1, _Any = Any + @noinline bar27031(tt::Tuple{T,T}, ::Type{Val{T}}) where {T} = notsame27031(tt) + @noinline notsame27031(tt::Tuple{T, T}) where {T} = error() + @noinline notsame27031(tt::Tuple{T, S}) where {T, S} = "OK" + foo27031() = bar27031((x, 1.0), Val{_Any}) + @test foo27031() == "OK" +end + +# Issue #51927 +let 𝕃 = Core.Compiler.fallback_lattice + @test apply_type_tfunc(𝕃, Const(Tuple{Vararg{Any,N}} where N), Int) == Type{NTuple{_A, Any}} where _A +end + +# Issue #52613 +@test (code_typed((Any,)) do x; TypeVar(x...); end)[1][2] === TypeVar + +# https://github.com/JuliaLang/julia/issues/53590 +func53590(b) = b ? Int : Float64 +function issue53590(b1, b2) + T1 = func53590(b1) + T2 = func53590(b2) + return typejoin(T1, T2) +end +@test issue53590(true, true) == Int +@test issue53590(true, false) == Real +@test issue53590(false, false) == Float64 +@test issue53590(false, true) == Real + +# Expr(:throw_undef_if_not) handling +@eval function has_tuin() + $(Expr(:throw_undef_if_not, :x, false)) +end +@test Core.Compiler.return_type(has_tuin, Tuple{}) === Union{} +@test_throws UndefVarError has_tuin() + +function gen_tuin_from_arg(world::UInt, source, _, _) + ci = make_codeinfo(Any[ + Expr(:throw_undef_if_not, :x, Core.Argument(2)), + ReturnNode(true), + ]; slottypes=Any[Any, Bool]) + ci.slotnames = Symbol[:var"#self#", :def] + ci +end + +@eval function has_tuin2(def) + $(Expr(:meta, :generated, gen_tuin_from_arg)) + $(Expr(:meta, :generated_only)) +end +@test_throws UndefVarError has_tuin2(false) +@test has_tuin2(true) + +# issue #53585 +let t = ntuple(i -> i % 8 == 1 ? Int64 : Float64, 4000) + @test only(Base.return_types(Base.promote_typeof, t)) == Type{Float64} + @test only(Base.return_types(vcat, t)) == Vector{Float64} +end + +# Infinite loop in inference on SSA assignment +const stop_infinite_loop::Base.Threads.Atomic{Bool} = Base.Threads.Atomic{Bool}(false) +function gen_infinite_loop_ssa_generator(world::UInt, source, _) + ci = make_codeinfo(Any[ + # Block 1 + (), + # Block 2 + PhiNode(Int32[1, 5], Any[SSAValue(1), SSAValue(3)]), + Expr(:call, tuple, SSAValue(2)), + Expr(:call, getindex, GlobalRef(@__MODULE__, :stop_infinite_loop)), + GotoIfNot(SSAValue(4), 2), + # Block 3 + ReturnNode(SSAValue(2)) + ]; slottypes=Any[Any]) + ci.slotnames = Symbol[:var"#self#"] + ci +end + +@eval function gen_infinite_loop_ssa() + $(Expr(:meta, :generated, gen_infinite_loop_ssa_generator)) + $(Expr(:meta, :generated_only)) + #= no body =# +end + +# We want to make sure that both this returns `Tuple` and that +# it doesn't infinite loop inside inference. +@test Core.Compiler.return_type(gen_infinite_loop_ssa, Tuple{}) === Tuple + +# inference local cache lookup with extended lattice elements that may be transformed +# by `matching_cache_argtypes` +@newinterp CachedConditionalInterp +Base.@constprop :aggressive function func_cached_conditional(x, y) + if x + @noinline sin(y) + else + 0.0 + end +end; +function test_func_cached_conditional(y) + y₁ = func_cached_conditional(isa(y, Float64), y) + y₂ = func_cached_conditional(isa(y, Float64), y) + return y₁, y₂ +end; +let interp = CachedConditionalInterp(); + @test Base.infer_return_type(test_func_cached_conditional, (Any,); interp) == Tuple{Float64, Float64} + @test count(interp.inf_cache) do result + result.linfo.def.name === :func_cached_conditional + end == 1 +end diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index 07d4c7bf363a8..3c408b06a87ae 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -71,7 +71,7 @@ function bar12620() foo_inl(i==1) end end -@test_throws UndefVarError(:y) bar12620() +@test_throws UndefVarError(:y, :local) bar12620() # issue #16165 @inline f16165(x) = (x = UInt(x) + 1) @@ -147,8 +147,10 @@ end s end - (src, _) = code_typed(sum27403, Tuple{Vector{Int}})[1] - @test !any(x -> x isa Expr && x.head === :invoke, src.code) + (src, _) = only(code_typed(sum27403, Tuple{Vector{Int}})) + @test !any(src.code) do x + x isa Expr && x.head === :invoke && x.args[2] !== Core.GlobalRef(Base, :throw_boundserror) + end end # check that ismutabletype(type) can be fully eliminated @@ -311,7 +313,7 @@ end const _a_global_array = [1] f_inline_global_getindex() = _a_global_array[1] let ci = code_typed(f_inline_global_getindex, Tuple{})[1].first - @test any(x->(isexpr(x, :call) && x.args[1] === GlobalRef(Base, :arrayref)), ci.code) + @test any(x->(isexpr(x, :call) && x.args[1] === GlobalRef(Base, :memoryrefget)), ci.code) end # Issue #29114 & #36087 - Inlining of non-tuple splats @@ -505,6 +507,17 @@ end Base.@constprop :aggressive noinlined_constprop_implicit(a) = a+g force_inline_constprop_implicit() = @inline noinlined_constprop_implicit(0) + function force_inline_constprop_cached1() + r1 = noinlined_constprop_implicit(0) + r2 = @inline noinlined_constprop_implicit(0) + return (r1, r2) + end + function force_inline_constprop_cached2() + r1 = @inline noinlined_constprop_implicit(0) + r2 = noinlined_constprop_implicit(0) + return (r1, r2) + end + @inline Base.@constprop :aggressive inlined_constprop_explicit(a) = a+g force_noinline_constprop_explicit() = @noinline inlined_constprop_explicit(0) @inline Base.@constprop :aggressive inlined_constprop_implicit(a) = a+g @@ -555,6 +568,12 @@ end let code = get_code(M.force_inline_constprop_implicit) @test all(!isinvoke(:noinlined_constprop_implicit), code) end + let code = get_code(M.force_inline_constprop_cached1) + @test count(isinvoke(:noinlined_constprop_implicit), code) == 1 + end + let code = get_code(M.force_inline_constprop_cached2) + @test count(isinvoke(:noinlined_constprop_implicit), code) == 1 + end let code = get_code(M.force_noinline_constprop_explicit) @test any(isinvoke(:inlined_constprop_explicit), code) @@ -568,6 +587,18 @@ end end end +@noinline fresh_edge_noinlined(a::Integer) = unresolvable(a) +let src = code_typed1((Integer,)) do x + @inline fresh_edge_noinlined(x) + end + @test count(iscall((src, fresh_edge_noinlined)), src.code) == 0 +end +let src = code_typed1((Integer,)) do x + @inline fresh_edge_noinlined(x) + end + @test count(iscall((src, fresh_edge_noinlined)), src.code) == 0 # should be idempotent +end + # force constant-prop' for `setproperty!` # https://github.com/JuliaLang/julia/pull/41882 let code = @eval Module() begin @@ -599,8 +630,8 @@ g41299(f::Tf, args::Vararg{Any,N}) where {Tf,N} = f(args...) # idempotency of callsite inlining function getcache(mi::Core.MethodInstance) cache = Core.Compiler.code_cache(Core.Compiler.NativeInterpreter()) - codeinf = Core.Compiler.get(cache, mi, nothing) - return isnothing(codeinf) ? nothing : codeinf + codeinst = Core.Compiler.get(cache, mi, nothing) + return isnothing(codeinst) ? nothing : codeinst end @noinline f42078(a) = sum(sincos(a)) let @@ -618,8 +649,8 @@ let end let # make sure to discard the inferred source mi = only(methods(f42078)).specializations::Core.MethodInstance - codeinf = getcache(mi)::Core.CodeInstance - @atomic codeinf.inferred = nothing + codeinst = getcache(mi)::Core.CodeInstance + @atomic codeinst.inferred = nothing end let # inference should re-infer `f42078(::Int)` and we should get the same code @@ -730,7 +761,7 @@ end let f(x) = (x...,) # Test splatting with a Union of non-{Tuple, SimpleVector} types that require creating new `iterate` calls # in inlining. For this particular case, we're relying on `iterate(::CaretesianIndex)` throwing an error, such - # the the original apply call is not union-split, but the inserted `iterate` call is. + # that the original apply call is not union-split, but the inserted `iterate` call is. @test code_typed(f, Tuple{Union{Int64, CartesianIndex{1}, CartesianIndex{3}}})[1][2] == Tuple{Int64} end @@ -777,8 +808,8 @@ end let src = code_typed((Union{Tuple{Int,Int,Int}, Vector{Int}},)) do xs g42840(xs, 2) end |> only |> first - # `(xs::Vector{Int})[a::Const(2)]` => `Base.arrayref(true, xs, 2)` - @test count(iscall((src, Base.arrayref)), src.code) == 1 + # `(xs::Vector{Int})[a::Const(2)]` + @test count(iscall((src, Base.memoryrefget)), src.code) == 1 @test count(isinvoke(:g42840), src.code) == 1 end @@ -974,6 +1005,14 @@ end end |> only |> first @test count(iscall((src,UnionAll)), src.code) == 0 end + # test >: + let src = code_typed((Any,Any)) do x, y + x >: y + end |> only |> first + idx = findfirst(iscall((src,<:)), src.code) + @test idx !== nothing + @test src.code[idx].args[2:3] == Any[#=y=#Argument(3), #=x=#Argument(2)] + end end # have_fma elimination inside ^ @@ -1562,20 +1601,22 @@ end # optimize `[push!|pushfirst!](::Vector{Any}, x...)` @testset "optimize `$f(::Vector{Any}, x...)`" for f = Any[push!, pushfirst!] @eval begin - let src = code_typed1((Vector{Any}, Any)) do xs, x - $f(xs, x) + for T in [Int, Any] + let src = code_typed1((Vector{T}, T)) do xs, x + $f(xs, x) + end + @test count(iscall((src, $f)), src.code) == 0 end - @test count(iscall((src, $f)), src.code) == 0 - @test count(src.code) do @nospecialize x - isa(x, Core.GotoNode) || - isa(x, Core.GotoIfNot) || - iscall((src, getfield))(x) - end == 0 # no loop should be involved for the common single arg case - end - let src = code_typed1((Vector{Any}, Any, Any)) do xs, x, y - $f(xs, x, y) + let effects = Base.infer_effects((Vector{T}, T)) do xs, x + $f(xs, x) + end + @test Core.Compiler.Core.Compiler.is_terminates(effects) + end + let src = code_typed1((Vector{T}, T, T)) do xs, x, y + $f(xs, x, y) + end + @test count(iscall((src, $f)), src.code) == 0 end - @test count(iscall((src, $f)), src.code) == 0 end let xs = Any[] $f(xs, :x, "y", 'z') @@ -1642,13 +1683,12 @@ function oc_capture_oc(z) end @test fully_eliminated(oc_capture_oc, (Int,)) +# inlining with unmatched type parameters @eval struct OldVal{T} - x::T (OV::Type{OldVal{T}})() where T = $(Expr(:new, :OV)) end -with_unmatched_typeparam1(x::OldVal{i}) where {i} = i -with_unmatched_typeparam2() = [ Base.donotdelete(OldVal{i}()) for i in 1:10000 ] -function with_unmatched_typeparam3() +@test OldVal{0}() === OldVal{0}.instance +function with_unmatched_typeparam() f(x::OldVal{i}) where {i} = i r = 0 for i = 1:10000 @@ -1656,17 +1696,15 @@ function with_unmatched_typeparam3() end return r end - -@testset "Inlining with unmatched type parameters" begin - let src = code_typed1(with_unmatched_typeparam1, (Any,)) - @test !any(@nospecialize(x) -> isexpr(x, :call) && length(x.args) == 1, src.code) - end - let src = code_typed1(with_unmatched_typeparam2) - @test !any(@nospecialize(x) -> isexpr(x, :call) && length(x.args) == 1, src.code) - end - let src = code_typed1(with_unmatched_typeparam3) - @test !any(@nospecialize(x) -> isexpr(x, :call) && length(x.args) == 1, src.code) +let src = code_typed1(with_unmatched_typeparam) + found = nothing + for x in src.code + if isexpr(x, :call) && length(x.args) == 1 + found = x + break + end end + @test isnothing(found) || (source=src, statement=found) end function twice_sitofp(x::Int, y::Int) @@ -1710,7 +1748,7 @@ let getfield_tfunc(@nospecialize xs...) = end @test fully_eliminated(Base.ismutable, Tuple{Base.RefValue}) -# TODO: Remove compute sparams for vararg_retrival +# TODO: Remove compute sparams for vararg_retrieval fvarargN_inline(x::Tuple{Vararg{Int, N}}) where {N} = N fvarargN_inline(args...) = fvarargN_inline(args) let src = code_typed1(fvarargN_inline, (Tuple{Vararg{Int}},)) @@ -1763,22 +1801,72 @@ let src = code_typed1((Atomic{Int},Union{Int,Float64})) do a, b end @test count(isinvokemodify(:mymax), src.code) == 2 end +global x_global_inc::Int = 1 +let src = code_typed1(()) do + @atomic (@__MODULE__).x_global_inc += 1 + end + @test count(isinvokemodify(:+), src.code) == 1 +end +let src = code_typed1((Ptr{Int},)) do a + unsafe_modify!(a, +, 1) + end + @test count(isinvokemodify(:+), src.code) == 1 +end +let src = code_typed1((AtomicMemoryRef{Int},)) do a + Core.memoryrefmodify!(a, +, 1, :sequentially_consistent, true) + end + @test count(isinvokemodify(:+), src.code) == 1 +end # apply `ssa_inlining_pass` multiple times -let interp = Core.Compiler.NativeInterpreter() +func_mul_int(a::Int, b::Int) = Core.Intrinsics.mul_int(a, b) +multi_inlining1(a::Int, b::Int) = @noinline func_mul_int(a, b) +let i::Int, continue_::Bool + interp = Core.Compiler.NativeInterpreter() # check if callsite `@noinline` annotation works - ir, = Base.code_ircode((Int,Int); optimize_until="inlining", interp) do a, b - @noinline a*b - end |> only - i = findfirst(isinvoke(:*), ir.stmts.stmt) + ir, = only(Base.code_ircode(multi_inlining1, (Int,Int); optimize_until="inlining", interp)) + i = findfirst(isinvoke(:func_mul_int), ir.stmts.stmt) @test i !== nothing - - # ok, now delete the callsite flag, and see the second inlining pass can inline the call + # now delete the callsite flag, and see the second inlining pass can inline the call @eval Core.Compiler $ir.stmts[$i][:flag] &= ~IR_FLAG_NOINLINE inlining = Core.Compiler.InliningState(interp) ir = Core.Compiler.ssa_inlining_pass!(ir, inlining, false) - @test count(isinvoke(:*), ir.stmts.stmt) == 0 - @test count(iscall((ir, Core.Intrinsics.mul_int)), ir.stmts.stmt) == 1 + @test findfirst(isinvoke(:func_mul_int), ir.stmts.stmt) === nothing + @test (i = findfirst(iscall((ir, Core.Intrinsics.mul_int)), ir.stmts.stmt)) !== nothing + lins = Base.IRShow.buildLineInfoNode(ir.debuginfo, nothing, i) + @test (continue_ = length(lins) == 2) # :multi_inlining1 -> :func_mul_int + if continue_ + def1 = lins[1].method + @test def1 isa Core.MethodInstance && def1.def.name === :multi_inlining1 + def2 = lins[2].method + @test def2 isa Core.MethodInstance && def2.def.name === :func_mul_int + end +end + +call_func_mul_int(a::Int, b::Int) = @noinline func_mul_int(a, b) +multi_inlining2(a::Int, b::Int) = call_func_mul_int(a, b) +let i::Int, continue_::Bool + interp = Core.Compiler.NativeInterpreter() + # check if callsite `@noinline` annotation works + ir, = only(Base.code_ircode(multi_inlining2, (Int,Int); optimize_until="inlining", interp)) + i = findfirst(isinvoke(:func_mul_int), ir.stmts.stmt) + @test i !== nothing + # now delete the callsite flag, and see the second inlining pass can inline the call + @eval Core.Compiler $ir.stmts[$i][:flag] &= ~IR_FLAG_NOINLINE + inlining = Core.Compiler.InliningState(interp) + ir = Core.Compiler.ssa_inlining_pass!(ir, inlining, false) + @test findfirst(isinvoke(:func_mul_int), ir.stmts.stmt) === nothing + @test (i = findfirst(iscall((ir, Core.Intrinsics.mul_int)), ir.stmts.stmt)) !== nothing + lins = Base.IRShow.buildLineInfoNode(ir.debuginfo, nothing, i) + @test_broken (continue_ = length(lins) == 3) # see TODO in `ir_inline_linetable!` + if continue_ + def1 = lins[1].method + @test def1 isa Core.MethodInstance && def1.def.name === :multi_inlining2 + def2 = lins[2].method + @test def2 isa Core.MethodInstance && def2.def.name === :call_func_mul_int + def3 = lins[3].method + @test def3 isa Core.MethodInstance && def3.def.name === :call_func_mul_int + end end # Test special purpose inliner for Core.ifelse @@ -1836,26 +1924,16 @@ end # Test that inlining can still use nothrow information from concrete-eval # even if the result itself is too big to be inlined, and nothrow is not # known without concrete-eval -const THE_BIG_TUPLE = ntuple(identity, 1024) +const THE_BIG_TUPLE = ntuple(identity, 1024); function return_the_big_tuple(err::Bool) err && error("BAD") return THE_BIG_TUPLE end -@noinline function return_the_big_tuple_noinline(err::Bool) - err && error("BAD") - return THE_BIG_TUPLE +@test fully_eliminated() do + return_the_big_tuple(false)[1] end -big_tuple_test1() = return_the_big_tuple(false)[1] -big_tuple_test2() = return_the_big_tuple_noinline(false)[1] - -@test fully_eliminated(big_tuple_test2, Tuple{}) -# Currently we don't run these cleanup passes, but let's make sure that -# if we did, inlining would be able to remove this -let ir = Base.code_ircode(big_tuple_test1, Tuple{})[1][1] - ir = Core.Compiler.compact!(ir, true) - ir = Core.Compiler.cfg_simplify!(ir) - ir = Core.Compiler.compact!(ir, true) - @test length(ir.stmts) == 1 +@test fully_eliminated() do + @inline return_the_big_tuple(false)[1] end # inlineable but removable call should be eligible for DCE @@ -2096,3 +2174,41 @@ let src = code_typed1() do end @test count(isinvoke(:iterate), src.code) == 0 end + +# JuliaLang/julia#53062: proper `joint_effects` for call with empty method matches +let ir = first(only(Base.code_ircode(setproperty!, (Base.RefValue{Int},Symbol,Base.RefValue{Int})))) + i = findfirst(iscall((ir, convert)), ir.stmts.stmt)::Int + @test iszero(ir.stmts.flag[i] & Core.Compiler.IR_FLAG_NOTHROW) +end +function issue53062(cond) + x = Ref{Int}(0) + if cond + x[] = x + else + return -1 + end +end +@test !Core.Compiler.is_nothrow(Base.infer_effects(issue53062, (Bool,))) +@test issue53062(false) == -1 +@test_throws MethodError issue53062(true) + +struct Issue52644 + tuple::Type{<:Tuple} +end +issue52644(::DataType) = :DataType +issue52644(::UnionAll) = :UnionAll +let ir = Base.code_ircode((Issue52644,); optimize_until="Inlining") do t + issue52644(t.tuple) + end |> only |> first + irfunc = Core.OpaqueClosure(ir) + @test irfunc(Issue52644(Tuple{})) === :DataType + @test irfunc(Issue52644(Tuple{<:Integer})) === :UnionAll +end +issue52644_single(x::DataType) = :DataType +let ir = Base.code_ircode((Issue52644,); optimize_until="Inlining") do t + issue52644_single(t.tuple) + end |> only |> first + irfunc = Core.OpaqueClosure(ir) + @test irfunc(Issue52644(Tuple{})) === :DataType + @test_throws MethodError irfunc(Issue52644(Tuple{<:Integer})) +end diff --git a/test/compiler/interpreter_exec.jl b/test/compiler/interpreter_exec.jl index 641656196c00d..f00bc92c7443d 100644 --- a/test/compiler/interpreter_exec.jl +++ b/test/compiler/interpreter_exec.jl @@ -2,28 +2,27 @@ # tests that interpreter matches codegen using Test -using Core: GotoIfNot, ReturnNode +using Core.IR # test that interpreter correctly handles PhiNodes (#29262) let m = Meta.@lower 1 + 1 @assert Meta.isexpr(m, :thunk) - src = m.args[1]::Core.CodeInfo + src = m.args[1]::CodeInfo src.code = Any[ # block 1 QuoteNode(:a), QuoteNode(:b), GlobalRef(@__MODULE__, :test29262), - GotoIfNot(Core.SSAValue(3), 6), + GotoIfNot(SSAValue(3), 6), # block 2 - Core.PhiNode(Int32[4], Any[Core.SSAValue(1)]), - Core.PhiNode(Int32[4, 5], Any[Core.SSAValue(2), Core.SSAValue(5)]), - ReturnNode(Core.SSAValue(6)), + PhiNode(Int32[4], Any[SSAValue(1)]), + PhiNode(Int32[4, 5], Any[SSAValue(2), SSAValue(5)]), + ReturnNode(SSAValue(6)), ] nstmts = length(src.code) - src.ssavaluetypes = Any[ Any for _ = 1:nstmts ] + src.ssavaluetypes = nstmts src.ssaflags = fill(UInt8(0x00), nstmts) - src.codelocs = fill(Int32(1), nstmts) - src.inferred = true + src.debuginfo = Core.DebugInfo(:none) Core.Compiler.verify_ir(Core.Compiler.inflate_ir(src)) global test29262 = true @test :a === @eval $m @@ -33,7 +32,7 @@ end let m = Meta.@lower 1 + 1 @assert Meta.isexpr(m, :thunk) - src = m.args[1]::Core.CodeInfo + src = m.args[1]::CodeInfo src.code = Any[ # block 1 QuoteNode(:a), @@ -41,85 +40,73 @@ let m = Meta.@lower 1 + 1 QuoteNode(:c), GlobalRef(@__MODULE__, :test29262), # block 2 - Core.PhiNode(Int32[4, 16], Any[false, true]), # false, true - Core.PhiNode(Int32[4, 16], Any[Core.SSAValue(1), Core.SSAValue(2)]), # :a, :b - Core.PhiNode(Int32[4, 16], Any[Core.SSAValue(3), Core.SSAValue(6)]), # :c, :a - Core.PhiNode(Int32[16], Any[Core.SSAValue(7)]), # NULL, :c + PhiNode(Int32[4, 16], Any[false, true]), # false, true + PhiNode(Int32[4, 16], Any[SSAValue(1), SSAValue(2)]), # :a, :b + PhiNode(Int32[4, 16], Any[SSAValue(3), SSAValue(6)]), # :c, :a + PhiNode(Int32[16], Any[SSAValue(7)]), # NULL, :c # block 3 - Core.PhiNode(Int32[], Any[]), # NULL, NULL - Core.PhiNode(Int32[17, 8], Any[true, Core.SSAValue(4)]), # test29262, test29262, [true] - Core.PhiNode(Int32[17], Vector{Any}(undef, 1)), # NULL, NULL - Core.PhiNode(Int32[8], Vector{Any}(undef, 1)), # NULL, NULL - Core.PhiNode(Int32[], Any[]), # NULL, NULL - Core.PhiNode(Int32[17, 8], Any[Core.SSAValue(2), Core.SSAValue(8)]), # NULL, :c, [:b] - Core.PhiNode(Int32[], Any[]), # NULL, NULL - GotoIfNot(Core.SSAValue(5), 5), + PhiNode(Int32[], Any[]), # NULL, NULL + PhiNode(Int32[17, 8], Any[true, SSAValue(4)]), # test29262, test29262, [true] + PhiNode(Int32[17], Vector{Any}(undef, 1)), # NULL, NULL + PhiNode(Int32[8], Vector{Any}(undef, 1)), # NULL, NULL + PhiNode(Int32[], Any[]), # NULL, NULL + PhiNode(Int32[17, 8], Any[SSAValue(2), SSAValue(8)]), # NULL, :c, [:b] + PhiNode(Int32[], Any[]), # NULL, NULL + GotoIfNot(SSAValue(5), 5), # block 4 - GotoIfNot(Core.SSAValue(10), 9), + GotoIfNot(SSAValue(10), 9), # block 5 - Expr(:call, GlobalRef(Core, :tuple), Core.SSAValue(6), Core.SSAValue(7), Core.SSAValue(8), Core.SSAValue(14)), - ReturnNode(Core.SSAValue(18)), + Expr(:call, GlobalRef(Core, :tuple), SSAValue(6), SSAValue(7), SSAValue(8), SSAValue(14)), + ReturnNode(SSAValue(18)), ] nstmts = length(src.code) - src.ssavaluetypes = Any[ Any for _ = 1:nstmts ] + src.ssavaluetypes = nstmts src.ssaflags = fill(UInt8(0x00), nstmts) - src.codelocs = fill(Int32(1), nstmts) - src.inferred = true + src.debuginfo = Core.DebugInfo(:none) + m.args[1] = copy(src) Core.Compiler.verify_ir(Core.Compiler.inflate_ir(src)) global test29262 = true @test (:b, :a, :c, :c) === @eval $m + m.args[1] = copy(src) global test29262 = false @test (:b, :a, :c, :b) === @eval $m end let m = Meta.@lower 1 + 1 @assert Meta.isexpr(m, :thunk) - src = m.args[1]::Core.CodeInfo + src = m.args[1]::CodeInfo src.code = Any[ # block 1 QuoteNode(:a), QuoteNode(:b), GlobalRef(@__MODULE__, :test29262), # block 2 - Expr(:enter, 11), + EnterNode(12), # block 3 - Core.UpsilonNode(), - Core.UpsilonNode(), - Core.UpsilonNode(Core.SSAValue(2)), - GotoIfNot(Core.SSAValue(3), 10), + UpsilonNode(), + UpsilonNode(), + UpsilonNode(SSAValue(2)), + GotoIfNot(SSAValue(3), 10), # block 4 - Core.UpsilonNode(Core.SSAValue(1)), + UpsilonNode(SSAValue(1)), # block 5 Expr(:throw_undef_if_not, :expected, false), + ReturnNode(), # unreachable # block 6 - Core.PhiCNode(Any[Core.SSAValue(5), Core.SSAValue(7), Core.SSAValue(9)]), # NULL, :a, :b - Core.PhiCNode(Any[Core.SSAValue(6)]), # NULL - Expr(:leave, Core.SSAValue(4)), + PhiCNode(Any[SSAValue(5), SSAValue(7), SSAValue(9)]), # NULL, :a, :b + PhiCNode(Any[SSAValue(6)]), # NULL + Expr(:pop_exception, SSAValue(4)), # block 7 - ReturnNode(Core.SSAValue(11)), + ReturnNode(SSAValue(12)), ] nstmts = length(src.code) - src.ssavaluetypes = Any[ Any for _ = 1:nstmts ] + src.ssavaluetypes = nstmts src.ssaflags = fill(UInt8(0x00), nstmts) - src.codelocs = fill(Int32(1), nstmts) - src.inferred = true + src.debuginfo = Core.DebugInfo(:none) Core.Compiler.verify_ir(Core.Compiler.inflate_ir(src)) global test29262 = true @test :a === @eval $m global test29262 = false @test :b === @eval $m + @test isempty(current_exceptions()) end - -# https://github.com/JuliaLang/julia/issues/47065 -# `Core.Compiler.sort!` should be able to handle a big list -let n = 1000 - ex = :(return 1) - for _ in 1:n - ex = :(rand() < .1 && $(ex)) - end - @eval global function f_1000_blocks() - $ex - return 0 - end -end -@test f_1000_blocks() == 0 diff --git a/test/compiler/invalidation.jl b/test/compiler/invalidation.jl index 848d475b85b69..76cf3cbdc0796 100644 --- a/test/compiler/invalidation.jl +++ b/test/compiler/invalidation.jl @@ -7,84 +7,28 @@ include("irutils.jl") using Test const CC = Core.Compiler -import Core: MethodInstance, CodeInstance -import .CC: WorldRange, WorldView -struct InvalidationTesterCache - dict::IdDict{MethodInstance,CodeInstance} -end -InvalidationTesterCache() = InvalidationTesterCache(IdDict{MethodInstance,CodeInstance}()) - -const INVALIDATION_TESTER_CACHE = InvalidationTesterCache() +struct InvalidationTesterToken end struct InvalidationTester <: CC.AbstractInterpreter - callback! world::UInt inf_params::CC.InferenceParams opt_params::CC.OptimizationParams inf_cache::Vector{CC.InferenceResult} - code_cache::InvalidationTesterCache - function InvalidationTester(callback! = nothing; + function InvalidationTester(; world::UInt = Base.get_world_counter(), inf_params::CC.InferenceParams = CC.InferenceParams(), opt_params::CC.OptimizationParams = CC.OptimizationParams(), - inf_cache::Vector{CC.InferenceResult} = CC.InferenceResult[], - code_cache::InvalidationTesterCache = INVALIDATION_TESTER_CACHE) - if callback! === nothing - callback! = function (replaced::MethodInstance) - # Core.println(replaced) # debug - delete!(code_cache.dict, replaced) - end - end - return new(callback!, world, inf_params, opt_params, inf_cache, code_cache) + inf_cache::Vector{CC.InferenceResult} = CC.InferenceResult[]) + return new(world, inf_params, opt_params, inf_cache) end end -struct InvalidationTesterCacheView - interp::InvalidationTester - dict::IdDict{MethodInstance,CodeInstance} -end - CC.InferenceParams(interp::InvalidationTester) = interp.inf_params CC.OptimizationParams(interp::InvalidationTester) = interp.opt_params -CC.get_world_counter(interp::InvalidationTester) = interp.world +CC.get_inference_world(interp::InvalidationTester) = interp.world CC.get_inference_cache(interp::InvalidationTester) = interp.inf_cache -CC.code_cache(interp::InvalidationTester) = WorldView(InvalidationTesterCacheView(interp, interp.code_cache.dict), WorldRange(interp.world)) -CC.get(wvc::WorldView{InvalidationTesterCacheView}, mi::MethodInstance, default) = get(wvc.cache.dict, mi, default) -CC.getindex(wvc::WorldView{InvalidationTesterCacheView}, mi::MethodInstance) = getindex(wvc.cache.dict, mi) -CC.haskey(wvc::WorldView{InvalidationTesterCacheView}, mi::MethodInstance) = haskey(wvc.cache.dict, mi) -function CC.setindex!(wvc::WorldView{InvalidationTesterCacheView}, ci::CodeInstance, mi::MethodInstance) - add_callback!(wvc.cache.interp.callback!, mi) - setindex!(wvc.cache.dict, ci, mi) -end - -function add_callback!(@nospecialize(callback!), mi::MethodInstance) - callback = function (replaced::MethodInstance, max_world, - seen::Base.IdSet{MethodInstance} = Base.IdSet{MethodInstance}()) - push!(seen, replaced) - callback!(replaced) - if isdefined(replaced, :backedges) - for item in replaced.backedges - isa(item, MethodInstance) || continue # might be `Type` object representing an `invoke` signature - mi = item - mi in seen && continue # otherwise fail into an infinite loop - var"#self#"(mi, max_world, seen) - end - end - return nothing - end - - if !isdefined(mi, :callbacks) - mi.callbacks = Any[callback] - else - callbacks = mi.callbacks::Vector{Any} - if !any(@nospecialize(cb)->cb===callback, callbacks) - push!(callbacks, callback) - end - end - return nothing -end - +CC.cache_owner(::InvalidationTester) = InvalidationTesterToken() # basic functionality test # ------------------------ @@ -96,33 +40,55 @@ basic_caller(x) = basic_callee(x) @test Base.return_types((Float64,); interp=InvalidationTester()) do x basic_caller(x) end |> only === Float64 -@test any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) - mi.def.name === :basic_callee + +let mi = Base.method_instance(basic_callee, (Float64,)) + ci = mi.cache + @test !isdefined(ci, :next) + @test ci.owner === InvalidationTesterToken() + @test ci.max_world == typemax(UInt) end -@test any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) - mi.def.name === :basic_caller + +let mi = Base.method_instance(basic_caller, (Float64,)) + ci = mi.cache + @test !isdefined(ci, :next) + @test ci.owner === InvalidationTesterToken() + @test ci.max_world == typemax(UInt) end # this redefinition below should invalidate the cache +const BASIC_CALLER_WORLD = Base.get_world_counter() basic_callee(x) = x, x -@test !any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) - mi.def.name === :basic_callee -end -@test !any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) - mi.def.name === :basic_caller +@test !isdefined(Base.method_instance(basic_callee, (Float64,)), :cache) +let mi = Base.method_instance(basic_caller, (Float64,)) + ci = mi.cache + @test !isdefined(ci, :next) + @test ci.owner === InvalidationTesterToken() + @test ci.max_world == BASIC_CALLER_WORLD end # re-run inference and check the result is updated (and new cache exists) @test Base.return_types((Float64,); interp=InvalidationTester()) do x basic_caller(x) end |> only === Tuple{Float64,Float64} -@test any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) - mi.def.name === :basic_callee +let mi = Base.method_instance(basic_callee, (Float64,)) + ci = mi.cache + @test !isdefined(ci, :next) + @test ci.owner === InvalidationTesterToken() + @test ci.max_world == typemax(UInt) end -@test any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) - mi.def.name === :basic_caller + +let mi = Base.method_instance(basic_caller, (Float64,)) + ci = mi.cache + @test isdefined(ci, :next) + @test ci.owner === InvalidationTesterToken() + @test ci.max_world == typemax(UInt) + ci = ci.next + @test !isdefined(ci, :next) + @test ci.owner === InvalidationTesterToken() + @test ci.max_world != typemax(UInt) end + # backedge optimization # --------------------- @@ -148,24 +114,49 @@ begin take!(GLOBAL_BUFFER) @test rt === Any @test any(iscall((src, pr48932_callee)), src.code) end - @test any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) - mi.def.name === :pr48932_callee + + let mi = only(Base.specializations(Base.only(Base.methods(pr48932_callee)))) + # Base.method_instance(pr48932_callee, (Any,)) + ci = mi.cache + @test isdefined(ci, :next) + @test ci.owner === InvalidationTesterToken() + @test ci.max_world == typemax(UInt) + + # In cache due to Base.return_types(pr48932_callee, (Any,)) + ci = ci.next + @test !isdefined(ci, :next) + @test ci.owner === nothing + @test ci.max_world == typemax(UInt) end - @test any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) - mi.def.name === :pr48932_caller + let mi = Base.method_instance(pr48932_caller, (Int,)) + ci = mi.cache + @test !isdefined(ci, :next) + @test ci.owner === InvalidationTesterToken() + @test ci.max_world == typemax(UInt) end + @test 42 == pr48932_caller(42) @test "42" == String(take!(GLOBAL_BUFFER)) # test that we didn't add the backedge from `pr48932_callee` to `pr48932_caller`: # this redefinition below should invalidate the cache of `pr48932_callee` but not that of `pr48932_caller` pr48932_callee(x) = (print(GLOBAL_BUFFER, x); nothing) - @test !any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) - mi.def.name === :pr48932_callee - end - @test any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) - mi.def.name === :pr48932_caller + + @test length(Base.methods(pr48932_callee)) == 2 + @test Base.only(Base.methods(pr48932_callee, Tuple{Any})) === first(Base.methods(pr48932_callee)) + @test isempty(Base.specializations(Base.only(Base.methods(pr48932_callee, Tuple{Any})))) + let mi = only(Base.specializations(Base.only(Base.methods(pr48932_caller)))) + # Base.method_instance(pr48932_callee, (Any,)) + ci = mi.cache + @test isdefined(ci, :next) + @test ci.owner === nothing + @test ci.max_world == typemax(UInt) + ci = ci.next + @test !isdefined(ci, :next) + @test ci.owner === InvalidationTesterToken() + @test ci.max_world == typemax(UInt) end + @test isnothing(pr48932_caller(42)) @test "42" == String(take!(GLOBAL_BUFFER)) end @@ -173,13 +164,13 @@ end # we can avoid adding backedge even if the callee's return type is not the top # when the return value is not used within the caller begin take!(GLOBAL_BUFFER) - pr48932_callee_inferrable(x) = (print(GLOBAL_BUFFER, x); Base.inferencebarrier(1)::Int) - pr48932_caller_unuse(x) = (pr48932_callee_inferrable(Base.inferencebarrier(x)); nothing) + pr48932_callee_inferable(x) = (print(GLOBAL_BUFFER, x); Base.inferencebarrier(1)::Int) + pr48932_caller_unuse(x) = (pr48932_callee_inferable(Base.inferencebarrier(x)); nothing) # assert that type and effects information inferred from `pr48932_callee(::Any)` are the top - let rt = only(Base.return_types(pr48932_callee_inferrable, (Any,))) + let rt = only(Base.return_types(pr48932_callee_inferable, (Any,))) @test rt === Int - effects = Base.infer_effects(pr48932_callee_inferrable, (Any,)) + effects = Base.infer_effects(pr48932_callee_inferable, (Any,)) @test Core.Compiler.Effects(effects) == Core.Compiler.Effects() end @@ -190,25 +181,43 @@ begin take!(GLOBAL_BUFFER) @inline pr48932_caller_unuse(x) end |> only @test rt === Nothing - @test any(iscall((src, pr48932_callee_inferrable)), src.code) + @test any(iscall((src, pr48932_callee_inferable)), src.code) end - @test any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) - mi.def.name === :pr48932_callee_inferrable + + let mi = only(Base.specializations(Base.only(Base.methods(pr48932_callee_inferable)))) + ci = mi.cache + @test isdefined(ci, :next) + @test ci.owner === InvalidationTesterToken() + @test ci.max_world == typemax(UInt) + ci = ci.next + @test !isdefined(ci, :next) + @test ci.owner === nothing + @test ci.max_world == typemax(UInt) end - @test any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) - mi.def.name === :pr48932_caller_unuse + let mi = Base.method_instance(pr48932_caller_unuse, (Int,)) + ci = mi.cache + @test !isdefined(ci, :next) + @test ci.owner === InvalidationTesterToken() + @test ci.max_world == typemax(UInt) end + @test isnothing(pr48932_caller_unuse(42)) @test "42" == String(take!(GLOBAL_BUFFER)) - # test that we didn't add the backedge from `pr48932_callee_inferrable` to `pr48932_caller_unuse`: - # this redefinition below should invalidate the cache of `pr48932_callee_inferrable` but not that of `pr48932_caller_unuse` - pr48932_callee_inferrable(x) = (print(GLOBAL_BUFFER, "foo"); x) - @test !any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) - mi.def.name === :pr48932_callee_inferrable - end - @test any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) - mi.def.name === :pr48932_caller_unuse + # test that we didn't add the backedge from `pr48932_callee_inferable` to `pr48932_caller_unuse`: + # this redefinition below should invalidate the cache of `pr48932_callee_inferable` but not that of `pr48932_caller_unuse` + pr48932_callee_inferable(x) = (print(GLOBAL_BUFFER, "foo"); x) + + @test isempty(Base.specializations(Base.only(Base.methods(pr48932_callee_inferable, Tuple{Any})))) + let mi = Base.method_instance(pr48932_caller_unuse, (Int,)) + ci = mi.cache + @test isdefined(ci, :next) + @test ci.owner === nothing + @test ci.max_world == typemax(UInt) + ci = ci.next + @test !isdefined(ci, :next) + @test ci.owner === InvalidationTesterToken() + @test ci.max_world == typemax(UInt) end @test isnothing(pr48932_caller_unuse(42)) @test "foo" == String(take!(GLOBAL_BUFFER)) @@ -234,24 +243,43 @@ begin take!(GLOBAL_BUFFER) @test rt === Any @test any(isinvoke(:pr48932_callee_inlined), src.code) end - @test any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) - mi.def.name === :pr48932_callee_inlined + + let mi = Base.method_instance(pr48932_callee_inlined, (Int,)) + ci = mi.cache + @test isdefined(ci, :next) + @test ci.owner === InvalidationTesterToken() + @test ci.max_world == typemax(UInt) + ci = ci.next + @test !isdefined(ci, :next) + @test ci.owner === nothing + @test ci.max_world == typemax(UInt) end - @test any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) - mi.def.name === :pr48932_caller_inlined + let mi = Base.method_instance(pr48932_caller_inlined, (Int,)) + ci = mi.cache + @test !isdefined(ci, :next) + @test ci.owner === InvalidationTesterToken() + @test ci.max_world == typemax(UInt) end + @test 42 == pr48932_caller_inlined(42) @test "42" == String(take!(GLOBAL_BUFFER)) # test that we added the backedge from `pr48932_callee_inlined` to `pr48932_caller_inlined`: # this redefinition below should invalidate the cache of `pr48932_callee_inlined` but not that of `pr48932_caller_inlined` @noinline pr48932_callee_inlined(@nospecialize x) = (print(GLOBAL_BUFFER, x); nothing) - @test !any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) - mi.def.name === :pr48932_callee_inlined - end - @test !any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) - mi.def.name === :pr48932_caller_inlined + + @test isempty(Base.specializations(Base.only(Base.methods(pr48932_callee_inlined, Tuple{Any})))) + let mi = Base.method_instance(pr48932_caller_inlined, (Int,)) + ci = mi.cache + @test isdefined(ci, :next) + @test ci.owner === nothing + @test ci.max_world != typemax(UInt) + ci = ci.next + @test !isdefined(ci, :next) + @test ci.owner === InvalidationTesterToken() + @test ci.max_world != typemax(UInt) end + @test isnothing(pr48932_caller_inlined(42)) @test "42" == String(take!(GLOBAL_BUFFER)) end diff --git a/test/compiler/irpasses.jl b/test/compiler/irpasses.jl index 225839d8e0cf3..b41241bf6a9a7 100644 --- a/test/compiler/irpasses.jl +++ b/test/compiler/irpasses.jl @@ -10,10 +10,7 @@ include("irutils.jl") # ======= ## Test that domsort doesn't mangle single-argument phis (#29262) -let m = Meta.@lower 1 + 1 - @assert Meta.isexpr(m, :thunk) - src = m.args[1]::CodeInfo - src.code = Any[ +let code = Any[ # block 1 Expr(:call, :opaque), GotoIfNot(Core.SSAValue(1), 10), @@ -31,13 +28,8 @@ let m = Meta.@lower 1 + 1 Core.PhiNode(Int32[2, 8], Any[0, Core.SSAValue(7)]), ReturnNode(Core.SSAValue(10)), ] - nstmts = length(src.code) - src.ssavaluetypes = nstmts - src.codelocs = fill(Int32(1), nstmts) - src.ssaflags = fill(Int32(0), nstmts) - ir = Core.Compiler.inflate_ir(src) - Core.Compiler.verify_ir(ir) - domtree = Core.Compiler.construct_domtree(ir.cfg.blocks) + ir = make_ircode(code) + domtree = Core.Compiler.construct_domtree(ir) ir = Core.Compiler.domsort_ssa!(ir, domtree) Core.Compiler.verify_ir(ir) phi = ir.stmts.stmt[3] @@ -45,10 +37,7 @@ let m = Meta.@lower 1 + 1 end # test that we don't stack-overflow in SNCA with large functions. -let m = Meta.@lower 1 + 1 - @assert Meta.isexpr(m, :thunk) - src = m.args[1]::CodeInfo - code = Any[] +let code = Any[] N = 2^15 for i in 1:2:N push!(code, Expr(:call, :opaque)) @@ -57,15 +46,8 @@ let m = Meta.@lower 1 + 1 # all goto here push!(code, Expr(:call, :opaque)) push!(code, ReturnNode(nothing)) - src.code = code - - nstmts = length(src.code) - src.ssavaluetypes = nstmts - src.codelocs = fill(Int32(1), nstmts) - src.ssaflags = fill(Int32(0), nstmts) - ir = Core.Compiler.inflate_ir(src) - Core.Compiler.verify_ir(ir) - domtree = Core.Compiler.construct_domtree(ir.cfg.blocks) + ir = make_ircode(code) + domtree = Core.Compiler.construct_domtree(ir) ir = Core.Compiler.domsort_ssa!(ir, domtree) Core.Compiler.verify_ir(ir) end @@ -73,7 +55,7 @@ end # SROA # ==== -import Core.Compiler: widenconst +using Core.Compiler: widenconst is_load_forwarded(src::CodeInfo) = !any(iscall((src, getfield)), src.code) is_scalar_replaced(src::CodeInfo) = @@ -503,7 +485,7 @@ function isdefined_elim() return arr end let src = code_typed1(isdefined_elim) - @test is_scalar_replaced(src) + @test count(isisdefined, src.code) == 0 end @test isdefined_elim() == Any[] @@ -695,10 +677,7 @@ end @test fully_eliminated(f_partial, Tuple{Float64}) # A SSAValue after the compaction line -let m = Meta.@lower 1 + 1 - @assert Meta.isexpr(m, :thunk) - src = m.args[1]::CodeInfo - src.code = Any[ +let code = Any[ # block 1 nothing, # block 2 @@ -717,7 +696,7 @@ let m = Meta.@lower 1 + 1 # block 5 ReturnNode(Core.SSAValue(2)), ] - src.ssavaluetypes = Any[ + ssavaluetypes = Any[ Nothing, Any, Bool, @@ -730,21 +709,14 @@ let m = Meta.@lower 1 + 1 Any, Any ] - nstmts = length(src.code) - src.codelocs = fill(one(Int32), nstmts) - src.ssaflags = fill(one(Int32), nstmts) - src.slotflags = fill(zero(UInt8), 3) - ir = Core.Compiler.inflate_ir(src) - @test Core.Compiler.verify_ir(ir) === nothing + slottypes = Any[Any, Any, Any] + ir = make_ircode(code; ssavaluetypes, slottypes) ir = @test_nowarn Core.Compiler.sroa_pass!(ir) @test Core.Compiler.verify_ir(ir) === nothing end # A lifted Core.ifelse with an eliminated branch (#50276) -let m = Meta.@lower 1 + 1 - @assert Meta.isexpr(m, :thunk) - src = m.args[1]::CodeInfo - src.code = Any[ +let code = Any[ # block 1 #= %1: =# Core.Argument(2), # block 2 @@ -768,7 +740,7 @@ let m = Meta.@lower 1 + 1 # block 5 #= %11: =# ReturnNode(false), ] - src.ssavaluetypes = Any[ + ssavaluetypes = Any[ Any, Union{Missing, Bool}, Any, @@ -781,12 +753,8 @@ let m = Meta.@lower 1 + 1 Any, Any ] - nstmts = length(src.code) - src.codelocs = fill(one(Int32), nstmts) - src.ssaflags = fill(one(Int32), nstmts) - src.slotflags = fill(zero(UInt8), 3) - ir = Core.Compiler.inflate_ir(src) - @test Core.Compiler.verify_ir(ir) === nothing + slottypes = Any[Any, Any, Any] + ir = make_ircode(code; ssavaluetypes, slottypes) ir = @test_nowarn Core.Compiler.sroa_pass!(ir) @test Core.Compiler.verify_ir(ir) === nothing end @@ -809,11 +777,8 @@ let src = code_typed(gcd, Tuple{Int, Int})[1].first Core.Compiler.verify_ir(ir) end -let m = Meta.@lower 1 + 1 - # Test that CFG simplify combines redundant basic blocks - @assert Meta.isexpr(m, :thunk) - src = m.args[1]::CodeInfo - src.code = Any[ +let # Test that CFG simplify combines redundant basic blocks + code = Any[ Core.Compiler.GotoNode(2), Core.Compiler.GotoNode(3), Core.Compiler.GotoNode(4), @@ -822,12 +787,7 @@ let m = Meta.@lower 1 + 1 Core.Compiler.GotoNode(7), ReturnNode(2) ] - nstmts = length(src.code) - src.ssavaluetypes = nstmts - src.codelocs = fill(Int32(1), nstmts) - src.ssaflags = fill(Int32(0), nstmts) - ir = Core.Compiler.inflate_ir(src) - Core.Compiler.verify_ir(ir) + ir = make_ircode(code) ir = Core.Compiler.cfg_simplify!(ir) Core.Compiler.verify_ir(ir) ir = Core.Compiler.compact!(ir) @@ -844,7 +804,7 @@ function each_stmt_a_bb(stmts, preds, succs) append!(ir.stmts.stmt, stmts) empty!(ir.stmts.type); append!(ir.stmts.type, [Nothing for _ = 1:length(stmts)]) empty!(ir.stmts.flag); append!(ir.stmts.flag, [0x0 for _ = 1:length(stmts)]) - empty!(ir.stmts.line); append!(ir.stmts.line, [Int32(0) for _ = 1:length(stmts)]) + empty!(ir.stmts.line); append!(ir.stmts.line, [Int32(0) for _ = 1:3length(stmts)]) empty!(ir.stmts.info); append!(ir.stmts.info, [NoCallInfo() for _ = 1:length(stmts)]) empty!(ir.cfg.blocks); append!(ir.cfg.blocks, [BasicBlock(StmtRange(i, i), preds[i], succs[i]) for i = 1:length(stmts)]) empty!(ir.cfg.index); append!(ir.cfg.index, [i for i = 2:length(stmts)]) @@ -923,11 +883,8 @@ let stmts = [ @test Set(term.val for term in terms if isa(term, ReturnNode)) == Set([1,2]) end -let m = Meta.@lower 1 + 1 - # Test that CFG simplify doesn't mess up when chaining past return blocks - @assert Meta.isexpr(m, :thunk) - src = m.args[1]::CodeInfo - src.code = Any[ +let # Test that CFG simplify doesn't mess up when chaining past return blocks + code = Any[ Core.Compiler.GotoIfNot(Core.Compiler.Argument(2), 3), Core.Compiler.GotoNode(4), ReturnNode(1), @@ -939,12 +896,7 @@ let m = Meta.@lower 1 + 1 ReturnNode(2), ReturnNode(3) ] - nstmts = length(src.code) - src.ssavaluetypes = nstmts - src.codelocs = fill(Int32(1), nstmts) - src.ssaflags = fill(Int32(0), nstmts) - ir = Core.Compiler.inflate_ir(src) - Core.Compiler.verify_ir(ir) + ir = make_ircode(code) ir = Core.Compiler.cfg_simplify!(ir) Core.Compiler.verify_ir(ir) @test length(ir.cfg.blocks) == 5 @@ -952,12 +904,9 @@ let m = Meta.@lower 1 + 1 @test isa(ret_2, Core.Compiler.ReturnNode) && ret_2.val == 2 end -let m = Meta.@lower 1 + 1 - # Test that CFG simplify doesn't try to merge every block in a loop into +let # Test that CFG simplify doesn't try to merge every block in a loop into # its predecessor - @assert Meta.isexpr(m, :thunk) - src = m.args[1]::CodeInfo - src.code = Any[ + code = Any[ # Block 1 Core.Compiler.GotoNode(2), # Block 2 @@ -965,12 +914,7 @@ let m = Meta.@lower 1 + 1 # Block 3 Core.Compiler.GotoNode(1) ] - nstmts = length(src.code) - src.ssavaluetypes = nstmts - src.codelocs = fill(Int32(1), nstmts) - src.ssaflags = fill(Int32(0), nstmts) - ir = Core.Compiler.inflate_ir(src) - Core.Compiler.verify_ir(ir) + ir = make_ircode(code) ir = Core.Compiler.cfg_simplify!(ir) Core.Compiler.verify_ir(ir) @test length(ir.cfg.blocks) == 1 @@ -1145,9 +1089,10 @@ end let # effect-freeness computation for array allocation # should eliminate dead allocations - good_dims = @static Int === Int64 ? (1:10) : (1:8) - Ns = @static Int === Int64 ? (1:10) : (1:8) + good_dims = [1, 2, 3, 4, 10] + Ns = [1, 2, 3, 4, 10] for dim = good_dims, N = Ns + Int64(dim)^N > typemax(Int) && continue dims = ntuple(i->dim, N) @test @eval fully_eliminated() do Array{Int,$N}(undef, $(dims...)) @@ -1157,14 +1102,14 @@ let # effect-freeness computation for array allocation # shouldn't eliminate erroneous dead allocations bad_dims = [-1, typemax(Int)] - for dim in bad_dims, N in 1:10 + for dim in bad_dims, N in [1, 2, 3, 4, 10], T in Any[Int, Union{Missing,Nothing}, Nothing, Any] dims = ntuple(i->dim, N) @test @eval !fully_eliminated() do - Array{Int,$N}(undef, $(dims...)) + Array{$T,$N}(undef, $(dims...)) nothing end - @test_throws "invalid Array" @eval let - Array{Int,$N}(undef, $(dims...)) + @test_throws "invalid " @eval let + Array{$T,$N}(undef, $(dims...)) nothing end end @@ -1441,15 +1386,21 @@ end # ifelse folding @test Core.Compiler.is_removable_if_unused(Base.infer_effects(exp, (Float64,))) @test !Core.Compiler.is_inlineable(code_typed1(exp, (Float64,))) -fully_eliminated(; retval=Core.Argument(2)) do x::Float64 +@test fully_eliminated(; retval=Core.Argument(2)) do x::Float64 return Core.ifelse(true, x, exp(x)) end -fully_eliminated(; retval=Core.Argument(2)) do x::Float64 +@test fully_eliminated(; retval=Core.Argument(2)) do x::Float64 return ifelse(true, x, exp(x)) # the optimization should be applied to post-inlining IR too end -fully_eliminated(; retval=Core.Argument(2)) do x::Float64 +@test fully_eliminated(; retval=Core.Argument(2)) do x::Float64 return ifelse(isa(x, Float64), x, exp(x)) end +func_coreifelse(c, x) = Core.ifelse(c, x, x) +func_ifelse(c, x) = ifelse(c, x, x) +@test fully_eliminated(func_coreifelse, (Bool,Float64); retval=Core.Argument(3)) +@test !fully_eliminated(func_coreifelse, (Any,Float64)) +@test fully_eliminated(func_ifelse, (Bool,Float64); retval=Core.Argument(3)) +@test !fully_eliminated(func_ifelse, (Any,Float64)) # PhiC fixup of compact! with cfg modification @inline function big_dead_throw_catch() @@ -1521,10 +1472,7 @@ end @test isnothing(f_with_early_try_catch_exit()) # Issue #51144 - UndefRefError during compaction -let m = Meta.@lower 1 + 1 - @assert Meta.isexpr(m, :thunk) - src = m.args[1]::CodeInfo - src.code = Any[ +let code = Any[ # block 1 → 2, 3 #= %1: =# Expr(:(=), Core.SlotNumber(4), Core.Argument(2)), #= %2: =# Expr(:call, :(===), Core.SlotNumber(4), nothing), @@ -1539,14 +1487,8 @@ let m = Meta.@lower 1 + 1 # block 5 #= %8: =# ReturnNode(nothing), # Must not insert a π-node here ] - nstmts = length(src.code) - nslots = 4 - src.ssavaluetypes = nstmts - src.codelocs = fill(Int32(1), nstmts) - src.ssaflags = fill(Int32(0), nstmts) - src.slotflags = fill(0, nslots) - src.slottypes = Any[Any, Union{Bool, Nothing}, Bool, Union{Bool, Nothing}] - ir = Core.Compiler.inflate_ir(src) + slottypes = Any[Any, Union{Bool, Nothing}, Bool, Union{Bool, Nothing}] + src = make_codeinfo(code; slottypes) mi = ccall(:jl_new_method_instance_uninit, Ref{Core.MethodInstance}, ()); mi.specTypes = Tuple{} @@ -1567,3 +1509,375 @@ let m = Meta.@lower 1 + 1 Core.Compiler.verify_ir(ir) end + +function f_with_merge_to_entry_block() + while true + i = @noinline rand(Int) + if @noinline isodd(i) + return i + end + end +end + +let (ir, _) = only(Base.code_ircode(f_with_merge_to_entry_block)) + Core.Compiler.verify_ir(ir) + ir = Core.Compiler.cfg_simplify!(ir) + Core.Compiler.verify_ir(ir) +end + +# Test that CFG simplify doesn't leave an un-renamed SSA Value +let # Test that CFG simplify doesn't try to merge every block in a loop into + # its predecessor + code = Any[ + # Block 1 + GotoIfNot(Argument(1), 3), + # Block 2 + GotoNode(5), + # Block 3 + Expr(:call, Base.inferencebarrier, 1), + GotoNode(6), + # Block 4 + Expr(:call, Base.inferencebarrier, 2), # fallthrough + # Block 5 + PhiNode(Int32[4, 5], Any[SSAValue(3), SSAValue(5)]), + ReturnNode(1) + ] + ir = make_ircode(code) + ir = Core.Compiler.cfg_simplify!(ir) + Core.Compiler.verify_ir(ir) + @test length(ir.cfg.blocks) == 4 +end + +# JET.test_opt(Core.Compiler.cfg_simplify!, (Core.Compiler.IRCode,)) + +# Test support for Core.OptimizedGenerics.KeyValue protocol +function persistent_dict_elim() + a = Base.PersistentDict(:a => 1) + return a[:a] +end + +# Ideally we would be able to fully eliminate this, +# but currently this would require an extra round of constprop +@test_broken fully_eliminated(persistent_dict_elim) +@test code_typed(persistent_dict_elim)[1][1].code[end] == Core.ReturnNode(1) + +function persistent_dict_elim_multiple() + a = Base.PersistentDict(:a => 1) + b = Base.PersistentDict(a, :b => 2) + return b[:a] +end +@test_broken fully_eliminated(persistent_dict_elim_multiple) +let code = code_typed(persistent_dict_elim_multiple)[1][1].code + @test count(x->isexpr(x, :invoke), code) == 0 + @test code[end] == Core.ReturnNode(1) +end + +function persistent_dict_elim_multiple_phi(c::Bool) + if c + a = Base.PersistentDict(:a => 1) + else + a = Base.PersistentDict(:a => 1) + end + b = Base.PersistentDict(a, :b => 2) + return b[:a] +end +@test_broken fully_eliminated(persistent_dict_elim_multiple_phi) +@test code_typed(persistent_dict_elim_multiple_phi)[1][1].code[end] == Core.ReturnNode(1) + +function persistent_dict_elim_multiple_phi2(c::Bool) + z = Base.inferencebarrier(1)::Int + if c + a = Base.PersistentDict(:a => z) + else + a = Base.PersistentDict(:a => z) + end + b = Base.PersistentDict(a, :b => 2) + return b[:a] +end +@test persistent_dict_elim_multiple_phi2(true) == 1 + +# Test CFG simplify with try/catch blocks +let code = Any[ + # Block 1 + GotoIfNot(Argument(1), 5), + # Block 2 + EnterNode(4), + # Block 3 + Expr(:leave, SSAValue(2)), + # Block 4 + GotoNode(5), + # Block 5 + ReturnNode(1) + ] + ir = make_ircode(code) + ir = Core.Compiler.cfg_simplify!(ir) + Core.Compiler.verify_ir(ir) + @test length(ir.cfg.blocks) <= 5 +end + +# Test CFG simplify with single predecessor phi node +let code = Any[ + # Block 1 + GotoNode(3), + # Block 2 + nothing, + # Block 3 + Expr(:call, Base.inferencebarrier, 1), + GotoNode(5), + # Block 4 + PhiNode(Int32[4], Any[SSAValue(3)]), + ReturnNode(SSAValue(5)) + ] + ir = make_ircode(code) + ir = Core.Compiler.cfg_simplify!(ir) + Core.Compiler.verify_ir(ir) + @test length(ir.cfg.blocks) <= 2 + ir = Core.Compiler.compact!(ir) + @test length(ir.stmts) <= 3 + @test (ir[SSAValue(length(ir.stmts))][:stmt]::ReturnNode).val !== nothing +end + +let code = Any[ + Expr(:call, Base.inferencebarrier, Argument(1)), # ::Bool + Expr(:call, Core.tuple, 1), # ::Tuple{Int} + Expr(:call, Core.tuple, 1.0), # ::Tuple{Float64} + Expr(:call, Core.ifelse, SSAValue(1), SSAValue(2), SSAValue(3)), # ::Tuple{Int} (e.g. from inlining) + Expr(:call, Core.getfield, SSAValue(4), 1), # ::Int + ReturnNode(SSAValue(5)) +] + try + argtypes = Any[Bool] + ssavaluetypes = Any[Bool, Tuple{Int}, Tuple{Float64}, Tuple{Int}, Int, Any] + ir = make_ircode(code; slottypes=argtypes, ssavaluetypes) + Core.Compiler.verify_ir(ir) + Core.Compiler.__set_check_ssa_counts(true) + ir = Core.Compiler.sroa_pass!(ir) + Core.Compiler.verify_ir(ir) + finally + Core.Compiler.__set_check_ssa_counts(false) + end +end + +# Test SROA all_same on NewNode +let code = Any[ + # Block 1 + Expr(:call, tuple, Argument(1)), + GotoIfNot(Argument(4), 5), + # Block 2 + Expr(:call, tuple, Argument(2)), + GotoIfNot(Argument(4), 9), + # Block 3 + PhiNode(Int32[2, 4], Any[SSAValue(1), SSAValue(3)]), + Expr(:call, getfield, SSAValue(5), 1), + Expr(:call, tuple, SSAValue(6), Argument(2)), # ::Tuple{Int, Int} + Expr(:call, tuple, SSAValue(7), Argument(3)), # ::Tuple{Tuple{Int, Int}, Int} + # Block 4 + PhiNode(Int32[4, 8], Any[nothing, SSAValue(8)]), + Expr(:call, Core.Intrinsics.not_int, Argument(4)), + GotoIfNot(SSAValue(10), 13), + # Block 5 + ReturnNode(1), + # Block 6 + PiNode(SSAValue(9), Tuple{Tuple{Int, Int}, Int}), + Expr(:call, getfield, SSAValue(13), 1), + Expr(:call, getfield, SSAValue(14), 1), + ReturnNode(SSAValue(15)) +] + + argtypes = Any[Int, Int, Int, Bool] + ssavaluetypes = Any[Tuple{Int}, Any, Tuple{Int}, Any, Tuple{Int}, Int, Tuple{Int, Int}, Tuple{Tuple{Int, Int}, Int}, + Union{Nothing, Tuple{Tuple{Int, Int}, Int}}, Bool, Any, Any, + Tuple{Tuple{Int, Int}, Int}, + Tuple{Int, Int}, Int, Any] + ir = make_ircode(code; slottypes=argtypes, ssavaluetypes) + Core.Compiler.verify_ir(ir) + ir = Core.Compiler.sroa_pass!(ir) + Core.Compiler.verify_ir(ir) + ir = Core.Compiler.compact!(ir) + Core.Compiler.verify_ir(ir) +end + +# Test correctness of current_scope folding +@eval function scope_folding() + $(Expr(:tryfinally, + Expr(:block, + Expr(:tryfinally, :(), :(), 2), + :(return Core.current_scope())), + :(), 1)) +end + +@eval function scope_folding_opt() + $(Expr(:tryfinally, + Expr(:block, + Expr(:tryfinally, :(), :(), :(Base.inferencebarrier(2))), + :(return Core.current_scope())), + :(), :(Base.inferencebarrier(1)))) +end + +@test scope_folding() == 1 +@test scope_folding_opt() == 1 +@test_broken fully_eliminated(scope_folding) +@test_broken fully_eliminated(scope_folding_opt) + +# Function that happened to have lots of sroa that +# happened to trigger a bad case in the renamer. We +# just want to check this doesn't crash in inference. +function f52610() + slots_dict = IdDict() + for () in Base.inferencebarrier(1) + for x in 1 + if Base.inferencebarrier(true) + slots_dict[x] = 0 + end + end + end + return nothing +end +@test code_typed(f52610)[1][2] === Nothing + +# Issue #52703 +@eval function f52703() + try + $(Expr(:tryfinally, + Expr(:block, + Expr(:tryfinally, :(), :(), 2), + :(return Base.inferencebarrier(Core.current_scope)()::Int)), + :(), 1)) + catch + return 1 + end + return 0 +end +@test code_typed(f52703)[1][2] === Int + +# Issue #52858 - compaction gets confused by pending node +let code = Any[ + # Block 1 + GotoIfNot(true, 6), + # Block 2 + Expr(:call, println, 1), + Expr(:call, Base.inferencebarrier, true), + GotoIfNot(SSAValue(3), 6), + # Block 3 + nothing, + # Block 4 + PhiNode(Int32[1, 4, 5], Any[1, 2, 3]), + ReturnNode(SSAValue(6)) +] + ir = make_ircode(code) + Core.Compiler.insert_node!(ir, SSAValue(5), + Core.Compiler.NewInstruction( + Expr(:call, println, 2), Nothing, Int32(1)), + #= attach_after = =# true) + ir = Core.Compiler.compact!(ir, true) + @test Core.Compiler.verify_ir(ir) === nothing + @test count(x->isa(x, GotoIfNot), ir.stmts.stmt) == 1 +end + +# Issue #52857 - Affinity of sroa definedness check +let code = Any[ + Expr(:new, ImmutableRef{Any}), + GotoIfNot(Argument(1), 4), + Expr(:call, GlobalRef(Base, :getfield), SSAValue(1), 1), # Will throw + ReturnNode(1) +] + ir = make_ircode(code; ssavaluetypes = Any[ImmutableRef{Any}, Any, Any, Any], slottypes=Any[Bool], verify=true) + ir = Core.Compiler.sroa_pass!(ir) + @test Core.Compiler.verify_ir(ir) === nothing + @test !any(iscall((ir, getfield)), ir.stmts.stmt) + @test length(ir.cfg.blocks[end].stmts) == 1 +end + +# https://github.com/JuliaLang/julia/issues/47065 +# `Core.Compiler.sort!` should be able to handle a big list +let n = 1000 + ex = :(return 1) + for _ in 1:n + ex = :(rand() < .1 && $(ex)) + end + @eval global function f_1000_blocks() + $ex + return 0 + end +end +@test f_1000_blocks() == 0 + +# https://github.com/JuliaLang/julia/issues/53521 +# Incorrect scope counting in :leave +using Base.ScopedValues +function f53521() + VALUE = ScopedValue(1) + @with VALUE => 2 begin + for i = 1 + @with VALUE => 3 begin + try + foo() + catch + nothing + end + end + end + end +end +@test code_typed(f53521)[1][2] === Nothing + +# Test that adce_pass! sets Refined on PhiNode values +let code = Any[ + # Basic Block 1 + GotoIfNot(false, 3) + # Basic Block 2 + nothing + # Basic Block 3 + PhiNode(Int32[1, 2], Any[1.0, 1]) + ReturnNode(Core.SSAValue(3)) +] + ir = make_ircode(code; ssavaluetypes=Any[Any, Nothing, Union{Int64, Float64}, Any]) + (ir, made_changes) = Core.Compiler.adce_pass!(ir) + @test made_changes + @test (ir[Core.SSAValue(length(ir.stmts))][:flag] & Core.Compiler.IR_FLAG_REFINED) != 0 +end + +# JuliaLang/julia#52991: statements that may not :terminate should not be deleted +@noinline Base.@assume_effects :effect_free :nothrow function issue52991(n) + local s = 0 + try + while true + yield() + if n - rand(1:10) > 0 + s += 1 + else + break + end + end + catch + end + return s +end +@test !Core.Compiler.is_removable_if_unused(Base.infer_effects(issue52991, (Int,))) +let src = code_typed1((Int,)) do x + issue52991(x) + nothing + end + @test count(isinvoke(:issue52991), src.code) == 1 +end +let t = @async begin + issue52991(11) # this call never terminates + nothing + end + sleep(1) + if istaskdone(t) + ok = false + else + ok = true + schedule(t, InterruptException(); error=true) + end + @test ok +end + +# JuliaLang/julia47664 +@test !fully_eliminated() do + any(isone, Iterators.repeated(0)) +end +@test !fully_eliminated() do + all(iszero, Iterators.repeated(0)) +end diff --git a/test/compiler/irutils.jl b/test/compiler/irutils.jl index d1c1431587b1f..c11444d8daabc 100644 --- a/test/compiler/irutils.jl +++ b/test/compiler/irutils.jl @@ -17,6 +17,7 @@ end isnew(@nospecialize x) = isexpr(x, :new) issplatnew(@nospecialize x) = isexpr(x, :splatnew) isreturn(@nospecialize x) = isa(x, ReturnNode) && isdefined(x, :val) +isisdefined(@nospecialize x) = isexpr(x, :isdefined) # check if `x` is a dynamic call of a given function iscall(y) = @nospecialize(x) -> iscall(y, x) @@ -55,3 +56,44 @@ end macro fully_eliminated(ex0...) return gen_call_with_extracted_types_and_kwargs(__module__, :fully_eliminated, ex0) end + +let m = Meta.@lower 1 + 1 + @assert Meta.isexpr(m, :thunk) + orig_src = m.args[1]::CodeInfo + global function make_codeinfo(code::Vector{Any}; + ssavaluetypes::Union{Nothing,Vector{Any}}=nothing, + slottypes::Union{Nothing,Vector{Any}}=nothing, + slotnames::Union{Nothing,Vector{Symbol}}=nothing) + src = copy(orig_src) + src.code = code + nstmts = length(src.code) + if ssavaluetypes === nothing + src.ssavaluetypes = nstmts + else + src.ssavaluetypes = ssavaluetypes + end + src.debuginfo = Core.DebugInfo(:none) + src.ssaflags = fill(zero(UInt32), nstmts) + if slottypes !== nothing + src.slottypes = slottypes + src.slotflags = fill(zero(UInt8), length(slottypes)) + end + if slotnames !== nothing + src.slotnames = slotnames + end + return src + end + global function make_ircode(code::Vector{Any}; + slottypes::Union{Nothing,Vector{Any}}=nothing, + verify::Bool=true, + kwargs...) + src = make_codeinfo(code; slottypes, kwargs...) + if slottypes !== nothing + ir = Core.Compiler.inflate_ir(src, slottypes) + else + ir = Core.Compiler.inflate_ir(src) + end + verify && Core.Compiler.verify_ir(ir) + return ir + end +end diff --git a/test/compiler/newinterp.jl b/test/compiler/newinterp.jl index 56a68f2a09545..d86a1831def79 100644 --- a/test/compiler/newinterp.jl +++ b/test/compiler/newinterp.jl @@ -1,45 +1,64 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +# TODO set up a version who defines new interpreter with persistent cache? + """ - @newinterp NewInterpreter + @newinterp NewInterpreter [ephemeral_cache::Bool=false] Defines new `NewInterpreter <: AbstractInterpreter` whose cache is separated from the native code cache, satisfying the minimum interface requirements. + +When the `ephemeral_cache=true` option is specified, `NewInterpreter` will hold +`CodeInstance` in an ephemeral non-integrated cache, rather than in the integrated +`Core.Compiler.InternalCodeCache`. +Keep in mind that ephemeral cache lacks support for invalidation and doesn't persist across +sessions. However it is an usual Julia object of the type `code_cache::IdDict{MethodInstance,CodeInstance}`, +making it easier for debugging and inspecting the compiler behavior. """ -macro newinterp(InterpName) +macro newinterp(InterpName, ephemeral_cache::Bool=false) + cache_token = QuoteNode(gensym(string(InterpName, "CacheToken"))) InterpCacheName = esc(Symbol(string(InterpName, "Cache"))) InterpName = esc(InterpName) C = Core CC = Core.Compiler quote + $(ephemeral_cache && quote struct $InterpCacheName dict::IdDict{$C.MethodInstance,$C.CodeInstance} end $InterpCacheName() = $InterpCacheName(IdDict{$C.MethodInstance,$C.CodeInstance}()) + end) struct $InterpName <: $CC.AbstractInterpreter meta # additional information world::UInt inf_params::$CC.InferenceParams opt_params::$CC.OptimizationParams inf_cache::Vector{$CC.InferenceResult} - code_cache::$InterpCacheName + $(ephemeral_cache && :(code_cache::$InterpCacheName)) function $InterpName(meta = nothing; world::UInt = Base.get_world_counter(), inf_params::$CC.InferenceParams = $CC.InferenceParams(), opt_params::$CC.OptimizationParams = $CC.OptimizationParams(), inf_cache::Vector{$CC.InferenceResult} = $CC.InferenceResult[], - code_cache::$InterpCacheName = $InterpCacheName()) - return new(meta, world, inf_params, opt_params, inf_cache, code_cache) + $(ephemeral_cache ? + Expr(:kw, :(code_cache::$InterpCacheName), :($InterpCacheName())) : + Expr(:kw, :_, :nothing))) + return $(ephemeral_cache ? + :(new(meta, world, inf_params, opt_params, inf_cache, code_cache)) : + :(new(meta, world, inf_params, opt_params, inf_cache))) end end $CC.InferenceParams(interp::$InterpName) = interp.inf_params $CC.OptimizationParams(interp::$InterpName) = interp.opt_params - $CC.get_world_counter(interp::$InterpName) = interp.world + $CC.get_inference_world(interp::$InterpName) = interp.world $CC.get_inference_cache(interp::$InterpName) = interp.inf_cache + $CC.cache_owner(::$InterpName) = $cache_token + $(ephemeral_cache && quote $CC.code_cache(interp::$InterpName) = $CC.WorldView(interp.code_cache, $CC.WorldRange(interp.world)) $CC.get(wvc::$CC.WorldView{$InterpCacheName}, mi::$C.MethodInstance, default) = get(wvc.cache.dict, mi, default) $CC.getindex(wvc::$CC.WorldView{$InterpCacheName}, mi::$C.MethodInstance) = getindex(wvc.cache.dict, mi) $CC.haskey(wvc::$CC.WorldView{$InterpCacheName}, mi::$C.MethodInstance) = haskey(wvc.cache.dict, mi) $CC.setindex!(wvc::$CC.WorldView{$InterpCacheName}, ci::$C.CodeInstance, mi::$C.MethodInstance) = setindex!(wvc.cache.dict, ci, mi) + end) end end diff --git a/test/compiler/ssair.jl b/test/compiler/ssair.jl index 0e7c2159d4851..9ff4d3d8a4220 100644 --- a/test/compiler/ssair.jl +++ b/test/compiler/ssair.jl @@ -9,16 +9,6 @@ include("irutils.jl") make_bb(preds, succs) = BasicBlock(Compiler.StmtRange(0, 0), preds, succs) -function make_ci(code) - ci = (Meta.@lower 1 + 1).args[1] - ci.code = code - nstmts = length(ci.code) - ci.ssavaluetypes = nstmts - ci.codelocs = fill(Int32(1), nstmts) - ci.ssaflags = fill(Int32(0), nstmts) - return ci -end - # TODO: this test is broken #let code = Any[ # GotoIfNot(SlotNumber(2), 4), @@ -38,7 +28,6 @@ end # false, false, false, false # )) # -# NullLineInfo = Core.LineInfoNode(Main, Symbol(""), Symbol(""), Int32(0), Int32(0)) # Compiler.run_passes(ci, 1, [NullLineInfo]) # # XXX: missing @test #end @@ -73,8 +62,8 @@ let cfg = CFG(BasicBlock[ @test dfs.from_pre[dfs.to_parent_pre[dfs.to_pre[5]]] == 4 let correct_idoms = Compiler.naive_idoms(cfg.blocks), correct_pidoms = Compiler.naive_idoms(cfg.blocks, true) - @test Compiler.construct_domtree(cfg.blocks).idoms_bb == correct_idoms - @test Compiler.construct_postdomtree(cfg.blocks).idoms_bb == correct_pidoms + @test Compiler.construct_domtree(cfg).idoms_bb == correct_idoms + @test Compiler.construct_postdomtree(cfg).idoms_bb == correct_pidoms # For completeness, reverse the order of pred/succ in the CFG and verify # the answer doesn't change (it does change the which node is chosen # as the semi-dominator, since it changes the DFS numbering). @@ -85,25 +74,22 @@ let cfg = CFG(BasicBlock[ c && (blocks[4] = make_bb(reverse(blocks[4].preds), blocks[4].succs)) d && (blocks[5] = make_bb(reverse(blocks[5].preds), blocks[5].succs)) cfg′ = CFG(blocks, cfg.index) - @test Compiler.construct_domtree(cfg′.blocks).idoms_bb == correct_idoms - @test Compiler.construct_postdomtree(cfg′.blocks).idoms_bb == correct_pidoms + @test Compiler.construct_domtree(cfg′).idoms_bb == correct_idoms + @test Compiler.construct_postdomtree(cfg′).idoms_bb == correct_pidoms end end end end -# test >: -let - f(a, b) = a >: b - code_typed(f, Tuple{Any, Any}) - # XXX: missing @test +# test code execution with the default compile-mode +module CompilerExecTest +include("interpreter_exec.jl") end -for compile in ("min", "yes") - cmd = `$(Base.julia_cmd()) --compile=$compile interpreter_exec.jl` - if !success(pipeline(Cmd(cmd, dir=@__DIR__); stdout=stdout, stderr=stderr)) - error("Interpreter test failed, cmd : $cmd") - end +# test code execution with the interpreter mode (compile=min) +module InterpreterExecTest +Base.Experimental.@compiler_options compile=min +include("interpreter_exec.jl") end # PR #32145 @@ -116,8 +102,9 @@ let cfg = CFG(BasicBlock[ make_bb([0, 1, 2] , [5] ), # 0 predecessor should be preserved make_bb([2, 3] , [] ), ], Int[]) - insts = Compiler.InstructionStream([], [], Any[], Int32[], UInt8[]) - ir = Compiler.IRCode(insts, cfg, Core.LineInfoNode[], Any[], Expr[], Compiler.VarState[]) + insts = Compiler.InstructionStream([], [], Core.Compiler.CallInfo[], Int32[], UInt32[]) + di = Compiler.DebugInfoStream(insts.line) + ir = Compiler.IRCode(insts, cfg, di, Any[], Expr[], Compiler.VarState[]) compact = Compiler.IncrementalCompact(ir, true) @test length(compact.cfg_transform.result_bbs) == 4 && 0 in compact.cfg_transform.result_bbs[3].preds end @@ -143,7 +130,7 @@ end @test f32579(0, false) === false # Test for bug caused by renaming blocks improperly, related to PR #32145 -let ci = make_ci([ +let code = Any[ # block 1 Expr(:boundscheck), Core.Compiler.GotoIfNot(SSAValue(1), 6), @@ -162,34 +149,34 @@ let ci = make_ci([ Core.Compiler.ReturnNode(Core.SSAValue(9)), # block 6 Core.Compiler.ReturnNode(Core.SSAValue(9)) - ]) - ir = Core.Compiler.inflate_ir(ci) + ] + ir = make_ircode(code) ir = Core.Compiler.compact!(ir, true) @test Core.Compiler.verify_ir(ir) === nothing end # Test that the verifier doesn't choke on cglobals (which aren't linearized) -let ci = make_ci([ +let code = Any[ Expr(:call, GlobalRef(Main, :cglobal), Expr(:call, Core.tuple, :(:c)), Nothing), Core.Compiler.ReturnNode() - ]) - ir = Core.Compiler.inflate_ir(ci) + ] + ir = make_ircode(code) @test Core.Compiler.verify_ir(ir) === nothing end # Test that GlobalRef in value position is non-canonical -let ci = make_ci([ +let code = Any[ Expr(:call, GlobalRef(Main, :something_not_defined_please)) ReturnNode(SSAValue(1)) - ]) - ir = Core.Compiler.inflate_ir(ci) + ] + ir = make_ircode(code; verify=false) ir = Core.Compiler.compact!(ir, true) @test_throws ErrorException Core.Compiler.verify_ir(ir, false) end # Issue #29107 -let ci = make_ci([ +let code = Any[ # Block 1 Core.Compiler.GotoNode(6), # Block 2 @@ -204,8 +191,8 @@ let ci = make_ci([ Core.Compiler.GotoNode(2), # Block 3 Core.Compiler.ReturnNode(1000) - ]) - ir = Core.Compiler.inflate_ir(ci) + ] + ir = make_ircode(code) ir = Core.Compiler.compact!(ir, true) # Make sure that if there is a call to `something` (block 2 should be # removed entirely with working DCE), it doesn't use any SSA values that @@ -220,9 +207,8 @@ let ci = make_ci([ end end -# Make sure dead blocks that are removed are not still referenced in live phi -# nodes -let ci = make_ci([ +# Make sure dead blocks that are removed are not still referenced in live phi nodes +let code = Any[ # Block 1 Core.Compiler.GotoNode(3), # Block 2 (no predecessors) @@ -230,18 +216,102 @@ let ci = make_ci([ # Block 3 Core.PhiNode(Int32[1, 2], Any[100, 200]), Core.Compiler.ReturnNode(Core.SSAValue(3)) - ]) - ir = Core.Compiler.inflate_ir(ci) + ] + ir = make_ircode(code; verify=false) ir = Core.Compiler.compact!(ir, true) @test Core.Compiler.verify_ir(ir) == nothing end # issue #37919 -let ci = code_lowered(()->@isdefined(_not_def_37919_), ())[1] +let ci = only(code_lowered(()->@isdefined(_not_def_37919_), ())) ir = Core.Compiler.inflate_ir(ci) @test Core.Compiler.verify_ir(ir) === nothing end +let code = Any[ + # block 1 + GotoIfNot(Argument(2), 4) + # block 2 + Expr(:call, throw, "potential throw") + ReturnNode() # unreachable + # block 3 + ReturnNode(Argument(3)) + ] + ir = make_ircode(code; slottypes=Any[Any,Bool,Int]) + visited = BitSet() + @test !Core.Compiler.visit_conditional_successors(ir, #=bb=#1) do succ::Int + push!(visited, succ) + return false + end + @test 2 ∈ visited + @test 3 ∈ visited + oc = Core.OpaqueClosure(ir) + @test oc(false, 1) == 1 + @test_throws "potential throw" oc(true, 1) +end + +let code = Any[ + # block 1 + GotoIfNot(Argument(2), 3) + # block 2 + ReturnNode(Argument(3)) + # block 3 + Expr(:call, throw, "potential throw") + ReturnNode() # unreachable + ] + ir = make_ircode(code; slottypes=Any[Any,Bool,Int]) + visited = BitSet() + @test !Core.Compiler.visit_conditional_successors(ir, #=bb=#1) do succ::Int + push!(visited, succ) + return false + end + @test 2 ∈ visited + @test 3 ∈ visited + oc = Core.OpaqueClosure(ir) + @test oc(true, 1) == 1 + @test_throws "potential throw" oc(false, 1) +end + +let code = Any[ + # block 1 + GotoIfNot(Argument(2), 5) + # block 2 + GotoNode(3) + # block 3 + Expr(:call, throw, "potential throw") + ReturnNode() + # block 4 + Expr(:call, Core.Intrinsics.add_int, Argument(3), Argument(4)) + GotoNode(7) + # block 5 + ReturnNode(SSAValue(5)) + ] + ir = make_ircode(code; slottypes=Any[Any,Bool,Int,Int]) + visited = BitSet() + @test !Core.Compiler.visit_conditional_successors(ir, #=bb=#1) do succ::Int + push!(visited, succ) + return false + end + @test 2 ∈ visited + @test 3 ∈ visited + @test 4 ∈ visited + @test 5 ∈ visited + oc = Core.OpaqueClosure(ir) + @test oc(false, 1, 1) == 2 + @test_throws "potential throw" oc(true, 1, 1) + + let buf = IOBuffer() + oc = Core.OpaqueClosure(ir; slotnames=Symbol[:ocfunc, :x, :y, :z]) + try + oc(true, 1, 1) + catch + Base.show_backtrace(buf, catch_backtrace()) + end + s = String(take!(buf)) + @test occursin("(x::Bool, y::$Int, z::$Int)", s) + end +end + # Test dynamic update of domtree with edge insertions and deletions in the # following CFG: # @@ -267,7 +337,7 @@ let cfg = CFG(BasicBlock[ make_bb([2, 6], []), make_bb([4], [5, 3]), ], Int[]) - domtree = Compiler.construct_domtree(cfg.blocks) + domtree = Compiler.construct_domtree(cfg) @test domtree.dfs_tree.to_pre == [1, 2, 4, 5, 3, 6] @test domtree.idoms_bb == Compiler.naive_idoms(cfg.blocks) == [0, 1, 1, 3, 1, 4] @@ -338,7 +408,7 @@ let # https://github.com/JuliaLang/julia/issues/42258 code_typed(Core.Compiler.setindex!, (Core.Compiler.UseRef,Core.Compiler.NewSSAValue); optimize=true) """ cmd = `$(Base.julia_cmd()) -g 2 -e $code` - stderr = Base.BufferStream() + stderr = IOBuffer() @test success(pipeline(Cmd(cmd); stdout, stderr)) @test readchomp(stderr) == "" end @@ -461,7 +531,7 @@ let ir = Base.code_ircode((Bool,Any)) do c, x end end # domination analysis - domtree = Core.Compiler.construct_domtree(ir.cfg.blocks) + domtree = Core.Compiler.construct_domtree(ir) @test Core.Compiler.dominates(domtree, 1, 2) @test Core.Compiler.dominates(domtree, 1, 3) @test Core.Compiler.dominates(domtree, 1, 4) @@ -472,7 +542,7 @@ let ir = Base.code_ircode((Bool,Any)) do c, x end end # post domination analysis - post_domtree = Core.Compiler.construct_postdomtree(ir.cfg.blocks) + post_domtree = Core.Compiler.construct_postdomtree(ir) @test Core.Compiler.postdominates(post_domtree, 4, 1) @test Core.Compiler.postdominates(post_domtree, 4, 2) @test Core.Compiler.postdominates(post_domtree, 4, 3) @@ -550,25 +620,27 @@ import Core.Compiler: NewInstruction, insert_node! let ir = Base.code_ircode((Int,Int); optimize_until="inlining") do a, b a^b end |> only |> first - @test length(ir.stmts) == 2 - @test Meta.isexpr(ir.stmts[1][:stmt], :invoke) + nstmts = length(ir.stmts) + invoke_idx = findfirst(@nospecialize(stmt)->Meta.isexpr(stmt, :invoke), ir.stmts.stmt) + @test invoke !== nothing - newssa = insert_node!(ir, SSAValue(1), NewInstruction(Expr(:call, println, SSAValue(1)), Nothing), #=attach_after=#true) + invoke_ssa = SSAValue(invoke_idx) + newssa = insert_node!(ir, invoke_ssa, NewInstruction(Expr(:call, println, invoke_ssa), Nothing), #=attach_after=#true) newssa = insert_node!(ir, newssa, NewInstruction(Expr(:call, println, newssa), Nothing), #=attach_after=#true) ir = Core.Compiler.compact!(ir) - @test length(ir.stmts) == 4 - @test Meta.isexpr(ir.stmts[1][:stmt], :invoke) - call1 = ir.stmts[2][:stmt] + @test length(ir.stmts) == nstmts + 2 + @test Meta.isexpr(ir.stmts[invoke_idx][:stmt], :invoke) + call1 = ir.stmts[invoke_idx+1][:stmt] @test iscall((ir,println), call1) - @test call1.args[2] === SSAValue(1) - call2 = ir.stmts[3][:stmt] + @test call1.args[2] === invoke_ssa + call2 = ir.stmts[invoke_idx+2][:stmt] @test iscall((ir,println), call2) - @test call2.args[2] === SSAValue(2) + @test call2.args[2] === SSAValue(invoke_idx+1) end # Issue #50379 - insert_node!(::IncrementalCompact, ...) at end of basic block -let ci = make_ci([ +let code = Any[ # block 1 #= %1: =# Expr(:boundscheck), #= %2: =# Core.Compiler.GotoIfNot(SSAValue(1), 4), @@ -577,8 +649,8 @@ let ci = make_ci([ # block 3 #= %4: =# Core.PhiNode(), #= %5: =# Core.Compiler.ReturnNode(), - ]) - ir = Core.Compiler.inflate_ir(ci) + ] + ir = make_ircode(code) # Insert another call at end of "block 2" compact = Core.Compiler.IncrementalCompact(ir) @@ -596,11 +668,11 @@ let ci = make_ci([ end # compact constant PiNode -let ci = make_ci(Any[ +let code = Any[ PiNode(0.0, Const(0.0)) ReturnNode(SSAValue(1)) - ]) - ir = Core.Compiler.inflate_ir(ci) + ] + ir = make_ircode(code) ir = Core.Compiler.compact!(ir) @test fully_eliminated(ir) end @@ -609,26 +681,23 @@ end let ir = Base.code_ircode((Int,Int); optimize_until="inlining") do a, b a^b end |> only |> first - invoke_idx = findfirst(ir.stmts.stmt) do @nospecialize(x) - Meta.isexpr(x, :invoke) - end + invoke_idx = findfirst(@nospecialize(stmt)->Meta.isexpr(stmt, :invoke), ir.stmts.stmt) @test invoke_idx !== nothing invoke_expr = ir.stmts.stmt[invoke_idx] + invoke_ssa = SSAValue(invoke_idx) # effect-ful node let compact = Core.Compiler.IncrementalCompact(Core.Compiler.copy(ir)) - insert_node!(compact, SSAValue(1), NewInstruction(Expr(:call, println, SSAValue(1)), Nothing), #=attach_after=#true) + insert_node!(compact, invoke_ssa, NewInstruction(Expr(:call, println, invoke_ssa), Nothing), #=attach_after=#true) state = Core.Compiler.iterate(compact) while state !== nothing state = Core.Compiler.iterate(compact, state[2]) end ir = Core.Compiler.finish(compact) - new_invoke_idx = findfirst(ir.stmts.stmt) do @nospecialize(x) - x == invoke_expr - end + new_invoke_idx = findfirst(@nospecialize(stmt)->stmt==invoke_expr, ir.stmts.stmt) @test new_invoke_idx !== nothing - new_call_idx = findfirst(ir.stmts.stmt) do @nospecialize(x) - iscall((ir,println), x) && x.args[2] === SSAValue(invoke_idx) + new_call_idx = findfirst(ir.stmts.stmt) do @nospecialize(stmt) + iscall((ir,println), stmt) && stmt.args[2] === SSAValue(new_invoke_idx) end @test new_call_idx !== nothing @test new_call_idx == new_invoke_idx+1 @@ -636,7 +705,7 @@ let ir = Base.code_ircode((Int,Int); optimize_until="inlining") do a, b # effect-free node let compact = Core.Compiler.IncrementalCompact(Core.Compiler.copy(ir)) - insert_node!(compact, SSAValue(1), NewInstruction(Expr(:call, GlobalRef(Base, :add_int), SSAValue(1), SSAValue(1)), Int), #=attach_after=#true) + insert_node!(compact, invoke_ssa, NewInstruction(Expr(:call, GlobalRef(Base, :add_int), invoke_ssa, invoke_ssa), Int), #=attach_after=#true) state = Core.Compiler.iterate(compact) while state !== nothing state = Core.Compiler.iterate(compact, state[2]) @@ -644,12 +713,10 @@ let ir = Base.code_ircode((Int,Int); optimize_until="inlining") do a, b ir = Core.Compiler.finish(compact) ir = Core.Compiler.finish(compact) - new_invoke_idx = findfirst(ir.stmts.stmt) do @nospecialize(x) - x == invoke_expr - end + new_invoke_idx = findfirst(@nospecialize(stmt)->stmt==invoke_expr, ir.stmts.stmt) @test new_invoke_idx !== nothing new_call_idx = findfirst(ir.stmts.stmt) do @nospecialize(x) - iscall((ir,Base.add_int), x) && x.args[2] === SSAValue(invoke_idx) + iscall((ir,Base.add_int), x) && x.args[2] === SSAValue(new_invoke_idx) end @test new_call_idx === nothing # should be deleted during the compaction end @@ -686,3 +753,82 @@ end end end end + +# Test that things don't break if one branch of the frontend PhiNode becomes unreachable +const global_error_switch_const1::Bool = false +function gen_unreachable_phinode_edge1(world::UInt, source, args...) + ci = make_codeinfo(Any[ + # block 1 + GlobalRef(@__MODULE__, :global_error_switch_const1), + GotoIfNot(SSAValue(1), 4), + # block 2 + Expr(:call, identity, Argument(3)), + # block 3 + PhiNode(Int32[2, 3], Any[Argument(2), SSAValue(3)]), + ReturnNode(SSAValue(4)) + ]; slottypes=Any[Any,Int,Int]) + ci.slotnames = Symbol[:var"#self#", :x, :y] + return ci +end +@eval function f_unreachable_phinode_edge1(x, y) + $(Expr(:meta, :generated, gen_unreachable_phinode_edge1)) + $(Expr(:meta, :generated_only)) + #= no body =# +end +@test f_unreachable_phinode_edge1(1, 2) == 1 + +const global_error_switch_const2::Bool = true +function gen_unreachable_phinode_edge2(world::UInt, source, args...) + ci = make_codeinfo(Any[ + # block 1 + GlobalRef(@__MODULE__, :global_error_switch_const2), + GotoIfNot(SSAValue(1), 4), + # block 2 + Expr(:call, identity, Argument(3)), + # block 3 + PhiNode(Int32[2, 3], Any[Argument(2), SSAValue(3)]), + ReturnNode(SSAValue(4)) + ]; slottypes=Any[Any,Int,Int]) + ci.slotnames = Symbol[:var"#self#", :x, :y] + return ci +end +@eval function f_unreachable_phinode_edge2(x, y) + $(Expr(:meta, :generated, gen_unreachable_phinode_edge2)) + $(Expr(:meta, :generated_only)) + #= no body =# +end +@test f_unreachable_phinode_edge2(1, 2) == 2 + +global global_error_switch::Bool = true +function gen_must_throw_phinode_edge(world::UInt, source, _) + ci = make_codeinfo(Any[ + # block 1 + GlobalRef(@__MODULE__, :global_error_switch), + GotoIfNot(SSAValue(1), 4), + # block 2 + Expr(:call, error, "This error is expected"), + # block 3 + PhiNode(Int32[2, 3], Any[1, 2]), + ReturnNode(SSAValue(4)) + ]; slottypes=Any[Any]) + ci.slotnames = Symbol[:var"#self#"] + return ci +end +@eval function f_must_throw_phinode_edge() + $(Expr(:meta, :generated, gen_must_throw_phinode_edge)) + $(Expr(:meta, :generated_only)) + #= no body =# +end +let ir = first(only(Base.code_ircode(f_must_throw_phinode_edge))) + @test !any(@nospecialize(x)->isa(x,PhiNode), ir.stmts.stmt) +end +@test_throws ErrorException f_must_throw_phinode_edge() +global global_error_switch = false +@test f_must_throw_phinode_edge() == 1 + +# Test roundtrip of debuginfo compression +let cl = Int32[32, 1, 1, 1000, 240, 230] + str = ccall(:jl_compress_codelocs, Any, (Int32, Any, Int), 378, cl, 2)::String; + cl2 = ccall(:jl_uncompress_codelocs, Any, (Any, Int), str, 2) + @test cl == cl2 +end diff --git a/test/compiler/tarjan.jl b/test/compiler/tarjan.jl new file mode 100644 index 0000000000000..11c6b68e58b1b --- /dev/null +++ b/test/compiler/tarjan.jl @@ -0,0 +1,167 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +using Core.Compiler: CFGReachability, DomTree, CFG, BasicBlock, StmtRange, dominates, + bb_unreachable, kill_edge! + +const CC = Core.Compiler + +function reachable(g::CFG, a::Int, b::Int; domtree=nothing) + visited = BitVector(false for _ = 1:length(g.blocks)) + worklist = Int[a] + while !isempty(worklist) + node = pop!(worklist) + node == b && return true + visited[node] = true + for child in g.blocks[node].succs + if domtree !== nothing && dominates(domtree, child, node) + continue # if provided `domtree`, ignore back-edges + end + + !visited[child] && push!(worklist, child) + end + end + return false +end + +function rand_cfg(V, E) + bbs = [BasicBlock(StmtRange(0,0), Int[], Int[]) for _ = 1:V] + + reachable = BitVector(false for _ = 1:V) + reachable[1] = true + + targets = BitVector(false for _ = 1:V) + + for _ = 1:E + # Pick any source (with at least 1 missing edge) + source, dest = 0, 0 + while true + source = rand(findall(reachable)) + for v = 1:V + targets[v] = !in(v, bbs[source].succs) + end + any(targets) && break + end + + # Pick any new target for source + dest = rand(findall(targets)) + + # Add edge to graph + push!(bbs[source].succs, dest) + push!(bbs[dest].preds, source) + + reachable[dest] = true + end + + return CFG(bbs, zeros(Int, V + 1)) +end + +function get_random_edge(cfg::CFG, V) + has_edge = [length(cfg.blocks[bb].succs) != 0 for bb in 1:V] + source = rand(findall(has_edge)) + target = rand(cfg.blocks[source].succs) + return source, target +end + +# Generate a random CFG with the requested number of vertices and edges, then simulate +# `deletions` edge removals and verify that reachability is maintained correctly. +# +# If `all_checks` is true, verify internal data structures as well with O(E^2) checks. +function test_reachability(V, E; deletions = 2E ÷ 3, all_checks=false) + + function check_reachability(reachability, cfg, domtree, all_checks) + for i = 1:V + # All nodes should be reported as unreachable only if we cannot reach them from BB #1. + @test reachable(cfg, 1, i) == !bb_unreachable(reachability, i) + + # All predecessors of a reachable block should be reachable. + if !bb_unreachable(reachability, i) + for pred in cfg.blocks[i].preds + @test !bb_unreachable(reachability, pred) + end + end + end + + if all_checks # checks for internal data structures - O(E^2) + + # Nodes should be mutually reachable iff they are in the same SCC. + scc = reachability.scc + reachable_nodes = BitSet(v for v = 1:V if !bb_unreachable(reachability, v)) + for i ∈ reachable_nodes + for j ∈ reachable_nodes + @test (reachable(cfg, i, j; domtree) && reachable(cfg, j, i; domtree)) == (scc[i] == scc[j]) + end + end + + # Nodes in any non-trivial SCC (ignoring backedges) should be marked irreducible. + irreducible = reachability.irreducible + for i ∈ reachable_nodes + in_nontrivial_scc = any(v != i && scc[v] == scc[i] for v = 1:V) + @test CC.getindex(irreducible, i) == in_nontrivial_scc + end + end + end + + cfg = rand_cfg(V, E) + domtree = Core.Compiler.construct_domtree(cfg) + reachability = CFGReachability(cfg, domtree) + check_reachability(reachability, cfg, domtree, all_checks) + + # track the reachable blocks/edges so that we can verify callbacks below + blocks = Set{Int}() + edges = Set{Tuple{Int,Int}}() + for bb in 1:V + !bb_unreachable(reachability, bb) && push!(blocks, bb) + for succ in cfg.blocks[bb].succs + push!(edges, (bb, succ)) + end + end + + killed_edges = Tuple{Int,Int}[] + killed_blocks = Int[] + for k = 1:deletions + length(blocks) == 1 && break # no more reachable blocks + + from, to = get_random_edge(cfg, V) + kill_edge!(reachability, cfg, from, to, + (from::Int, to::Int) -> push!(killed_edges, (from, to)), + (bb::Int) -> push!(killed_blocks, bb), + ) + + # If these nodes are still reachable, to and from edges should have been removed. + @test !reachable(cfg, 1, from) || !in(to, cfg.blocks[from].succs) + @test !reachable(cfg, 1, to) || !in(from, cfg.blocks[to].preds) + + check_reachability(reachability, cfg, domtree, all_checks) + + for bb in 1:V + if bb_unreachable(reachability, bb) && in(bb, blocks) + # If the block changed from reachable -> unreachable, we should have gotten a callback. + @test bb in killed_blocks + delete!(blocks, bb) + end + end + for (from, to) in edges + if !in(from, cfg.blocks[to].preds) && !bb_unreachable(reachability, to) + # If the edge changed from reachable -> unreachable and feeds into a reachable BasicBlock, + # we should have gotten a callback. + @test (from, to) in killed_edges + delete!(edges, (from, to)) + end + end + + empty!(killed_edges) + empty!(killed_blocks) + end +end + +@testset "CFGReachability tests" begin + test_reachability(1, 0; all_checks=true) + + test_reachability(10, 15; all_checks=true) + test_reachability(10, 15; all_checks=true) + test_reachability(10, 15; all_checks=true) + + test_reachability(100, 150; all_checks=false) + test_reachability(100, 150; all_checks=false) + test_reachability(100, 1000; all_checks=false) +end diff --git a/test/copy.jl b/test/copy.jl index 633beee5f2af3..11cfbb9f14d76 100644 --- a/test/copy.jl +++ b/test/copy.jl @@ -213,11 +213,13 @@ end @testset "copying CodeInfo" begin _testfunc() = nothing ci,_ = code_typed(_testfunc, ())[1] - ci.edges = [_testfunc] + if isdefined(ci, :edges) + ci.edges = [_testfunc] - ci2 = copy(ci) - # Test that edges are not shared - @test ci2.edges !== ci.edges + ci2 = copy(ci) + # Test that edges are not shared + @test ci2.edges !== ci.edges + end end @testset "issue #34025" begin diff --git a/test/core.jl b/test/core.jl index af9a896de59f8..ed1e1fc6757d2 100644 --- a/test/core.jl +++ b/test/core.jl @@ -14,15 +14,17 @@ include("testenv.jl") # sanity tests that our built-in types are marked correctly for const fields for (T, c) in ( (Core.CodeInfo, []), - (Core.CodeInstance, [:def, :rettype, :rettype_const, :ipo_purity_bits, :argescapes]), + (Core.CodeInstance, [:def, :owner, :rettype, :exctype, :rettype_const, :ipo_purity_bits, :analysis_results]), (Core.Method, [#=:name, :module, :file, :line, :primary_world, :sig, :slot_syms, :external_mt, :nargs, :called, :nospecialize, :nkw, :isva, :is_for_opaque_closure, :constprop=#]), (Core.MethodInstance, [#=:def, :specTypes, :sparam_vals=#]), (Core.MethodTable, [:module]), - (Core.TypeMapEntry, [:sig, :simplesig, :guardsigs, :min_world, :max_world, :func, :isleafsig, :issimplesig, :va]), + (Core.TypeMapEntry, [:sig, :simplesig, :guardsigs, :func, :isleafsig, :issimplesig, :va]), (Core.TypeMapLevel, []), (Core.TypeName, [:name, :module, :names, :wrapper, :mt, :hash, :n_uninitialized, :flags]), (DataType, [:name, :super, :parameters, :instance, :hash]), (TypeVar, [:name, :ub, :lb]), + (Core.Memory, [:length, :ptr]), + (Core.GenericMemoryRef, [:mem, :ptr_or_offset]), ) @test Set((fieldname(T, i) for i in 1:fieldcount(T) if isconst(T, i))) == Set(c) end @@ -30,14 +32,16 @@ end # sanity tests that our built-in types are marked correctly for atomic fields for (T, c) in ( (Core.CodeInfo, []), - (Core.CodeInstance, [:next, :inferred, :purity_bits, :invoke, :specptr, :precompile]), - (Core.Method, []), + (Core.CodeInstance, [:next, :min_world, :max_world, :inferred, :debuginfo, :purity_bits, :invoke, :specptr, :specsigflags, :precompile]), + (Core.Method, [:primary_world, :deleted_world]), (Core.MethodInstance, [:uninferred, :cache, :precompiled]), (Core.MethodTable, [:defs, :leafcache, :cache, :max_args]), - (Core.TypeMapEntry, [:next]), + (Core.TypeMapEntry, [:next, :min_world, :max_world]), (Core.TypeMapLevel, [:arg1, :targ, :name1, :tname, :list, :any]), (Core.TypeName, [:cache, :linearcache]), (DataType, [:types, :layout]), + (Core.Memory, []), + (Core.GenericMemoryRef, []), ) @test Set((fieldname(T, i) for i in 1:fieldcount(T) if Base.isfieldatomic(T, i))) == Set(c) end @@ -107,6 +111,21 @@ let abcd = ABCDconst(1, 2, 3, 4) abcd.d = nothing) @test (1, 2, "not constant", 4) === (abcd.a, abcd.b, abcd.c, abcd.d) end +# Issue #52686 +struct A52686{T} end +struct B52686{T, S} + a::A52686{<:T} +end +function func52686() + @eval begin + struct A52686{T} end + struct B52686{T, S} + a::A52686{<:T} + end + end + return true +end +@test func52686() # test `===` handling null pointer in struct #44712 struct N44712 @@ -220,8 +239,8 @@ k11840(::Type{Union{Tuple{Int32}, Tuple{Int64}}}) = '2' # issue #20511 f20511(x::DataType) = 0 f20511(x) = 1 -Type{Integer} # cache this -@test f20511(Union{Integer,T} where T <: Unsigned) == 1 +Type{AbstractSet} # cache this +@test f20511(Union{AbstractSet,Set{T}} where T) == 1 # join @test typejoin(Int8,Int16) === Signed @@ -548,7 +567,7 @@ function i18408() return (x -> i) end let f = i18408() - @test_throws UndefVarError(:i) f(0) + @test_throws UndefVarError(:i, :local) f(0) end # issue #23558 @@ -608,7 +627,7 @@ begin global f7234_cnt += -10000 end end -@test_throws UndefVarError(:glob_x2) f7234_a() +@test_throws UndefVarError(:glob_x2, :local) f7234_a() @test f7234_cnt == 1 begin global glob_x2 = 24 @@ -618,7 +637,7 @@ begin global f7234_cnt += -10000 end end -@test_throws UndefVarError(:glob_x2) f7234_b() +@test_throws UndefVarError(:glob_x2, :local) f7234_b() @test f7234_cnt == 2 # globals can accessed if declared for i = 1:2 @@ -733,11 +752,11 @@ function f21900() global f21900_cnt += -1000 nothing end -@test_throws UndefVarError(:x_global_undefined_error) f21900() +@test_throws UndefVarError(:x_global_undefined_error, @__MODULE__) f21900() @test f21900_cnt == 1 # use @eval so this runs as a toplevel scope block -@test_throws UndefVarError(:foo21900) @eval begin +@test_throws UndefVarError(:foo21900, @__MODULE__) @eval begin for i21900 = 1:10 local bar21900 for j21900 = 1:10 @@ -750,7 +769,7 @@ end @test !@isdefined(foo21900) @test !@isdefined(bar21900) bar21900 = 0 -@test_throws UndefVarError(:foo21900) @eval begin +@test_throws UndefVarError(:foo21900, @__MODULE__) @eval begin for i21900 = 1:10 global bar21900 for j21900 = 1:10 @@ -1161,6 +1180,10 @@ let A = [1] @test x == 1 end +# Make sure that `Module` is not resolved to `Core.Module` during sysimg generation +# so that users can define their own binding named `Module` in Main. +@test !Base.isbindingresolved(Main, :Module) + # Module() constructor @test names(Module(:anonymous), all = true, imported = true) == [:anonymous] @test names(Module(:anonymous, false), all = true, imported = true) == [:anonymous] @@ -1441,6 +1464,9 @@ let @test unsafe_load(p2) == 101 unsafe_store!(p2, 909, 3) @test a2 == [101,102,909] + # test for issue 51954 + @test pointer(a.ref.mem)===pointer(a) + @test pointer(a.ref.mem,2)===pointer(a,2) end @test unsafe_pointer_to_objref(ccall(:jl_call1, Ptr{Cvoid}, (Any,Any), @@ -2594,7 +2620,7 @@ struct D14919 <: Function; end for f in (:Any, :Function, :(Core.Builtin), :(Union{Nothing, Type}), :(Union{typeof(+), Type}), :(Union{typeof(+), typeof(-)}), :(Base.Callable)) @test_throws ErrorException("Method dispatch is unimplemented currently for this method signature") @eval (::$f)() = 1 end -for f in (:(Core.arrayref), :((::typeof(Core.arrayref))), :((::Core.IntrinsicFunction))) +for f in (:(Core.getfield), :((::typeof(Core.getfield))), :((::Core.IntrinsicFunction))) @test_throws ErrorException("cannot add methods to a builtin function") @eval $f() = 1 end @@ -4169,7 +4195,7 @@ let foo(x::Union{T, Nothing}, y::Union{T, Nothing}) where {T} = 1 end let foo(x::Union{T, Nothing}, y::Union{T, Nothing}) where {T} = T @test foo(1, nothing) === Int - @test_throws UndefVarError(:T) foo(nothing, nothing) + @test_throws UndefVarError(:T, :static_parameter) foo(nothing, nothing) end module TestMacroGlobalFunction @@ -4223,14 +4249,14 @@ foo9677(x::Array) = invoke(foo9677, Tuple{AbstractArray}, x) # issue #6846 f6846() = (please6846; 2) -@test_throws UndefVarError(:please6846) f6846() +@test_throws UndefVarError(:please6846, @__MODULE__) f6846() module M6846 macro f() return esc(:(please6846; 2)) end end -@test_throws UndefVarError(:please6846) @M6846.f() +@test_throws UndefVarError(:please6846, @__MODULE__) @M6846.f() # issue #14758 @test isa(@eval(f14758(; $([]...)) = ()), Function) @@ -4334,6 +4360,7 @@ function f15180(x::T) where T end @test map(f15180(1), [1,2]) == [(Int,1),(Int,1)] +using Base: _growbeg!, _deletebeg!, _growend!, _deleteend! struct ValueWrapper vpadding::NTuple{2,VecElement{UInt}} value @@ -4342,43 +4369,44 @@ end Base.convert(::Type{ValueWrapper}, x) = ValueWrapper(x) for T in (Any, ValueWrapper) let ary = Vector{T}(undef, 10) - check_undef_and_fill(ary, rng) = for i in rng - @test !isassigned(ary, i) + check_undef_and_fill(ary, rng) = all(i -> begin + isassigned(ary, i) && return false ary[i] = (Float64(i), i) # some non-cached content - @test isassigned(ary, i) - end + isassigned(ary, i) || return false + return true + end, rng) # Check if the memory is initially zerod and fill it with value # to check if these values are not reused later. - check_undef_and_fill(ary, 1:10) + @test check_undef_and_fill(ary, 1:10) # Check if the memory grown at the end are zerod - ccall(:jl_array_grow_end, Cvoid, (Any, Csize_t), ary, 10) - check_undef_and_fill(ary, 11:20) + _growend!(ary, 10) + @test check_undef_and_fill(ary, 11:20) # Make sure the content of the memory deleted at the end are not reused - ccall(:jl_array_del_end, Cvoid, (Any, Csize_t), ary, 5) - ccall(:jl_array_grow_end, Cvoid, (Any, Csize_t), ary, 5) - check_undef_and_fill(ary, 16:20) + _deleteend!(ary, 5) + _growend!(ary, 5) + @test check_undef_and_fill(ary, 16:20) # Now check grow/del_end ary = Vector{T}(undef, 1010) - check_undef_and_fill(ary, 1:1010) + @test check_undef_and_fill(ary, 1:1010) # This del_beg should move the buffer - ccall(:jl_array_del_beg, Cvoid, (Any, Csize_t), ary, 1000) - ccall(:jl_array_grow_beg, Cvoid, (Any, Csize_t), ary, 1000) - check_undef_and_fill(ary, 1:1000) + _deletebeg!(ary, 1000) + _growbeg!(ary, 1000) + @test check_undef_and_fill(ary, 1:1000) ary = Vector{T}(undef, 1010) - check_undef_and_fill(ary, 1:1010) + @test check_undef_and_fill(ary, 1:1010) # This del_beg should not move the buffer - ccall(:jl_array_del_beg, Cvoid, (Any, Csize_t), ary, 10) - ccall(:jl_array_grow_beg, Cvoid, (Any, Csize_t), ary, 10) - check_undef_and_fill(ary, 1:10) + _deletebeg!(ary, 10) + _growbeg!(ary, 10) + @test check_undef_and_fill(ary, 1:10) ary = Vector{T}(undef, 1010) - check_undef_and_fill(ary, 1:1010) - ccall(:jl_array_grow_end, Cvoid, (Any, Csize_t), ary, 10) - check_undef_and_fill(ary, 1011:1020) - ccall(:jl_array_del_end, Cvoid, (Any, Csize_t), ary, 10) - ccall(:jl_array_grow_beg, Cvoid, (Any, Csize_t), ary, 10) - check_undef_and_fill(ary, 1:10) + @test check_undef_and_fill(ary, 1:1010) + _growend!(ary, 10) + @test check_undef_and_fill(ary, 1011:1020) + _deleteend!(ary, 10) + _growbeg!(ary, 10) + @test check_undef_and_fill(ary, 1:10) # Make sure newly malloc'd buffers are filled with 0 # test this for a few different sizes since we need to make sure @@ -4391,33 +4419,51 @@ for T in (Any, ValueWrapper) GC.gc() GC.gc() GC.gc() - ccall(:jl_array_grow_beg, Cvoid, (Any, Csize_t), ary, 4) - ccall(:jl_array_del_beg, Cvoid, (Any, Csize_t), ary, 4) - ccall(:jl_array_grow_end, Cvoid, (Any, Csize_t), ary, n) - ccall(:jl_array_grow_beg, Cvoid, (Any, Csize_t), ary, 4) - check_undef_and_fill(ary, 1:(2n + 4)) + _growbeg!(ary, 4) + _deletebeg!(ary, 4) + _growend!(ary, n) + _growbeg!(ary, 4) + @test check_undef_and_fill(ary, 1:(2n + 4)) end ary = Vector{T}(undef, 100) - ccall(:jl_array_grow_end, Cvoid, (Any, Csize_t), ary, 10000) + _growend!(ary, 10000) ary[:] = 1:length(ary) - ccall(:jl_array_del_beg, Cvoid, (Any, Csize_t), ary, 10000) + _deletebeg!(ary, 10000) # grow on the back until a buffer reallocation happens cur_ptr = pointer(ary) while cur_ptr == pointer(ary) len = length(ary) - ccall(:jl_array_grow_end, Cvoid, (Any, Csize_t), ary, 10) - for i in (len + 1):(len + 10) - @test !isassigned(ary, i) - end + _growend!(ary, 10) + result = @test all(i -> !isassigned(ary, i), (len + 1):(len + 10)) + result isa Test.Pass || break end - ary = Vector{T}(undef, 100) - ary[:] = 1:length(ary) - ccall(:jl_array_grow_at, Cvoid, (Any, Csize_t, Csize_t), ary, 50, 10) - for i in 51:60 - @test !isassigned(ary, i) - end + # growat when copy into start of same buffer + ary = Vector{T}(undef, 10) + ary[:] = 1:10 + pushfirst!(ary, 0) + Base._growat!(ary, 3, 5) + @test all(i -> !isassigned(ary, i), 3:7) + @test all(i -> isassigned(ary, i), 8:length(ary)) + @test all(i -> isassigned(ary, i), 1:2) + + # growat when copy into end of same buffer + ary = Vector{T}(undef, 10) + ary[:] = 1:10 + push!(ary, 11) + Base._growat!(ary, 6, 10) + @test all(i -> !isassigned(ary, i), 6:15) + @test all(i -> isassigned(ary, i), 16:length(ary)) + @test all(i -> isassigned(ary, i), 1:5) + + # growat when copy to new buffer + ary = Vector{T}(undef, 10) + ary[:] = 1:10 + Base._growat!(ary, 6, 10) + @test all(i -> !isassigned(ary, i), 6:15) + @test all(i -> isassigned(ary, i), 16:length(ary)) + @test all(i -> isassigned(ary, i), 1:5) end end @@ -4503,8 +4549,13 @@ end # Make sure arrayset can handle `Array{T}` (where `T` is a type and not a # `TypeVar`) without crashing let - function arrayset_unknown_dim(::Type{T}, n) where T - Base.arrayset(true, reshape(Vector{T}(undef, 1), fill(1, n)...), 2, 1) + @noinline function arrayset_unknown_dim(::Type{T}, n) where T + a = Vector{T}(undef, 1) + fill!(a, 0) + a = reshape(a, fill(1, n)...)::Array{T} + @test a[1] === 0 + Core.memoryrefset!(a.ref, 2, :not_atomic, true) + @test a[1] === 2 end arrayset_unknown_dim(Any, 1) arrayset_unknown_dim(Any, 2) @@ -4514,46 +4565,6 @@ let arrayset_unknown_dim(Int, 3) end -module TestSharedArrayResize -using Test -# Attempting to change the shape of a shared array should unshare it and -# not modify the original data -function test_shared_array_resize(::Type{T}) where T - len = 100 - a = Vector{T}(undef, len) - function test_unshare(f) - a′ = reshape(reshape(a, (len ÷ 2, 2)), len) - a[:] = 1:length(a) - # The operation should fail on the owner shared array - # and has no side effect. - @test_throws ErrorException f(a) - @test a == [1:len;] - @test a′ == [1:len;] - @test pointer(a) == pointer(a′) - # The operation should pass on the non-owner shared array - # and should unshare the arrays with no effect on the original one. - f(a′) - @test a == [1:len;] - @test pointer(a) != pointer(a′) - end - - test_unshare(a->ccall(:jl_array_del_end, Cvoid, (Any, Csize_t), a, 0)) - test_unshare(a->ccall(:jl_array_del_end, Cvoid, (Any, Csize_t), a, 1)) - test_unshare(a->ccall(:jl_array_del_beg, Cvoid, (Any, Csize_t), a, 0)) - test_unshare(a->ccall(:jl_array_del_beg, Cvoid, (Any, Csize_t), a, 1)) - test_unshare(a->deleteat!(a, 10)) - test_unshare(a->deleteat!(a, 90)) - test_unshare(a->ccall(:jl_array_grow_end, Cvoid, (Any, Csize_t), a, 0)) - test_unshare(a->ccall(:jl_array_grow_end, Cvoid, (Any, Csize_t), a, 1)) - test_unshare(a->ccall(:jl_array_grow_beg, Cvoid, (Any, Csize_t), a, 0)) - test_unshare(a->ccall(:jl_array_grow_beg, Cvoid, (Any, Csize_t), a, 1)) - test_unshare(a->insert!(a, 10, 10)) - test_unshare(a->insert!(a, 90, 90)) -end -test_shared_array_resize(Int) -test_shared_array_resize(Any) -end - # Copy of `#undef` copyto!(Vector{Any}(undef, 10), Vector{Any}(undef, 10)) function test_copy_alias(::Type{T}) where T @@ -4948,7 +4959,7 @@ function trigger14878() w.ext[:14878] = B14878(junk) # global junk not defined! return w end -@test_throws UndefVarError(:junk) trigger14878() +@test_throws UndefVarError(:junk, @__MODULE__) trigger14878() # issue #1090 function f1090(x)::Int @@ -5188,9 +5199,9 @@ let x = 1 @noinline g18444(a) = (x += 1; a[]) f18444_1(a) = invoke(sin, Tuple{Int}, g18444(a)) f18444_2(a) = invoke(sin, Tuple{Integer}, g18444(a)) - @test_throws ErrorException("invoke: argument type error") f18444_1(Ref{Any}(1.0)) + @test_throws "TypeError: in invoke: argument type error, expected" f18444_1(Ref{Any}(1.0)) @test x == 2 - @test_throws ErrorException("invoke: argument type error") f18444_2(Ref{Any}(1.0)) + @test_throws "TypeError: in invoke: argument type error, expected" f18444_2(Ref{Any}(1.0)) @test x == 3 @test f18444_1(Ref{Any}(1)) === sin(1) @test x == 4 @@ -5266,9 +5277,9 @@ GC.enable(true) @test isa(which(bad_tvars, ()), Method) @test bad_tvars() === 1 @test_warn "declares type variable T but does not use it" @eval bad_tvars2() where {T} = T -@test_throws UndefVarError(:T) bad_tvars2() +@test_throws UndefVarError(:T, :static_parameter) bad_tvars2() missing_tvar(::T...) where {T} = T -@test_throws UndefVarError(:T) missing_tvar() +@test_throws UndefVarError(:T, :static_parameter) missing_tvar() @test missing_tvar(1) === Int @test missing_tvar(1, 2, 3) === Int @test_throws MethodError missing_tvar(1, 2, "3") @@ -5513,6 +5524,9 @@ let a = Base.StringVector(2^17) @test sizeof(c) == 0 end +# issue #53990 / https://github.com/JuliaLang/julia/pull/53896#discussion_r1555087951 +@test Base.StringVector(UInt64(2)) isa Vector{UInt8} + @test_throws ArgumentError eltype(Bottom) # issue #16424, re-evaluating type definitions @@ -5884,7 +5898,7 @@ function f_unused_undefined_sp(::T...) where T T return 0 end -@test_throws UndefVarError(:T) f_unused_undefined_sp() +@test_throws UndefVarError(:T, :static_parameter) f_unused_undefined_sp() # note: the constant `5` here should be > DataType.ninitialized. # This tests that there's no crash due to accessing Type.body.layout. @@ -6026,10 +6040,10 @@ const unboxedunions = [Union{Int8, Nothing}, @test Base.isbitsunion(unboxedunions[2]) @test Base.isbitsunion(unboxedunions[3]) -@test Base.bitsunionsize(unboxedunions[1]) == 1 -@test Base.bitsunionsize(unboxedunions[2]) == 2 -@test Base.bitsunionsize(unboxedunions[3]) == 16 -@test Base.bitsunionsize(unboxedunions[4]) == 8 +@test Base.aligned_sizeof(unboxedunions[1]) == 1 +@test Base.aligned_sizeof(unboxedunions[2]) == 2 +@test Base.aligned_sizeof(unboxedunions[3]) == 16 +@test Base.aligned_sizeof(unboxedunions[4]) == 8 @test sizeof(unboxedunions[1]) == 1 @test sizeof(unboxedunions[2]) == 2 @@ -6337,7 +6351,7 @@ for U in unboxedunions resize!(A, len) @test length(A) === len @test A[1] === initvalue2(F2) - @test typeof(A[end]) === F + @test typeof(A[end]) === F2 # deleteat! F = Base.uniontypes(U)[2] @@ -6425,304 +6439,291 @@ for U in unboxedunions end end -@testset "jl_array_grow_at_end" begin +@testset "array _growatend!" begin # start w/ array, set & check elements, grow it, check that elements stayed correct, set & check elements A = Vector{Union{Missing, UInt8}}(undef, 2) -Base.arrayset(true, A, 0x01, 1) -Base.arrayset(true, A, missing, 2) -@test Base.arrayref(true, A, 1) === 0x01 -@test Base.arrayref(true, A, 2) === missing +setindex!(A, 0x01, 1) +setindex!(A, missing, 2) +@test getindex(A, 1) === 0x01 +@test getindex(A, 2) === missing -# grow_at_end 2 resize!(A, 5) -@test Base.arrayref(true, A, 1) === 0x01 -@test Base.arrayref(true, A, 2) === missing -@test Base.arrayref(true, A, 3) === missing -@test Base.arrayref(true, A, 4) === missing -@test Base.arrayref(true, A, 5) === missing -Base.arrayset(true, A, 0x03, 3) -Base.arrayset(true, A, missing, 4) -Base.arrayset(true, A, 0x05, 5) -@test Base.arrayref(true, A, 1) === 0x01 -@test Base.arrayref(true, A, 2) === missing -@test Base.arrayref(true, A, 3) === 0x03 -@test Base.arrayref(true, A, 4) === missing -@test Base.arrayref(true, A, 5) === 0x05 +@test getindex(A, 1) === 0x01 +@test getindex(A, 2) === missing +# The rest of the values are unspecified +setindex!(A, 0x03, 3) +setindex!(A, missing, 4) +setindex!(A, 0x05, 5) +@test isequal(A, [0x01, missing, 0x03, missing, 0x05]) # grow_at_end 1 Base._growat!(A, 4, 1) -@test Base.arrayref(true, A, 1) === 0x01 -@test Base.arrayref(true, A, 2) === missing -@test Base.arrayref(true, A, 3) === 0x03 -@test Base.arrayref(true, A, 4) === missing -@test Base.arrayref(true, A, 5) === missing -@test Base.arrayref(true, A, 6) === 0x05 - -Base.arrayset(true, A, missing, 1) -Base.arrayset(true, A, 0x02, 2) -Base.arrayset(true, A, missing, 3) -Base.arrayset(true, A, 0x04, 4) -Base.arrayset(true, A, missing, 5) -Base.arrayset(true, A, 0x06, 6) -@test Base.arrayref(true, A, 1) === missing -@test Base.arrayref(true, A, 2) === 0x02 -@test Base.arrayref(true, A, 3) === missing -@test Base.arrayref(true, A, 4) === 0x04 -@test Base.arrayref(true, A, 5) === missing -@test Base.arrayref(true, A, 6) === 0x06 +@test getindex(A, 1) === 0x01 +@test getindex(A, 2) === missing +@test getindex(A, 3) === 0x03 +#A[4] is unspecified +@test getindex(A, 5) === missing +@test getindex(A, 6) === 0x05 + +setindex!(A, missing, 1) +setindex!(A, 0x02, 2) +setindex!(A, missing, 3) +setindex!(A, 0x04, 4) +setindex!(A, missing, 5) +setindex!(A, 0x06, 6) +@test isequal(A, [missing, 0x2, missing, 0x4, missing, 0x6]) # grow_at_end 5 Base._growat!(A, 4, 1) -@test Base.arrayref(true, A, 1) === missing -@test Base.arrayref(true, A, 2) === 0x02 -@test Base.arrayref(true, A, 3) === missing -@test Base.arrayref(true, A, 4) === missing -@test Base.arrayref(true, A, 5) === 0x04 -@test Base.arrayref(true, A, 6) === missing -@test Base.arrayref(true, A, 7) === 0x06 +@test getindex(A, 1) === missing +@test getindex(A, 2) === 0x02 +@test getindex(A, 3) === missing +#A[4] is unspecified +@test getindex(A, 5) === 0x04 +@test getindex(A, 6) === missing +@test getindex(A, 7) === 0x06 # grow_at_end 6 resize!(A, 8) -@test Base.arrayref(true, A, 1) === missing -@test Base.arrayref(true, A, 2) === 0x02 -@test Base.arrayref(true, A, 3) === missing -@test Base.arrayref(true, A, 4) === missing -@test Base.arrayref(true, A, 5) === 0x04 -@test Base.arrayref(true, A, 6) === missing -@test Base.arrayref(true, A, 7) === 0x06 -@test Base.arrayref(true, A, 8) === missing +@test getindex(A, 1) === missing +@test getindex(A, 2) === 0x02 +@test getindex(A, 3) === missing +# A[4] still unspecified +@test getindex(A, 5) === 0x04 +@test getindex(A, 6) === missing +@test getindex(A, 7) === 0x06 +# A[8] is unspecified but test that it exists +@test getindex(A, 8) isa Any # grow_at_end 4 resize!(A, 1048576) resize!(A, 1048577) -@test Base.arrayref(true, A, 1) === missing -@test Base.arrayref(true, A, 2) === 0x02 -@test Base.arrayref(true, A, 3) === missing -@test Base.arrayref(true, A, 4) === missing -@test Base.arrayref(true, A, 5) === 0x04 -@test Base.arrayref(true, A, 6) === missing -@test Base.arrayref(true, A, 7) === 0x06 -@test Base.arrayref(true, A, 8) === missing +@test getindex(A, 1) === missing +@test getindex(A, 2) === 0x02 +@test getindex(A, 3) === missing +# A[4] is stil still unspecified +@test getindex(A, 5) === 0x04 +@test getindex(A, 6) === missing +@test getindex(A, 7) === 0x06 +@test getindex(A, 8) === missing +# 9:1048577 are unspecified foreach(9:1048577) do i - @test Base.arrayref(true, A, i) === missing -end -foreach(9:1048577) do i - Base.arrayset(true, A, i % UInt8, i) - @test Base.arrayref(true, A, i) === i % UInt8 + setindex!(A, i % UInt8, i) + @test getindex(A, i) === i % UInt8 end # grow_at_end 3 A = Vector{Union{Missing, UInt8}}(undef, 1048577) foreach(1:1048577) do i - @test Base.arrayref(true, A, i) === missing - Base.arrayset(true, A, i % UInt8, i) - @test Base.arrayref(true, A, i) === i % UInt8 + @test getindex(A, i) === missing + setindex!(A, i % UInt8, i) + @test getindex(A, i) === i % UInt8 end Base._growat!(A, 1048576, 1) @test length(A) == 1048578 foreach(1:1048575) do i - @test Base.arrayref(true, A, i) === i % UInt8 + @test getindex(A, i) === i % UInt8 @test A[i] === i % UInt8 end -@test Base.arrayref(true, A, 1048576) === missing -@test Base.arrayref(true, A, 1048577) === 1048576 % UInt8 -@test Base.arrayref(true, A, 1048578) === 1048577 % UInt8 +@test getindex(A, 1048576) === missing +@test getindex(A, 1048577) === 1048576 % UInt8 +@test getindex(A, 1048578) === 1048577 % UInt8 end # @testset -@testset "jl_array_grow_at_beg" begin +@testset "array _growatbeg!" begin # grow_at_beg 4 A = Vector{Union{Missing, UInt8}}(undef, 5) -Base.arrayset(true, A, 0x01, 1) -Base.arrayset(true, A, missing, 2) -Base.arrayset(true, A, 0x03, 3) -Base.arrayset(true, A, missing, 4) -Base.arrayset(true, A, 0x05, 5) +setindex!(A, 0x01, 1) +setindex!(A, missing, 2) +setindex!(A, 0x03, 3) +setindex!(A, missing, 4) +setindex!(A, 0x05, 5) Base._growat!(A, 1, 1) -@test Base.arrayref(true, A, 1) === missing -@test Base.arrayref(true, A, 2) === 0x01 -@test Base.arrayref(true, A, 3) === missing -@test Base.arrayref(true, A, 4) === 0x03 -@test Base.arrayref(true, A, 5) === missing -@test Base.arrayref(true, A, 6) === 0x05 +@test getindex(A, 1) === missing +@test getindex(A, 2) === 0x01 +@test getindex(A, 3) === missing +@test getindex(A, 4) === 0x03 +@test getindex(A, 5) === missing +@test getindex(A, 6) === 0x05 # grow_at_beg 2 Base._growat!(A, 1, 1) -@test Base.arrayref(true, A, 1) === missing -@test Base.arrayref(true, A, 2) === missing -@test Base.arrayref(true, A, 3) === 0x01 -@test Base.arrayref(true, A, 4) === missing -@test Base.arrayref(true, A, 5) === 0x03 -@test Base.arrayref(true, A, 6) === missing -@test Base.arrayref(true, A, 7) === 0x05 +@test getindex(A, 1) === missing +@test getindex(A, 2) === missing +@test getindex(A, 3) === 0x01 +@test getindex(A, 4) === missing +@test getindex(A, 5) === 0x03 +@test getindex(A, 6) === missing +@test getindex(A, 7) === 0x05 # grow_at_beg 1 Base._growat!(A, 2, 1) -@test Base.arrayref(true, A, 1) === missing -@test Base.arrayref(true, A, 2) === missing -@test Base.arrayref(true, A, 3) === missing -@test Base.arrayref(true, A, 4) === 0x01 -@test Base.arrayref(true, A, 5) === missing -@test Base.arrayref(true, A, 6) === 0x03 -@test Base.arrayref(true, A, 7) === missing -@test Base.arrayref(true, A, 8) === 0x05 +@test getindex(A, 1) === missing +@test getindex(A, 2) === missing +@test getindex(A, 3) === missing +@test getindex(A, 4) === 0x01 +@test getindex(A, 5) === missing +@test getindex(A, 6) === 0x03 +@test getindex(A, 7) === missing +@test getindex(A, 8) === 0x05 # grow_at_beg 9 Base._growat!(A, 1, 1) -@test Base.arrayref(true, A, 1) === missing -@test Base.arrayref(true, A, 2) === missing -@test Base.arrayref(true, A, 3) === missing -@test Base.arrayref(true, A, 4) === missing -@test Base.arrayref(true, A, 5) === 0x01 -@test Base.arrayref(true, A, 6) === missing -@test Base.arrayref(true, A, 7) === 0x03 -@test Base.arrayref(true, A, 8) === missing -@test Base.arrayref(true, A, 9) === 0x05 +@test getindex(A, 1) === missing +@test getindex(A, 2) === missing +@test getindex(A, 3) === missing +@test getindex(A, 4) === missing +@test getindex(A, 5) === 0x01 +@test getindex(A, 6) === missing +@test getindex(A, 7) === 0x03 +@test getindex(A, 8) === missing +@test getindex(A, 9) === 0x05 # grow_at_beg 8 A = Vector{Union{Missing, UInt8}}(undef, 5) -Base.arrayset(true, A, 0x01, 1) -Base.arrayset(true, A, missing, 2) -Base.arrayset(true, A, 0x03, 3) -Base.arrayset(true, A, missing, 4) -Base.arrayset(true, A, 0x05, 5) +setindex!(A, 0x01, 1) +setindex!(A, missing, 2) +setindex!(A, 0x03, 3) +setindex!(A, missing, 4) +setindex!(A, 0x05, 5) Base._growat!(A, 2, 1) Base._growat!(A, 2, 1) -@test Base.arrayref(true, A, 1) === 0x01 -@test Base.arrayref(true, A, 2) === missing -@test Base.arrayref(true, A, 3) === missing -@test Base.arrayref(true, A, 4) === missing -@test Base.arrayref(true, A, 5) === 0x03 -@test Base.arrayref(true, A, 6) === missing -@test Base.arrayref(true, A, 7) === 0x05 +@test getindex(A, 1) === 0x01 +@test getindex(A, 2) === missing +@test getindex(A, 3) === missing +@test getindex(A, 4) === missing +@test getindex(A, 5) === 0x03 +@test getindex(A, 6) === missing +@test getindex(A, 7) === 0x05 # grow_at_beg 5 A = Vector{Union{Missing, UInt8}}(undef, 5) -Base.arrayset(true, A, 0x01, 1) -Base.arrayset(true, A, missing, 2) -Base.arrayset(true, A, 0x03, 3) -Base.arrayset(true, A, missing, 4) -Base.arrayset(true, A, 0x05, 5) +setindex!(A, 0x01, 1) +setindex!(A, missing, 2) +setindex!(A, 0x03, 3) +setindex!(A, missing, 4) +setindex!(A, 0x05, 5) Base._growat!(A, 4, 1) Base._growat!(A, 4, 1) -@test Base.arrayref(true, A, 1) === 0x01 -@test Base.arrayref(true, A, 2) === missing -@test Base.arrayref(true, A, 3) === 0x03 -@test Base.arrayref(true, A, 4) === missing -@test Base.arrayref(true, A, 5) === missing -@test Base.arrayref(true, A, 6) === missing -@test Base.arrayref(true, A, 7) === 0x05 +@test getindex(A, 1) === 0x01 +@test getindex(A, 2) === missing +@test getindex(A, 3) === 0x03 +@test getindex(A, 4) === missing +@test getindex(A, 5) === missing +@test getindex(A, 6) === missing +@test getindex(A, 7) === 0x05 # grow_at_beg 6 Base._growat!(A, 2, 3) -@test Base.arrayref(true, A, 1) === 0x01 -@test Base.arrayref(true, A, 2) === missing -@test Base.arrayref(true, A, 3) === missing -@test Base.arrayref(true, A, 4) === missing -@test Base.arrayref(true, A, 5) === missing -@test Base.arrayref(true, A, 6) === 0x03 -@test Base.arrayref(true, A, 7) === missing -@test Base.arrayref(true, A, 8) === missing -@test Base.arrayref(true, A, 9) === missing -@test Base.arrayref(true, A, 10) === 0x05 +@test getindex(A, 1) === 0x01 +@test getindex(A, 2) === missing +@test getindex(A, 3) === missing +@test getindex(A, 4) === missing +@test getindex(A, 5) === missing +@test getindex(A, 6) === 0x03 +@test getindex(A, 7) === missing +@test getindex(A, 8) === missing +@test getindex(A, 9) === missing +@test getindex(A, 10) === 0x05 # grow_at_beg 3 A = Vector{Union{Missing, UInt8}}(undef, 1048577) -Base.arrayset(true, A, 0x01, 1) -Base.arrayset(true, A, missing, 2) -Base.arrayset(true, A, 0x03, 3) -Base.arrayset(true, A, missing, 4) -Base.arrayset(true, A, 0x05, 5) +setindex!(A, 0x01, 1) +setindex!(A, missing, 2) +setindex!(A, 0x03, 3) +setindex!(A, missing, 4) +setindex!(A, 0x05, 5) Base._growat!(A, 2, 1) -@test Base.arrayref(true, A, 1) === 0x01 -@test Base.arrayref(true, A, 2) === missing -@test Base.arrayref(true, A, 3) === missing -@test Base.arrayref(true, A, 4) === 0x03 -@test Base.arrayref(true, A, 5) === missing -@test Base.arrayref(true, A, 6) === 0x05 +@test getindex(A, 1) === 0x01 +@test getindex(A, 2) === missing +@test getindex(A, 3) === missing +@test getindex(A, 4) === 0x03 +@test getindex(A, 5) === missing +@test getindex(A, 6) === 0x05 foreach(7:length(A)) do i - @test Base.arrayref(true, A, i) === missing - Base.arrayset(true, A, i % UInt8, i) - @test Base.arrayref(true, A, i) === i % UInt8 + @test getindex(A, i) === missing + setindex!(A, i % UInt8, i) + @test getindex(A, i) === i % UInt8 end end # @testset -@testset "jl_array_del_at_beg" begin +@testset "array _deleteatbeg!" begin A = Vector{Union{Missing, UInt8}}(undef, 5) -Base.arrayset(true, A, 0x01, 1) -Base.arrayset(true, A, missing, 2) -Base.arrayset(true, A, 0x03, 3) -Base.arrayset(true, A, missing, 4) -Base.arrayset(true, A, 0x05, 5) +setindex!(A, 0x01, 1) +setindex!(A, missing, 2) +setindex!(A, 0x03, 3) +setindex!(A, missing, 4) +setindex!(A, 0x05, 5) Base._deleteat!(A, 2, 1) -@test Base.arrayref(true, A, 1) === 0x01 -@test Base.arrayref(true, A, 2) === 0x03 -@test Base.arrayref(true, A, 3) === missing -@test Base.arrayref(true, A, 4) === 0x05 +@test getindex(A, 1) === 0x01 +@test getindex(A, 2) === 0x03 +@test getindex(A, 3) === missing +@test getindex(A, 4) === 0x05 Base._deleteat!(A, 1, 1) -@test Base.arrayref(true, A, 1) === 0x03 -@test Base.arrayref(true, A, 2) === missing -@test Base.arrayref(true, A, 3) === 0x05 +@test getindex(A, 1) === 0x03 +@test getindex(A, 2) === missing +@test getindex(A, 3) === 0x05 A = Vector{Union{Missing, UInt8}}(undef, 5) -Base.arrayset(true, A, 0x01, 1) -Base.arrayset(true, A, missing, 2) -Base.arrayset(true, A, 0x03, 3) -Base.arrayset(true, A, missing, 4) -Base.arrayset(true, A, 0x05, 5) +setindex!(A, 0x01, 1) +setindex!(A, missing, 2) +setindex!(A, 0x03, 3) +setindex!(A, missing, 4) +setindex!(A, 0x05, 5) Base._growat!(A, 1, 1) -@test Base.arrayref(true, A, 1) === missing -@test Base.arrayref(true, A, 2) === 0x01 -@test Base.arrayref(true, A, 3) === missing -@test Base.arrayref(true, A, 4) === 0x03 -@test Base.arrayref(true, A, 5) === missing -@test Base.arrayref(true, A, 6) === 0x05 +@test getindex(A, 1) === missing +@test getindex(A, 2) === 0x01 +@test getindex(A, 3) === missing +@test getindex(A, 4) === 0x03 +@test getindex(A, 5) === missing +@test getindex(A, 6) === 0x05 Base._deleteat!(A, 2, 1) -@test Base.arrayref(true, A, 1) === missing -@test Base.arrayref(true, A, 2) === missing -@test Base.arrayref(true, A, 3) === 0x03 -@test Base.arrayref(true, A, 4) === missing -@test Base.arrayref(true, A, 5) === 0x05 +@test getindex(A, 1) === missing +@test getindex(A, 2) === missing +@test getindex(A, 3) === 0x03 +@test getindex(A, 4) === missing +@test getindex(A, 5) === 0x05 Base._deleteat!(A, 1, 2) -@test Base.arrayref(true, A, 1) === 0x03 -@test Base.arrayref(true, A, 2) === missing -@test Base.arrayref(true, A, 3) === 0x05 +@test getindex(A, 1) === 0x03 +@test getindex(A, 2) === missing +@test getindex(A, 3) === 0x05 Base._deleteat!(A, 1, 1) -@test Base.arrayref(true, A, 1) === missing -@test Base.arrayref(true, A, 2) === 0x05 +@test getindex(A, 1) === missing +@test getindex(A, 2) === 0x05 end # @testset -@testset "jl_array_del_at_end" begin +@testset "array _deleteatend!" begin A = Vector{Union{Missing, UInt8}}(undef, 5) -Base.arrayset(true, A, 0x01, 1) -Base.arrayset(true, A, missing, 2) -Base.arrayset(true, A, 0x03, 3) -Base.arrayset(true, A, missing, 4) -Base.arrayset(true, A, 0x05, 5) +setindex!(A, 0x01, 1) +setindex!(A, missing, 2) +setindex!(A, 0x03, 3) +setindex!(A, missing, 4) +setindex!(A, 0x05, 5) Base._deleteat!(A, 5, 1) -@test Base.arrayref(true, A, 1) === 0x01 -@test Base.arrayref(true, A, 2) === missing -@test Base.arrayref(true, A, 3) === 0x03 -@test Base.arrayref(true, A, 4) === missing +@test getindex(A, 1) === 0x01 +@test getindex(A, 2) === missing +@test getindex(A, 3) === 0x03 +@test getindex(A, 4) === missing Base._deleteat!(A, 3, 1) -@test Base.arrayref(true, A, 1) === 0x01 -@test Base.arrayref(true, A, 2) === missing -@test Base.arrayref(true, A, 3) === missing +@test getindex(A, 1) === 0x01 +@test getindex(A, 2) === missing +@test getindex(A, 3) === missing end # @testset @@ -6744,23 +6745,23 @@ end # jl_array_shrink let A=Vector{Union{UInt8, Missing}}(undef, 1048577) - Base.arrayset(true, A, 0x01, 1) - Base.arrayset(true, A, missing, 2) - Base.arrayset(true, A, 0x03, 3) - Base.arrayset(true, A, missing, 4) - Base.arrayset(true, A, 0x05, 5) + setindex!(A, 0x01, 1) + setindex!(A, missing, 2) + setindex!(A, 0x03, 3) + setindex!(A, missing, 4) + setindex!(A, 0x05, 5) deleteat!(A, 6:1048577) - @test Base.arrayref(true, A, 1) === 0x01 - @test Base.arrayref(true, A, 2) === missing - @test Base.arrayref(true, A, 3) === 0x03 - @test Base.arrayref(true, A, 4) === missing - @test Base.arrayref(true, A, 5) === 0x05 + @test getindex(A, 1) === 0x01 + @test getindex(A, 2) === missing + @test getindex(A, 3) === 0x03 + @test getindex(A, 4) === missing + @test getindex(A, 5) === 0x05 sizehint!(A, 5) - @test Base.arrayref(true, A, 1) === 0x01 - @test Base.arrayref(true, A, 2) === missing - @test Base.arrayref(true, A, 3) === 0x03 - @test Base.arrayref(true, A, 4) === missing - @test Base.arrayref(true, A, 5) === 0x05 + @test getindex(A, 1) === 0x01 + @test getindex(A, 2) === missing + @test getindex(A, 3) === 0x03 + @test getindex(A, 4) === missing + @test getindex(A, 5) === 0x05 end # copyto!/vcat w/ internal padding @@ -6778,14 +6779,14 @@ primitive type TypeWith24Bits 24 end TypeWith24Bits(x::UInt32) = Core.Intrinsics.trunc_int(TypeWith24Bits, x) let x = TypeWith24Bits(0x112233), y = TypeWith24Bits(0x445566), z = TypeWith24Bits(0x778899) a = [x, x] - Core.arrayset(true, a, y, 2) + Core.memoryrefset!(Core.memoryref(a.ref, 2, true), y, :not_atomic, true) @test a == [x, y] a[2] = z @test a == [x, z] @test pointer(a, 2) - pointer(a, 1) == 4 b = [(x, x), (x, x)] - Core.arrayset(true, b, (x, y), 2) + Core.memoryrefset!(Core.memoryref(b.ref, 2, true), (x, y), :not_atomic, true) @test b == [(x, x), (x, y)] b[2] = (y, z) @test b == [(x, x), (y, z)] @@ -6887,7 +6888,7 @@ end # issue #21004 const PTuple_21004{N,T} = NTuple{N,VecElement{T}} @test_throws ArgumentError("too few elements for tuple type $PTuple_21004") PTuple_21004(1) -@test_throws UndefVarError(:T) PTuple_21004_2{N,T} = NTuple{N, VecElement{T}}(1) +@test_throws UndefVarError(:T, :static_parameter) PTuple_21004_2{N,T} = NTuple{N, VecElement{T}}(1) #issue #22792 foo_22792(::Type{<:Union{Int8,Int,UInt}}) = 1; @@ -7185,7 +7186,7 @@ end c28399 = 42 @test g28399(0)() == 42 @test g28399(1)() == 42 -@test_throws UndefVarError(:__undef_28399__) f28399() +@test_throws UndefVarError(:__undef_28399__, @__MODULE__) f28399() # issue #28445 mutable struct foo28445 @@ -7378,21 +7379,27 @@ let fc = FieldConvert(1.0, [2.0], 0x3, 0x4, 0x5) end @test ftype_eval[] == 1 let code = code_lowered(FieldConvert)[1].code - @test code[1] == Expr(:call, GlobalRef(Core, :apply_type), GlobalRef(@__MODULE__, :FieldConvert), GlobalRef(@__MODULE__, :FieldTypeA), Expr(:static_parameter, 1)) - @test code[2] == Expr(:call, GlobalRef(Core, :fieldtype), Core.SSAValue(1), 1) - @test code[7] == Expr(:(=), Core.SlotNumber(10), Expr(:call, GlobalRef(Base, :convert), Core.SSAValue(2), Core.SlotNumber(10))) - @test code[8] == Core.SlotNumber(10) - @test code[9] == Expr(:call, GlobalRef(Core, :fieldtype), Core.SSAValue(1), 2) - @test code[14] == Expr(:(=), Core.SlotNumber(9), Expr(:call, GlobalRef(Base, :convert), Core.SSAValue(9), Core.SlotNumber(9))) - @test code[15] == Core.SlotNumber(9) - @test code[16] == Expr(:call, GlobalRef(Core, :fieldtype), Core.SSAValue(1), 4) - @test code[21] == Expr(:(=), Core.SlotNumber(8), Expr(:call, GlobalRef(Base, :convert), Core.SSAValue(16), Core.SlotNumber(8))) - @test code[22] == Core.SlotNumber(8) - @test code[23] == Expr(:call, GlobalRef(Core, :fieldtype), Core.SSAValue(1), 5) - @test code[28] == Expr(:(=), Core.SlotNumber(7), Expr(:call, GlobalRef(Base, :convert), Core.SSAValue(23), Core.SlotNumber(7))) - @test code[29] == Core.SlotNumber(7) - @test code[30] == Expr(:new, Core.SSAValue(1), Core.SSAValue(8), Core.SSAValue(15), Core.SlotNumber(4), Core.SSAValue(22), Core.SSAValue(29)) - @test code[31] == Core.ReturnNode(Core.SSAValue(30)) + local fc_global_ssa, sp1_ssa, apply_type_ssa, field_type_ssa, + field_type2_ssa, field_type4_ssa, field_type5_ssa, + slot_read_1, slot_read_2, slot_read_3, slot_read_4, + new_ssa + @test code[(fc_global_ssa = 1;)] == GlobalRef(@__MODULE__, :FieldConvert) + @test code[(sp1_ssa = 2;)] == Expr(:static_parameter, 1) + @test code[(apply_type_ssa = 3;)] == Expr(:call, GlobalRef(Core, :apply_type), Core.SSAValue(fc_global_ssa), GlobalRef(@__MODULE__, :FieldTypeA), Core.SSAValue(sp1_ssa)) + @test code[(field_type_ssa = 4;)] == Expr(:call, GlobalRef(Core, :fieldtype), Core.SSAValue(apply_type_ssa), 1) + @test code[10] == Expr(:(=), Core.SlotNumber(10), Expr(:call, GlobalRef(Base, :convert), Core.SSAValue(field_type_ssa), Core.SlotNumber(10))) + @test code[(slot_read_1 = 11;)] == Core.SlotNumber(10) + @test code[(field_type2_ssa = 12;)] == Expr(:call, GlobalRef(Core, :fieldtype), Core.SSAValue(apply_type_ssa), 2) + @test code[18] == Expr(:(=), Core.SlotNumber(9), Expr(:call, GlobalRef(Base, :convert), Core.SSAValue(field_type2_ssa), Core.SlotNumber(9))) + @test code[(slot_read_2 = 19;)] == Core.SlotNumber(9) + @test code[(field_type4_ssa = 20;)] == Expr(:call, GlobalRef(Core, :fieldtype), Core.SSAValue(apply_type_ssa), 4) + @test code[26] == Expr(:(=), Core.SlotNumber(8), Expr(:call, GlobalRef(Base, :convert), Core.SSAValue(field_type4_ssa), Core.SlotNumber(8))) + @test code[(slot_read_3 = 27;)] == Core.SlotNumber(8) + @test code[(field_type5_ssa = 28;)] == Expr(:call, GlobalRef(Core, :fieldtype), Core.SSAValue(apply_type_ssa), 5) + @test code[34] == Expr(:(=), Core.SlotNumber(7), Expr(:call, GlobalRef(Base, :convert), Core.SSAValue(field_type5_ssa), Core.SlotNumber(7))) + @test code[(slot_read_4 = 35;)] == Core.SlotNumber(7) + @test code[(new_ssa = 36;)] == Expr(:new, Core.SSAValue(apply_type_ssa), Core.SSAValue(slot_read_1), Core.SSAValue(slot_read_2), Core.SlotNumber(4), Core.SSAValue(slot_read_3), Core.SSAValue(slot_read_4)) + @test code[37] == Core.ReturnNode(Core.SSAValue(new_ssa)) end # Issue #32820 @@ -7504,16 +7511,11 @@ function f34482() Base.not_int("ABC") 1 end -function g34482() - Core.Intrinsics.arraylen(1) - 1 -end function h34482() Core.Intrinsics.bitcast(1, 1) 1 end @test_throws ErrorException f34482() -@test_throws TypeError g34482() @test_throws TypeError h34482() struct NFANode34126 @@ -7986,14 +7988,14 @@ code_typed(f47476, (Int, Int, Vararg{Union{Int, NTuple{2,Int}}},)) code_typed(f47476, (Int, Int, Int, Vararg{Union{Int, NTuple{2,Int}}},)) code_typed(f47476, (Int, Int, Int, Int, Vararg{Union{Int, NTuple{2,Int}}},)) @test f47476(1, 2, 3, 4, 5, 6, (7, 8)) === 2 -@test_throws UndefVarError(:N) f47476(1, 2, 3, 4, 5, 6, 7) +@test_throws UndefVarError(:N, :static_parameter) f47476(1, 2, 3, 4, 5, 6, 7) vect47476(::Type{T}) where {T} = T @test vect47476(Type{Type{Type{Int32}}}) === Type{Type{Type{Int32}}} @test vect47476(Type{Type{Type{Int64}}}) === Type{Type{Type{Int64}}} g47476(::Union{Nothing,Int,Val{T}}...) where {T} = T -@test_throws UndefVarError(:T) g47476(nothing, 1, nothing, 2, nothing, 3, nothing, 4, nothing, 5) +@test_throws UndefVarError(:T, :static_parameter) g47476(nothing, 1, nothing, 2, nothing, 3, nothing, 4, nothing, 5) @test g47476(nothing, 1, nothing, 2, nothing, 3, nothing, 4, nothing, 5, Val(6)) === 6 let spec = only(methods(g47476)).specializations::Core.SimpleVector @test !isempty(spec) @@ -8020,12 +8022,11 @@ for T in (Int, String, Symbol, Module) @test Core.Compiler.is_foldable(Base.infer_effects(hash, (Tuple{T},))) @test Core.Compiler.is_foldable(Base.infer_effects(objectid, (Tuple{T,T},))) @test Core.Compiler.is_foldable(Base.infer_effects(hash, (Tuple{T,T},))) + @test Core.Compiler.is_foldable(Base.infer_effects(objectid, (Ref{T},))) + @test Core.Compiler.is_foldable(Base.infer_effects(objectid, (Tuple{Ref{T}},))) + @test Core.Compiler.is_foldable(Base.infer_effects(objectid, (Tuple{Vector{T}},))) end -@test !Core.Compiler.is_consistent(Base.infer_effects(objectid, (Ref{Int},))) -@test !Core.Compiler.is_consistent(Base.infer_effects(objectid, (Tuple{Ref{Int}},))) -# objectid for datatypes is inconsistant for types that have unbound type parameters. -@test !Core.Compiler.is_consistent(Base.infer_effects(objectid, (DataType,))) -@test !Core.Compiler.is_consistent(Base.infer_effects(objectid, (Tuple{Vector{Int}},))) +@test Core.Compiler.is_foldable(Base.infer_effects(objectid, (DataType,))) # donotdelete should not taint consistency of the containing function f_donotdete(x) = (Core.Compiler.donotdelete(x); 1) @@ -8065,6 +8066,72 @@ end @test Core.Compiler.is_foldable(Base.infer_effects(length, (Core.SimpleVector,))) @test Core.Compiler.is_foldable(Base.infer_effects(getindex, (Core.SimpleVector,Int))) -let lin = Core.LineInfoNode(Base, first(methods(convert)), :foo, Int32(5), Int32(0)) - @test convert(LineNumberNode, lin) == LineNumberNode(5, :foo) +# Test that a nothrow-globalref doesn't get outlined during lowering +module WellKnownGlobal + global well_known = 1 +end +macro insert_global() + Expr(:call, GlobalRef(Base, :println), GlobalRef(WellKnownGlobal, :well_known)) +end +check_globalref_lowering() = @insert_global +let src = code_lowered(check_globalref_lowering)[1] + @test length(src.code) == 2 +end + +# Test correctness of widen_diagonal +let widen_diagonal(x::UnionAll) = Base.rewrap_unionall(Base.widen_diagonal(Base.unwrap_unionall(x), x), x) + @test Tuple{Int,Float64} <: widen_diagonal(NTuple) + @test Tuple{Int,Float64} <: widen_diagonal(Tuple{T,T} where {T}) + @test Tuple{Real,Int,Float64} <: widen_diagonal(Tuple{S,Vararg{T}} where {S, T<:S}) + @test Tuple{Int,Int,Float64,Float64} <: widen_diagonal(Tuple{S,S,Vararg{T}} where {S, T<:S}) + @test Union{Tuple{T}, Tuple{T,Int}} where {T} === widen_diagonal(Union{Tuple{T}, Tuple{T,Int}} where {T}) + @test Tuple === widen_diagonal(Union{Tuple{Vararg{S}}, Tuple{Vararg{T}}} where {S, T}) + @test Tuple{Vararg{Val{<:Set}}} == widen_diagonal(Tuple{Vararg{T}} where T<:Val{<:Set}) +end + +# Test try/catch/else ordering +function test_try_catch_else() + local x + try + x = 1 + catch + rethrow() + else + return x + end +end +@test test_try_catch_else() == 1 + +# #52433 +@test_throws ErrorException Core.Intrinsics.pointerref(Ptr{Vector{Int64}}(C_NULL), 1, 0) + +# #53034 (Union normalization for typevar elimination) +@test Tuple{Int,Any} <: Tuple{Union{Int,T},T} where {T>:Int} +@test Tuple{Int,Any} <: Tuple{Union{Int,T},T} where {T>:Integer} +# #53034 (Union normalization for Type elimination) +@test Int isa Type{Union{Int,T2} where {T2<:T1}} where {T1} +@test Int isa Type{Union{Int,T1}} where {T1} +@test Int isa Union{UnionAll, Type{Union{Int,T2} where {T2<:T1}}} where {T1} +@test Int isa Union{Union, Type{Union{Int,T1}}} where {T1} +@test_broken Int isa Union{UnionAll, Type{Union{Int,T2} where {T2<:T1}} where {T1}} +@test_broken Int isa Union{Union, Type{Union{Int,T1}} where {T1}} + +let M = @__MODULE__ + @test Core.set_binding_type!(M, :a_typed_global, Tuple{Union{Integer,Nothing}}) === nothing + @test Core.get_binding_type(M, :a_typed_global) === Tuple{Union{Integer,Nothing}} + @test Core.set_binding_type!(M, :a_typed_global, Tuple{Union{Integer,Nothing}}) === nothing + @test Core.set_binding_type!(M, :a_typed_global, Union{Tuple{Integer},Tuple{Nothing}}) === nothing + @test_throws(ErrorException("cannot set type for global $(nameof(M)).a_typed_global. It already has a value or is already set to a different type."), + Core.set_binding_type!(M, :a_typed_global, Union{Nothing,Tuple{Union{Integer,Nothing}}})) + @test Core.set_binding_type!(M, :a_typed_global) === nothing + @test Core.get_binding_type(M, :a_typed_global) === Tuple{Union{Integer,Nothing}} end + +@test Base.unsafe_convert(Ptr{Int}, [1]) !== C_NULL + +# Test that new macros are allowed to be defined inside Expr(:toplevel) returned by macros +macro macroception() + Expr(:toplevel, :(macro foo() 1 end), :(@foo)) +end + +@test (@macroception()) === 1 diff --git a/test/corelogging.jl b/test/corelogging.jl index 9626f48e4b407..778e70aecd406 100644 --- a/test/corelogging.jl +++ b/test/corelogging.jl @@ -103,12 +103,12 @@ end logmsg = (function() @info msg x=y end, function() @info msg x=y z=1+1 end)[i] @test_logs (Error, Test.Ignored(), Test.Ignored(), :logevent_error) catch_exceptions=true logmsg() - @test_throws UndefVarError(:msg) collect_test_logs(logmsg) - @test (only(collect_test_logs(logmsg, catch_exceptions=true)[1]).kwargs[:exception]::Tuple{UndefVarError, Vector})[1] === UndefVarError(:msg) + @test_throws UndefVarError(:msg, :local) collect_test_logs(logmsg) + @test (only(collect_test_logs(logmsg, catch_exceptions=true)[1]).kwargs[:exception]::Tuple{UndefVarError, Vector})[1] === UndefVarError(:msg, :local) msg = "the msg" @test_logs (Error, Test.Ignored(), Test.Ignored(), :logevent_error) catch_exceptions=true logmsg() - @test_throws UndefVarError(:y) collect_test_logs(logmsg) - @test (only(collect_test_logs(logmsg, catch_exceptions=true)[1]).kwargs[:exception]::Tuple{UndefVarError, Vector})[1] === UndefVarError(:y) + @test_throws UndefVarError(:y, :local) collect_test_logs(logmsg) + @test (only(collect_test_logs(logmsg, catch_exceptions=true)[1]).kwargs[:exception]::Tuple{UndefVarError, Vector})[1] === UndefVarError(:y, :local) y = "the y" @test_logs (Info,"the msg") logmsg() @test only(collect_test_logs(logmsg)[1]).kwargs[:x] === "the y" diff --git a/test/dict.jl b/test/dict.jl index 0284ec9399ca4..ca8a598de0b81 100644 --- a/test/dict.jl +++ b/test/dict.jl @@ -162,6 +162,30 @@ end # issue #39117 @test Dict(t[1]=>t[2] for t in zip((1,"2"), (2,"2"))) == Dict{Any,Any}(1=>2, "2"=>"2") + + @testset "issue #33147" begin + expected = try; Base._throw_dict_kv_error(); catch e; e; end + @test_throws expected Dict(i for i in 1:2) + @test_throws expected Dict(nothing for i in 1:2) + @test_throws expected Dict(() for i in 1:2) + @test_throws expected Dict((i, i, i) for i in 1:2) + @test_throws expected Dict(nothing) + @test_throws expected Dict((1,)) + @test_throws expected Dict(1:2) + @test_throws expected Dict(((),)) + @test_throws expected IdDict(((),)) + @test_throws expected WeakKeyDict(((),)) + @test_throws expected IdDict(nothing) + @test_throws expected WeakKeyDict(nothing) + @test Dict(1:0) isa Dict + @test Dict(()) isa Dict + try + Dict(i => error("$i") for i in 1:3) + catch ex + @test ex isa ErrorException + @test length(Base.current_exceptions()) == 1 + end + end end @testset "empty tuple ctor" begin @@ -639,13 +663,13 @@ end @test d == IdDict(1=>1, 2=>2, 3=>3) @test eltype(d) == Pair{Int,Int} @test_throws KeyError d[:a] - @test_throws ArgumentError d[:a] = 1 + @test_throws TypeError d[:a] = 1 @test_throws MethodError d[1] = :a # copy constructor d = IdDict(Pair(1,1), Pair(2,2), Pair(3,3)) @test collect(values(IdDict{Int,Float64}(d))) == collect(values(d)) - @test_throws ArgumentError IdDict{Float64,Int}(d) + @test_throws TypeError IdDict{Float64,Int}(d) # misc constructors @test typeof(IdDict(1=>1, :a=>2)) == IdDict{Any,Int} @@ -672,7 +696,7 @@ end @test_throws MethodError get!(d, "b", "b") @test delete!(d, "a") === d @test !haskey(d, "a") - @test_throws ArgumentError get!(IdDict{Symbol,Any}(), 2, "b") + @test_throws TypeError get!(IdDict{Symbol,Any}(), 2, "b") @test get!(IdDict{Int,Int}(), 1, 2.0) === 2 @test get!(()->2.0, IdDict{Int,Int}(), 1) === 2 @@ -1084,17 +1108,33 @@ Dict(1 => rand(2,3), 'c' => "asdf") # just make sure this does not trigger a dep GC.@preserve A B C D nothing end -mutable struct CollidingHash -end -Base.hash(::CollidingHash, h::UInt) = hash(UInt(0), h) - -struct PredictableHash - x::UInt -end -Base.hash(x::PredictableHash, h::UInt) = x.x - import Base.PersistentDict @testset "PersistentDict" begin + @testset "HAMT HashState" begin + key = :key + h = Base.HAMT.HashState(key) + h1 = Base.HAMT.HashState(key, objectid(key), 0, 0) + h2 = Base.HAMT.HashState(h, key) # reconstruct + @test h.hash == h1.hash + @test h.hash == h2.hash + + hs = Base.HAMT.next(h1) + @test hs.depth == 1 + recompute_depth = (Base.HAMT.MAX_SHIFT ÷ Base.HAMT.BITS_PER_LEVEL) + 1 + for i in 2:recompute_depth + hs = Base.HAMT.next(hs) + @test hs.depth == i + end + @test hs.depth == recompute_depth + @test hs.shift == 0 + hsr = Base.HAMT.HashState(hs, key) + @test hs.hash == hsr.hash + @test hs.depth == hsr.depth + @test hs.shift == hsr.shift + + @test Core.Compiler.is_removable_if_unused(Base.infer_effects(Base.HAMT.init_hamt, (Type{Vector{Any}},Type{Int},Vector{Any},Int))) + @test Core.Compiler.is_removable_if_unused(Base.infer_effects(Base.HAMT.HAMT{Vector{Any},Int}, (Pair{Vector{Any},Int},))) + end @testset "basics" begin dict = PersistentDict{Int, Int}() @test_throws KeyError dict[1] @@ -1145,6 +1185,21 @@ import Base.PersistentDict @test dict[4] == 1 end + @testset "objectid" begin + c = [0] + dict = PersistentDict{Any, Int}(c => 1, [1] => 2) + @test dict[c] == 1 + c[1] = 1 + @test dict[c] == 1 + + c[1] = 0 + dict = PersistentDict{Any, Int}((c,) => 1, ([1],) => 2) + @test dict[(c,)] == 1 + + c[1] = 1 + @test dict[(c,)] == 1 + end + @testset "stress" begin N = 2^14 dict = PersistentDict{Int, Int}() @@ -1164,53 +1219,6 @@ import Base.PersistentDict end @test isempty(dict) end - - @testset "CollidingHash" begin - dict = PersistentDict{CollidingHash, Nothing}() - dict = PersistentDict(dict, CollidingHash(), nothing) - @test_throws ErrorException PersistentDict(dict, CollidingHash(), nothing) - end - - # Test the internal implementation - @testset "PredictableHash" begin - dict = PersistentDict{PredictableHash, Nothing}() - for i in 1:Base.HashArrayMappedTries.ENTRY_COUNT - key = PredictableHash(UInt(i-1)) # Level 0 - dict = PersistentDict(dict, key, nothing) - end - @test length(dict.trie.data) == Base.HashArrayMappedTries.ENTRY_COUNT - @test dict.trie.bitmap == typemax(Base.HashArrayMappedTries.BITMAP) - for entry in dict.trie.data - @test entry isa Base.HashArrayMappedTries.Leaf - end - - dict = PersistentDict{PredictableHash, Nothing}() - for i in 1:Base.HashArrayMappedTries.ENTRY_COUNT - key = PredictableHash(UInt(i-1) << Base.HashArrayMappedTries.BITS_PER_LEVEL) # Level 1 - dict = PersistentDict(dict, key, nothing) - end - @test length(dict.trie.data) == 1 - @test length(dict.trie.data[1].data) == 32 - - max_level = (Base.HashArrayMappedTries.NBITS ÷ Base.HashArrayMappedTries.BITS_PER_LEVEL) - dict = PersistentDict{PredictableHash, Nothing}() - for i in 1:Base.Base.HashArrayMappedTries.ENTRY_COUNT - key = PredictableHash(UInt(i-1) << (max_level * Base.HashArrayMappedTries.BITS_PER_LEVEL)) # Level 12 - dict = PersistentDict(dict, key, nothing) - end - data = dict.trie.data - for level in 1:max_level - @test length(data) == 1 - data = only(data).data - end - last_level_nbits = Base.HashArrayMappedTries.NBITS - (max_level * Base.HashArrayMappedTries.BITS_PER_LEVEL) - if Base.HashArrayMappedTries.NBITS == 64 - @test last_level_nbits == 4 - elseif Base.HashArrayMappedTries.NBITS == 32 - @test last_level_nbits == 2 - end - @test length(data) == 2^last_level_nbits - end end @testset "issue #19995, hash of dicts" begin @@ -1492,9 +1500,9 @@ end sizehint!(d, 10) @test length(d.slots) < 100 sizehint!(d, 1000) - Base._sizehint!(d, 1; shrink = false) + sizehint!(d, 1; shrink = false) @test length(d.slots) >= 1000 - Base._sizehint!(d, 1; shrink = true) + sizehint!(d, 1; shrink = true) @test length(d.slots) < 1000 end @@ -1528,3 +1536,31 @@ Base.hash(::BadHash, ::UInt)=UInt(1) d[BadHash(1)]=nothing @test !(BadHash(2) in keys(d)) end + +# Issue #52066 +let d = Dict() + d[1] = 'a' + d[1.0] = 'b' + @test only(d) === Pair{Any,Any}(1.0, 'b') +end + +@testset "UnionAll `keytype` and `valtype` (issue #53115)" begin + K = Int8 + V = Int16 + dicts = ( + AbstractDict, IdDict, Dict, WeakKeyDict, Base.ImmutableDict, + Base.PersistentDict, Iterators.Pairs + ) + + @testset "D: $D" for D ∈ dicts + @test_throws MethodError keytype(D) + @test_throws MethodError keytype(D{<:Any,V}) + @test keytype(D{K }) == K + @test keytype(D{K, V}) == K + + @test_throws MethodError valtype(D) + @test valtype(D{<:Any,V}) == V + @test_throws MethodError valtype(D{K }) + @test valtype(D{K, V}) == V + end +end diff --git a/test/docs.jl b/test/docs.jl index 401759cd7cdb0..61d0271c25811 100644 --- a/test/docs.jl +++ b/test/docs.jl @@ -69,6 +69,37 @@ $$latex literal$$ """ function break_me_docs end + +# `hasdoc` returns `true` on a name with a docstring. +@test Docs.hasdoc(Base, :map) +# `hasdoc` returns `false` on a name without a docstring. +@test !isdefined(Base, :_this_name_doesnt_exist_) && !Docs.hasdoc(Base, :_this_name_doesnt_exist_) +@test isdefined(Base, :_typed_vcat) && !Docs.hasdoc(Base, :_typed_vcat) + +"This module has names without documentation." +module _ModuleWithUndocumentedNames +export f +public ⨳, @foo +f() = 1 +g() = 2 +⨳(a,b) = a * b +macro foo(); nothing; end +⊕(a,b) = a + b +end + +"This module has some documentation." +module _ModuleWithSomeDocumentedNames +export f +"f() is 1." +f() = 1 +g() = 2 +end + +@test Docs.undocumented_names(_ModuleWithUndocumentedNames) == [Symbol("@foo"), :f, :⨳] +@test isempty(Docs.undocumented_names(_ModuleWithSomeDocumentedNames)) +@test Docs.undocumented_names(_ModuleWithSomeDocumentedNames; private=true) == [:eval, :g, :include] + + # issue #11548 module ModuleMacroDoc @@ -1434,7 +1465,7 @@ end end struct t_docs_abc end -@test "t_docs_abc" in accessible(@__MODULE__) +@test "t_docs_abc" in string.(accessible(@__MODULE__)) # Call overloading issues #20087 and #44889 """ @@ -1525,3 +1556,21 @@ Base.@ccallable c51586_long()::Int = 3 @test docstrings_equal(@doc(c51586_short()), doc"ensure we can document ccallable functions") @test docstrings_equal(@doc(c51586_long()), doc"ensure we can document ccallable functions") + +@testset "Docs docstrings" begin + undoc = Docs.undocumented_names(Docs) + @test_broken isempty(undoc) + @test undoc == [Symbol("@var")] +end + +# Docing the macroception macro +macro docmacroception() + Expr(:toplevel, macroexpand(__module__, :(@Base.__doc__ macro docmacrofoo() 1 end); recursive=false), :(@docmacrofoo)) +end + +""" +This docmacroception has a docstring +""" +@docmacroception() + +@test Docs.hasdoc(@__MODULE__, :var"@docmacrofoo") diff --git a/test/embedding/embedding-test.jl b/test/embedding/embedding-test.jl index c10cc6a16fee8..34ef9a796ba56 100644 --- a/test/embedding/embedding-test.jl +++ b/test/embedding/embedding-test.jl @@ -21,6 +21,7 @@ end close(err.in) out_task = @async readlines(out) @test readline(err) == "MethodError: no method matching this_function_has_no_methods()" + @test readline(err) == "The function `this_function_has_no_methods` exists, but no method is defined for this combination of argument types." @test success(p) lines = fetch(out_task) @test length(lines) == 11 diff --git a/test/embedding/embedding.c b/test/embedding/embedding.c index c5b8845b7c823..746c59fc8ce1f 100644 --- a/test/embedding/embedding.c +++ b/test/embedding/embedding.c @@ -86,17 +86,17 @@ int main() // (aka, is gc-rooted until) the program reaches the corresponding JL_GC_POP() JL_GC_PUSH1(&x); - double* xData = jl_array_data(x); + double* xData = jl_array_data(x, double); size_t i; - for (i = 0; i < jl_array_len(x); i++) + for (i = 0; i < jl_array_nrows(x); i++) xData[i] = i; jl_function_t *func = jl_get_function(jl_base_module, "reverse!"); jl_call1(func, (jl_value_t*) x); printf("x = ["); - for (i = 0; i < jl_array_len(x); i++) + for (i = 0; i < jl_array_nrows(x); i++) printf("%e ", xData[i]); printf("]\n"); fflush(stdout); diff --git a/test/env.jl b/test/env.jl index 4a3529f6d4081..7f6962cf675aa 100644 --- a/test/env.jl +++ b/test/env.jl @@ -133,7 +133,9 @@ end for _v in (v, uppercasefirst(v), uppercase(v)) ENV["testing_gbe"] = _v @test Base.get_bool_env("testing_gbe", false) == true + @test Base.get_bool_env(() -> false, "testing_gbe") == true @test Base.get_bool_env("testing_gbe", true) == true + @test Base.get_bool_env(() -> true, "testing_gbe") == true end end end @@ -142,26 +144,34 @@ end for _v in (v, uppercasefirst(v), uppercase(v)) ENV["testing_gbe"] = _v @test Base.get_bool_env("testing_gbe", true) == false + @test Base.get_bool_env(() -> true, "testing_gbe") == false @test Base.get_bool_env("testing_gbe", false) == false + @test Base.get_bool_env(() -> false, "testing_gbe") == false end end end @testset "empty" begin ENV["testing_gbe"] = "" @test Base.get_bool_env("testing_gbe", true) == true + @test Base.get_bool_env(() -> true, "testing_gbe") == true @test Base.get_bool_env("testing_gbe", false) == false + @test Base.get_bool_env(() -> false, "testing_gbe") == false end @testset "undefined" begin delete!(ENV, "testing_gbe") @test !haskey(ENV, "testing_gbe") @test Base.get_bool_env("testing_gbe", true) == true + @test Base.get_bool_env(() -> true, "testing_gbe") == true @test Base.get_bool_env("testing_gbe", false) == false + @test Base.get_bool_env(() -> false, "testing_gbe") == false end @testset "unrecognized" begin for v in ("truw", "falls") ENV["testing_gbe"] = v @test Base.get_bool_env("testing_gbe", true) === nothing + @test_throws ArgumentError Base.get_bool_env("testing_gbe", true, throw=true) @test Base.get_bool_env("testing_gbe", false) === nothing + @test_throws ArgumentError Base.get_bool_env("testing_gbe", false, throw=true) end end diff --git a/test/error.jl b/test/error.jl index e9cdfa100bc81..af1474fb091cd 100644 --- a/test/error.jl +++ b/test/error.jl @@ -96,7 +96,11 @@ end f44319(1) catch e s = sprint(showerror, e) - @test s == "MethodError: no method matching f44319(::Int$(Sys.WORD_SIZE))\n\nClosest candidates are:\n f44319()\n @ $curmod_str none:0\n" + @test s == """MethodError: no method matching f44319(::Int$(Sys.WORD_SIZE)) + The function `f44319` exists, but no method is defined for this combination of argument types. + + Closest candidates are:\n f44319()\n @ $curmod_str none:0 + """ end end @@ -123,3 +127,36 @@ end visited = test_exceptions(Base) test_exceptions(Core, visited) end + +# inference quality test for `error` +@test Base.infer_return_type(error, (Any,)) === Union{} +@test Base.infer_return_type(xs->error(xs...), (Vector{Any},)) === Union{} +module Issue54029 +export raise54029 +Base.Experimental.@max_methods 1 +raise54029(x) = error(x) +end +using .Issue54029 +@test Base.infer_return_type(raise54029, (Any,)) === Union{} +@test Base.infer_return_type(xs->raise54029(xs...), (Vector{Any},)) === Union{} + +@testset "CompositeException" begin + ce = CompositeException() + @test isempty(ce) + @test length(ce) == 0 + @test eltype(ce) == Any + str = sprint(showerror, ce) + @test str == "CompositeException()\n" + push!(ce, ErrorException("something sad has happened")) + @test !isempty(ce) + @test length(ce) == 1 + pushfirst!(ce, ErrorException("something sad has happened even earlier")) + @test length(ce) == 2 + # test iterate + for ex in ce + @test ex isa ErrorException + end + push!(ce, ErrorException("something sad has happened yet again")) + str = sprint(showerror, ce) + @test str == "something sad has happened even earlier\n\n...and 2 more exceptions.\n" +end diff --git a/test/errorshow.jl b/test/errorshow.jl index 3098b684375ec..5d0640601b398 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -5,6 +5,12 @@ using Random, LinearAlgebra # For curmod_* include("testenv.jl") +# re-register only the error hints that are being tested here ( +Base.Experimental.register_error_hint(Base.noncallable_number_hint_handler, MethodError) +Base.Experimental.register_error_hint(Base.string_concatenation_hint_handler, MethodError) +Base.Experimental.register_error_hint(Base.methods_on_iterable, MethodError) +Base.Experimental.register_error_hint(Base.nonsetable_type_hint_handler, MethodError) + @testset "SystemError" begin err = try; systemerror("reason", Cint(0)); false; catch ex; ex; end::SystemError @@ -74,8 +80,12 @@ Base.show_method_candidates(buf, Base.MethodError(method_c1,(1, "", ""))) Base.show_method_candidates(buf, Base.MethodError(method_c1,(1., "", ""))) @test occursin("\n\nClosest candidates are:\n method_c1(::Float64, ::AbstractString...)$cmod$cfile$c1line\n", String(take!(buf))) -# Have no matches so should return empty +# Have no matches, but still print up to 3 Base.show_method_candidates(buf, Base.MethodError(method_c1,(1, 1, 1))) +@test occursin("\n\nClosest candidates are:\n method_c1(!Matched::Float64, !Matched::AbstractString...)$cmod$cfile$c1line\n", String(take!(buf))) + +function nomethodsfunc end +Base.show_method_candidates(buf, Base.MethodError(nomethodsfunc,(1, 1, 1))) @test isempty(String(take!(buf))) # matches the implicit constructor -> convert method @@ -350,7 +360,7 @@ let undefvar err_str = @except_str Vector{Any}(undef, 1)[1] UndefRefError @test err_str == "UndefRefError: access to undefined reference" err_str = @except_str undefvar UndefVarError - @test err_str == "UndefVarError: `undefvar` not defined" + @test err_str == "UndefVarError: `undefvar` not defined in local scope" err_str = @except_str read(IOBuffer(), UInt8) EOFError @test err_str == "EOFError: read end of file" err_str = @except_str Dict()[:doesnotexist] KeyError @@ -460,7 +470,7 @@ let err_str, @test startswith(sprint(show, which(StructWithUnionAllMethodDefs{<:Integer}, (Any,))), "($(curmod_prefix)StructWithUnionAllMethodDefs{T} where T<:Integer)(x)") @test repr("text/plain", FunctionLike()) == "(::$(curmod_prefix)FunctionLike) (generic function with 1 method)" - @test repr("text/plain", Core.arraysize) == "arraysize (built-in function)" + @test repr("text/plain", Core.getfield) == "getfield (built-in function)" err_str = @except_stackframe String() ErrorException @test err_str == "String() at $sn:$(method_defs_lineno + 0)" @@ -501,7 +511,7 @@ let @test (@macroexpand @fastmath + ) == :(Base.FastMath.add_fast) @test (@macroexpand @fastmath min(1) ) == :(Base.FastMath.min_fast(1)) let err = try; @macroexpand @doc "" f() = @x; catch ex; ex; end - @test err == UndefVarError(Symbol("@x")) + @test err == UndefVarError(Symbol("@x"), @__MODULE__) end @test (@macroexpand @seven_dollar $bar) == 7 x = 2 @@ -534,6 +544,14 @@ end @test (@macroexpand1 @nest2b 42) == _macroexpand1(:(@nest2b 42)) end +module TwoargMacroExpand +macro modulecontext(); return __module__; end +end +@test (@__MODULE__) == @macroexpand TwoargMacroExpand.@modulecontext +@test TwoargMacroExpand == @macroexpand TwoargMacroExpand @modulecontext +@test (@__MODULE__) == @macroexpand1 TwoargMacroExpand.@modulecontext +@test TwoargMacroExpand == @macroexpand1 TwoargMacroExpand @modulecontext + foo_9965(x::Float64; w=false) = x foo_9965(x::Int) = 2x @@ -574,11 +592,12 @@ module EnclosingModule abstract type AbstractTypeNoConstructors end end let - method_error = MethodError(EnclosingModule.AbstractTypeNoConstructors, ()) + method_error = MethodError(EnclosingModule.AbstractTypeNoConstructors, (), Base.get_world_counter()) # Test that it shows a special message when no constructors have been defined by the user. - @test sprint(showerror, method_error) == - "MethodError: no constructors have been defined for $(EnclosingModule.AbstractTypeNoConstructors)" + @test startswith(sprint(showerror, method_error), + """MethodError: no constructors have been defined for $(EnclosingModule.AbstractTypeNoConstructors) + The type `$(EnclosingModule.AbstractTypeNoConstructors)` exists, but no method is defined for this combination of argument types when trying to construct it.""") # Does it go back to previous behaviour when there *is* at least # one constructor defined? @@ -637,6 +656,24 @@ end @test startswith(str, "MethodError: no method matching f21006(::Tuple{})") @test !occursin("The applicable method may be too new", str) end + + str = sprint(Base.showerror, MethodError(+, (1.0, 2.0))) + @test startswith(str, "MethodError: no method matching +(::Float64, ::Float64)") + @test occursin("This error has been manually thrown, explicitly", str) + + str = sprint(Base.showerror, MethodError(+, (1.0, 2.0), Base.get_world_counter())) + @test startswith(str, "MethodError: no method matching +(::Float64, ::Float64)") + @test occursin("This error has been manually thrown, explicitly", str) + + str = sprint(Base.showerror, MethodError(Core.kwcall, ((; a=3.0), +, 1.0, 2.0))) + @test startswith(str, "MethodError: no method matching +(::Float64, ::Float64; a::Float64)") + @test occursin("This error has been manually thrown, explicitly", str) + + str = sprint(Base.showerror, MethodError(Core.kwcall, ((; a=3.0), +, 1.0, 2.0), Base.get_world_counter())) + @test startswith(str, "MethodError: no method matching +(::Float64, ::Float64; a::Float64)") + @test occursin("This method does not support all of the given keyword arguments", str) + + @test_throws "MethodError: no method matching kwcall()" Core.kwcall() end # Issue #50200 @@ -645,8 +682,9 @@ using Base.Experimental: @opaque test_no_error(f) = @test f() === nothing function test_worldage_error(f) ex = try; f(); error("Should not have been reached") catch ex; ex; end - @test occursin("The applicable method may be too new", sprint(Base.showerror, ex)) - @test !occursin("!Matched::", sprint(Base.showerror, ex)) + strex = sprint(Base.showerror, ex) + @test occursin("The applicable method may be too new", strex) + @test !occursin("!Matched::", sprint(Base.showerror, strex)) end global callback50200 @@ -709,6 +747,22 @@ let err_str @test count(==("Maybe you forgot to use an operator such as *, ^, %, / etc. ?"), split(err_str, '\n')) == 1 end +let err_str + a = [1 2; 3 4]; + err_str = @except_str (a[1][2] = 5) MethodError + @test occursin("\nAre you trying to index into an array? For multi-dimensional arrays, separate the indices with commas: ", err_str) + @test occursin("a[1, 2]", err_str) + @test occursin("rather than a[1][2]", err_str) +end + +let err_str + d = Dict + err_str = @except_str (d[1] = 5) MethodError + @test occursin("\nYou attempted to index the type Dict, rather than an instance of the type. Make sure you create the type using its constructor: ", err_str) + @test occursin("d = Dict([...])", err_str) + @test occursin(" rather than d = Dict", err_str) +end + # Execute backtrace once before checking formatting, see #38858 backtrace() @@ -987,6 +1041,34 @@ let err_str @test occursin("String concatenation is performed with *", err_str) end +struct MissingLength; end +struct MissingSize; end +Base.IteratorSize(::Type{MissingSize}) = Base.HasShape{2}() +Base.iterate(::MissingLength) = nothing +Base.iterate(::MissingSize) = nothing + +let err_str + expected = "Finding the minimum of an iterable is performed with `minimum`." + err_str = @except_str min([1,2,3]) MethodError + @test occursin(expected, err_str) + err_str = @except_str min((i for i in 1:3)) MethodError + @test occursin(expected, err_str) + expected = "Finding the maximum of an iterable is performed with `maximum`." + err_str = @except_str max([1,2,3]) MethodError + @test occursin(expected, err_str) + + expected = "You may need to implement the `length` method or define `IteratorSize` for this type to be `SizeUnknown`." + err_str = @except_str length(MissingLength()) MethodError + @test occursin(expected, err_str) + err_str = @except_str collect(MissingLength()) MethodError + @test occursin(expected, err_str) + expected = "You may need to implement the `length` and `size` methods for `IteratorSize` `HasShape`." + err_str = @except_str size(MissingSize()) MethodError + @test occursin(expected, err_str) + err_str = @except_str collect(MissingSize()) MethodError + @test occursin(expected, err_str) +end + @testset "unused argument names" begin g(::Int) = backtrace() bt = g(1) @@ -1079,3 +1161,28 @@ let e = @test_throws MethodError convert(TypeCompareError{Float64,1}, TypeCompar @test occursin("TypeCompareError{Float64,1}", str) @test !occursin("TypeCompareError{Float64{},2}", str) # No {...} for types without params end + +@testset "InexactError for Inf16 should print '16' (#51087)" begin + @test sprint(showerror, InexactError(:UInt128, UInt128, Inf16)) == "InexactError: UInt128(Inf16)" + + for IntType in [Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UInt128] + IntStr = string(IntType) + for InfVal in Any[Inf, Inf16, Inf32, Inf64] + InfStr = repr(InfVal) + e = @test_throws InexactError IntType(InfVal) + str = sprint(Base.showerror, e.value) + @test occursin("InexactError: $IntStr($InfStr)", str) + end + end +end + +# error message hint from PR #22647 +@test_throws "Many shells" cd("~") +@test occursin("Many shells", sprint(showerror, Base.IOError("~", Base.UV_ENOENT))) + +# issue #47559" +@test_throws("MethodError: no method matching invoke Returns(::Any, ::Val{N}) where N", + invoke(Returns, Tuple{Any,Val{N}} where N, 1, Val(1))) + +f33793(x::Float32, y::Float32) = 1 +@test_throws "\nClosest candidates are:\n f33793(!Matched::Float32, !Matched::Float32)\n" f33793(Float64(0.0), Float64(0.0)) diff --git a/test/euler.jl b/test/euler.jl index 9af79a44cc0d3..c8d0e9a734fd2 100644 --- a/test/euler.jl +++ b/test/euler.jl @@ -2,7 +2,7 @@ ## Project Euler # -# problems: http://projecteuler.net/problems +# problems: https://projecteuler.net/problems # solutions: https://code.google.com/p/projecteuler-solutions/wiki/ProjectEulerSolutions #1: 233168 diff --git a/test/fastmath.jl b/test/fastmath.jl index 34744f325ad7f..120c0ece685bb 100644 --- a/test/fastmath.jl +++ b/test/fastmath.jl @@ -256,6 +256,22 @@ end @testset "literal powers" begin @test @fastmath(2^-2) == @fastmath(2.0^-2) == 0.25 + # Issue #53817 + # Note that exponent -2^63 fails testing because of issue #53881 + # Therefore we test with -(2^63-1). For Int == Int32 there is an analogue restriction. + # See also PR #53860. + if Int == Int64 + @test @fastmath(2^-9223372036854775807) === 0.0 + @test_throws DomainError @fastmath(2^-9223372036854775809) + @test @fastmath(1^-9223372036854775807) isa Float64 + @test @fastmath(1^-9223372036854775809) isa Int + elseif Int == Int32 + @test @fastmath(2^-2147483647) === 0.0 + @test_throws DomainError @fastmath(2^-2147483649) + @test @fastmath(1^-2147483647) isa Float64 + @test @fastmath(1^-2147483649) isa Int + end + @test_throws MethodError @fastmath(^(2)) end @testset "sincos fall-backs" begin diff --git a/test/file.jl b/test/file.jl index 0b6440934c41a..6db4285da3f49 100644 --- a/test/file.jl +++ b/test/file.jl @@ -31,6 +31,8 @@ if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER symlink(subdir, dirlink) @test stat(dirlink) == stat(subdir) @test readdir(dirlink) == readdir(subdir) + @test map(o->o.names, Base.Filesystem._readdirx(dirlink)) == map(o->o.names, Base.Filesystem._readdirx(subdir)) + @test realpath.(Base.Filesystem._readdirx(dirlink)) == realpath.(Base.Filesystem._readdirx(subdir)) # relative link relsubdirlink = joinpath(subdir, "rel_subdirlink") @@ -38,6 +40,7 @@ if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER symlink(reldir, relsubdirlink) @test stat(relsubdirlink) == stat(subdir2) @test readdir(relsubdirlink) == readdir(subdir2) + @test Base.Filesystem._readdirx(relsubdirlink) == Base.Filesystem._readdirx(subdir2) # creation of symlink to directory that does not yet exist new_dir = joinpath(subdir, "new_dir") @@ -56,6 +59,7 @@ if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER mkdir(new_dir) touch(foo_file) @test readdir(new_dir) == readdir(nedlink) + @test realpath.(Base.Filesystem._readdirx(new_dir)) == realpath.(Base.Filesystem._readdirx(nedlink)) rm(foo_file) rm(new_dir) @@ -124,6 +128,9 @@ end end @test_throws ArgumentError tempname(randstring()) end +@testset "tempname with suffix" begin + @test !isfile(tempname(suffix = "_foo.txt")) +end child_eval(code::String) = eval(Meta.parse(readchomp(`$(Base.julia_cmd()) -E $code`))) @@ -355,7 +362,7 @@ chmod(file, filemode(file) | 0o222) @test filesize(file) == 0 # issue #26685 -@test !isfile("http://google.com") +@test !isfile("https://google.com") if Sys.iswindows() permissions = 0o444 @@ -637,9 +644,11 @@ end MAX_PATH = (Sys.iswindows() ? 260 - length(PATH_PREFIX) : 255) - 9 for i = 0:9 local tmp = joinpath(PATH_PREFIX, "x"^MAX_PATH * "123456789"[1:i]) - @test withenv(var => tmp) do - tempdir() - end == tmp + no_error_logging() do + @test withenv(var => tmp) do + tempdir() + end == tmp + end end end @@ -1444,6 +1453,10 @@ rm(dirwalk, recursive=true) touch(randstring()) end @test issorted(readdir()) + @test issorted(Base.Filesystem._readdirx()) + @test map(o->o.name, Base.Filesystem._readdirx()) == readdir() + @test map(o->o.path, Base.Filesystem._readdirx()) == readdir(join=true) + @test count(isfile, readdir(join=true)) == count(isfile, Base.Filesystem._readdirx()) end end end @@ -1666,23 +1679,44 @@ else ) end -@testset "chmod/isexecutable" begin +@testset "chmod/isexecutable/isreadable/iswritable" begin mktempdir() do dir - mkdir(joinpath(dir, "subdir")) + subdir = joinpath(dir, "subdir") fpath = joinpath(dir, "subdir", "foo") - # Test that we can actually set the executable bit on all platforms. + @test !ispath(subdir) + mkdir(subdir) + @test ispath(subdir) + + @test !ispath(fpath) touch(fpath) + @test ispath(fpath) + + # Test that we can actually set the executable/readable/writeable bit on all platforms. chmod(fpath, 0o644) @test !Sys.isexecutable(fpath) + @test Sys.isreadable(fpath) + @test Sys.iswritable(fpath) skip=Sys.iswindows() chmod(fpath, 0o755) @test Sys.isexecutable(fpath) + @test Sys.isreadable(fpath) + @test Sys.iswritable(fpath) skip=Sys.iswindows() + chmod(fpath, 0o444) + @test !Sys.isexecutable(fpath) + @test Sys.isreadable(fpath) + @test !Sys.iswritable(fpath) + chmod(fpath, 0o244) + @test !Sys.isexecutable(fpath) + @test !Sys.isreadable(fpath) skip=Sys.iswindows() + @test Sys.iswritable(fpath) skip=Sys.iswindows() # Ensure that, on Windows, where inheritance is default, # chmod still behaves as we expect. if Sys.iswindows() - chmod(joinpath(dir, "subdir"), 0o666) - @test Sys.isexecutable(fpath) + chmod(subdir, 0o666) + @test !Sys.isexecutable(fpath) + @test Sys.isreadable(fpath) + @test_skip Sys.iswritable(fpath) end # Reset permissions to all at the end, so it can be deleted properly. @@ -1703,6 +1737,28 @@ if Sys.iswindows() end end +# Unusually for structs, we test this explicitly because the fields of StatStruct +# is part of its documentation, and therefore cannot change. +@testset "StatStruct has promised fields" begin + f, io = mktemp() + s = stat(f) + @test s isa Base.StatStruct + + @test s.desc isa Union{String, Base.OS_HANDLE} + @test s.size isa Int64 + @test s.device isa UInt + @test s.inode isa UInt + @test s.mode isa UInt + @test s.nlink isa Int + @test s.uid isa UInt + @test s.gid isa UInt + @test s.rdev isa UInt + @test s.blksize isa Int64 + @test s.blocks isa Int64 + @test s.mtime isa Float64 + @test s.ctime isa Float64 +end + @testset "StatStruct show's extended details" begin f, io = mktemp() s = stat(f) diff --git a/test/filesystem.jl b/test/filesystem.jl index 79beea9f66ac1..566ca08ec910e 100644 --- a/test/filesystem.jl +++ b/test/filesystem.jl @@ -40,3 +40,9 @@ import Base.Filesystem: S_IRUSR, S_IRGRP, S_IROTH @test S_IRUSR & ~S_IRGRP == S_IRUSR @test typeof(S_IRUSR) == typeof(S_IRGRP) == typeof(S_IROTH) end + +@testset "Base.Filesystem docstrings" begin + undoc = Docs.undocumented_names(Base.Filesystem) + @test_broken isempty(undoc) + @test undoc == [:File, :Filesystem, :cptree, :futime, :rename, :sendfile, :unlink] +end diff --git a/test/float16.jl b/test/float16.jl index 75f9b55b6d51c..10fb6b37db16d 100644 --- a/test/float16.jl +++ b/test/float16.jl @@ -203,6 +203,11 @@ const minsubf16_32 = Float32(minsubf16) # issues #33076 @test Float16(1f5) == Inf16 +# issue #52394 +@test Float16(10^8 // (10^9 + 1)) == convert(Float16, 10^8 // (10^9 + 1)) == Float16(0.1) +@test Float16((typemax(UInt128)-0x01) // typemax(UInt128)) == Float16(1.0) +@test Float32((typemax(UInt128)-0x01) // typemax(UInt128)) == Float32(1.0) + @testset "conversion to Float16 from" begin for T in (Float32, Float64, BigFloat) @testset "conversion from $T" begin diff --git a/test/functional.jl b/test/functional.jl index fce64c0e5720a..3436fb8911cc1 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -52,9 +52,6 @@ end # foreach let a = [] - foreach(()->push!(a,0)) - @test a == [0] - a = [] foreach(x->push!(a,x), [1,5,10]) @test a == [1,5,10] a = [] diff --git a/test/gc.jl b/test/gc.jl index e085c1d8658e5..e9960bc907438 100644 --- a/test/gc.jl +++ b/test/gc.jl @@ -15,6 +15,19 @@ function run_gctest(file) end end +function run_nonzero_page_utilization_test() + GC.gc() + page_utilization = Base.gc_page_utilization_data() + # at least one of the pools should have nonzero page_utilization + @test any(page_utilization .> 0) +end + +function run_pg_size_test() + page_size = @ccall jl_get_pg_size()::UInt64 + # supported page sizes: 4KB and 16KB + @test page_size == (1 << 12) || page_size == (1 << 14) +end + # !!! note: # Since we run our tests on 32bit OS as well we confine ourselves # to parameters that allocate about 512MB of objects. Max RSS is lower @@ -25,3 +38,12 @@ end run_gctest("gc/objarray.jl") run_gctest("gc/chunks.jl") end + +@testset "GC page metrics" begin + run_nonzero_page_utilization_test() + run_pg_size_test() +end + +@testset "Base.GC docstrings" begin + @test isempty(Docs.undocumented_names(GC)) +end diff --git a/test/gcext/gcext.c b/test/gcext/gcext.c index 90b5ee82d80b5..d5bf91ec8c9ab 100644 --- a/test/gcext/gcext.c +++ b/test/gcext/gcext.c @@ -32,7 +32,7 @@ static inline int lt_ptr(void *a, void *b) return (uintptr_t)a < (uintptr_t)b; } -/* align pointer to full word if mis-aligned */ +/* align pointer to full word if misaligned */ static inline void *align_ptr(void *p) { uintptr_t u = (uintptr_t)p; diff --git a/test/gmp.jl b/test/gmp.jl index 8bfe90ec7d9e4..13413abe55f9d 100644 --- a/test/gmp.jl +++ b/test/gmp.jl @@ -220,6 +220,8 @@ end end @testset "combinatorics" begin @test factorial(BigInt(40)) == parse(BigInt,"815915283247897734345611269596115894272000000000") + @test_throws DomainError factorial(BigInt(-1)) + @test_throws DomainError factorial(BigInt(rand(-999:-2))) @test binomial(BigInt(1), -1) == BigInt(0) @test binomial(BigInt(1), 2) == BigInt(0) @test binomial(BigInt(-53), 42) == parse(BigInt,"959509335087854414441273718") diff --git a/test/hashing.jl b/test/hashing.jl index 7c3024640aa32..173a31d10a6a9 100644 --- a/test/hashing.jl +++ b/test/hashing.jl @@ -302,3 +302,5 @@ struct AUnionParam{T<:Union{Nothing,Float32,Float64}} end # test hashing of rational with odd denominator @test hash(5//3) == hash(big(5)//3) end + +@test Core.Compiler.is_foldable_nothrow(Base.infer_effects(hash, Tuple{Type{Int}, UInt})) diff --git a/test/intfuncs.jl b/test/intfuncs.jl index ceaac235a3da9..ed661b2806fb5 100644 --- a/test/intfuncs.jl +++ b/test/intfuncs.jl @@ -221,7 +221,7 @@ end @test_throws MethodError gcdx(MyOtherRational(2//3), MyOtherRational(3//4)) end -@testset "invmod" begin +@testset "invmod(n, m)" begin @test invmod(6, 31) === 26 @test invmod(-1, 3) === 2 @test invmod(1, -3) === -2 @@ -256,6 +256,37 @@ end end end +@testset "invmod(n)" begin + for T in (Int8,UInt8,Int16,UInt16,Int32,UInt32,Int64,UInt64,Int128,UInt128) + if sizeof(T) ≤ 2 + # test full domain for small types + for a = typemin(T)+true:T(2):typemax(T) + b = invmod(a) + @test a * b == 1 + end + else + # test random sample for large types + for _ = 1:2^12 + a = rand(T) | true + b = invmod(a) + @test a * b == 1 + end + end + end +end + +@testset "invmod(n, T)" begin + for S in (Int8,UInt8,Int16,UInt16,Int32,UInt32,Int64,UInt64,Int128,UInt128), + T in (Int8,UInt8,Int16,UInt16,Int32,UInt32,Int64,UInt64,Int128,UInt128) + for _ = 1:2^8 + a = rand(S) | true + b = invmod(a, T) + @test (a * b) % T == 1 + @test (a % T) * b == 1 + end + end +end + @testset "powermod" begin @test powermod(2, 3, 5) == 3 @test powermod(2, 3, -5) == -2 diff --git a/test/intrinsics.jl b/test/intrinsics.jl index 3c49afe2c4d7e..6fbe4e5364354 100644 --- a/test/intrinsics.jl +++ b/test/intrinsics.jl @@ -180,28 +180,12 @@ end @test_intrinsic Core.Intrinsics.fptoui UInt Float16(3.3) UInt(3) end -if Sys.ARCH == :aarch64 || Sys.ARCH === :powerpc64le || Sys.ARCH === :ppc64le - # On AArch64 we are following the `_Float16` ABI. Buthe these functions expect `Int16`. - # TODO: SHould we have `Chalf == Int16` and `Cfloat16 == Float16`? - extendhfsf2(x::Float16) = ccall("extern __extendhfsf2", llvmcall, Float32, (UInt16,), reinterpret(UInt16, x)) - gnu_h2f_ieee(x::Float16) = ccall("extern __gnu_h2f_ieee", llvmcall, Float32, (UInt16,), reinterpret(UInt16, x)) - truncsfhf2(x::Float32) = reinterpret(Float16, ccall("extern __truncsfhf2", llvmcall, UInt16, (Float32,), x)) - gnu_f2h_ieee(x::Float32) = reinterpret(Float16, ccall("extern __gnu_f2h_ieee", llvmcall, UInt16, (Float32,), x)) - truncdfhf2(x::Float64) = reinterpret(Float16, ccall("extern __truncdfhf2", llvmcall, UInt16, (Float64,), x)) -else - extendhfsf2(x::Float16) = ccall("extern __extendhfsf2", llvmcall, Float32, (Float16,), x) - gnu_h2f_ieee(x::Float16) = ccall("extern __gnu_h2f_ieee", llvmcall, Float32, (Float16,), x) - truncsfhf2(x::Float32) = ccall("extern __truncsfhf2", llvmcall, Float16, (Float32,), x) - gnu_f2h_ieee(x::Float32) = ccall("extern __gnu_f2h_ieee", llvmcall, Float16, (Float32,), x) - truncdfhf2(x::Float64) = ccall("extern __truncdfhf2", llvmcall, Float16, (Float64,), x) -end - @testset "Float16 intrinsics (crt)" begin - @test extendhfsf2(Float16(3.3)) == 3.3007812f0 + gnu_h2f_ieee(x::Float16) = ccall("julia__gnu_h2f_ieee", Float32, (Float16,), x) + gnu_f2h_ieee(x::Float32) = ccall("julia__gnu_f2h_ieee", Float16, (Float32,), x) + @test gnu_h2f_ieee(Float16(3.3)) == 3.3007812f0 - @test truncsfhf2(3.3f0) == Float16(3.3) @test gnu_f2h_ieee(3.3f0) == Float16(3.3) - @test truncdfhf2(3.3) == Float16(3.3) end using Base.Experimental: @force_compile @@ -236,7 +220,7 @@ for TT in (Int8, Int16, Int32, Int64, Int128, Int256, Int512, Complex{Int32}, Co @test_throws TypeError Core.Intrinsics.atomic_pointerreplace(p, T(10), S(3), :sequentially_consistent, :sequentially_consistent) end @test Core.Intrinsics.pointerref(p, 1, 1) === T(10) === r[] - if sizeof(r) > 8 + if sizeof(r) > 2*sizeof(Int) @test_throws ErrorException("atomic_pointerref: invalid pointer for atomic operation") unsafe_load(p, :sequentially_consistent) @test_throws ErrorException("atomic_pointerset: invalid pointer for atomic operation") unsafe_store!(p, T(1), :sequentially_consistent) @test_throws ErrorException("atomic_pointerswap: invalid pointer for atomic operation") unsafe_swap!(p, T(100), :sequentially_consistent) diff --git a/test/iobuffer.jl b/test/iobuffer.jl index ec77903b4a5b8..d82a68c61f780 100644 --- a/test/iobuffer.jl +++ b/test/iobuffer.jl @@ -120,6 +120,7 @@ end Base.compact(io) @test position(io) == 0 @test ioslength(io) == 0 + Base._resize!(io,0) Base.ensureroom(io,50) @test position(io) == 0 @test ioslength(io) == 0 @@ -195,6 +196,31 @@ end @test position(skip(io, -3)) == 0 end +@testset "issue #53908" begin + @testset "offset $first" for first in (false, true) + b = collect(0x01:0x05) + sizehint!(b, 100; first) # make offset non zero + io = IOBuffer(b) + @test position(skip(io, 4)) == 4 + @test position(skip(io, typemax(Int))) == 5 + @test position(skip(io, typemax(Int128))) == 5 + @test position(skip(io, typemax(Int32))) == 5 + @test position(skip(io, typemin(Int))) == 0 + @test position(skip(io, typemin(Int128))) == 0 + @test position(skip(io, typemin(Int32))) == 0 + @test position(skip(io, 4)) == 4 + @test position(skip(io, -2)) == 2 + @test position(skip(io, -2)) == 0 + @test position(seek(io, -2)) == 0 + @test position(seek(io, typemax(Int))) == 5 + @test position(seek(io, typemax(Int128))) == 5 + @test position(seek(io, typemax(Int32))) == 5 + @test position(seek(io, typemin(Int))) == 0 + @test position(seek(io, typemin(Int128))) == 0 + @test position(seek(io, typemin(Int32))) == 0 + end +end + @testset "pr #11554" begin io = IOBuffer(SubString("***αhelloworldω***", 4, 16)) io2 = IOBuffer(Vector{UInt8}(b"goodnightmoon"), read=true, write=true) @@ -357,3 +383,8 @@ end seek(io,0) @test Base.read_sub(io,v,1,1) == [1,0] end + +@testset "with offset" begin + b = pushfirst!([0x02], 0x01) + @test take!(IOBuffer(b)) == [0x01, 0x02] +end diff --git a/test/iostream.jl b/test/iostream.jl index bc4751fb1fca7..4ba2423f0f558 100644 --- a/test/iostream.jl +++ b/test/iostream.jl @@ -119,6 +119,24 @@ end end end +@testset "read!/write(::IO, A::StridedArray)" begin + s1 = reshape(view(rand(UInt8, 16), 1:16), 2, 2, 2, 2) + s2 = view(s1, 1:2, 1:2, 1:2, 1:2) + s3 = view(s1, 1:2, 1:2, 1, 1:2) + mktemp() do path, io + b = Vector{UInt8}(undef, 17) + for s::StridedArray in (s3, s1, s2) + @test write(io, s) == length(s) + seek(io, 0) + @test readbytes!(io, b) == length(s) + seek(io, 0) + @test view(b, 1:length(s)) == vec(s) + @test read!(io, fill!(deepcopy(s), 0)) == s + seek(io, 0) + end + end +end + @test Base.open_flags(read=false, write=true, append=false) == (read=false, write=true, create=true, truncate=true, append=false) @testset "issue #30978" begin diff --git a/test/iterators.jl b/test/iterators.jl index 59588bdac9684..3616a17b31d31 100644 --- a/test/iterators.jl +++ b/test/iterators.jl @@ -11,6 +11,10 @@ using Dates: Date, Day # issue #4718 @test collect(Iterators.filter(x->x[1], zip([true, false, true, false],"abcd"))) == [(true,'a'),(true,'c')] +# issue #45085 +@test_throws ArgumentError Iterators.reverse(zip("abc", "abcd")) +@test_throws ArgumentError Iterators.reverse(zip("abc", Iterators.cycle("ab"))) + let z = zip(1:2) @test size(z) == (2,) @test collect(z) == [(1,), (2,)] @@ -250,6 +254,22 @@ let i = 0 @test !Base.isdone(cycle(0:3), 1) end +@testset "cycle(iter, n)" begin + @test collect(cycle(0:3, 2)) == [0, 1, 2, 3, 0, 1, 2, 3] + @test collect(cycle(Iterators.filter(iseven, 1:4), 2)) == [2, 4, 2, 4] + @test collect(take(cycle(countfrom(11), 3), 4)) == 11:14 + + @test isempty(cycle(1:0)) == isempty(cycle(1:0, 3)) == true + @test isempty(cycle(1:5, 0)) + @test isempty(cycle(Iterators.filter(iseven, 1:4), 0)) + + @test eltype(cycle(0:3, 2)) === Int + @test Base.IteratorEltype(cycle(0:3, 2)) == Base.HasEltype() + + Base.haslength(cycle(0:3, 2)) == false # but not sure we should test these + Base.IteratorSize(cycle(0:3, 2)) == Base.SizeUnknown() +end + # repeated # -------- let i = 0 @@ -849,8 +869,10 @@ end v, s = iterate(z) @test Base.isdone(z, s) end - # Stateful wrapping mutable iterators of known length (#43245) - @test length(Iterators.Stateful(Iterators.Stateful(1:5))) == 5 + # Stateful does not define length + let s = Iterators.Stateful(Iterators.Stateful(1:5)) + @test_throws MethodError length(s) + end end @testset "pair for Svec" begin @@ -862,6 +884,10 @@ end @testset "inference for large zip #26765" begin x = zip(1:2, ["a", "b"], (1.0, 2.0), Base.OneTo(2), Iterators.repeated("a"), 1.0:0.2:2.0, (1 for i in 1:2), Iterators.Stateful(["a", "b", "c"]), (1.0 for i in 1:2, j in 1:3)) + @test Base.IteratorSize(x) isa Base.SizeUnknown + x = zip(1:2, ["a", "b"], (1.0, 2.0), Base.OneTo(2), Iterators.repeated("a"), 1.0:0.2:2.0, + (1 for i in 1:2), Iterators.cycle(Iterators.Stateful(["a", "b", "c"])), (1.0 for i in 1:2, j in 1:3)) + @test Base.IteratorSize(x) isa Base.HasLength @test @inferred(length(x)) == 2 z = Iterators.filter(x -> x[1] >= 1, x) @test @inferred(eltype(z)) <: Tuple{Int,String,Float64,Int,String,Float64,Any,String,Any} @@ -870,20 +896,20 @@ end end @testset "Stateful fix #30643" begin - @test Base.IteratorSize(1:10) isa Base.HasShape + @test Base.IteratorSize(1:10) isa Base.HasShape{1} a = Iterators.Stateful(1:10) - @test Base.IteratorSize(a) isa Base.HasLength - @test length(a) == 10 + @test Base.IteratorSize(a) isa Base.SizeUnknown + @test !Base.isdone(a) @test length(collect(a)) == 10 - @test length(a) == 0 + @test Base.isdone(a) b = Iterators.Stateful(Iterators.take(1:10,3)) - @test Base.IteratorSize(b) isa Base.HasLength - @test length(b) == 3 + @test Base.IteratorSize(b) isa Base.SizeUnknown + @test !Base.isdone(b) @test length(collect(b)) == 3 - @test length(b) == 0 + @test Base.isdone(b) c = Iterators.Stateful(Iterators.countfrom(1)) @test Base.IteratorSize(c) isa Base.IsInfinite - @test length(Iterators.take(c,3)) == 3 + @test !Base.isdone(Iterators.take(c,3)) @test length(collect(Iterators.take(c,3))) == 3 d = Iterators.Stateful(Iterators.filter(isodd,1:10)) @test Base.IteratorSize(d) isa Base.SizeUnknown @@ -1001,3 +1027,25 @@ end end @test v == () end + +@testset "collect partition substring" begin + @test collect(Iterators.partition(lstrip("01111", '0'), 2)) == ["11", "11"] +end + +let itr = (i for i in 1:9) # Base.eltype == Any + @test first(Iterators.partition(itr, 3)) isa Vector{Any} + @test collect(zip(repeat([Iterators.Stateful(itr)], 3)...)) == [(1, 2, 3), (4, 5, 6), (7, 8, 9)] +end + +@testset "no single-argument map methods" begin + maps = (tuple, Returns(nothing), (() -> nothing)) + mappers = (Iterators.map, map, foreach) + for f ∈ maps, m ∈ mappers + @test !applicable(m, f) + @test !hasmethod(m, Tuple{typeof(f)}) + end +end + +@testset "Iterators docstrings" begin + @test isempty(Docs.undocumented_names(Iterators)) +end diff --git a/test/llvmcall2.jl b/test/llvmcall2.jl index 07b27fc407433..e3e89bb916f2d 100644 --- a/test/llvmcall2.jl +++ b/test/llvmcall2.jl @@ -73,3 +73,12 @@ end jl_str = unsafe_string(str) @test length(jl_str) > 4 end + + +# boolean structs +const NT4I = NTuple{4, VecElement{Int}} +const NT4B = NTuple{4, VecElement{Bool}} +f_nt4b(x, y) = ccall("llvm.sadd.with.overflow", llvmcall, Pair{NT4B, NT4B}, (NT4B, NT4B), x, y) +f_nt4i(x, y) = ccall("llvm.sadd.with.overflow", llvmcall, Pair{NT4I, NT4B}, (NT4I, NT4I), x, y) +@test f_nt4b((false, true, false, true), (false, false, true, true)) === (NT4B((false, true, true, false)) => NT4B((false, false, false, true))) +@test f_nt4i((typemin(Int), 0, typemax(Int), typemax(Int)), (-1, typemax(Int),-1, 1)) === (NT4I((typemax(Int), typemax(Int), typemax(Int)-1, typemin(Int))) => NT4B((true, false, false, true))) diff --git a/test/llvmpasses/Makefile b/test/llvmpasses/Makefile index 7318d1b67da02..d9fdfa190f3cf 100644 --- a/test/llvmpasses/Makefile +++ b/test/llvmpasses/Makefile @@ -30,4 +30,7 @@ update-help: $(JULIAHOME)/deps/srccache/llvm/llvm/utils/update_test_checks.py \ --help -.PHONY: $(TESTS) $(addprefix update-,$(TESTS_ll)) check all . +clean: + rm -rf .lit_test_times.txt Output + +.PHONY: $(TESTS) $(addprefix update-,$(TESTS_ll)) check all clean update-help . diff --git a/test/llvmpasses/alloc-opt-gcframe.ll b/test/llvmpasses/alloc-opt-gcframe.ll index 97fa75df01c50..e01bd900c71e7 100644 --- a/test/llvmpasses/alloc-opt-gcframe.ll +++ b/test/llvmpasses/alloc-opt-gcframe.ll @@ -16,13 +16,13 @@ target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" ; TYPED-NEXT: [[ptls_load:%.*]] = load {}*, {}** [[ptls_field]], align 8, !tbaa !0 ; TYPED-NEXT: [[ppjl_ptls:%.*]] = bitcast {}* [[ptls_load]] to {}** ; TYPED-NEXT: [[ptls_i8:%.*]] = bitcast {}** [[ppjl_ptls]] to i8* -; TYPED-NEXT: %v = call noalias nonnull dereferenceable({{[0-9]+}}) {} addrspace(10)* @ijl_gc_pool_alloc_instrumented(i8* [[ptls_i8]], i32 [[SIZE_T:[0-9]+]], i32 16, i64 {{.*}} @tag {{.*}}) +; TYPED-NEXT: %v = call noalias nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) {} addrspace(10)* @ijl_gc_pool_alloc_instrumented(i8* [[ptls_i8]], i32 [[SIZE_T:[0-9]+]], i32 16, i64 {{.*}} @tag {{.*}}) ; TYPED: store atomic {} addrspace(10)* @tag, {} addrspace(10)* addrspace(10)* {{.*}} unordered, align 8, !tbaa !4 ; OPAQUE: %current_task = getelementptr inbounds ptr, ptr %gcstack, i64 -12 ; OPAQUE: [[ptls_field:%.*]] = getelementptr inbounds ptr, ptr %current_task, i64 16 ; OPAQUE-NEXT: [[ptls_load:%.*]] = load ptr, ptr [[ptls_field]], align 8, !tbaa !0 -; OPAQUE-NEXT: %v = call noalias nonnull dereferenceable({{[0-9]+}}) ptr addrspace(10) @ijl_gc_pool_alloc_instrumented(ptr [[ptls_load]], i32 [[SIZE_T:[0-9]+]], i32 16, i64 {{.*}} @tag {{.*}}) +; OPAQUE-NEXT: %v = call noalias nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) ptr addrspace(10) @ijl_gc_pool_alloc_instrumented(ptr [[ptls_load]], i32 [[SIZE_T:[0-9]+]], i32 16, i64 {{.*}} @tag {{.*}}) ; OPAQUE: store atomic ptr addrspace(10) @tag, ptr addrspace(10) {{.*}} unordered, align 8, !tbaa !4 define {} addrspace(10)* @return_obj() { diff --git a/test/llvmpasses/alloc-opt-pass.ll b/test/llvmpasses/alloc-opt-pass.ll index 521fd88d582ee..6bee0fd325105 100644 --- a/test/llvmpasses/alloc-opt-pass.ll +++ b/test/llvmpasses/alloc-opt-pass.ll @@ -81,6 +81,7 @@ L3: ; CHECK-LABEL: @legal_int_types ; CHECK: alloca [12 x i8] ; CHECK-NOT: alloca i96 +; CHECK: store [12 x i8] zeroinitializer, ; CHECK: ret void define void @legal_int_types() { %pgcstack = call {}*** @julia.get_pgcstack() @@ -143,6 +144,7 @@ L2: ; CHECK: alloca ; CHECK-NOT: call token(...) @llvm.julia.gc_preserve_begin ; CHECK: call void @llvm.lifetime.start +; CHECK: store [8 x i8] zeroinitializer, ; CHECK-NOT: call void @llvm.lifetime.end define void @lifetime_no_preserve_end({}* noalias nocapture noundef nonnull sret({}) %0) { %pgcstack = call {}*** @julia.get_pgcstack() @@ -160,3 +162,39 @@ define void @lifetime_no_preserve_end({}* noalias nocapture noundef nonnull sret ret void } ; CHECK-LABEL: }{{$}} + + +; CHECK-LABEL: @initializers +; CHECK: alloca [1 x i8] +; CHECK-DAG: alloca [2 x i8] +; CHECK-DAG: alloca [3 x i8] +; CHECK-DAG: freeze [1 x i8] undef +; CHECK-DAG: store [1 x i8] % +; CHECK-DAG: store [3 x i8] zeroinitializer, +; CHECK-NOT: store +; CHECK-NOT: zeroinitializer +; CHECK: ret void +define void @initializers() { + %pgcstack = call {}*** @julia.get_pgcstack() + %ptls = call {}*** @julia.ptls_states() + %ptls_i8 = bitcast {}*** %ptls to i8* + + %var1 = call {} addrspace(10)* @julia.gc_alloc_obj(i8* %ptls_i8, i64 1, {} addrspace(10)* @tag) #0 + %var2 = addrspacecast {} addrspace(10)* %var1 to {} addrspace(11)* + %var3 = call {}* @julia.pointer_from_objref({} addrspace(11)* %var2) + + %var4 = call {} addrspace(10)* @julia.gc_alloc_obj(i8* %ptls_i8, i64 2, {} addrspace(10)* @tag) #1 + %var5 = addrspacecast {} addrspace(10)* %var4 to {} addrspace(11)* + %var6 = call {}* @julia.pointer_from_objref({} addrspace(11)* %var5) + + %var7 = call {} addrspace(10)* @julia.gc_alloc_obj(i8* %ptls_i8, i64 3, {} addrspace(10)* @tag) #2 + %var8 = addrspacecast {} addrspace(10)* %var7 to {} addrspace(11)* + %var9 = call {}* @julia.pointer_from_objref({} addrspace(11)* %var8) + + ret void +} +; CHECK-LABEL: }{{$}} + +attributes #0 = { allockind("alloc") } +attributes #1 = { allockind("alloc,uninitialized") } +attributes #2 = { allockind("alloc,zeroed") } diff --git a/test/llvmpasses/cpu-features.ll b/test/llvmpasses/cpu-features.ll index 5125743248fec..323f5e24015e9 100644 --- a/test/llvmpasses/cpu-features.ll +++ b/test/llvmpasses/cpu-features.ll @@ -3,6 +3,8 @@ ; RUN: opt -enable-new-pm=1 --opaque-pointers=0 --load-pass-plugin=libjulia-codegen%shlibext -passes='CPUFeatures,simplifycfg' -S %s | FileCheck %s ; RUN: opt -enable-new-pm=1 --opaque-pointers=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='CPUFeatures,simplifycfg' -S %s | FileCheck %s +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128-ni:10:11:12:13" +target triple = "x86_64-linux-gnu" declare i1 @julia.cpu.have_fma.f64() declare double @with_fma(double %0, double %1, double %2) diff --git a/test/llvmpasses/final-lower-gc.ll b/test/llvmpasses/final-lower-gc.ll index eabeb20ce5636..eb3b68662c2b4 100644 --- a/test/llvmpasses/final-lower-gc.ll +++ b/test/llvmpasses/final-lower-gc.ll @@ -78,8 +78,8 @@ top: %pgcstack = call {}*** @julia.get_pgcstack() %ptls = call {}*** @julia.ptls_states() %ptls_i8 = bitcast {}*** %ptls to i8* -; TYPED: %v = call noalias nonnull dereferenceable({{[0-9]+}}) {} addrspace(10)* @ijl_gc_pool_alloc_instrumented -; OPAQUE: %v = call noalias nonnull dereferenceable({{[0-9]+}}) ptr addrspace(10) @ijl_gc_pool_alloc_instrumented +; TYPED: %v = call noalias nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) {} addrspace(10)* @ijl_gc_pool_alloc_instrumented +; OPAQUE: %v = call noalias nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) ptr addrspace(10) @ijl_gc_pool_alloc_instrumented %v = call {} addrspace(10)* @julia.gc_alloc_bytes(i8* %ptls_i8, i64 8, i64 12341234) %0 = bitcast {} addrspace(10)* %v to {} addrspace(10)* addrspace(10)* %1 = getelementptr {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %0, i64 -1 @@ -94,8 +94,8 @@ top: %ptls = call {}*** @julia.ptls_states() %ptls_i8 = bitcast {}*** %ptls to i8* ; CHECK: %0 = add i64 %size, 8 -; TYPED: %v = call noalias nonnull dereferenceable(8) {} addrspace(10)* @ijl_gc_alloc_typed(i8* %ptls_i8, i64 %0, i64 12341234) -; OPAQUE: %v = call noalias nonnull dereferenceable(8) ptr addrspace(10) @ijl_gc_alloc_typed(ptr %ptls_i8, i64 %0, i64 12341234) +; TYPED: %v = call noalias nonnull align {{[0-9]+}} dereferenceable(8) {} addrspace(10)* @ijl_gc_alloc_typed(i8* %ptls_i8, i64 %0, i64 12341234) +; OPAQUE: %v = call noalias nonnull align {{[0-9]+}} dereferenceable(8) ptr addrspace(10) @ijl_gc_alloc_typed(ptr %ptls_i8, i64 %0, i64 12341234) %v = call {} addrspace(10)* @julia.gc_alloc_bytes(i8* %ptls_i8, i64 %size, i64 12341234) %0 = bitcast {} addrspace(10)* %v to {} addrspace(10)* addrspace(10)* %1 = getelementptr {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %0, i64 -1 diff --git a/test/llvmpasses/gc-invariant-verifier.ll b/test/llvmpasses/gc-invariant-verifier.ll new file mode 100644 index 0000000000000..ef32521427da1 --- /dev/null +++ b/test/llvmpasses/gc-invariant-verifier.ll @@ -0,0 +1,13 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + +; RUN: opt -enable-new-pm=1 --opaque-pointers=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='function(GCInvariantVerifier)' -S %s | FileCheck %s + +; CHECK-LABEL: @vectorized_addrspacecast +define ptr addrspace(10) @vectorized_addrspacecast() { +top: + ret ptr addrspace(10) null + +vector.ph: + %0 = addrspacecast <4 x ptr addrspace(10)> zeroinitializer to <4 x ptr addrspace(11)> + unreachable +} diff --git a/test/llvmpasses/julia-simdloop-memoryssa.ll b/test/llvmpasses/julia-simdloop-memoryssa.ll index 0c1c4ac021996..863e5234caa67 100644 --- a/test/llvmpasses/julia-simdloop-memoryssa.ll +++ b/test/llvmpasses/julia-simdloop-memoryssa.ll @@ -16,10 +16,10 @@ loop: %i = phi i64 [0, %top], [%nexti, %loop] %aptr = getelementptr double, double *%a, i64 %i %bptr = getelementptr double, double *%b, i64 %i -; CHECK: MemoryUse([[MPHI]]) MayAlias +; CHECK: MemoryUse([[MPHI]]) ; CHECK: llvm.mem.parallel_loop_access %aval = load double, double *%aptr -; CHECK: MemoryUse([[MPHI]]) MayAlias +; CHECK: MemoryUse([[MPHI]]) %bval = load double, double *%aptr %cval = fadd double %aval, %bval ; CHECK: [[MSSA_USE]] = MemoryDef([[MPHI]]) @@ -40,7 +40,7 @@ loop: %i = phi i64 [0, %top], [%nexti, %loop] %v = phi double [0.000000e+00, %top], [%nextv, %loop] %aptr = getelementptr double, double *%a, i64 %i -; CHECK: MemoryUse(liveOnEntry) MayAlias +; CHECK: MemoryUse(liveOnEntry) %aval = load double, double *%aptr %nextv = fsub double %v, %aval ; CHECK: fsub reassoc contract double %v, %aval diff --git a/test/llvmpasses/late-lower-gc-addrspaces.ll b/test/llvmpasses/late-lower-gc-addrspaces.ll index b47a523d8a364..77f8e2ac685ce 100644 --- a/test/llvmpasses/late-lower-gc-addrspaces.ll +++ b/test/llvmpasses/late-lower-gc-addrspaces.ll @@ -67,7 +67,7 @@ top: ; TYPED-NEXT: [[ptls_load:%.*]] = load {}*, {}** [[ptls_field]], align 8, !tbaa !0 ; TYPED-NEXT: [[ppjl_ptls:%.*]] = bitcast {}* [[ptls_load]] to {}** ; TYPED-NEXT: [[ptls_i8:%.*]] = bitcast {}** [[ppjl_ptls]] to i8* -; TYPED-NEXT: %v = call {} addrspace(10)* @julia.gc_alloc_bytes(i8* [[ptls_i8]], [[SIZE_T:i.[0-9]+]] 8, i64 {{.*}} @tag {{.*}}) +; TYPED-NEXT: %v = call noalias nonnull {} addrspace(10)* @julia.gc_alloc_bytes(i8* [[ptls_i8]], [[SIZE_T:i.[0-9]+]] 8, i64 {{.*}} @tag {{.*}}) ; TYPED-NEXT: [[V2:%.*]] = bitcast {} addrspace(10)* %v to {} addrspace(10)* addrspace(10)* ; TYPED-NEXT: [[V_HEADROOM:%.*]] = getelementptr inbounds {} addrspace(10)*, {} addrspace(10)* addrspace(10)* [[V2]], i64 -1 ; TYPED-NEXT: store atomic {} addrspace(10)* @tag, {} addrspace(10)* addrspace(10)* [[V_HEADROOM]] unordered, align 8, !tbaa !4 @@ -75,7 +75,7 @@ top: ; OPAQUE: %current_task = getelementptr inbounds ptr, ptr %0, i64 -12 ; OPAQUE-NEXT: [[ptls_field:%.*]] = getelementptr inbounds ptr, ptr %current_task, i64 16 ; OPAQUE-NEXT: [[ptls_load:%.*]] = load ptr, ptr [[ptls_field]], align 8, !tbaa !0 -; OPAQUE-NEXT: %v = call ptr addrspace(10) @julia.gc_alloc_bytes(ptr [[ptls_load]], [[SIZE_T:i.[0-9]+]] 8, i64 {{.*}} @tag {{.*}}) +; OPAQUE-NEXT: %v = call noalias nonnull ptr addrspace(10) @julia.gc_alloc_bytes(ptr [[ptls_load]], [[SIZE_T:i.[0-9]+]] 8, i64 {{.*}} @tag {{.*}}) ; OPAQUE-NEXT: [[V_HEADROOM:%.*]] = getelementptr inbounds ptr addrspace(10), ptr addrspace(10) %v, i64 -1 ; OPAQUE-NEXT: store atomic ptr addrspace(10) @tag, ptr addrspace(10) [[V_HEADROOM]] unordered, align 8, !tbaa !4 %v = call noalias {} addrspace(10)* @julia.gc_alloc_obj({}** %current_task, i64 8, {} addrspace(10)* @tag) @@ -100,7 +100,7 @@ top: ; TYPED-NEXT: [[ptls_load:%.*]] = load {}*, {}** [[ptls_field]], align 8, !tbaa !0 ; TYPED-NEXT: [[ppjl_ptls:%.*]] = bitcast {}* [[ptls_load]] to {}** ; TYPED-NEXT: [[ptls_i8:%.*]] = bitcast {}** [[ppjl_ptls]] to i8* -; TYPED-NEXT: %v = call {} addrspace(10)* @julia.gc_alloc_bytes(i8* [[ptls_i8]], [[SIZE_T:i.[0-9]+]] 8, i64 {{.*}} @tag {{.*}}) +; TYPED-NEXT: %v = call noalias nonnull {} addrspace(10)* @julia.gc_alloc_bytes(i8* [[ptls_i8]], [[SIZE_T:i.[0-9]+]] 8, i64 {{.*}} @tag {{.*}}) ; TYPED-NEXT: [[V2:%.*]] = bitcast {} addrspace(10)* %v to {} addrspace(10)* addrspace(10)* ; TYPED-NEXT: [[V_HEADROOM:%.*]] = getelementptr inbounds {} addrspace(10)*, {} addrspace(10)* addrspace(10)* [[V2]], i64 -1 ; TYPED-NEXT: store atomic {} addrspace(10)* @tag, {} addrspace(10)* addrspace(10)* [[V_HEADROOM]] unordered, align 8, !tbaa !4 @@ -108,7 +108,7 @@ top: ; OPAQUE: %current_task = getelementptr inbounds ptr, ptr %0, i64 -12 ; OPAQUE-NEXT: [[ptls_field:%.*]] = getelementptr inbounds ptr, ptr %current_task, i64 16 ; OPAQUE-NEXT: [[ptls_load:%.*]] = load ptr, ptr [[ptls_field]], align 8, !tbaa !0 -; OPAQUE-NEXT: %v = call ptr addrspace(10) @julia.gc_alloc_bytes(ptr [[ptls_load]], [[SIZE_T:i.[0-9]+]] 8, i64 {{.*}} @tag {{.*}}) +; OPAQUE-NEXT: %v = call noalias nonnull ptr addrspace(10) @julia.gc_alloc_bytes(ptr [[ptls_load]], [[SIZE_T:i.[0-9]+]] 8, i64 {{.*}} @tag {{.*}}) ; OPAQUE-NEXT: [[V_HEADROOM:%.*]] = getelementptr inbounds ptr addrspace(10), ptr addrspace(10) %v, i64 -1 ; OPAQUE-NEXT: store atomic ptr addrspace(10) @tag, ptr addrspace(10) [[V_HEADROOM]] unordered, align 8, !tbaa !4 %v = call noalias {} addrspace(10)* @julia.gc_alloc_obj({}** %current_task, i64 8, {} addrspace(10)* @tag) diff --git a/test/llvmpasses/late-lower-gc.ll b/test/llvmpasses/late-lower-gc.ll index 4d45ef61faa85..6dee18da5975f 100644 --- a/test/llvmpasses/late-lower-gc.ll +++ b/test/llvmpasses/late-lower-gc.ll @@ -64,7 +64,7 @@ top: ; TYPED-NEXT: [[ptls_load:%.*]] = load {}*, {}** [[ptls_field]], align 8, !tbaa !0 ; TYPED-NEXT: [[ppjl_ptls:%.*]] = bitcast {}* [[ptls_load]] to {}** ; TYPED-NEXT: [[ptls_i8:%.*]] = bitcast {}** [[ppjl_ptls]] to i8* -; TYPED-NEXT: %v = call {} addrspace(10)* @julia.gc_alloc_bytes(i8* [[ptls_i8]], [[SIZE_T:i.[0-9]+]] 8, i64 {{.*}} @tag {{.*}}) +; TYPED-NEXT: %v = call noalias nonnull {} addrspace(10)* @julia.gc_alloc_bytes(i8* [[ptls_i8]], [[SIZE_T:i.[0-9]+]] 8, i64 {{.*}} @tag {{.*}}) ; TYPED-NEXT: [[V2:%.*]] = bitcast {} addrspace(10)* %v to {} addrspace(10)* addrspace(10)* ; TYPED-NEXT: [[V_HEADROOM:%.*]] = getelementptr inbounds {} addrspace(10)*, {} addrspace(10)* addrspace(10)* [[V2]], i64 -1 ; TYPED-NEXT: store atomic {} addrspace(10)* @tag, {} addrspace(10)* addrspace(10)* [[V_HEADROOM]] unordered, align 8, !tbaa !4 @@ -72,7 +72,7 @@ top: ; OPAQUE: %current_task = getelementptr inbounds ptr, ptr %0, i64 -12 ; OPAQUE-NEXT: [[ptls_field:%.*]] = getelementptr inbounds ptr, ptr %current_task, i64 16 ; OPAQUE-NEXT: [[ptls_load:%.*]] = load ptr, ptr [[ptls_field]], align 8, !tbaa !0 -; OPAQUE-NEXT: %v = call ptr addrspace(10) @julia.gc_alloc_bytes(ptr [[ptls_load]], [[SIZE_T:i.[0-9]+]] 8, i64 {{.*}} @tag {{.*}}) +; OPAQUE-NEXT: %v = call noalias nonnull ptr addrspace(10) @julia.gc_alloc_bytes(ptr [[ptls_load]], [[SIZE_T:i.[0-9]+]] 8, i64 {{.*}} @tag {{.*}}) ; OPAQUE-NEXT: [[V_HEADROOM:%.*]] = getelementptr inbounds ptr addrspace(10), ptr addrspace(10) %v, i64 -1 ; OPAQUE-NEXT: store atomic ptr addrspace(10) @tag, ptr addrspace(10) [[V_HEADROOM]] unordered, align 8, !tbaa !4 %v = call noalias {} addrspace(10)* @julia.gc_alloc_obj({}** %current_task, i64 8, {} addrspace(10)* @tag) @@ -97,7 +97,7 @@ top: ; TYPED-NEXT: [[ptls_load:%.*]] = load {}*, {}** [[ptls_field]], align 8, !tbaa !0 ; TYPED-NEXT: [[ppjl_ptls:%.*]] = bitcast {}* [[ptls_load]] to {}** ; TYPED-NEXT: [[ptls_i8:%.*]] = bitcast {}** [[ppjl_ptls]] to i8* -; TYPED-NEXT: %v = call {} addrspace(10)* @julia.gc_alloc_bytes(i8* [[ptls_i8]], [[SIZE_T:i.[0-9]+]] 8, i64 {{.*}} @tag {{.*}}) +; TYPED-NEXT: %v = call noalias nonnull {} addrspace(10)* @julia.gc_alloc_bytes(i8* [[ptls_i8]], [[SIZE_T:i.[0-9]+]] 8, i64 {{.*}} @tag {{.*}}) ; TYPED-NEXT: [[V2:%.*]] = bitcast {} addrspace(10)* %v to {} addrspace(10)* addrspace(10)* ; TYPED-NEXT: [[V_HEADROOM:%.*]] = getelementptr inbounds {} addrspace(10)*, {} addrspace(10)* addrspace(10)* [[V2]], i64 -1 ; TYPED-NEXT: store atomic {} addrspace(10)* @tag, {} addrspace(10)* addrspace(10)* [[V_HEADROOM]] unordered, align 8, !tbaa !4 @@ -105,7 +105,7 @@ top: ; OPAQUE: %current_task = getelementptr inbounds ptr, ptr %0, i64 -12 ; OPAQUE-NEXT: [[ptls_field:%.*]] = getelementptr inbounds ptr, ptr %current_task, i64 16 ; OPAQUE-NEXT: [[ptls_load:%.*]] = load ptr, ptr [[ptls_field]], align 8, !tbaa !0 -; OPAQUE-NEXT: %v = call ptr addrspace(10) @julia.gc_alloc_bytes(ptr [[ptls_load]], [[SIZE_T:i.[0-9]+]] 8, i64 {{.*}} @tag {{.*}}) +; OPAQUE-NEXT: %v = call noalias nonnull ptr addrspace(10) @julia.gc_alloc_bytes(ptr [[ptls_load]], [[SIZE_T:i.[0-9]+]] 8, i64 {{.*}} @tag {{.*}}) ; OPAQUE-NEXT: [[V_HEADROOM:%.*]] = getelementptr inbounds ptr addrspace(10), ptr addrspace(10) %v, i64 -1 ; OPAQUE-NEXT: store atomic ptr addrspace(10) @tag, ptr addrspace(10) [[V_HEADROOM]] unordered, align 8, !tbaa !4 %v = call noalias {} addrspace(10)* @julia.gc_alloc_obj({}** %current_task, i64 8, {} addrspace(10)* @tag) diff --git a/test/llvmpasses/lower-handlers-addrspaces.ll b/test/llvmpasses/lower-handlers-addrspaces.ll index 8b85a71705f60..779caf9aa6bdf 100644 --- a/test/llvmpasses/lower-handlers-addrspaces.ll +++ b/test/llvmpasses/lower-handlers-addrspaces.ll @@ -8,8 +8,8 @@ target triple = "amdgcn-amd-amdhsa" target datalayout = "e-p:64:64-p1:64:64-p2:32:32-p3:32:32-p4:64:64-p5:32:32-p6:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-v2048:2048-n32:64-S32-A5-G1-ni:7-ni:10:11:12:13" attributes #1 = { returns_twice } -declare i32 @julia.except_enter() #1 -declare void @ijl_pop_handler(i32) +declare i32 @julia.except_enter({}*) #1 +declare void @ijl_pop_handler({}*, i32) declare i8**** @julia.ptls_states() declare i8**** @julia.get_pgcstack() @@ -19,7 +19,7 @@ top: ; CHECK: call void @llvm.lifetime.start ; CHECK: call void @ijl_enter_handler ; CHECK: setjmp - %r = call i32 @julia.except_enter() + %r = call i32 @julia.except_enter({}* null) %cmp = icmp eq i32 %r, 0 br i1 %cmp, label %try, label %catch try: @@ -27,7 +27,7 @@ try: catch: br label %after after: - call void @ijl_pop_handler(i32 1) + call void @ijl_pop_handler({}* null, i32 1) ; CHECK: llvm.lifetime.end ret void } diff --git a/test/llvmpasses/lower-handlers.ll b/test/llvmpasses/lower-handlers.ll index a250edddcaa81..2361fb5b84ef6 100644 --- a/test/llvmpasses/lower-handlers.ll +++ b/test/llvmpasses/lower-handlers.ll @@ -5,8 +5,8 @@ ; RUN: opt -enable-new-pm=1 --opaque-pointers=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='function(LowerExcHandlers)' -S %s | FileCheck %s attributes #1 = { returns_twice } -declare i32 @julia.except_enter() #1 -declare void @ijl_pop_handler(i32) +declare i32 @julia.except_enter({}*) #1 +declare void @ijl_pop_handler({}*, i32) declare i8**** @julia.ptls_states() declare i8**** @julia.get_pgcstack() @@ -16,7 +16,7 @@ top: ; CHECK: call void @llvm.lifetime.start ; CHECK: call void @ijl_enter_handler ; CHECK: setjmp - %r = call i32 @julia.except_enter() + %r = call i32 @julia.except_enter({}* null) %cmp = icmp eq i32 %r, 0 br i1 %cmp, label %try, label %catch try: @@ -24,7 +24,7 @@ try: catch: br label %after after: - call void @ijl_pop_handler(i32 1) + call void @ijl_pop_handler({}* null, i32 1) ; CHECK: llvm.lifetime.end ret void } diff --git a/test/llvmpasses/multiversioning-clone-only.ll b/test/llvmpasses/multiversioning-clone-only.ll index 6cd407f2e461f..59a3f64a25c35 100644 --- a/test/llvmpasses/multiversioning-clone-only.ll +++ b/test/llvmpasses/multiversioning-clone-only.ll @@ -4,33 +4,34 @@ ; RUN: opt -enable-new-pm=1 --opaque-pointers=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='JuliaMultiVersioning' -S %s | FileCheck %s --allow-unused-prefixes=false --check-prefixes=CHECK,OPAQUE +; CHECK: @jl_gvar_base = hidden constant i64 0 +; CHECK: @jl_gvar_offsets = hidden constant [0 x i32] zeroinitializer ; CHECK: @jl_fvar_idxs = hidden constant [1 x i32] zeroinitializer ; CHECK: @jl_gvar_idxs = hidden constant [0 x i32] zeroinitializer ; TYPED: @subtarget_cloned_gv = hidden global i64* null ; OPAQUE: @subtarget_cloned_gv = hidden global ptr null ; TYPED: @subtarget_cloned.reloc_slot = hidden global i32 (i32)* null ; OPAQUE: @subtarget_cloned.reloc_slot = hidden global ptr null -; CHECK: @jl_fvar_offsets = hidden constant [2 x i32] [i32 1, i32 0] -; CHECK: @jl_gvar_base = hidden constant i64 0 -; CHECK: @jl_gvar_offsets = hidden constant [1 x i32] zeroinitializer +; CHECK: @jl_fvar_count = hidden constant i64 1 +; TYPED: @jl_fvar_ptrs = hidden global [1 x i64*] [i64* bitcast (i32 (i32)* @subtarget_cloned to i64*)] +; OPAQUE: @jl_fvar_ptrs = hidden global [1 x ptr] [ptr @subtarget_cloned] ; CHECK: @jl_clone_slots = hidden constant [5 x i32] -; CHECK-SAME: i32 2, i32 0, {{.*}} sub {{.*}}@subtarget_cloned.reloc_slot{{.*}}@jl_gvar_base +; CHECK-SAME: i32 2, i32 0, {{.*}} sub {{.*}}@subtarget_cloned.reloc_slot{{.*}}@jl_clone_slots ; CHECK: @jl_clone_idxs = hidden constant [13 x i32] ; COM: TODO actually check the clone idxs maybe? -; CHECK: @jl_clone_offsets = hidden constant [4 x i32] -; CHECK-SAME: sub -; CHECK-SAME: @subtarget_cloned.1 -; CHECK-SAME: @subtarget_cloned -; CHECK-SAME: sub -; CHECK-SAME: @subtarget_cloned.2 -; CHECK-SAME: @subtarget_cloned -; CHECK-SAME: sub - -@jl_fvars = global [1 x i64*] [i64* bitcast (i32 (i32)* @subtarget_cloned to i64*)], align 16 -@jl_gvars = global [0 x i64*] zeroinitializer, align 16 -@jl_fvar_idxs = hidden constant [1 x i32] [i32 0], align 16 -@jl_gvar_idxs = hidden constant [0 x i32] zeroinitializer, align 16 -@subtarget_cloned_gv = hidden global i64* bitcast (i32 (i32)* @subtarget_cloned to i64*), align 16 +; TYPED: @jl_clone_ptrs = hidden constant [4 x i64*] +; TYPED-SAME: @subtarget_cloned.1 +; TYPED-SAME: @subtarget_cloned.2 +; TYPED-SAME: @subtarget_cloned +; TYPED-SAME: @subtarget_cloned +; OPAQUE: @jl_clone_ptrs = hidden constant [4 x ptr] [ptr @subtarget_cloned.1, ptr @subtarget_cloned.2, ptr @subtarget_cloned, ptr @subtarget_cloned] + +@jl_fvars = global [1 x i64*] [i64* bitcast (i32 (i32)* @subtarget_cloned to i64*)], align 8 +@jl_gvar_base = hidden constant i64 zeroinitializer, align 8 +@jl_gvar_offsets = hidden constant [0 x i32] zeroinitializer, align 8 +@jl_fvar_idxs = hidden constant [1 x i32] [i32 0], align 8 +@jl_gvar_idxs = hidden constant [0 x i32] zeroinitializer, align 8 +@subtarget_cloned_gv = hidden global i64* bitcast (i32 (i32)* @subtarget_cloned to i64*), align 8 @subtarget_cloned_aliased = alias i32 (i32), i32 (i32)* @subtarget_cloned diff --git a/test/llvmpasses/multiversioning-x86.ll b/test/llvmpasses/multiversioning-x86.ll new file mode 100644 index 0000000000000..1bc1b288a5879 --- /dev/null +++ b/test/llvmpasses/multiversioning-x86.ll @@ -0,0 +1,131 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + +; RUN: opt -enable-new-pm=1 --opaque-pointers=0 --load-pass-plugin=libjulia-codegen%shlibext -passes='JuliaMultiVersioning,CPUFeatures' -S %s | FileCheck %s --allow-unused-prefixes=false --check-prefixes=CHECK,TYPED + +; RUN: opt -enable-new-pm=1 --opaque-pointers=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='JuliaMultiVersioning,CPUFeatures' -S %s | FileCheck %s --allow-unused-prefixes=false --check-prefixes=CHECK,OPAQUE + + +; COM: This test checks that multiversioning actually happens from start to finish +; COM: We need the fvars for a proper test + + + +; TYPED: @jl_gvar_ptrs = global [0 x i64*] zeroinitializer, align 8 +; OPAQUE: @jl_gvar_ptrs = global [0 x ptr] zeroinitializer, align 8 +; CHECK: @jl_fvar_idxs = hidden constant [5 x i32] [i32 0, i32 1, i32 2, i32 3, i32 4], align 8 +; CHECK: @jl_gvar_idxs = hidden constant [0 x i32] zeroinitializer, align 8 +; TYPED: @simd_test.reloc_slot = hidden global i32 (<4 x i32>)* null +; OPAQUE: @simd_test.reloc_slot = hidden global ptr null +; TYPED: @jl_fvar_ptrs = hidden global [5 x i64*] [i64* bitcast (i32 (i32)* @boring to i64*), i64* bitcast (float (float, float)* @fastmath_test to i64*), i64* bitcast (i32 (i32)* @loop_test to i64*), i64* bitcast (i32 (<4 x i32>)* @simd_test to i64*), i64* bitcast (i32 (<4 x i32>)* @simd_test_call to i64*)] +; OPAQUE: @jl_fvar_ptrs = hidden global [5 x ptr] [ptr @boring, ptr @fastmath_test, ptr @loop_test, ptr @simd_test, ptr @simd_test_call] +; TYPED: @jl_clone_slots = hidden constant [3 x i32] [i32 1, i32 3, i32 trunc (i64 sub (i64 ptrtoint (i32 (<4 x i32>)** @simd_test.reloc_slot to i64), i64 ptrtoint ([3 x i32]* @jl_clone_slots to i64)) to i32)] +; OPAQUE: @jl_clone_slots = hidden constant [3 x i32] [i32 1, i32 3, i32 trunc (i64 sub (i64 ptrtoint (ptr @simd_test.reloc_slot to i64), i64 ptrtoint (ptr @jl_clone_slots to i64)) to i32)] +; CHECK: @jl_clone_idxs = hidden constant [10 x i32] [i32 -2147483647, i32 3, i32 -2147483647, i32 3, i32 4, i32 1, i32 1, i32 2, i32 -2147483645, i32 4] +; TYPED: @jl_clone_ptrs = hidden constant [9 x i64*] [i64* bitcast (i32 (i32)* @boring.1 to i64*), i64* bitcast (float (float, float)* @fastmath_test.1 to i64*), i64* bitcast (i32 (i32)* @loop_test.1 to i64*), i64* bitcast (i32 (<4 x i32>)* @simd_test.1 to i64*), i64* bitcast (i32 (<4 x i32>)* @simd_test_call.1 to i64*), i64* bitcast (float (float, float)* @fastmath_test.2 to i64*), i64* bitcast (i32 (i32)* @loop_test.2 to i64*), i64* bitcast (i32 (<4 x i32>)* @simd_test.2 to i64*), i64* bitcast (i32 (<4 x i32>)* @simd_test_call.2 to i64*)] +; OPAQUE: @jl_clone_ptrs = hidden constant [9 x ptr] [ptr @boring.1, ptr @fastmath_test.1, ptr @loop_test.1, ptr @simd_test.1, ptr @simd_test_call.1, ptr @fastmath_test.2, ptr @loop_test.2, ptr @simd_test.2, ptr @simd_test_call.2] + + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128-ni:10:11:12:13" +target triple = "x86_64-linux-gnu" + +@jl_fvars = global [5 x i64*] [i64* bitcast (i32 (i32)* @boring to i64*), + i64* bitcast (float (float, float)* @fastmath_test to i64*), + i64* bitcast (i32 (i32)* @loop_test to i64*), + i64* bitcast (i32 (<4 x i32>)* @simd_test to i64*), + i64* bitcast (i32 (<4 x i32>)* @simd_test_call to i64*) + ], align 8 +@jl_gvar_ptrs = global [0 x i64*] zeroinitializer, align 8 +@jl_fvar_idxs = hidden constant [5 x i32] [i32 0, i32 1, i32 2, i32 3, i32 4], align 8 +@jl_gvar_idxs = hidden constant [0 x i32] zeroinitializer, align 8 + +declare i1 @julia.cpu.have_fma.f32() + +; CHECK: @boring{{.*}}#[[BORING_BASE:[0-9]+]] +define noundef i32 @boring(i32 noundef %0) { + ret i32 %0 +} + +; CHECK: @fastmath_test{{.*}}#[[NOT_BORING_BASE:[0-9]+]] +; CHECK: %3 = sitofp i1 false to float +define noundef float @fastmath_test(float noundef %0, float noundef %1) { + %3 = call i1 @julia.cpu.have_fma.f32() + %4 = sitofp i1 %3 to float + %5 = fadd fast float %0, %4 + ret float %5 +} + +; CHECK: @loop_test{{.*}}#[[NOT_BORING_BASE:[0-9]+]] +define noundef i32 @loop_test(i32 noundef %0) { + %2 = icmp sgt i32 %0, 0 + br i1 %2, label %5, label %3 + +3: ; preds = %5, %1 + %4 = phi i32 [ 0, %1 ], [ %9, %5 ] + ret i32 %4 + +5: ; preds = %1, %5 + %6 = phi i32 [ %10, %5 ], [ 0, %1 ] + %7 = phi i32 [ %9, %5 ], [ 0, %1 ] + %8 = lshr i32 %6, 1 + %9 = add nuw nsw i32 %8, %7 + %10 = add nuw nsw i32 %6, 1 + %11 = icmp eq i32 %10, %0 + br i1 %11, label %3, label %5;, !llvm.loop - +} + +; CHECK: @simd_test{{.*}}#[[SIMD_BASE_RELOC:[0-9]+]] +define noundef i32 @simd_test(<4 x i32> noundef %0) { + %2 = extractelement <4 x i32> %0, i64 0 + ret i32 %2 +} + +; CHECK: @simd_test_call{{.*}}#[[NOT_BORING_BASE:[0-9]+]] +define noundef i32 @simd_test_call(<4 x i32> noundef %0) { + %2 = call noundef i32 @simd_test(<4 x i32> noundef %0) + ret i32 %2 +} + +; CHECK: @boring{{.*}}#[[BORING_CLONE:[0-9]+]] + +; CHECK: @fastmath_test{{.*}}#[[NOT_BORING_CLONE1:[0-9]+]] +; CHECK: %3 = sitofp i1 false to float + +; CHECK: @fastmath_test{{.*}}#[[NOT_BORING_CLONE2:[0-9]+]] +; CHECK: %3 = sitofp i1 true to float + +; CHECK: @loop_test{{.*}}#[[NOT_BORING_CLONE1:[0-9]+]] + +; CHECK: @loop_test{{.*}}#[[NOT_BORING_CLONE2:[0-9]+]] + +; CHECK: @simd_test{{.*}}#[[SIMD_CLONE1:[0-9]+]] + +; CHECK: @simd_test{{.*}}#[[SIMD_CLONE2:[0-9]+]] + +; CHECK: @simd_test_call{{.*}}#[[NOT_BORING_CLONE1:[0-9]+]] +; TYPED: %2 = load i32 (<4 x i32>)*, i32 (<4 x i32>)** @simd_test.reloc_slot, align 8, !tbaa !8, !invariant.load !12 +; OPAQUE: %2 = load ptr, ptr @simd_test.reloc_slot, align 8, !tbaa !8, !invariant.load !12 +; CHECK: %3 = call noundef i32 %2(<4 x i32> noundef %0) + +; CHECK: @simd_test_call{{.*}}#[[NOT_BORING_CLONE2:[0-9]+]] +; CHECK: %2 = call noundef i32 @simd_test.2(<4 x i32> noundef %0) + +; CHECK-DAG: attributes #[[BORING_BASE]] = { "julia.mv.clone"="0" "julia.mv.clones"="2" "julia.mv.fvar" "target-cpu"="x86-64" "target-features"="+cx16,-sse3,-pclmul,-ssse3,-fma,-sse4.1,-sse4.2,-movbe,-popcnt,-aes,-xsave,-avx,-f16c,-rdrnd,-fsgsbase,-bmi,-avx2,-bmi2,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-sahf,-lzcnt,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8" } +; CHECK-DAG: attributes #[[NOT_BORING_BASE]] = { "julia.mv.clone"="0" "julia.mv.clones"="6" "julia.mv.fvar" "target-cpu"="x86-64" "target-features"="+cx16,-sse3,-pclmul,-ssse3,-fma,-sse4.1,-sse4.2,-movbe,-popcnt,-aes,-xsave,-avx,-f16c,-rdrnd,-fsgsbase,-bmi,-avx2,-bmi2,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-sahf,-lzcnt,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8" } +; CHECK-DAG: attributes #[[SIMD_BASE_RELOC]] = { "julia.mv.clone"="0" "julia.mv.clones"="6" "julia.mv.reloc" "target-cpu"="x86-64" "target-features"="+cx16,-sse3,-pclmul,-ssse3,-fma,-sse4.1,-sse4.2,-movbe,-popcnt,-aes,-xsave,-avx,-f16c,-rdrnd,-fsgsbase,-bmi,-avx2,-bmi2,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-sahf,-lzcnt,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8" } +; CHECK-DAG: attributes #[[BORING_CLONE]] = { "julia.mv.clone"="1" "julia.mv.clones"="2" "julia.mv.fvar" "target-cpu"="sandybridge" "target-features"="+sahf,+avx,+xsave,+popcnt,+sse4.2,+sse4.1,+cx16,+ssse3,+pclmul,+sse3,-fma,-movbe,-aes,-f16c,-rdrnd,-fsgsbase,-bmi,-avx2,-bmi2,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-lzcnt,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8" } +; CHECK-DAG: attributes #[[NOT_BORING_CLONE1]] = { "julia.mv.clone"="1" "julia.mv.clones"="6" "julia.mv.fvar" "target-cpu"="sandybridge" "target-features"="+sahf,+avx,+xsave,+popcnt,+sse4.2,+sse4.1,+cx16,+ssse3,+pclmul,+sse3,-fma,-movbe,-aes,-f16c,-rdrnd,-fsgsbase,-bmi,-avx2,-bmi2,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-lzcnt,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8" } +; CHECK-DAG: attributes #[[NOT_BORING_CLONE2]] = { "julia.mv.clone"="2" "julia.mv.clones"="6" "julia.mv.fvar" "target-cpu"="haswell" "target-features"="+lzcnt,+sahf,+bmi2,+avx2,+bmi,+fsgsbase,+f16c,+avx,+xsave,+popcnt,+movbe,+sse4.2,+sse4.1,+cx16,+fma,+ssse3,+pclmul,+sse3,-aes,-rdrnd,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8" } +; CHECK-DAG: attributes #[[SIMD_CLONE1]] = { "julia.mv.clone"="1" "julia.mv.clones"="6" "julia.mv.reloc" "target-cpu"="sandybridge" "target-features"="+sahf,+avx,+xsave,+popcnt,+sse4.2,+sse4.1,+cx16,+ssse3,+pclmul,+sse3,-fma,-movbe,-aes,-f16c,-rdrnd,-fsgsbase,-bmi,-avx2,-bmi2,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-lzcnt,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8" } +; CHECK-DAG: attributes #[[SIMD_CLONE2]] = { "julia.mv.clone"="2" "julia.mv.clones"="6" "julia.mv.reloc" "target-cpu"="haswell" "target-features"="+lzcnt,+sahf,+bmi2,+avx2,+bmi,+fsgsbase,+f16c,+avx,+xsave,+popcnt,+movbe,+sse4.2,+sse4.1,+cx16,+fma,+ssse3,+pclmul,+sse3,-aes,-rdrnd,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8" } + + +!llvm.module.flags = !{!0, !2} + + +!0 = !{i32 1, !"julia.mv.enable", i32 1} +!1 = !{!1} +!2 = !{i32 1, !"julia.mv.specs", !3} +!3 = !{!4, !5, !6} +!4 = !{!"x86-64", !"+cx16,-sse3,-pclmul,-ssse3,-fma,-sse4.1,-sse4.2,-movbe,-popcnt,-aes,-xsave,-avx,-f16c,-rdrnd,-fsgsbase,-bmi,-avx2,-bmi2,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-sahf,-lzcnt,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8", i32 0, i32 0} +!5 = !{!"sandybridge", !"+sahf,+avx,+xsave,+popcnt,+sse4.2,+sse4.1,+cx16,+ssse3,+pclmul,+sse3,-fma,-movbe,-aes,-f16c,-rdrnd,-fsgsbase,-bmi,-avx2,-bmi2,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-lzcnt,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8", i32 0, i32 2} +!6 = !{!"haswell", !"+lzcnt,+sahf,+bmi2,+avx2,+bmi,+fsgsbase,+f16c,+avx,+xsave,+popcnt,+movbe,+sse4.2,+sse4.1,+cx16,+fma,+ssse3,+pclmul,+sse3,-aes,-rdrnd,-rtm,-avx512f,-avx512dq,-rdseed,-adx,-avx512ifma,-clflushopt,-clwb,-avx512pf,-avx512er,-avx512cd,-sha,-avx512bw,-avx512vl,-prefetchwt1,-avx512vbmi,-pku,-waitpkg,-avx512vbmi2,-shstk,-gfni,-vaes,-vpclmulqdq,-avx512vnni,-avx512bitalg,-avx512vpopcntdq,-rdpid,-cldemote,-movdiri,-movdir64b,-enqcmd,-uintr,-avx512vp2intersect,-serialize,-tsxldtrk,-pconfig,-amx-bf16,-avx512fp16,-amx-tile,-amx-int8,-sse4a,-prfchw,-xop,-fma4,-tbm,-mwaitx,-xsaveopt,-xsavec,-xsaves,-clzero,-wbnoinvd,-avxvnni,-avx512bf16,-ptwrite,+sse2,+mmx,+fxsr,+64bit,+cx8", i32 1, i32 284} diff --git a/test/llvmpasses/names.jl b/test/llvmpasses/names.jl index 6cb92dd7d3724..fe692d0fab787 100644 --- a/test/llvmpasses/names.jl +++ b/test/llvmpasses/names.jl @@ -22,16 +22,6 @@ function f2(a, b, c, d, e...) return a + b + c + d + sum(e) end -# COM: check basic parameter names + array allocation function name -function f3(a, b, c, d) - return [a + b + c + d] -end - -# COM: check basic parameter name + array allocation function name + array -function f4(n) - return zeros(n) -end - mutable struct D i::Int64 end @@ -77,9 +67,19 @@ mutable struct Barrier b end +# COM: check write barrier names +function f8(b,y) + b.b = y + return b +end + struct Named x::Int end + +function fmemory(nel) + return Memory{Int64}(undef,nel) +end # CHECK-LABEL: define {{(swiftcc )?}}double @julia_f1 # CHECK-SAME: double %"a::Float64" # CHECK-SAME: double %"b::Float64" @@ -96,12 +96,12 @@ end # CHECK: ret double # CHECK: } -# CHECK-LABEL: define nonnull {} addrspace(10)* @jfptr_f1 +# CHECK-LABEL: define nonnull ptr @jfptr_f1 # CHECK-SAME: %"function::Core.Function" # CHECK-SAME: %"args::Any[]" # CHECK-SAME: %"nargs::UInt32" # CHECK: %"+Core.Float64 -# CHECK: ret {} addrspace(10)* +# CHECK: ret ptr # CHECK: } emit(f1, Float64, Float64, Float64, Float64) @@ -133,40 +133,20 @@ emit(f2, Float64, Float64, Float64, Float64, Float64, Float64) # CHECK-SAME: double %"e[3]::Float64" emit(f2, Float64, Float64, Float64, Float64, Float64, Float64, Float64) -# CHECK: define {{(swiftcc )?}}nonnull {} addrspace(10)* @julia_f3 -# CHECK-SAME: double %"a::Float64" -# CHECK-SAME: double %"b::Float64" -# CHECK-SAME: double %"c::Float64" -# CHECK-SAME: double %"d::Float64" -# CHECK: call nonnull {} addrspace(10)* {{.*}} @jlplt_ijl_alloc_array_1d -# CHECK-SAME: @"+Core.Array -emit(f3, Float64, Float64, Float64, Float64) - -# CHECK: define {{(swiftcc )?}}nonnull {} addrspace(10)* @julia_f4 -# CHECK-SAME: %"n::Int64" -# CHECK: call nonnull {} addrspace(10)* {{.*}} @jlplt_ijl_alloc_array_1d -# CHECK-SAME: @"+Core.Array -# CHECK: %.length_ptr -# CHECK: %.length -# CHECK: %.data -emit(f4, Int64) - -# CHECK: define {{(swiftcc )?}}nonnull {} addrspace(10)* @julia_f5 +# CHECK: define {{(swiftcc )?}}nonnull ptr @julia_f5 # CHECK-SAME: %"a::A" # CHECK: %"a::A.b_ptr.c_ptr.d emit(f5, A) -# CHECK: define {{(swiftcc )?}}nonnull {} addrspace(10)* @julia_f6 +# CHECK: define {{(swiftcc )?}}nonnull ptr @julia_f6 # CHECK-SAME: %"e::E" # CHECK: %jlcallframe # CHECK: %gcframe -# CHECK: %frame.nroots # CHECK: %frame.prev # CHECK: %task.gcstack # CHECK: %ptls_field # CHECK: %ptls_load # CHECK: %safepoint -# CHECK: %"e::E.f_ptr" # CHECK: %"e::E.f" # CHECK: %"e::E.f.tag_addr" # CHECK: %"e::E.f.tag" @@ -180,20 +160,23 @@ emit(f6, E) # CHECK: define {{(swiftcc )?}}i64 @julia_f7 # CHECK-SAME: %"a::Tuple" # CHECK: %"a::Tuple[2]_ptr.unbox -emit(f7,Tuple{Int,Int}) +emit(f7, Tuple{Int,Int}) -# CHECK: define {{(swiftcc )?}}nonnull {} addrspace(10)* @julia_Barrier -# CHECK-SAME: %"b::Int64" +# CHECK: define {{(swiftcc )?}}nonnull ptr @julia_f8 +# CHECK-SAME: %"y::Int64" # CHECK: %parent_bits # CHECK: %parent_old_marked # CHECK: %child_bit # CHECK: %child_not_marked -emit(Barrier, Int64) +emit(f8, Barrier, Int) -# CHECK: define {{(swiftcc )?}}nonnull {} addrspace(10)* @julia_Barrier +# CHECK: define {{(swiftcc )?}}nonnull ptr @julia_Barrier # CHECK-SAME: %"b::Named" # CHECK: %"new::Barrier" # CHECK: %"box::Named" -# CHECK: %parent_bits -# CHECK: %parent_old_marked emit(Barrier, Named) + +# CHECK: define {{(swiftcc )?}}nonnull ptr @julia_fmemory +# CHECK-SAME: %"nel::Int64" +# CHECK: %"Memory{Int64}[]" +emit(fmemory, Int64) diff --git a/test/llvmpasses/pipeline-o2.jl b/test/llvmpasses/pipeline-o2.jl index 9fd42562f96aa..3ce2f692fc32e 100644 --- a/test/llvmpasses/pipeline-o2.jl +++ b/test/llvmpasses/pipeline-o2.jl @@ -2,8 +2,8 @@ # RUN: export JULIA_LLVM_ARGS="--opaque-pointers=0" -# RUN: julia --startup-file=no -O2 --check-bounds=yes %s %t -O && llvm-link -S %t/* | FileCheck %s --check-prefixes=ALL -# RUN: julia --startup-file=no -O3 --check-bounds=yes %s %t -O && llvm-link -S %t/* | FileCheck %s --check-prefixes=ALL +# RUNx: julia --startup-file=no -O2 --check-bounds=yes %s %t -O && llvm-link -S %t/* | FileCheck %s --check-prefixes=ALL +# RUNx: julia --startup-file=no -O3 --check-bounds=yes %s %t -O && llvm-link -S %t/* | FileCheck %s --check-prefixes=ALL # RUN: julia --startup-file=no -O2 --check-bounds=no %s %t -O && llvm-link -S %t/* | FileCheck %s --check-prefixes=ALL,BC_OFF # RUN: julia --startup-file=no -O3 --check-bounds=no %s %t -O && llvm-link -S %t/* | FileCheck %s --check-prefixes=ALL,BC_OFF @@ -13,8 +13,8 @@ # RUN: export JULIA_LLVM_ARGS="--opaque-pointers=1" -# RUN: julia --startup-file=no -O2 --check-bounds=yes %s %t -O && llvm-link -S %t/* | FileCheck %s --check-prefixes=ALL -# RUN: julia --startup-file=no -O3 --check-bounds=yes %s %t -O && llvm-link -S %t/* | FileCheck %s --check-prefixes=ALL +# RUNx: julia --startup-file=no -O2 --check-bounds=yes %s %t -O && llvm-link -S %t/* | FileCheck %s --check-prefixes=ALL +# RUNx: julia --startup-file=no -O3 --check-bounds=yes %s %t -O && llvm-link -S %t/* | FileCheck %s --check-prefixes=ALL # RUN: julia --startup-file=no -O2 --check-bounds=no %s %t -O && llvm-link -S %t/* | FileCheck %s --check-prefixes=ALL,BC_OFF # RUN: julia --startup-file=no -O3 --check-bounds=no %s %t -O && llvm-link -S %t/* | FileCheck %s --check-prefixes=ALL,BC_OFF diff --git a/test/llvmpasses/remove-addrspaces.ll b/test/llvmpasses/remove-addrspaces.ll index b0415671e2d93..90043a7d85cf4 100644 --- a/test/llvmpasses/remove-addrspaces.ll +++ b/test/llvmpasses/remove-addrspaces.ll @@ -5,6 +5,12 @@ ; RUN: opt -enable-new-pm=1 --opaque-pointers=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='RemoveJuliaAddrspaces' -S %s | FileCheck %s --check-prefixes=CHECK,OPAQUE +; COM: check that package image fptrs work +@pjlsys_BoundsError_32 = internal global {} addrspace(10)* ({}***, {} addrspace(10)*, [1 x i64] addrspace(11)*)* null +; CHECK: @pjlsys_BoundsError_32 = internal global +; TYPED-SAME: {}* ({}***, {}*, [1 x i64]*)* null +; OPAQUE-SAME: ptr null + define i64 @getindex({} addrspace(10)* nonnull align 16 dereferenceable(40)) { ; CHECK-LABEL: @getindex top: diff --git a/test/llvmpasses/safepoint_stress.jl b/test/llvmpasses/safepoint_stress.jl index c1867decc7f25..e02aee7206b12 100644 --- a/test/llvmpasses/safepoint_stress.jl +++ b/test/llvmpasses/safepoint_stress.jl @@ -14,7 +14,7 @@ define void @stress(i64 %a, i64 %b) { %ptls = call {}*** @julia.ptls_states() """) -# CHECK: %gcframe = alloca {} addrspace(10)*, i32 10002 +# CHECK: %gcframe = alloca ptr addrspace(10), i32 10002 for i = 1:10000 println("\t%arg$i = call {} addrspace(10)* @alloc()") end diff --git a/test/loading.jl b/test/loading.jl index d7e967f209eaa..4cd0cb7dac823 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -721,16 +721,17 @@ end @testset "expansion of JULIA_DEPOT_PATH" begin s = Sys.iswindows() ? ';' : ':' tmp = "/this/does/not/exist" - DEFAULT = Base.append_default_depot_path!(String[]) + default = joinpath(homedir(), ".julia") + bundled = Base.append_bundled_depot_path!(String[]) cases = Dict{Any,Vector{String}}( - nothing => DEFAULT, + nothing => [default; bundled], "" => [], - "$s" => DEFAULT, - "$tmp$s" => [tmp; DEFAULT], - "$s$tmp" => [DEFAULT; tmp], + "$s" => [default; bundled], + "$tmp$s" => [tmp; bundled], + "$s$tmp" => [bundled; tmp], ) for (env, result) in pairs(cases) - script = "DEPOT_PATH == $(repr(result)) || error()" + script = "DEPOT_PATH == $(repr(result)) || error(\"actual depot \" * join(DEPOT_PATH,':') * \" does not match expected depot \" * join($(repr(result)), ':'))" cmd = `$(Base.julia_cmd()) --startup-file=no -e $script` cmd = addenv(cmd, "JULIA_DEPOT_PATH" => env) cmd = pipeline(cmd; stdout, stderr) @@ -795,8 +796,10 @@ end @testset "`Base.project_names` and friends" begin # Some functions in Pkg assumes that these tuples have the same length n = length(Base.project_names) - @test length(Base.manifest_names) == n @test length(Base.preferences_names) == n + + # there are two manifest names per project name + @test length(Base.manifest_names) == 2n end @testset "Manifest formats" begin @@ -825,6 +828,33 @@ end end end +@testset "Manifest name preferential loading" begin + mktempdir() do tmp + proj = joinpath(tmp, "Project.toml") + touch(proj) + for man_name in ( + "Manifest.toml", + "JuliaManifest.toml", + "Manifest-v$(VERSION.major).$(VERSION.minor).toml", + "JuliaManifest-v$(VERSION.major).$(VERSION.minor).toml" + ) + touch(joinpath(tmp, man_name)) + man = basename(Base.project_file_manifest_path(proj)) + @test man == man_name + end + end + mktempdir() do tmp + # check that another version isn't preferred + proj = joinpath(tmp, "Project.toml") + touch(proj) + touch(joinpath(tmp, "Manifest-v1.5.toml")) + @test Base.project_file_manifest_path(proj) == nothing + touch(joinpath(tmp, "Manifest.toml")) + man = basename(Base.project_file_manifest_path(proj)) + @test man == "Manifest.toml" + end +end + @testset "error message loading pkg bad module name" begin mktempdir() do tmp old_loadpath = copy(LOAD_PATH) @@ -1027,7 +1057,9 @@ end $ew HasDepWithExtensions.do_something() || error("do_something errored") using ExtDep2 $ew using ExtDep2 - $ew HasExtensions.ext_folder_loaded || error("ext_folder_loaded not set") + using ExtDep3 + $ew using ExtDep3 + $ew HasExtensions.ext_dep_loaded || error("ext_dep_loaded not set") end """ return `$(Base.julia_cmd()) $compile --startup-file=no -e $cmd` @@ -1075,6 +1107,8 @@ end Base.get_extension(HasExtensions, :Extension) isa Module || error("expected extension to load") using ExtDep2 Base.get_extension(HasExtensions, :ExtensionFolder) isa Module || error("expected extension to load") + using ExtDep3 + Base.get_extension(HasExtensions, :ExtensionDep) isa Module || error("expected extension to load") end """ for compile in (`--compiled-modules=no`, ``) @@ -1083,6 +1117,36 @@ end cmd_proj_ext = addenv(cmd_proj_ext, "JULIA_LOAD_PATH" => join([joinpath(proj, "HasExtensions.jl"), joinpath(proj, "EnvWithDeps")], sep)) run(cmd_proj_ext) end + + # Sysimage extensions + # The test below requires that LinearAlgebra is in the sysimage and that it has not been loaded yet. + # if it gets moved out, this test will need to be updated. + # We run this test in a new process so we are not vulnerable to a previous test having loaded LinearAlgebra + sysimg_ext_test_code = """ + uuid_key = Base.PkgId(Base.UUID("37e2e46d-f89d-539d-b4ee-838fcccc9c8e"), "LinearAlgebra") + Base.in_sysimage(uuid_key) || error("LinearAlgebra not in sysimage") + haskey(Base.explicit_loaded_modules, uuid_key) && error("LinearAlgebra already loaded") + using HasExtensions + Base.get_extension(HasExtensions, :LinearAlgebraExt) === nothing || error("unexpectedly got an extension") + using LinearAlgebra + haskey(Base.explicit_loaded_modules, uuid_key) || error("LinearAlgebra not loaded") + Base.get_extension(HasExtensions, :LinearAlgebraExt) isa Module || error("expected extension to load") + """ + cmd = `$(Base.julia_cmd()) --startup-file=no -e $sysimg_ext_test_code` + cmd = addenv(cmd, "JULIA_LOAD_PATH" => join([proj, "@stdlib"], sep)) + run(cmd) + + + # Extensions in implicit environments + old_load_path = copy(LOAD_PATH) + try + empty!(LOAD_PATH) + push!(LOAD_PATH, joinpath(@__DIR__, "project", "Extensions", "ImplicitEnv")) + pkgid_B = Base.PkgId(Base.uuid5(Base.identify_package("A").uuid, "BExt"), "BExt") + @test Base.identify_package(pkgid_B, "B") isa Base.PkgId + finally + copy!(LOAD_PATH, old_load_path) + end finally try rm(depot_path, force=true, recursive=true) @@ -1092,7 +1156,7 @@ end end end -pkgimage(val) = val == 1 ? `--pkgimage=yes` : `--pkgimage=no` +pkgimage(val) = val == 1 ? `--pkgimages=yes` : `--pkgimages=no` opt_level(val) = `-O$val` debug_level(val) = `-g$val` inline(val) = val == 1 ? `--inline=yes` : `--inline=no` @@ -1223,8 +1287,18 @@ end @test Base.check_src_module_wrap(p, fpath) write(fpath, """ - # using foo - module Foo + \"\"\" + Foo + \"\"\" module Foo + using Bar + end + """) + @test Base.check_src_module_wrap(p, fpath) + + write(fpath, """ + @doc let x = 1 + x + end module Foo using Bar end """) @@ -1264,3 +1338,295 @@ end @test_throws ErrorException Base.check_src_module_wrap(p, fpath) end end + +@testset "relocatable upgrades #51989" begin + mktempdir() do depot + # realpath is needed because Pkg is used for one of the precompile paths below, and Pkg calls realpath on the + # project path so the cache file slug will be different if the tempdir is given as a symlink + # (which it often is on MacOS) which would break the test. + project_path = joinpath(realpath(depot), "project") + mkpath(project_path) + + # Create fake `Foo.jl` package with two files: + foo_path = joinpath(depot, "dev", "Foo51989") + mkpath(joinpath(foo_path, "src")) + open(joinpath(foo_path, "src", "Foo51989.jl"); write=true) do io + println(io, """ + module Foo51989 + include("internal.jl") + end + """) + end + open(joinpath(foo_path, "src", "internal.jl"); write=true) do io + println(io, "const a = \"asd\"") + end + open(joinpath(foo_path, "Project.toml"); write=true) do io + println(io, """ + name = "Foo51989" + uuid = "00000000-0000-0000-0000-000000000001" + version = "1.0.0" + """) + end + + # In our depot, `dev` and then `precompile` this `Foo` package. + @test success(addenv( + `$(Base.julia_cmd()) --project=$project_path --startup-file=no -e 'import Pkg; Pkg.develop("Foo51989"); Pkg.precompile(); exit(0)'`, + "JULIA_DEPOT_PATH" => depot)) + + # Get the size of the generated `.ji` file so that we can ensure that it gets altered + foo_compiled_path = joinpath(depot, "compiled", "v$(VERSION.major).$(VERSION.minor)", "Foo51989") + cache_path = joinpath(foo_compiled_path, only(filter(endswith(".ji"), readdir(foo_compiled_path)))) + cache_size = filesize(cache_path) + + # Next, remove the dependence on `internal.jl` and delete it: + rm(joinpath(foo_path, "src", "internal.jl")) + open(joinpath(foo_path, "src", "Foo51989.jl"); write=true) do io + truncate(io, 0) + println(io, """ + module Foo51989 + end + """) + end + + # Try to load `Foo`; this should trigger recompilation, not an error! + @test success(addenv( + `$(Base.julia_cmd()) --project=$project_path --startup-file=no -e 'using Foo51989; exit(0)'`, + "JULIA_DEPOT_PATH" => depot, + )) + + # Ensure that there is still only one `.ji` file (it got replaced + # and the file size changed). + @test length(filter(endswith(".ji"), readdir(foo_compiled_path))) == 1 + @test filesize(cache_path) != cache_size + end +end + +@testset "code coverage disabled during precompilation" begin + mktempdir() do depot + cov_test_dir = joinpath(@__DIR__, "project", "deps", "CovTest.jl") + cov_cache_dir = joinpath(depot, "compiled", "v$(VERSION.major).$(VERSION.minor)", "CovTest") + function rm_cov_files() + for cov_file in filter(endswith(".cov"), readdir(joinpath(cov_test_dir, "src"), join=true)) + rm(cov_file) + end + @test !cov_exists() + end + cov_exists() = !isempty(filter(endswith(".cov"), readdir(joinpath(cov_test_dir, "src")))) + + rm_cov_files() # clear out any coverage files first + @test !cov_exists() + + cd(cov_test_dir) do + # In our depot, precompile CovTest.jl with coverage on + @test success(addenv( + `$(Base.julia_cmd()) --startup-file=no --pkgimage=yes --code-coverage=@ --project -e 'using CovTest; exit(0)'`, + "JULIA_DEPOT_PATH" => depot, + )) + @test !isempty(filter(!endswith(".ji"), readdir(cov_cache_dir))) # check that object cache file(s) exists + @test !cov_exists() + rm_cov_files() + + # same again but call foo(), which is in the pkgimage, and should generate coverage + @test success(addenv( + `$(Base.julia_cmd()) --startup-file=no --pkgimage=yes --code-coverage=@ --project -e 'using CovTest; foo(); exit(0)'`, + "JULIA_DEPOT_PATH" => depot, + )) + @test cov_exists() + rm_cov_files() + + # same again but call bar(), which is NOT in the pkgimage, and should generate coverage + @test success(addenv( + `$(Base.julia_cmd()) --startup-file=no --pkgimage=yes --code-coverage=@ --project -e 'using CovTest; bar(); exit(0)'`, + "JULIA_DEPOT_PATH" => depot, + )) + @test cov_exists() + rm_cov_files() + end + end +end + +@testset "command-line flags" begin + mktempdir() do depot_path mktempdir() do dir + # generate a Parent.jl and Child.jl package, with Parent depending on Child + open(joinpath(dir, "Child.jl"), "w") do io + println(io, """ + module Child + end""") + end + open(joinpath(dir, "Parent.jl"), "w") do io + println(io, """ + module Parent + using Child + end""") + end + + # helper function to load a package and return the output + function load_package(name, args=``) + code = "using $name" + cmd = addenv(`$(Base.julia_cmd()) -e $code $args`, + "JULIA_LOAD_PATH" => dir, + "JULIA_DEPOT_PATH" => depot_path, + "JULIA_DEBUG" => "loading") + + out = Pipe() + proc = run(pipeline(cmd, stdout=out, stderr=out)) + close(out.in) + + log = @async String(read(out)) + @test success(proc) + fetch(log) + end + + log = load_package("Parent", `--compiled-modules=no --pkgimages=no`) + @test !occursin(r"Generating (cache|object cache) file", log) + @test !occursin(r"Loading (cache|object cache) file", log) + + + ## tests for `--compiled-modules`, which generates cache files + + log = load_package("Child", `--compiled-modules=yes --pkgimages=no`) + @test occursin(r"Generating cache file for Child", log) + @test occursin(r"Loading cache file .+ for Child", log) + + # with `--compiled-modules=existing` we should only precompile Child + log = load_package("Parent", `--compiled-modules=existing --pkgimages=no`) + @test !occursin(r"Generating cache file for Child", log) + @test occursin(r"Loading cache file .+ for Child", log) + @test !occursin(r"Generating cache file for Parent", log) + @test !occursin(r"Loading cache file .+ for Parent", log) + + # the default is `--compiled-modules=yes`, which should now precompile Parent + log = load_package("Parent", `--pkgimages=no`) + @test !occursin(r"Generating cache file for Child", log) + @test occursin(r"Loading cache file .+ for Child", log) + @test occursin(r"Generating cache file for Parent", log) + @test occursin(r"Loading cache file .+ for Parent", log) + + + ## tests for `--pkgimages`, which generates object cache files + + log = load_package("Child", `--compiled-modules=yes --pkgimages=yes`) + @test occursin(r"Generating object cache file for Child", log) + @test occursin(r"Loading object cache file .+ for Child", log) + + # with `--pkgimages=existing` we should only generate code for Child + log = load_package("Parent", `--compiled-modules=yes --pkgimages=existing`) + @test !occursin(r"Generating object cache file for Child", log) + @test occursin(r"Loading object cache file .+ for Child", log) + @test !occursin(r"Generating object cache file for Parent", log) + @test !occursin(r"Loading object cache file .+ for Parent", log) + + # the default is `--pkgimages=yes`, which should now generate code for Parent + log = load_package("Parent") + @test !occursin(r"Generating object cache file for Child", log) + @test occursin(r"Loading object cache file .+ for Child", log) + @test occursin(r"Generating object cache file for Parent", log) + @test occursin(r"Loading object cache file .+ for Parent", log) + end end +end + +@testset "including non-existent file throws proper error #52462" begin + mktempdir() do depot + project_path = joinpath(depot, "project") + mkpath(project_path) + + # Create a `Foo.jl` package + foo_path = joinpath(depot, "dev", "Foo52462") + mkpath(joinpath(foo_path, "src")) + open(joinpath(foo_path, "src", "Foo52462.jl"); write=true) do io + println(io, """ + module Foo52462 + include("non-existent.jl") + end + """) + end + open(joinpath(foo_path, "Project.toml"); write=true) do io + println(io, """ + name = "Foo52462" + uuid = "00000000-0000-0000-0000-000000000001" + version = "1.0.0" + """) + end + + file = joinpath(depot, "dev", "non-existent.jl") + @test_throws SystemError("opening file $(repr(file))") include(file) + end +end + +@testset "-m" begin + rot13proj = joinpath(@__DIR__, "project", "Rot13") + @test readchomp(`$(Base.julia_cmd()) --startup-file=no --project=$rot13proj -m Rot13 --project nowhere ABJURER`) == "--cebwrpg abjurer NOWHERE " +end + +@testset "workspace loading" begin + old_load_path = copy(LOAD_PATH) + try + empty!(LOAD_PATH) + push!(LOAD_PATH, joinpath(@__DIR__, "project", "SubProject")) + @test Base.get_preferences()["value"] == 1 + @test Base.get_preferences()["x"] == 1 + + empty!(LOAD_PATH) + push!(LOAD_PATH, joinpath(@__DIR__, "project", "SubProject", "sub")) + id = Base.identify_package("Devved") + @test isfile(Base.locate_package(id)) + @test Base.identify_package("Devved2") === nothing + id3 = Base.identify_package("MyPkg") + @test isfile(Base.locate_package(id3)) + + empty!(LOAD_PATH) + push!(LOAD_PATH, joinpath(@__DIR__, "project", "SubProject", "PackageThatIsSub")) + id_pkg = Base.identify_package("PackageThatIsSub") + @test Base.identify_package(id_pkg, "Devved") === nothing + id_dev2 = Base.identify_package(id_pkg, "Devved2") + @test isfile(Base.locate_package(id_dev2)) + id_mypkg = Base.identify_package("MyPkg") + @test isfile(Base.locate_package(id_mypkg)) + id_dev = Base.identify_package(id_mypkg, "Devved") + @test isfile(Base.locate_package(id_dev)) + @test Base.get_preferences()["value"] == 2 + @test Base.get_preferences()["x"] == 1 + @test Base.get_preferences()["y"] == 2 + + empty!(LOAD_PATH) + push!(LOAD_PATH, joinpath(@__DIR__, "project", "SubProject", "PackageThatIsSub", "test")) + id_pkg = Base.identify_package("PackageThatIsSub") + @test isfile(Base.locate_package(id_pkg)) + @test Base.identify_package(id_pkg, "Devved") === nothing + id_dev2 = Base.identify_package(id_pkg, "Devved2") + @test isfile(Base.locate_package(id_dev2)) + id_mypkg = Base.identify_package("MyPkg") + @test isfile(Base.locate_package(id_mypkg)) + id_dev = Base.identify_package(id_mypkg, "Devved") + @test isfile(Base.locate_package(id_dev)) + @test Base.get_preferences()["value"] == 3 + @test Base.get_preferences()["x"] == 1 + @test Base.get_preferences()["y"] == 2 + @test Base.get_preferences()["z"] == 3 + + empty!(LOAD_PATH) + push!(LOAD_PATH, joinpath(@__DIR__, "project", "SubProject", "test")) + id_mypkg = Base.identify_package("MyPkg") + id_dev = Base.identify_package(id_mypkg, "Devved") + @test isfile(Base.locate_package(id_dev)) + @test Base.identify_package("Devved2") === nothing + + finally + copy!(LOAD_PATH, old_load_path) + end +end + +@testset "project path handling" begin + old_load_path = copy(LOAD_PATH) + try + push!(LOAD_PATH, joinpath(@__DIR__, "project", "ProjectPath")) + id_project = Base.identify_package("ProjectPath") + Base.locate_package(id_project) + @test Base.locate_package(id_project) == joinpath(@__DIR__, "project", "ProjectPath", "CustomPath.jl") + + id_dep = Base.identify_package("ProjectPathDep") + @test Base.locate_package(id_dep) == joinpath(@__DIR__, "project", "ProjectPath", "ProjectPathDep", "CustomPath.jl") + finally + copy!(LOAD_PATH, old_load_path) + end +end diff --git a/test/math.jl b/test/math.jl index df5c0d23d37a4..53468b76c16ae 100644 --- a/test/math.jl +++ b/test/math.jl @@ -47,6 +47,17 @@ has_fma = Dict( clamp!(x, 1, 3) @test x == [1.0, 1.0, 2.0, 3.0, 3.0] end + + @test clamp(typemax(UInt64), Int64) === typemax(Int64) + @test clamp(typemin(Int), UInt64) === typemin(UInt64) + @test clamp(Int16(-1), UInt16) === UInt16(0) + @test clamp(-1, 2, UInt(0)) === UInt(2) + @test clamp(typemax(UInt16), Int16) === Int16(32767) + + # clamp should not allocate a BigInt for typemax(Int16) + x = big(2) ^ 100 + @test (@allocated clamp(x, Int16)) == 0 + end @testset "constants" begin @@ -1098,7 +1109,7 @@ pcnfloat(x) = prevfloat(x), x, nextfloat(x) import Base.Math: COSH_SMALL_X, H_SMALL_X, H_MEDIUM_X, H_LARGE_X @testset "sinh" begin - for T in (Float32, Float64) + for T in (Float16, Float32, Float64) @test sinh(zero(T)) === zero(T) @test sinh(-zero(T)) === -zero(T) @test sinh(nextfloat(zero(T))) === nextfloat(zero(T)) @@ -1106,15 +1117,17 @@ import Base.Math: COSH_SMALL_X, H_SMALL_X, H_MEDIUM_X, H_LARGE_X @test sinh(T(1000)) === T(Inf) @test sinh(-T(1000)) === -T(Inf) @test isnan_type(T, sinh(T(NaN))) - for x in Iterators.flatten(pcnfloat.([H_SMALL_X(T), H_MEDIUM_X(T), H_LARGE_X(T)])) - @test sinh(x) ≈ sinh(big(x)) rtol=eps(T) - @test sinh(-x) ≈ sinh(big(-x)) rtol=eps(T) + if T ∈ (Float32, Float64) + for x in Iterators.flatten(pcnfloat.([H_SMALL_X(T), H_MEDIUM_X(T), H_LARGE_X(T)])) + @test sinh(x) ≈ sinh(big(x)) rtol=eps(T) + @test sinh(-x) ≈ sinh(big(-x)) rtol=eps(T) + end end end end @testset "cosh" begin - for T in (Float32, Float64) + for T in (Float16, Float32, Float64) @test cosh(zero(T)) === one(T) @test cosh(-zero(T)) === one(T) @test cosh(nextfloat(zero(T))) === one(T) @@ -1122,15 +1135,17 @@ end @test cosh(T(1000)) === T(Inf) @test cosh(-T(1000)) === T(Inf) @test isnan_type(T, cosh(T(NaN))) - for x in Iterators.flatten(pcnfloat.([COSH_SMALL_X(T), H_MEDIUM_X(T), H_LARGE_X(T)])) - @test cosh(x) ≈ cosh(big(x)) rtol=eps(T) - @test cosh(-x) ≈ cosh(big(-x)) rtol=eps(T) + if T ∈ (Float32, Float64) + for x in Iterators.flatten(pcnfloat.([COSH_SMALL_X(T), H_MEDIUM_X(T), H_LARGE_X(T)])) + @test cosh(x) ≈ cosh(big(x)) rtol=eps(T) + @test cosh(-x) ≈ cosh(big(-x)) rtol=eps(T) + end end end end @testset "tanh" begin - for T in (Float32, Float64) + for T in (Float16, Float32, Float64) @test tanh(zero(T)) === zero(T) @test tanh(-zero(T)) === -zero(T) @test tanh(nextfloat(zero(T))) === nextfloat(zero(T)) @@ -1138,9 +1153,11 @@ end @test tanh(T(1000)) === one(T) @test tanh(-T(1000)) === -one(T) @test isnan_type(T, tanh(T(NaN))) - for x in Iterators.flatten(pcnfloat.([H_SMALL_X(T), T(1.0), H_MEDIUM_X(T)])) - @test tanh(x) ≈ tanh(big(x)) rtol=eps(T) - @test tanh(-x) ≈ -tanh(big(x)) rtol=eps(T) + if T ∈ (Float32, Float64) + for x in Iterators.flatten(pcnfloat.([H_SMALL_X(T), T(1.0), H_MEDIUM_X(T)])) + @test tanh(x) ≈ tanh(big(x)) rtol=eps(T) + @test tanh(-x) ≈ -tanh(big(x)) rtol=eps(T) + end end end @test tanh(18.0) ≈ tanh(big(18.0)) rtol=eps(Float64) @@ -1148,7 +1165,7 @@ end end @testset "asinh" begin - for T in (Float32, Float64) + for T in (Float16, Float32, Float64) @test asinh(zero(T)) === zero(T) @test asinh(-zero(T)) === -zero(T) @test asinh(nextfloat(zero(T))) === nextfloat(zero(T)) @@ -1162,7 +1179,7 @@ end end @testset "acosh" begin - for T in (Float32, Float64) + for T in (Float16, Float32, Float64) @test_throws DomainError acosh(T(0.1)) @test acosh(one(T)) === zero(T) @test isnan_type(T, acosh(T(NaN))) @@ -1173,7 +1190,7 @@ end end @testset "atanh" begin - for T in (Float32, Float64) + for T in (Float16, Float32, Float64) @test_throws DomainError atanh(T(1.1)) @test atanh(zero(T)) === zero(T) @test atanh(-zero(T)) === -zero(T) @@ -1355,6 +1372,16 @@ end # hypot on Complex returns Real @test (@inferred hypot(3, 4im)) === 5.0 @test (@inferred hypot(3, 4im, 12)) === 13.0 + @testset "promotion, issue #53505" begin + @testset "Int,$T" for T in (Float16, Float32, Float64, BigFloat) + for args in ((3, 4), (3, 4, 12)) + for i in eachindex(args) + targs = ntuple(j -> (j == i) ? T(args[j]) : args[j], length(args)) + @test (@inferred hypot(targs...)) isa float(eltype(promote(targs...))) + end + end + end + end end struct BadFloatWrapper <: AbstractFloat @@ -1449,6 +1476,25 @@ end # two cases where we have observed > 1 ULP in the past @test 0.0013653274095082324^-97.60372292227069 == 4.088393948750035e279 @test 8.758520413376658e-5^70.55863059215994 == 5.052076767078296e-287 + + # issue #53881 + c53881 = 2.2844135865398217e222 # check correctness within 2 ULPs + @test prevfloat(1.0) ^ -Int64(2)^62 ≈ c53881 atol=2eps(c53881) + @test 2.0 ^ typemin(Int) == 0.0 + @test (-1.0) ^ typemin(Int) == 1.0 + Z = Int64(2) + E = prevfloat(1.0) + @test E ^ (-Z^54) ≈ 7.38905609893065 + @test E ^ (-Z^62) ≈ 2.2844135865231613e222 + @test E ^ (-Z^63) == Inf + @test abs(E ^ (Z^62-1) * E ^ (-Z^62+1) - 1) <= eps(1.0) + n, x = -1065564664, 0.9999997040311492 + @test abs(x^n - Float64(big(x)^n)) / eps(x^n) == 0 # ULPs + @test E ^ (big(2)^100 + 1) == 0 + @test E ^ 6705320061009595392 == nextfloat(0.0) + n = Int64(1024 / log2(E)) + @test E^n == Inf + @test E^float(n) == Inf end # Test that sqrt behaves correctly and doesn't exhibit fp80 double rounding. @@ -1542,33 +1588,64 @@ end end @testset "constant-foldability of core math functions" begin - for fn in (:sin, :cos, :tan, :log, :log2, :log10, :log1p, :exponent, :sqrt, :cbrt, :fourthroot, - :asin, :atan, :acos, :sinh, :cosh, :tanh, :asinh, :acosh, :atanh, - :exp, :exp2, :exp10, :expm1 - ) - for T in (Float16, Float32, Float64) - @testset let f = getfield(@__MODULE__, fn), T = T - @test Core.Compiler.is_foldable(Base.infer_effects(f, (T,))) + for T = Any[Float16, Float32, Float64] + @testset let T = T + for f = Any[sin, cos, tan, log, log2, log10, log1p, exponent, sqrt, cbrt, fourthroot, + asin, atan, acos, sinh, cosh, tanh, asinh, acosh, atanh, exp, exp2, exp10, expm1] + @testset let f = f + @test Base.infer_return_type(f, (T,)) != Union{} + @test Core.Compiler.is_foldable(Base.infer_effects(f, (T,))) + end end + @test Core.Compiler.is_foldable(Base.infer_effects(^, (T,Int))) + @test Core.Compiler.is_foldable(Base.infer_effects(^, (T,T))) end end end; @testset "removability of core math functions" begin - for T in (Float16, Float32, Float64) + for T = Any[Float16, Float32, Float64] @testset let T = T - for f in (exp, exp2, exp10) + for f = Any[exp, exp2, exp10, expm1] @testset let f = f @test Core.Compiler.is_removable_if_unused(Base.infer_effects(f, (T,))) end end - @test Core.Compiler.is_foldable(Base.infer_effects(^, (T,Int))) - @test Core.Compiler.is_foldable(Base.infer_effects(^, (T,T))) end end end; +@testset "exception type inference of core math functions" begin + MathErrorT = Union{DomainError, InexactError} + for T = (Float16, Float32, Float64) + @testset let T = T + for f = Any[sin, cos, tan, log, log2, log10, log1p, exponent, sqrt, cbrt, fourthroot, + asin, atan, acos, sinh, cosh, tanh, asinh, acosh, atanh, exp, exp2, exp10, expm1] + @testset let f = f + @test Base.infer_exception_type(f, (T,)) <: MathErrorT + end + end + @test Base.infer_exception_type(^, (T,Int)) <: MathErrorT + @test Base.infer_exception_type(^, (T,T)) <: MathErrorT + end + end +end; +@test Base.infer_return_type((Int,)) do x + local r = nothing + try + r = sin(x) + catch err + if err isa DomainError + r = 0.0 + end + end + return r +end === Float64 @testset "BigInt Rationals with special funcs" begin @test sinpi(big(1//1)) == big(0.0) @test tanpi(big(1//1)) == big(0.0) @test cospi(big(1//1)) == big(-1.0) end + +@testset "Docstrings" begin + @test isempty(Docs.undocumented_names(MathConstants)) +end diff --git a/test/meta.jl b/test/meta.jl index 36a8acbfe08dd..5fb1ebc0d3647 100644 --- a/test/meta.jl +++ b/test/meta.jl @@ -43,77 +43,70 @@ end @test foundfunc(h_inlined(), :g_inlined) @test foundfunc(h_noinlined(), :g_noinlined) -using Base: pushmeta!, popmeta! +using Base: popmeta! -macro attach(val, ex) - esc(_attach(val, ex)) +macro attach_meta(val, ex) + esc(_attach_meta(val, ex)) end +_attach_meta(val, ex) = Base.pushmeta!(ex, Expr(:test, val)) -_attach(val, ex) = pushmeta!(ex, :test, val) - -@attach 42 function dummy() +@attach_meta 42 function dummy() false end - -asts = code_lowered(dummy, Tuple{}) -@test length(asts) == 1 -ast = asts[1] - -body = Expr(:block) -body.args = ast.code - -@test popmeta!(body, :test) == (true, [42]) -@test popmeta!(body, :nonexistent) == (false, []) +let ast = only(code_lowered(dummy, Tuple{})) + body = Expr(:block) + body.args = ast.code + @test popmeta!(body, :test) == (true, [42]) + @test popmeta!(body, :nonexistent) == (false, []) +end # Simple popmeta!() tests -ex1 = quote - $(Expr(:meta, :foo)) - x*x+1 +let ex1 = quote + $(Expr(:meta, :foo)) + x*x+1 + end + @test popmeta!(ex1, :foo)[1] + @test !popmeta!(ex1, :foo)[1] + @test !popmeta!(ex1, :bar)[1] + @test !(popmeta!(:(x*x+1), :foo)[1]) end -@test popmeta!(ex1, :foo)[1] -@test !popmeta!(ex1, :foo)[1] -@test !popmeta!(ex1, :bar)[1] -@test !(popmeta!(:(x*x+1), :foo)[1]) # Find and pop meta information from general ast locations -multi_meta = quote - $(Expr(:meta, :foo1)) - y = x - $(Expr(:meta, :foo2, :foo3)) - begin - $(Expr(:meta, :foo4, Expr(:foo5, 1, 2))) +let multi_meta = quote + $(Expr(:meta, :foo1)) + y = x + $(Expr(:meta, :foo2, :foo3)) + begin + $(Expr(:meta, :foo4, Expr(:foo5, 1, 2))) + end + x*x+1 end - x*x+1 -end -@test popmeta!(deepcopy(multi_meta), :foo1) == (true, []) -@test popmeta!(deepcopy(multi_meta), :foo2) == (true, []) -@test popmeta!(deepcopy(multi_meta), :foo3) == (true, []) -@test popmeta!(deepcopy(multi_meta), :foo4) == (true, []) -@test popmeta!(deepcopy(multi_meta), :foo5) == (true, [1,2]) -@test popmeta!(deepcopy(multi_meta), :bar) == (false, []) - -# Test that popmeta!() removes meta blocks entirely when they become empty. -for m in [:foo1, :foo2, :foo3, :foo4, :foo5] - @test popmeta!(multi_meta, m)[1] + @test popmeta!(deepcopy(multi_meta), :foo1) == (true, []) + @test popmeta!(deepcopy(multi_meta), :foo2) == (true, []) + @test popmeta!(deepcopy(multi_meta), :foo3) == (true, []) + @test popmeta!(deepcopy(multi_meta), :foo4) == (true, []) + @test popmeta!(deepcopy(multi_meta), :foo5) == (true, [1,2]) + @test popmeta!(deepcopy(multi_meta), :bar) == (false, []) + + # Test that popmeta!() removes meta blocks entirely when they become empty. + ast = :(dummy() = $multi_meta) + for m in [:foo1, :foo2, :foo3, :foo4, :foo5] + @test popmeta!(multi_meta, m)[1] + end + @test Base.findmeta(ast)[1] == 0 end -@test Base.findmeta(multi_meta.args)[1] == 0 # Test that pushmeta! can push across other macros, # in the case multiple pushmeta!-based macros are combined - -@attach 40 @attach 41 @attach 42 dummy_multi() = return nothing - -asts = code_lowered(dummy_multi, Tuple{}) -@test length(asts) == 1 -ast = asts[1] - -body = Expr(:block) -body.args = ast.code - -@test popmeta!(body, :test) == (true, [40]) -@test popmeta!(body, :test) == (true, [41]) -@test popmeta!(body, :test) == (true, [42]) -@test popmeta!(body, :nonexistent) == (false, []) +@attach_meta 40 @attach_meta 41 @attach_meta 42 dummy_multi() = return nothing +let ast = only(code_lowered(dummy_multi, Tuple{})) + body = Expr(:block) + body.args = ast.code + @test popmeta!(body, :test) == (true, [40]) + @test popmeta!(body, :test) == (true, [41]) + @test popmeta!(body, :test) == (true, [42]) + @test popmeta!(body, :nonexistent) == (false, []) +end # tests to fully cover functions in base/meta.jl using Base.Meta @@ -254,14 +247,14 @@ end f(::T) where {T} = T ci = code_lowered(f, Tuple{Int})[1] @test Meta.partially_inline!(ci.code, [], Tuple{typeof(f),Int}, Any[Int], 0, 0, :propagate) == - Any[Core.ReturnNode(QuoteNode(Int))] + Any[QuoteNode(Int), Core.ReturnNode(Core.SSAValue(1))] g(::Val{x}) where {x} = x ? 1 : 0 ci = code_lowered(g, Tuple{Val{true}})[1] -@test Meta.partially_inline!(ci.code, [], Tuple{typeof(g),Val{true}}, Any[true], 0, 0, :propagate)[1] == - Core.GotoIfNot(QuoteNode(true), 3) -@test Meta.partially_inline!(ci.code, [], Tuple{typeof(g),Val{true}}, Any[true], 0, 2, :propagate)[1] == - Core.GotoIfNot(QuoteNode(true), 5) +@test Meta.partially_inline!(ci.code, [], Tuple{typeof(g),Val{true}}, Any[true], 0, 0, :propagate)[2] == + Core.GotoIfNot(Core.SSAValue(1), 4) +@test Meta.partially_inline!(ci.code, [], Tuple{typeof(g),Val{true}}, Any[true], 0, 2, :propagate)[2] == + Core.GotoIfNot(Core.SSAValue(3), 6) @testset "inlining with isdefined" begin isdefined_slot(x) = @isdefined(x) @@ -284,3 +277,7 @@ ci = code_lowered(g, Tuple{Val{true}})[1] [], 0, 0, :propagate)[1] == Expr(:isdefined, GlobalRef(Base, :foo)) end + +@testset "Base.Meta docstrings" begin + @test isempty(Docs.undocumented_names(Meta)) +end diff --git a/test/misc.jl b/test/misc.jl index 79b684badf1e0..6c2f0f89135fd 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -129,6 +129,36 @@ let l = ReentrantLock() @test_throws ErrorException unlock(l) end +# Lockable{T, L<:AbstractLock} +using Base: Lockable +let + @test_broken Base.isexported(Base, :Lockable) + lockable = Lockable(Dict("foo" => "hello"), ReentrantLock()) + # note field access is non-public + @test lockable.value["foo"] == "hello" + @test @lock(lockable, lockable[]["foo"]) == "hello" + lock(lockable) do d + @test d["foo"] == "hello" + end + lock(lockable) do d + d["foo"] = "goodbye" + end + @test lockable.value["foo"] == "goodbye" + @lock lockable begin + @test lockable[]["foo"] == "goodbye" + end + l = trylock(lockable) + try + @test l + finally + unlock(lockable) + end + # Test 1-arg constructor + lockable2 = Lockable(Dict("foo" => "hello")) + @test lockable2.lock isa ReentrantLock + @test @lock(lockable2, lockable2[]["foo"]) == "hello" +end + for l in (Threads.SpinLock(), ReentrantLock()) @test get_finalizers_inhibited() == 0 @test lock(get_finalizers_inhibited, l) == 1 @@ -221,12 +251,14 @@ let c = Ref(0), @test c[] == 100 end -@test_throws ErrorException("deadlock detected: cannot wait on current task") wait(current_task()) +@test_throws ConcurrencyViolationError("deadlock detected: cannot wait on current task") wait(current_task()) + +@test_throws ConcurrencyViolationError("Cannot yield to currently running task!") yield(current_task()) # issue #41347 let t = @async 1 wait(t) - @test_throws ErrorException yield(t) + @test_throws ConcurrencyViolationError yield(t) end let t = @async error(42) @@ -265,6 +297,9 @@ let stats = @timed sin(1) @test stats.value == sin(1) @test isa(stats.time, Real) && stats.time >= 0 + @test isa(stats.compile_time, Real) && stats.compile_time >= 0 + @test isa(stats.recompile_time, Real) && stats.recompile_time >= 0 + @test stats.compile_time <= stats.time # The return type of gcstats was changed in Julia 1.4 (# 34147) # Test that the 1.0 API still works @@ -1058,7 +1093,7 @@ Base.setindex!(xs::InvokeXs2, @nospecialize(v::Any), idx::Int) = xs.xs[idx] = v @test @invoke(f2(1::Real)) === Integer end - # when argment's type annotation is omitted, it should be specified as `Core.Typeof(x)` + # when argument's type annotation is omitted, it should be specified as `Core.Typeof(x)` let f(_) = Any f(x::Integer) = Integer @test f(1) === Integer @@ -1284,10 +1319,56 @@ end end end +module KwdefWithEsc + const Int1 = Int + const val1 = 42 + macro define_struct() + quote + @kwdef struct $(esc(:Struct)) + a + b = val1 + c::Int1 + d::Int1 = val1 + + $(esc(quote + e + f = val2 + g::Int2 + h::Int2 = val2 + end)) + + $(esc(:(i = val2))) + $(esc(:(j::Int2))) + $(esc(:(k::Int2 = val2))) + + l::$(esc(:Int2)) + m::$(esc(:Int2)) = val1 + + n = $(esc(:val2)) + o::Int1 = $(esc(:val2)) + + $(esc(:p)) + $(esc(:q)) = val1 + $(esc(:s))::Int1 + $(esc(:t))::Int1 = val1 + end + end + end +end + +module KwdefWithEsc_TestModule + using ..KwdefWithEsc + const Int2 = Int + const val2 = 42 + KwdefWithEsc.@define_struct() +end +@test isdefined(KwdefWithEsc_TestModule, :Struct) + @testset "exports of modules" begin - for (_, mod) in Base.loaded_modules + @testset "$mod" for (_, mod) in Base.loaded_modules mod === Main && continue # Main exports everything - for v in names(mod) + @testset "$v" for v in names(mod) + isdefined(mod, v) || @error "missing $v in $mod" @test isdefined(mod, v) end end @@ -1300,6 +1381,11 @@ end @test sort([a, b]) == [b, a] end +@testset "UUID display" begin + a = Base.UUID("dbd321ed-e87e-4f33-9511-65b7d01cdd55") + @test repr(a) == "$(Base.UUID)(\"dbd321ed-e87e-4f33-9511-65b7d01cdd55\")" +end + @testset "Libc.rand" begin low, high = extrema(Libc.rand(Float64) for i=1:10^4) # these fail with probability 2^(-10^4) ≈ 5e-3011 @@ -1335,8 +1421,10 @@ end open(tmppath, "w") do tmpio redirect_stderr(tmpio) do GC.enable_logging(true) + @test GC.logging_enabled() GC.gc() GC.enable_logging(false) + @test !GC.logging_enabled() end end @test occursin("GC: pause", read(tmppath, String)) @@ -1353,7 +1441,7 @@ end end # Test that read fault on a prot-none region does not incorrectly give -# ReadOnlyMemoryEror, but rather crashes the program +# ReadOnlyMemoryError, but rather crashes the program const MAP_ANONYMOUS_PRIVATE = Sys.isbsd() ? 0x1002 : 0x22 let script = :( let ptr = Ptr{Cint}(ccall(:jl_mmap, Ptr{Cvoid}, @@ -1382,10 +1470,40 @@ end @testset "Base/timing.jl" begin @test Base.jit_total_bytes() >= 0 - # sanity check `@allocations` returns what we expect in some very simple cases - @test (@allocations "a") == 0 - @test (@allocations "a" * "b") == 0 # constant propagation - @test (@allocations "a" * Base.inferencebarrier("b")) == 1 + # sanity check `@allocations` returns what we expect in some very simple cases. + # These are inside functions because `@allocations` uses `Experimental.@force_compile` + # so can be affected by other code in the same scope. + @test (() -> @allocations "a")() == 0 + @test (() -> @allocations "a" * "b")() == 0 # constant propagation + @test (() -> @allocations "a" * Base.inferencebarrier("b"))() == 1 + + _lock_conflicts, _nthreads = eval(Meta.parse(read(`$(Base.julia_cmd()) -tauto -E ' + _lock_conflicts = @lock_conflicts begin + l = ReentrantLock() + Threads.@threads for i in 1:Threads.nthreads() + lock(l) do + sleep(1) + end + end + end + _lock_conflicts,Threads.nthreads() + '`, String))) + @test _lock_conflicts > 0 skip=(_nthreads < 2) # can only test if the worker can multithread +end + +#TODO: merge with `@testset "Base/timing.jl"` once https://github.com/JuliaLang/julia/issues/52948 is resolved +@testset "Base/timing.jl2" begin + # Test the output of `format_bytes()` + inputs = [(factor * (Int64(1000)^e),binary) for binary in (false,true), factor in (1,2), e in 0:6][:] + expected_output = ["1 byte", "1 byte", "2 bytes", "2 bytes", "1000 bytes", "1000 bytes", "2.000 kB", "1.953 KiB", + "1000.000 kB", "976.562 KiB", "2.000 MB", "1.907 MiB", "1000.000 MB", "953.674 MiB", + "2.000 GB", "1.863 GiB", "1000.000 GB", "931.323 GiB", "2.000 TB", "1.819 TiB", + "1000.000 TB", "909.495 TiB", "2.000 PB", "1.776 PiB", "1000.000 PB", "888.178 PiB", + "2000.000 PB", "1776.357 PiB"] + + for ((n, binary), expected) in zip(inputs, expected_output) + @test Base.format_bytes(n; binary) == expected + end end @testset "in_finalizer" begin @@ -1402,3 +1520,13 @@ end GC.gc(true); yield() @test in_fin[] end + +@testset "Base docstrings" begin + undoc = Docs.undocumented_names(Base) + @test_broken isempty(undoc) + @test undoc == [:BufferStream, :CanonicalIndexError, :CapturedException, :Filesystem, :IOServer, :InvalidStateException, :Order, :PipeEndpoint, :Sort, :TTY] +end + +@testset "Base.Libc docstrings" begin + @test isempty(Docs.undocumented_names(Libc)) +end diff --git a/test/missing.jl b/test/missing.jl index 36155eb32fe49..f588b2dabe904 100644 --- a/test/missing.jl +++ b/test/missing.jl @@ -596,7 +596,7 @@ end @test @coalesce(missing) === missing @test @coalesce(1, error("failed")) === 1 - @test_throws ErrorException @coalesce(missing, error("failed")) + @test_throws ErrorException("failed") @coalesce(missing, error("failed")) end mutable struct Obj; x; end @@ -615,8 +615,7 @@ mutable struct Obj; x; end end @testset "showerror missing function" begin - me = try missing(1) catch e e end - @test sprint(showerror, me) == "MethodError: objects of type Missing are not callable" + @test_throws "MethodError: objects of type Missing are not callable" missing(1) end @testset "sort and sortperm with $(eltype(X))" for (X, P, RP) in @@ -651,3 +650,29 @@ for func in (round, ceil, floor, trunc) @test Core.Compiler.is_foldable(Base.infer_effects(func, (Type{Int},Union{Int,Missing}))) end end + +@testset "Custom Missing type" begin + struct NewMissing end + Base.ismissing(::NewMissing) = true + Base.coalesce(x::NewMissing, y...) = coalesce(y...) + Base.isless(::NewMissing, ::NewMissing) = false + Base.isless(::NewMissing, ::Any) = false + Base.isless(::Any, ::NewMissing) = true + Base.isequal(::NewMissing, ::Missing) = true + Base.isequal(::Missing, ::NewMissing) = true + arr = [missing 1 2 3 missing 10 11 12 missing] + newarr = Union{Int, NewMissing}[ismissing(v) ? NewMissing() : v for v in arr] + + @test all(skipmissing(arr) .== skipmissing(newarr)) + @test all(eachindex(skipmissing(arr)) .== eachindex(skipmissing(newarr))) + @test all(keys(skipmissing(arr)) .== keys(skipmissing(newarr))) + @test_broken sum(skipmissing(arr)) == sum(skipmissing(newarr)) + @test filter(>(10), skipmissing(arr)) == filter(>(10), skipmissing(newarr)) + @test isequal(sort(vec(arr)), sort(vec(newarr))) + + @test_throws MissingException skipmissing(newarr)[findfirst(ismissing, newarr)] + @test coalesce(NewMissing(), 1) == coalesce(NewMissing(), NewMissing(), 1) == 1 + @test coalesce(NewMissing()) === coalesce(NewMissing(), NewMissing()) === missing + @test @coalesce(NewMissing(), 1) == @coalesce(NewMissing(), NewMissing(), 1) == 1 + @test @coalesce(NewMissing()) === @coalesce(NewMissing(), NewMissing()) === missing +end diff --git a/test/mod2pi.jl b/test/mod2pi.jl index 5b0cb906bcef2..0eeac6f1e3ce4 100644 --- a/test/mod2pi.jl +++ b/test/mod2pi.jl @@ -26,7 +26,7 @@ # 3.14159265359, -3.14159265359 # pi/16*k +/- 0.00001 for k in [-20:20] # to cover all quadrants # numerators of continuous fraction approximations to pi -# see http://oeis.org/A002485 +# see https://oeis.org/A002485 # (reason: for max cancellation, we want x = k*pi + eps for small eps, so x/k ≈ pi) testCases = [ diff --git a/test/namedtuple.jl b/test/namedtuple.jl index 20737d68db1bb..48aa8ea4a2591 100644 --- a/test/namedtuple.jl +++ b/test/namedtuple.jl @@ -94,6 +94,9 @@ end conv_res = @test_throws MethodError convert(NamedTuple{(:a,),Tuple{I}} where I<:AbstractString, (;a=1)) @test conv_res.value.f === convert && conv_res.value.args === (AbstractString, 1) + + conv6 = convert(NamedTuple{(:a,),Tuple{NamedTuple{(:b,), Tuple{Int}}}}, ((1,),)) + @test conv6 === (a = (b = 1,),) end @test NamedTuple{(:a,:c)}((b=1,z=2,c=3,aa=4,a=5)) === (a=5, c=3) @@ -134,6 +137,14 @@ end @test map(string, (x=1, y=2)) == (x="1", y="2") @test map(round, (x=UInt, y=Int), (x=3.1, y=2//3)) == (x=UInt(3), y=1) +@testset "filter" begin + @test filter(isodd, (a=1,b=2,c=3)) === (a=1, c=3) + @test filter(i -> true, (;)) === (;) + longnt = NamedTuple{ntuple(i -> Symbol(:a, i), 20)}(ntuple(identity, 20)) + @test filter(iseven, longnt) === NamedTuple{ntuple(i -> Symbol(:a, 2i), 10)}(ntuple(i -> 2i, 10)) + @test filter(x -> x<2, (longnt..., z=1.5)) === (a1=1, z=1.5) +end + @test merge((a=1, b=2), (a=10,)) == (a=10, b=2) @test merge((a=1, b=2), (a=10, z=20)) == (a=10, b=2, z=20) @test merge((a=1, b=2), (z=20,)) == (a=1, b=2, z=20) @@ -426,3 +437,18 @@ let c = (a=1, b=2), d = (b=3, c=(d=1,)) @test @inferred(mergewith51009((x,y)->y, c, d)) === (a = 1, b = 3, c = (d = 1,)) end + +@test_throws ErrorException NamedTuple{(), Union{}} +for NT in (NamedTuple{(:a, :b), Union{}}, NamedTuple{(:a, :b), T} where T<:Union{}) + @test fieldtype(NT, 1) == Union{} + @test fieldtype(NT, :b) == Union{} + @test_throws ErrorException fieldtype(NT, :c) + @test_throws BoundsError fieldtype(NT, 0) + @test_throws BoundsError fieldtype(NT, 3) + @test Base.return_types((Type{NT},)) do NT; fieldtype(NT, :a); end == Any[Type{Union{}}] + @test fieldtype(NamedTuple{<:Any, Union{}}, 1) == Union{} +end +let NT = NamedTuple{<:Any, Union{}} + @test fieldtype(NT, 100) == Union{} + @test only(Base.return_types((Type{NT},)) do NT; fieldtype(NT, 100); end) >: Type{Union{}} +end diff --git a/test/numbers.jl b/test/numbers.jl index a9d126aa33d5a..234e999e9427e 100644 --- a/test/numbers.jl +++ b/test/numbers.jl @@ -679,6 +679,9 @@ end @test copysign(big(-1), 0x02) == 1 @test copysign(big(-1.0), 0x02) == 1.0 @test copysign(-1//2, 0x01) == 1//2 + + # Verify overflow is checked with rational + @test_throws OverflowError copysign(typemin(Int)//1, 1) end @testset "isnan/isinf/isfinite" begin @@ -1111,10 +1114,30 @@ end end @testset "Irrational zero and one" begin - @test one(pi) === true - @test zero(pi) === false - @test one(typeof(pi)) === true - @test zero(typeof(pi)) === false + for i in (π, ℯ, γ, catalan) + @test one(i) === true + @test zero(i) === false + @test one(typeof(i)) === true + @test zero(typeof(i)) === false + end +end + +@testset "Irrational iszero, isfinite, isinteger, and isone" begin + for i in (π, ℯ, γ, catalan) + @test !iszero(i) + @test !isone(i) + @test !isinteger(i) + @test isfinite(i) + end +end + +@testset "Irrational promote_type" begin + for T in (Float16, Float32, Float64) + for i in (π, ℯ, γ, catalan) + @test T(2.0) * i ≈ T(2.0) * T(i) + @test T(2.0) * i isa T + end + end end @testset "Irrationals compared with Irrationals" begin @@ -2686,7 +2709,7 @@ end @test divrem(a,-(a-20), RoundDown) == (div(a,-(a-20), RoundDown), rem(a,-(a-20), RoundDown)) end -@testset "rem2pi $T" for T in (Float16, Float32, Float64, BigFloat) +@testset "rem2pi $T" for T in (Float16, Float32, Float64, BigFloat, Int8, Int16, Int32, Int64, Int128) @test rem2pi(T(1), RoundToZero) == 1 @test rem2pi(T(1), RoundNearest) == 1 @test rem2pi(T(1), RoundDown) == 1 @@ -2773,6 +2796,20 @@ Base.literal_pow(::typeof(^), ::PR20530, ::Val{p}) where {p} = 2 @test [2,4,8].^-2 == [0.25, 0.0625, 0.015625] @test [2, 4, 8].^-2 .* 4 == [1.0, 0.25, 0.0625] # nested literal_pow @test ℯ^-2 == exp(-2) ≈ inv(ℯ^2) ≈ (ℯ^-1)^2 ≈ sqrt(ℯ^-4) + + if Int === Int32 + p = 2147483647 + @test x^p == 1 + @test x^2147483647 == 2 + @test (@fastmath x^p) == 1 + @test (@fastmath x^2147483647) == 2 + elseif Int === Int64 + p = 9223372036854775807 + @test x^p == 1 + @test x^9223372036854775807 == 2 + @test (@fastmath x^p) == 1 + @test (@fastmath x^9223372036854775807) == 2 + end end module M20889 # do we get the expected behavior without importing Base.^? using Test @@ -3141,3 +3178,32 @@ end @test n == G(F(n)) == F(G(n)) end end + +@testset "`precision`" begin + Fs = (Float16, Float32, Float64, BigFloat) + + @testset "type vs instance" begin + @testset "F: $F" for F ∈ Fs + @test precision(F) == precision(one(F)) + @test precision(F, base = 2) == precision(one(F), base = 2) + @test precision(F, base = 3) == precision(one(F), base = 3) + end + end + + @testset "`precision` of `Union` shouldn't recur infinitely, #52909" begin + @testset "i: $i" for i ∈ eachindex(Fs) + @testset "j: $j" for j ∈ (i + 1):lastindex(Fs) + S = Fs[i] + T = Fs[j] + @test_throws MethodError precision(Union{S,T}) + @test_throws MethodError precision(Union{S,T}, base = 3) + end + end + end +end + +@testset "irrational special values" begin + for v ∈ (π, ℯ, γ, catalan, φ) + @test v === typemin(v) === typemax(v) + end +end diff --git a/test/offsetarray.jl b/test/offsetarray.jl index 3e1acff67b546..9d6a8b08c0b1f 100644 --- a/test/offsetarray.jl +++ b/test/offsetarray.jl @@ -403,7 +403,8 @@ v2 = copy(v) v = OffsetArray(v0, (-3,)) @test lastindex(v) == 1 @test v ≈ v -@test axes(v') === (Base.OneTo(1), OffsetArrays.IdOffsetRange(Base.OneTo(4), -3)) +@test (@inferred axes(v')[1]) === OffsetArrays.IdOffsetRange(Base.OneTo(1)) +@test (@inferred axes(v')[2]) === OffsetArrays.IdOffsetRange(Base.OneTo(4), -3) @test parent(v) == collect(v) rv = reverse(v) @test axes(rv) == axes(v) @@ -627,15 +628,15 @@ end B = OffsetArray(reshape(1:24, 4, 3, 2), -5, 6, -7) for R in (fill(0, -4:-1), fill(0, -4:-1, 7:7), fill(0, -4:-1, 7:7, -6:-6)) @test @inferred(maximum!(R, B)) == reshape(maximum(B, dims=(2,3)), axes(R)) == reshape(21:24, axes(R)) - @test @allocated(maximum!(R, B)) <= 300 + @test @allocated(maximum!(R, B)) <= 400 @test @inferred(minimum!(R, B)) == reshape(minimum(B, dims=(2,3)), axes(R)) == reshape(1:4, axes(R)) - @test @allocated(minimum!(R, B)) <= 300 + @test @allocated(minimum!(R, B)) <= 400 end for R in (fill(0, -4:-4, 7:9), fill(0, -4:-4, 7:9, -6:-6)) @test @inferred(maximum!(R, B)) == reshape(maximum(B, dims=(1,3)), axes(R)) == reshape(16:4:24, axes(R)) - @test @allocated(maximum!(R, B)) <= 300 + @test @allocated(maximum!(R, B)) <= 400 @test @inferred(minimum!(R, B)) == reshape(minimum(B, dims=(1,3)), axes(R)) == reshape(1:4:9, axes(R)) - @test @allocated(minimum!(R, B)) <= 300 + @test @allocated(minimum!(R, B)) <= 400 end @test_throws DimensionMismatch maximum!(fill(0, -4:-1, 7:7, -6:-6, 1:1), B) @test_throws DimensionMismatch minimum!(fill(0, -4:-1, 7:7, -6:-6, 1:1), B) @@ -863,3 +864,8 @@ end # this is fixed in #40038, so the evaluation of its CartesianIndices should work @test CartesianIndices(A) == CartesianIndices(B) end + +@testset "indexing views (#53249)" begin + v = view([1,2,3,4], :) + @test v[Base.IdentityUnitRange(2:3)] == OffsetArray(2:3, 2:3) +end diff --git a/test/opaque_closure.jl b/test/opaque_closure.jl index e809013ba31be..490ff282b7a53 100644 --- a/test/opaque_closure.jl +++ b/test/opaque_closure.jl @@ -189,8 +189,6 @@ let ci = @code_lowered const_int() cig.slotnames = Symbol[Symbol("#self#")] cig.slottypes = Any[Any] cig.slotflags = UInt8[0x00] - @assert cig.min_world == UInt(1) - @assert cig.max_world == typemax(UInt) return cig end end @@ -241,13 +239,21 @@ let foo::Int = 42 end let oc = @opaque a->sin(a) - @test length(code_typed(oc, (Int,))) == 1 + let opt = code_typed(oc, (Int,)) + @test length(opt) == 1 + @test opt[1][2] === Float64 + end + let unopt = code_typed(oc, (Int,); optimize=false) + @test length(unopt) == 1 + end end # constructing an opaque closure from IRCode let src = first(only(code_typed(+, (Int, Int)))) - ir = Core.Compiler.inflate_ir(src) - @test OpaqueClosure(src)(40, 2) == 42 + ir = Core.Compiler.inflate_ir(src, Core.Compiler.VarState[], src.slottypes) + @test ir.debuginfo.def === nothing + ir.debuginfo.def = Symbol(@__FILE__) + @test OpaqueClosure(src; sig=Tuple{Int, Int}, rettype=Int, nargs=2)(40, 2) == 42 oc = OpaqueClosure(ir) @test oc(40, 2) == 42 @test isa(oc, OpaqueClosure{Tuple{Int,Int}, Int}) @@ -257,6 +263,7 @@ end let ir = first(only(Base.code_ircode(sin, (Int,)))) @test OpaqueClosure(ir)(42) == sin(42) @test OpaqueClosure(ir)(42) == sin(42) # the `OpaqueClosure(::IRCode)` constructor should be non-destructive + @test length(code_typed(OpaqueClosure(ir))) == 1 ir = first(only(Base.code_ircode(sin, (Float64,)))) @test OpaqueClosure(ir)(42.) == sin(42.) @test OpaqueClosure(ir)(42.) == sin(42.) # the `OpaqueClosure(::IRCode)` constructor should be non-destructive @@ -266,11 +273,13 @@ end let src = code_typed((Int,Int)) do x, y... return (x, y) end |> only |> first - let oc = OpaqueClosure(src) + let oc = OpaqueClosure(src; rettype=Tuple{Int, Tuple{Int}}, sig=Tuple{Int, Int}, nargs=2, isva=true) @test oc(1,2) === (1,(2,)) @test_throws MethodError oc(1,2,3) end - ir = Core.Compiler.inflate_ir(src) + ir = Core.Compiler.inflate_ir(src, Core.Compiler.VarState[], src.slottypes) + @test ir.debuginfo.def === nothing + ir.debuginfo.def = Symbol(@__FILE__) let oc = OpaqueClosure(ir; isva=true) @test oc(1,2) === (1,(2,)) @test_throws MethodError oc(1,2,3) @@ -347,3 +356,17 @@ let ir = Base.code_ircode((Int,Int)) do x, y code_llvm(io, oc, (Int,Int)) @test occursin("j_*_", String(take!(io))) end + +foopaque() = Base.Experimental.@opaque(@noinline x::Int->println(x))(1) + +code_llvm(devnull,foopaque,()) #shouldn't crash + +let ir = first(only(Base.code_ircode(sin, (Int,)))) + oc = Core.OpaqueClosure(ir) + @test (Base.show_method(IOBuffer(), oc.source::Method); true) +end + +let ir = first(only(Base.code_ircode(sin, (Int,)))) + oc = Core.OpaqueClosure(ir; do_compile=false) + @test oc(1) == sin(1) +end diff --git a/test/operators.jl b/test/operators.jl index cf72d83ab0076..95006235692a0 100644 --- a/test/operators.jl +++ b/test/operators.jl @@ -195,7 +195,7 @@ end @test repr(uppercase ∘ first) == "uppercase ∘ first" @test sprint(show, "text/plain", uppercase ∘ first) == "uppercase ∘ first" - # test keyword ags in composition + # test keyword args in composition function kwf(a;b,c); a + b + c; end @test (abs2 ∘ kwf)(1,b=2,c=3) == 36 diff --git a/test/ordering.jl b/test/ordering.jl index 972d48c17b1af..3b5385b99be68 100644 --- a/test/ordering.jl +++ b/test/ordering.jl @@ -51,3 +51,9 @@ struct SomeOtherOrder <: Base.Order.Ordering end @test ord(<, abs, false, Forward) === By(abs, Lt(<)) @test ord(<, abs, true, Forward) === ReverseOrdering(By(abs, Lt(<))) @test ord(<, abs, true, Reverse) === By(abs, Lt(<)) + +@testset "Base.Order docstrings" begin + undoc = Docs.undocumented_names(Base.Order) + @test_broken isempty(undoc) + @test undoc == [:DirectOrdering, :ForwardOrdering, :Order, :ordtype] +end diff --git a/test/parse.jl b/test/parse.jl index 69092b2c4188d..e2b94a45cc446 100644 --- a/test/parse.jl +++ b/test/parse.jl @@ -296,6 +296,8 @@ end @test_throws ArgumentError parse(Complex{T}, bad) end @test_throws ArgumentError parse(Complex{Int}, "3 + 4.2im") + @test_throws ArgumentError parse(ComplexF64, "3 β+ 4im") + @test_throws ArgumentError parse(ComplexF64, "3 + 4αm") end @testset "parse and tryparse type inference" begin diff --git a/test/precompile.jl b/test/precompile.jl index 671535c360625..ffd6e24789aba 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -1,11 +1,10 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -original_depot_path = copy(Base.DEPOT_PATH) -original_load_path = copy(Base.LOAD_PATH) - -using Test, Distributed, Random +using Test, Distributed, Random, Logging using REPL # doc lookup function +include("precompile_utils.jl") + Foo_module = :Foo4b3a94a1a081a8cb Foo2_module = :F2oo4b3a94a1a081a8cb FooBase_module = :FooBase4b3a94a1a081a8cb @@ -16,35 +15,9 @@ FooBase_module = :FooBase4b3a94a1a081a8cb end using .ConflictingBindings -function precompile_test_harness(@nospecialize(f), testset::String) - @testset "$testset" begin - precompile_test_harness(f, true) - end -end -function precompile_test_harness(@nospecialize(f), separate::Bool) - load_path = mktempdir() - load_cache_path = separate ? mktempdir() : load_path - try - pushfirst!(LOAD_PATH, load_path) - pushfirst!(DEPOT_PATH, load_cache_path) - f(load_path) - finally - try - rm(load_path, force=true, recursive=true) - catch err - @show err - end - if separate - try - rm(load_cache_path, force=true, recursive=true) - catch err - @show err - end - end - filter!((≠)(load_path), LOAD_PATH) - separate && filter!((≠)(load_cache_path), DEPOT_PATH) - end - nothing +@testset "object_build_id" begin + @test Base.object_build_id([1]) === nothing + @test Base.object_build_id(Base) == Base.module_build_id(Base) end # method root provenance @@ -115,6 +88,8 @@ precompile_test_harness(false) do dir d = den(a) return h end + abstract type AbstractAlgebraMap{A} end + struct GAPGroupHomomorphism{A, B} <: AbstractAlgebraMap{GAPGroupHomomorphism{B, A}} end end """) write(Foo2_file, @@ -130,7 +105,7 @@ precompile_test_harness(false) do dir write(Foo_file, """ module $Foo_module - import $FooBase_module, $FooBase_module.typeA + import $FooBase_module, $FooBase_module.typeA, $FooBase_module.GAPGroupHomomorphism import $Foo2_module: $Foo2_module, override, overridenc import $FooBase_module.hash import Test @@ -213,6 +188,8 @@ precompile_test_harness(false) do dir Base.convert(::Type{Some{Value18343}}, ::Value18343{Some}) = 2 Base.convert(::Type{Ref}, ::Value18343{T}) where {T} = 3 + const GAPType1 = GAPGroupHomomorphism{Nothing, Nothing} + const GAPType2 = GAPGroupHomomorphism{1, 2} # issue #28297 mutable struct Result @@ -271,6 +248,26 @@ precompile_test_harness(false) do dir # check that Tasks work from serialized state ch1 = Channel(x -> nothing) ch2 = Channel(x -> (push!(x, 2); nothing), Inf) + + # check that Memory aliasing is respected + a_vec_int = Int[] + push!(a_vec_int, 1, 2) + a_mat_int = reshape(a_vec_int, (1, 2)) + + a_vec_any = Any[] + push!(a_vec_any, 1, 2) + a_mat_any = reshape(a_vec_any, (1, 2)) + + a_vec_union = Union{Int,Nothing}[] + push!(a_vec_union, 1, 2) + a_mat_union = reshape(a_vec_union, (1, 2)) + + a_vec_inline = Pair{Int,Any}[] + push!(a_vec_inline, 1=>2, 3=>4) + a_mat_inline = reshape(a_vec_inline, (1, 2)) + + oid_vec_int = objectid(a_vec_int) + oid_mat_int = objectid(a_mat_int) end """) # Issue #12623 @@ -332,6 +329,37 @@ precompile_test_harness(false) do dir @test !isready(Foo.ch2) end + let + @test Foo.a_vec_int == Int[1, 2] + @test Foo.a_mat_int == Int[1 2] + Foo.a_mat_int[1, 2] = 3 + @test Foo.a_vec_int[2] === 3 + + @test Foo.a_vec_any == Int[1, 2] + @test Foo.a_mat_any == Int[1 2] + Foo.a_mat_any[1, 2] = 3 + @test Foo.a_vec_any[2] === 3 + + @test Foo.a_vec_union == Union{Int,Nothing}[1, 2] + @test Foo.a_mat_union == Union{Int,Nothing}[1 2] + Foo.a_mat_union[1, 2] = 3 + @test Foo.a_vec_union[2] === 3 + Foo.a_mat_union[1, 2] = nothing + @test Foo.a_vec_union[2] === nothing + + @test Foo.a_vec_inline == Pair{Int,Any}[1=>2, 3=>4] + @test Foo.a_mat_inline == Pair{Int,Any}[1=>2 3=>4] + Foo.a_mat_inline[1, 2] = 5=>6 + @test Foo.a_vec_inline[2] === Pair{Int,Any}(5, 6) + + @test objectid(Foo.a_vec_int) === Foo.oid_vec_int + @test objectid(Foo.a_mat_int) === Foo.oid_mat_int + @test Foo.oid_vec_int !== Foo.oid_mat_int + @test Base.object_build_id(Foo.a_vec_int) == Base.object_build_id(Foo.a_mat_int) + @test Base.object_build_id(Foo) == Base.module_build_id(Foo) + @test Base.object_build_id(Foo.a_vec_int) == Base.module_build_id(Foo) + end + @eval begin function ccallable_test() Base.llvmcall( ("""declare i32 @f35014(i32) @@ -355,11 +383,11 @@ precompile_test_harness(false) do dir else ocachefile = nothing end - # use _require_from_serialized to ensure that the test fails if - # the module doesn't reload from the image: + # use _require_from_serialized to ensure that the test fails if + # the module doesn't reload from the image: @test_warn "@ccallable was already defined for this method name" begin @test_logs (:warn, "Replacing module `$Foo_module`") begin - m = Base._require_from_serialized(Base.PkgId(Foo), cachefile, ocachefile) + m = Base._require_from_serialized(Base.PkgId(Foo), cachefile, ocachefile, Foo_file) @test isa(m, Module) end end @@ -381,10 +409,11 @@ precompile_test_harness(false) do dir @test string(Base.Docs.doc(Foo.Bar.bar)) == "bar function\n" @test string(Base.Docs.doc(Foo.Bar)) == "Bar module\n" - modules, (deps, requires), required_modules, _... = Base.parse_cache_header(cachefile) - discard_module = mod_fl_mt -> (mod_fl_mt.filename, mod_fl_mt.mtime) + modules, (deps, _, requires), required_modules, _... = Base.parse_cache_header(cachefile) + discard_module = mod_fl_mt -> mod_fl_mt.filename @test modules == [ Base.PkgId(Foo) => Base.module_build_id(Foo) % UInt64 ] - @test map(x -> x.filename, deps) == [ Foo_file, joinpath(dir, "foo.jl"), joinpath(dir, "bar.jl") ] + # foo.jl and bar.jl are never written to disk, so they are not relocatable + @test map(x -> x.filename, deps) == [ Foo_file, joinpath("@depot", "foo.jl"), joinpath("@depot", "bar.jl") ] @test requires == [ Base.PkgId(Foo) => Base.PkgId(string(FooBase_module)), Base.PkgId(Foo) => Base.PkgId(Foo2), Base.PkgId(Foo) => Base.PkgId(Test), @@ -416,13 +445,14 @@ precompile_test_harness(false) do dir # and their dependencies Dict(Base.PkgId(Base.root_module(Base, :SHA)) => Base.module_build_id(Base.root_module(Base, :SHA))), Dict(Base.PkgId(Base.root_module(Base, :Markdown)) => Base.module_build_id(Base.root_module(Base, :Markdown))), + Dict(Base.PkgId(Base.root_module(Base, :StyledStrings)) => Base.module_build_id(Base.root_module(Base, :StyledStrings))), # and their dependencies Dict(Base.PkgId(Base.root_module(Base, :Base64)) => Base.module_build_id(Base.root_module(Base, :Base64))), ) @test Dict(modules) == modules_ok @test discard_module.(deps) == deps1 - modules, (deps, requires), required_modules, _... = Base.parse_cache_header(cachefile; srcfiles_only=true) + modules, (_, deps, requires), required_modules, _... = Base.parse_cache_header(cachefile) @test map(x -> x.filename, deps) == [Foo_file] @test current_task()(0x01, 0x4000, 0x30031234) == 2 @@ -485,7 +515,7 @@ precompile_test_harness(false) do dir """) Nest = Base.require(Main, Nest_module) cachefile = joinpath(cachedir, "$Nest_module.ji") - modules, (deps, requires), required_modules, _... = Base.parse_cache_header(cachefile) + modules, (deps, _, requires), required_modules, _... = Base.parse_cache_header(cachefile) @test last(deps).modpath == ["NestInner"] UsesB_module = :UsesB4b3a94a1a081a8cb @@ -507,7 +537,7 @@ precompile_test_harness(false) do dir """) UsesB = Base.require(Main, UsesB_module) cachefile = joinpath(cachedir, "$UsesB_module.ji") - modules, (deps, requires), required_modules, _... = Base.parse_cache_header(cachefile) + modules, (deps, _, requires), required_modules, _... = Base.parse_cache_header(cachefile) id1, id2 = only(requires) @test Base.pkgorigins[id1].cachepath == cachefile @test Base.pkgorigins[id2].cachepath == joinpath(cachedir, "$B_module.ji") @@ -523,6 +553,16 @@ precompile_test_harness(false) do dir @test Base.compilecache(Base.PkgId("Baz")) == Base.PrecompilableError() # due to __precompile__(false) + OverwriteMethodError_file = joinpath(dir, "OverwriteMethodError.jl") + write(OverwriteMethodError_file, + """ + module OverwriteMethodError + Base.:(+)(x::Bool, y::Bool) = false + end + """) + + @test (@test_warn "overwritten in module OverwriteMethodError" Base.compilecache(Base.PkgId("OverwriteMethodError"))) == Base.PrecompilableError() # due to piracy + UseBaz_file = joinpath(dir, "UseBaz.jl") write(UseBaz_file, """ @@ -566,7 +606,7 @@ precompile_test_harness(false) do dir end """) - cachefile, _ = Base.compilecache(Base.PkgId("FooBar")) + cachefile, _ = @test_logs (:debug, r"Precompiling FooBar") min_level=Logging.Debug match_mode=:any Base.compilecache(Base.PkgId("FooBar")) empty_prefs_hash = Base.get_preferences_hash(nothing, String[]) @test cachefile == Base.compilecache_path(Base.PkgId("FooBar"), empty_prefs_hash) @test isfile(joinpath(cachedir, "FooBar.ji")) @@ -584,12 +624,12 @@ precompile_test_harness(false) do dir fb_uuid = Base.module_build_id(FooBar) sleep(2); touch(FooBar_file) insert!(DEPOT_PATH, 1, dir2) - @test Base.stale_cachefile(FooBar_file, joinpath(cachedir, "FooBar.ji")) === true + @test Base.stale_cachefile(FooBar_file, joinpath(cachedir, "FooBar.ji")) isa Tsc @eval using FooBar1 @test !isfile(joinpath(cachedir2, "FooBar.ji")) @test !isfile(joinpath(cachedir, "FooBar1.ji")) @test isfile(joinpath(cachedir2, "FooBar1.ji")) - @test Base.stale_cachefile(FooBar_file, joinpath(cachedir, "FooBar.ji")) === true + @test Base.stale_cachefile(FooBar_file, joinpath(cachedir, "FooBar.ji")) isa Tsc @test Base.stale_cachefile(FooBar1_file, joinpath(cachedir2, "FooBar1.ji")) isa Tsc @test fb_uuid == Base.module_build_id(FooBar) fb_uuid1 = Base.module_build_id(FooBar1) @@ -609,7 +649,7 @@ precompile_test_harness(false) do dir error("break me") end """) - @test_warn r"LoadError: break me\nStacktrace:\n \[1\] [\e01m\[]*error" try + @test_warn r"LoadError: break me\nStacktrace:\n[ ]*\[1\] [\e01m\[]*error" try Base.require(Main, :FooBar2) error("the \"break me\" test failed") catch exc @@ -727,15 +767,15 @@ precompile_test_harness("code caching") do dir @test hasspec # Test that compilation adds to method roots with appropriate provenance m = which(setindex!, (Dict{M.X,Any}, Any, M.X)) - @test M.X ∈ m.roots + @test Memory{M.X} ∈ m.roots # Check that roots added outside of incremental builds get attributed to a moduleid of 0 Base.invokelatest() do Dict{M.X2,Any}()[M.X2()] = nothing end - @test M.X2 ∈ m.roots + @test Memory{M.X2} ∈ m.roots groups = group_roots(m) - @test M.X ∈ groups[Mid] # attributed to M - @test M.X2 ∈ groups[0] # activate module is not known + @test Memory{M.X} ∈ groups[Mid] # attributed to M + @test Memory{M.X2} ∈ groups[0] # activate module is not known @test !isempty(groups[Bid]) # Check that internal methods and their roots are accounted appropriately minternal = which(M.getelsize, (Vector,)) @@ -785,10 +825,10 @@ precompile_test_harness("code caching") do dir end mT = which(push!, (Vector{T} where T, Any)) groups = group_roots(mT) - @test M2.Y ∈ groups[M2id] - @test M2.Z ∈ groups[M2id] - @test M.X ∈ groups[Mid] - @test M.X ∉ groups[M2id] + @test Memory{M2.Y} ∈ groups[M2id] + @test Memory{M2.Z} ∈ groups[M2id] + @test Memory{M.X} ∈ groups[Mid] + @test Memory{M.X} ∉ groups[M2id] # backedges of external MethodInstances # Root gets used by RootA and RootB, and both consumers end up inferring the same MethodInstance from Root # Do both callers get listed as backedges? @@ -1630,14 +1670,16 @@ precompile_test_harness("Issue #46558") do load_path @test (@eval $Foo.foo(1)) == 2 end +# TODO: Decide if we need to keep supporting this. precompile_test_harness("issue #46296") do load_path write(joinpath(load_path, "CodeInstancePrecompile.jl"), """ module CodeInstancePrecompile mi = first(Base.specializations(first(methods(identity)))) - ci = Core.CodeInstance(mi, Any, nothing, nothing, zero(Int32), typemin(UInt), - typemax(UInt), zero(UInt32), zero(UInt32), nothing, 0x00) + ci = Core.CodeInstance(mi, nothing, Any, Any, nothing, nothing, zero(Int32), typemin(UInt), + typemax(UInt), zero(UInt32), zero(UInt32), nothing, 0x00, + Core.DebugInfo(mi)) __init__() = @assert ci isa Core.CodeInstance @@ -1647,6 +1689,12 @@ precompile_test_harness("issue #46296") do load_path (@eval (using CodeInstancePrecompile)) end +@testset "Precompile external abstract interpreter" begin + dir = @__DIR__ + @test success(pipeline(Cmd(`$(Base.julia_cmd()) precompile_absint1.jl`; dir); stdout, stderr)) + @test success(pipeline(Cmd(`$(Base.julia_cmd()) precompile_absint2.jl`; dir); stdout, stderr)) +end + precompile_test_harness("Recursive types") do load_path write(joinpath(load_path, "RecursiveTypeDef.jl"), """ @@ -1712,7 +1760,7 @@ precompile_test_harness("PkgCacheInspector") do load_path try # isvalid_cache_header returns checksum id or zero Base.isvalid_cache_header(io) == 0 && throw(ArgumentError("Invalid header in cache file $cachefile.")) - depmodnames = Base.parse_cache_header(io)[3] + depmodnames = Base.parse_cache_header(io, cachefile)[3] Base.isvalid_file_crc(io) || throw(ArgumentError("Invalid checksum in cache file $cachefile.")) finally close(io) @@ -1730,15 +1778,15 @@ precompile_test_harness("PkgCacheInspector") do load_path end if ocachefile !== nothing - sv = ccall(:jl_restore_package_image_from_file, Any, (Cstring, Any, Cint, Cstring), ocachefile, depmods, true, "PCI") + sv = ccall(:jl_restore_package_image_from_file, Any, (Cstring, Any, Cint, Cstring, Cint), ocachefile, depmods, true, "PCI", false) else sv = ccall(:jl_restore_incremental, Any, (Cstring, Any, Cint, Cstring), cachefile, depmods, true, "PCI") end - modules, init_order, external_methods, new_specializations, new_method_roots, external_targets, edges = sv - m = only(external_methods) + modules, init_order, external_methods, new_ext_cis, new_method_roots, external_targets, edges = sv + m = only(external_methods).func::Method @test m.name == :repl_cmd && m.nargs < 2 - @test any(new_specializations) do ci + @test new_ext_cis === nothing || any(new_ext_cis) do ci mi = ci.def mi.specTypes == Tuple{typeof(Base.repl_cmd), Int, String} end @@ -1858,7 +1906,32 @@ precompile_test_harness("Issue #50538") do load_path @test !isdefined(I50538, :undefglobal) end -empty!(Base.DEPOT_PATH) -append!(Base.DEPOT_PATH, original_depot_path) -empty!(Base.LOAD_PATH) -append!(Base.LOAD_PATH, original_load_path) +precompile_test_harness("Test flags") do load_path + write(joinpath(load_path, "TestFlags.jl"), + """ + module TestFlags + end + """) + + current_flags = Base.CacheFlags() + modified_flags = Base.CacheFlags( + current_flags.use_pkgimages, + current_flags.debug_level, + 2, + current_flags.inline, + 3 + ) + ji, ofile = Base.compilecache(Base.PkgId("TestFlags"); flags=`--check-bounds=no -O3`) + open(ji, "r") do io + Base.isvalid_cache_header(io) + _, _, _, _, _, _, _, flags = Base.parse_cache_header(io, ji) + cacheflags = Base.CacheFlags(flags) + @test cacheflags.check_bounds == 2 + @test cacheflags.opt_level == 3 + end + id = Base.identify_package("TestFlags") + @test Base.isprecompiled(id, ;flags=modified_flags) + @test !Base.isprecompiled(id, ;flags=current_flags) +end + +finish_precompile_test!() diff --git a/test/precompile_absint1.jl b/test/precompile_absint1.jl new file mode 100644 index 0000000000000..7bc0382ffda85 --- /dev/null +++ b/test/precompile_absint1.jl @@ -0,0 +1,81 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +using Test + +include("precompile_utils.jl") + +precompile_test_harness() do load_path + write(joinpath(load_path, "SimpleModule.jl"), :(module SimpleModule + basic_callee(x) = x + basic_caller(x) = basic_callee(x) + end) |> string) + + newinterp_path = abspath("compiler/newinterp.jl") + write(joinpath(load_path, "TestAbsIntPrecompile1.jl"), :(module TestAbsIntPrecompile1 + import SimpleModule: basic_caller, basic_callee + + module Custom + include("$($newinterp_path)") + @newinterp PrecompileInterpreter + end + + Base.return_types((Float64,)) do x + basic_caller(x) + end + Base.return_types((Float64,); interp=Custom.PrecompileInterpreter()) do x + basic_caller(x) + end + Base.return_types((Vector{Float64},)) do x + sum(x) + end + Base.return_types((Vector{Float64},); interp=Custom.PrecompileInterpreter()) do x + sum(x) + end + end) |> string) + Base.compilecache(Base.PkgId("TestAbsIntPrecompile1")) + + @eval let + using TestAbsIntPrecompile1 + cache_owner = Core.Compiler.cache_owner( + TestAbsIntPrecompile1.Custom.PrecompileInterpreter()) + let m = only(methods(TestAbsIntPrecompile1.basic_callee)) + mi = only(Base.specializations(m)) + ci = mi.cache + @test isdefined(ci, :next) + @test ci.owner === nothing + @test ci.max_world == typemax(UInt) + @test Base.module_build_id(TestAbsIntPrecompile1) == + Base.object_build_id(ci) + ci = ci.next + @test !isdefined(ci, :next) + @test ci.owner === cache_owner + @test ci.max_world == typemax(UInt) + @test Base.module_build_id(TestAbsIntPrecompile1) == + Base.object_build_id(ci) + end + let m = only(methods(sum, (Vector{Float64},))) + found = false + for mi in Base.specializations(m) + if mi isa Core.MethodInstance && mi.specTypes == Tuple{typeof(sum),Vector{Float64}} + ci = mi.cache + @test isdefined(ci, :next) + @test ci.owner === cache_owner + @test ci.max_world == typemax(UInt) + @test Base.module_build_id(TestAbsIntPrecompile1) == + Base.object_build_id(ci) + ci = ci.next + @test !isdefined(ci, :next) + @test ci.owner === nothing + @test ci.max_world == typemax(UInt) + @test Base.module_build_id(TestAbsIntPrecompile1) == + Base.object_build_id(ci) + found = true + break + end + end + @test found + end + end +end + +finish_precompile_test!() diff --git a/test/precompile_absint2.jl b/test/precompile_absint2.jl new file mode 100644 index 0000000000000..066dcbaece4c4 --- /dev/null +++ b/test/precompile_absint2.jl @@ -0,0 +1,104 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +using Test + +include("precompile_utils.jl") + +precompile_test_harness() do load_path + write(joinpath(load_path, "SimpleModule.jl"), :(module SimpleModule + basic_callee(x) = x + basic_caller(x) = basic_callee(x) + end) |> string) + + newinterp_path = abspath("compiler/newinterp.jl") + write(joinpath(load_path, "TestAbsIntPrecompile2.jl"), :(module TestAbsIntPrecompile2 + import SimpleModule: basic_caller, basic_callee + + module Custom + const CC = Core.Compiler + include("$($newinterp_path)") + @newinterp PrecompileInterpreter + struct CustomData + inferred + CustomData(@nospecialize inferred) = new(inferred) + end + function CC.transform_result_for_cache(interp::PrecompileInterpreter, + mi::Core.MethodInstance, valid_worlds::CC.WorldRange, result::CC.InferenceResult) + inferred_result = @invoke CC.transform_result_for_cache(interp::CC.AbstractInterpreter, + mi::Core.MethodInstance, valid_worlds::CC.WorldRange, result::CC.InferenceResult) + return CustomData(inferred_result) + end + function CC.src_inlining_policy(interp::PrecompileInterpreter, @nospecialize(src), + @nospecialize(info::CC.CallInfo), stmt_flag::UInt32) + if src isa CustomData + src = src.inferred + end + return @invoke CC.src_inlining_policy(interp::CC.AbstractInterpreter, src::Any, + info::CC.CallInfo, stmt_flag::UInt32) + end + CC.retrieve_ir_for_inlining(cached_result::Core.CodeInstance, src::CustomData) = + CC.retrieve_ir_for_inlining(cached_result, src.inferred) + CC.retrieve_ir_for_inlining(mi::Core.MethodInstance, src::CustomData, preserve_local_sources::Bool) = + CC.retrieve_ir_for_inlining(mi, src.inferred, preserve_local_sources) + end + + Base.return_types((Float64,)) do x + basic_caller(x) + end + Base.return_types((Float64,); interp=Custom.PrecompileInterpreter()) do x + basic_caller(x) + end + Base.return_types((Vector{Float64},)) do x + sum(x) + end + Base.return_types((Vector{Float64},); interp=Custom.PrecompileInterpreter()) do x + sum(x) + end + end) |> string) + Base.compilecache(Base.PkgId("TestAbsIntPrecompile2")) + + @eval let + using TestAbsIntPrecompile2 + cache_owner = Core.Compiler.cache_owner( + TestAbsIntPrecompile2.Custom.PrecompileInterpreter()) + let m = only(methods(TestAbsIntPrecompile2.basic_callee)) + mi = only(Base.specializations(m)) + ci = mi.cache + @test isdefined(ci, :next) + @test ci.owner === nothing + @test ci.max_world == typemax(UInt) + @test Base.module_build_id(TestAbsIntPrecompile2) == + Base.object_build_id(ci) + ci = ci.next + @test !isdefined(ci, :next) + @test ci.owner === cache_owner + @test ci.max_world == typemax(UInt) + @test Base.module_build_id(TestAbsIntPrecompile2) == + Base.object_build_id(ci) + end + let m = only(methods(sum, (Vector{Float64},))) + found = false + for mi = Base.specializations(m) + if mi isa Core.MethodInstance && mi.specTypes == Tuple{typeof(sum),Vector{Float64}} + ci = mi.cache + @test isdefined(ci, :next) + @test ci.owner === cache_owner + @test ci.max_world == typemax(UInt) + @test Base.module_build_id(TestAbsIntPrecompile2) == + Base.object_build_id(ci) + ci = ci.next + @test !isdefined(ci, :next) + @test ci.owner === nothing + @test ci.max_world == typemax(UInt) + @test Base.module_build_id(TestAbsIntPrecompile2) == + Base.object_build_id(ci) + found = true + break + end + end + @test found + end + end +end + +finish_precompile_test!() diff --git a/test/precompile_utils.jl b/test/precompile_utils.jl new file mode 100644 index 0000000000000..55eba353f2ada --- /dev/null +++ b/test/precompile_utils.jl @@ -0,0 +1,41 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +function precompile_test_harness(@nospecialize(f), testset::String) + @testset "$testset" precompile_test_harness(f, true) +end +function precompile_test_harness(@nospecialize(f), separate::Bool=true) + load_path = mktempdir() + load_cache_path = separate ? mktempdir() : load_path + try + pushfirst!(LOAD_PATH, load_path) + pushfirst!(DEPOT_PATH, load_cache_path) + f(load_path) + finally + try + rm(load_path, force=true, recursive=true) + catch err + @show err + end + if separate + try + rm(load_cache_path, force=true, recursive=true) + catch err + @show err + end + end + filter!((≠)(load_path), LOAD_PATH) + separate && filter!((≠)(load_cache_path), DEPOT_PATH) + end + return nothing +end + +let original_depot_path = copy(Base.DEPOT_PATH) + original_load_path = copy(Base.LOAD_PATH) + + global function finish_precompile_test!() + empty!(Base.DEPOT_PATH) + append!(Base.DEPOT_PATH, original_depot_path) + empty!(Base.LOAD_PATH) + append!(Base.LOAD_PATH, original_load_path) + end +end diff --git a/test/project/Extensions/EnvWithHasExtensions/Manifest.toml b/test/project/Extensions/EnvWithHasExtensions/Manifest.toml index 8ac961fa1a9a9..004ef7892c173 100644 --- a/test/project/Extensions/EnvWithHasExtensions/Manifest.toml +++ b/test/project/Extensions/EnvWithHasExtensions/Manifest.toml @@ -1,8 +1,8 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.9.0-beta4" +julia_version = "1.12.0-DEV" manifest_format = "2.0" -project_hash = "caa716752e6dff3d77c3de929ebbb5d2024d04ef" +project_hash = "a4c480cfa7da9610333d5c42623bf746bd286c5f" [[deps.ExtDep]] deps = ["SomePackage"] @@ -18,10 +18,12 @@ version = "0.1.0" [deps.HasExtensions.extensions] Extension = "ExtDep" ExtensionFolder = ["ExtDep", "ExtDep2"] + LinearAlgebraExt = "LinearAlgebra" [deps.HasExtensions.weakdeps] ExtDep = "fa069be4-f60b-4d4c-8b95-f8008775090c" ExtDep2 = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d" + LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" [[deps.SomePackage]] path = "../SomePackage" diff --git a/test/project/Extensions/ExtDep3.jl/Project.toml b/test/project/Extensions/ExtDep3.jl/Project.toml new file mode 100644 index 0000000000000..690b2f1cffff4 --- /dev/null +++ b/test/project/Extensions/ExtDep3.jl/Project.toml @@ -0,0 +1,4 @@ +name = "ExtDep3" +uuid = "a5541f1e-a556-4fdc-af15-097880d743a1" +version = "0.1.0" +authors = ["Kristoffer "] diff --git a/test/project/Extensions/ExtDep3.jl/src/ExtDep3.jl b/test/project/Extensions/ExtDep3.jl/src/ExtDep3.jl new file mode 100644 index 0000000000000..96a0b472d06c5 --- /dev/null +++ b/test/project/Extensions/ExtDep3.jl/src/ExtDep3.jl @@ -0,0 +1,5 @@ +module ExtDep3 + +greet() = print("Hello World!") + +end # module ExtDep3 diff --git a/test/project/Extensions/HasDepWithExtensions.jl/Manifest.toml b/test/project/Extensions/HasDepWithExtensions.jl/Manifest.toml index 52542fc822094..5706aba59d1e0 100644 --- a/test/project/Extensions/HasDepWithExtensions.jl/Manifest.toml +++ b/test/project/Extensions/HasDepWithExtensions.jl/Manifest.toml @@ -1,8 +1,8 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.10.0-DEV" +julia_version = "1.12.0-DEV" manifest_format = "2.0" -project_hash = "d523b3401f72a1ed34b7b43749fd2655c6b78542" +project_hash = "4e196b07f2ee7adc48ac9d528d42b3cf3737c7a0" [[deps.ExtDep]] deps = ["SomePackage"] @@ -15,15 +15,27 @@ path = "../ExtDep2" uuid = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d" version = "0.1.0" +[[deps.ExtDep3]] +path = "../ExtDep3.jl" +uuid = "a5541f1e-a556-4fdc-af15-097880d743a1" +version = "0.1.0" + [[deps.HasExtensions]] +deps = ["ExtDep3"] path = "../HasExtensions.jl" uuid = "4d3288b3-3afc-4bb6-85f3-489fffe514c8" version = "0.1.0" -weakdeps = ["ExtDep", "ExtDep2"] [deps.HasExtensions.extensions] Extension = "ExtDep" + ExtensionDep = "ExtDep3" ExtensionFolder = ["ExtDep", "ExtDep2"] + LinearAlgebraExt = "LinearAlgebra" + + [deps.HasExtensions.weakdeps] + ExtDep = "fa069be4-f60b-4d4c-8b95-f8008775090c" + ExtDep2 = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d" + LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" [[deps.SomePackage]] path = "../SomePackage" diff --git a/test/project/Extensions/HasDepWithExtensions.jl/Project.toml b/test/project/Extensions/HasDepWithExtensions.jl/Project.toml index 8f308a9fbee72..aa4956caada74 100644 --- a/test/project/Extensions/HasDepWithExtensions.jl/Project.toml +++ b/test/project/Extensions/HasDepWithExtensions.jl/Project.toml @@ -5,4 +5,5 @@ version = "0.1.0" [deps] ExtDep = "fa069be4-f60b-4d4c-8b95-f8008775090c" ExtDep2 = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d" +ExtDep3 = "a5541f1e-a556-4fdc-af15-097880d743a1" HasExtensions = "4d3288b3-3afc-4bb6-85f3-489fffe514c8" diff --git a/test/project/Extensions/HasExtensions.jl/Manifest.toml b/test/project/Extensions/HasExtensions.jl/Manifest.toml index 55f7958701a75..429c6598fc4f4 100644 --- a/test/project/Extensions/HasExtensions.jl/Manifest.toml +++ b/test/project/Extensions/HasExtensions.jl/Manifest.toml @@ -1,7 +1,10 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.10.0-DEV" +julia_version = "1.12.0-DEV" manifest_format = "2.0" -project_hash = "c87947f1f1f070eea848950c304d668a112dec3d" +project_hash = "c0bb526b75939a74a6195ee4819e598918a22ad7" -[deps] +[[deps.ExtDep3]] +path = "../ExtDep3.jl" +uuid = "a5541f1e-a556-4fdc-af15-097880d743a1" +version = "0.1.0" diff --git a/test/project/Extensions/HasExtensions.jl/Project.toml b/test/project/Extensions/HasExtensions.jl/Project.toml index 72577de36d65d..fe21a1423f543 100644 --- a/test/project/Extensions/HasExtensions.jl/Project.toml +++ b/test/project/Extensions/HasExtensions.jl/Project.toml @@ -2,10 +2,16 @@ name = "HasExtensions" uuid = "4d3288b3-3afc-4bb6-85f3-489fffe514c8" version = "0.1.0" +[deps] +ExtDep3 = "a5541f1e-a556-4fdc-af15-097880d743a1" + [weakdeps] ExtDep = "fa069be4-f60b-4d4c-8b95-f8008775090c" ExtDep2 = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" [extensions] Extension = "ExtDep" +ExtensionDep = "ExtDep3" ExtensionFolder = ["ExtDep", "ExtDep2"] +LinearAlgebraExt = "LinearAlgebra" diff --git a/test/project/Extensions/HasExtensions.jl/ext/ExtensionDep.jl b/test/project/Extensions/HasExtensions.jl/ext/ExtensionDep.jl new file mode 100644 index 0000000000000..e2710d4d89bbb --- /dev/null +++ b/test/project/Extensions/HasExtensions.jl/ext/ExtensionDep.jl @@ -0,0 +1,9 @@ +module ExtensionDep + +using HasExtensions, ExtDep3 + +function __init__() + HasExtensions.ext_dep_loaded = true +end + +end diff --git a/test/project/Extensions/HasExtensions.jl/ext/LinearAlgebraExt.jl b/test/project/Extensions/HasExtensions.jl/ext/LinearAlgebraExt.jl new file mode 100644 index 0000000000000..19f87cb849417 --- /dev/null +++ b/test/project/Extensions/HasExtensions.jl/ext/LinearAlgebraExt.jl @@ -0,0 +1,3 @@ +module LinearAlgebraExt + +end diff --git a/test/project/Extensions/HasExtensions.jl/src/HasExtensions.jl b/test/project/Extensions/HasExtensions.jl/src/HasExtensions.jl index dbfaeec4f8812..9d9785f87f790 100644 --- a/test/project/Extensions/HasExtensions.jl/src/HasExtensions.jl +++ b/test/project/Extensions/HasExtensions.jl/src/HasExtensions.jl @@ -6,5 +6,6 @@ foo(::HasExtensionsStruct) = 1 ext_loaded = false ext_folder_loaded = false +ext_dep_loaded = false end # module diff --git a/test/project/Extensions/ImplicitEnv/A/Project.toml b/test/project/Extensions/ImplicitEnv/A/Project.toml new file mode 100644 index 0000000000000..043272d4bd015 --- /dev/null +++ b/test/project/Extensions/ImplicitEnv/A/Project.toml @@ -0,0 +1,9 @@ +name = "A" +uuid = "299a509a-2181-4868-8714-15151945d902" +version = "0.1.0" + +[weakdeps] +B = "c2c18cb0-3543-497c-ac2a-523c527589e5" + +[extensions] +BExt = "B" diff --git a/test/project/Extensions/ImplicitEnv/A/ext/BExt.jl b/test/project/Extensions/ImplicitEnv/A/ext/BExt.jl new file mode 100644 index 0000000000000..70be6435bcbe8 --- /dev/null +++ b/test/project/Extensions/ImplicitEnv/A/ext/BExt.jl @@ -0,0 +1,3 @@ +module BExt + +end diff --git a/test/project/Extensions/ImplicitEnv/A/src/A.jl b/test/project/Extensions/ImplicitEnv/A/src/A.jl new file mode 100644 index 0000000000000..ab16fa1de96af --- /dev/null +++ b/test/project/Extensions/ImplicitEnv/A/src/A.jl @@ -0,0 +1,5 @@ +module A + +greet() = print("Hello World!") + +end # module A diff --git a/test/project/Extensions/ImplicitEnv/B/Project.toml b/test/project/Extensions/ImplicitEnv/B/Project.toml new file mode 100644 index 0000000000000..d919c27be0467 --- /dev/null +++ b/test/project/Extensions/ImplicitEnv/B/Project.toml @@ -0,0 +1,3 @@ +name = "B" +uuid = "c2c18cb0-3543-497c-ac2a-523c527589e5" +version = "0.1.0" diff --git a/test/project/Extensions/ImplicitEnv/B/src/B.jl b/test/project/Extensions/ImplicitEnv/B/src/B.jl new file mode 100644 index 0000000000000..79b5a1204765f --- /dev/null +++ b/test/project/Extensions/ImplicitEnv/B/src/B.jl @@ -0,0 +1,5 @@ +module B + +greet() = print("Hello World!") + +end # module B diff --git a/test/project/ProjectPath/CustomPath.jl b/test/project/ProjectPath/CustomPath.jl new file mode 100644 index 0000000000000..8fe764fa066dc --- /dev/null +++ b/test/project/ProjectPath/CustomPath.jl @@ -0,0 +1,5 @@ +module ProjectPath + +greet() = print("Hello World!") + +end # module ProjectPath diff --git a/test/project/ProjectPath/Manifest.toml b/test/project/ProjectPath/Manifest.toml new file mode 100644 index 0000000000000..123e7f575062a --- /dev/null +++ b/test/project/ProjectPath/Manifest.toml @@ -0,0 +1,18 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.12.0-DEV" +manifest_format = "2.0" +project_hash = "51ade905d618e4aa369bc869841376219cc36cb1" + +[[deps.ProjectPath]] +deps = ["ProjectPathDep"] +path = "." +entryfile = "CustomPath.jl" +uuid = "32833bde-7fc1-4d28-8365-9d01e1bcbc1b" +version = "0.1.0" + +[[deps.ProjectPathDep]] +path = "ProjectPathDep" +entryfile = "CustomPath.jl" +uuid = "f18633fc-8799-43ff-aa06-99ed830dc572" +version = "0.1.0" diff --git a/test/project/ProjectPath/Project.toml b/test/project/ProjectPath/Project.toml new file mode 100644 index 0000000000000..a434f78e9c211 --- /dev/null +++ b/test/project/ProjectPath/Project.toml @@ -0,0 +1,7 @@ +name = "ProjectPath" +uuid = "32833bde-7fc1-4d28-8365-9d01e1bcbc1b" +entryfile = "CustomPath.jl" +version = "0.1.0" + +[deps] +ProjectPathDep = "f18633fc-8799-43ff-aa06-99ed830dc572" diff --git a/test/project/ProjectPath/ProjectPathDep/CustomPath.jl b/test/project/ProjectPath/ProjectPathDep/CustomPath.jl new file mode 100644 index 0000000000000..adbe508f0c7f9 --- /dev/null +++ b/test/project/ProjectPath/ProjectPathDep/CustomPath.jl @@ -0,0 +1,5 @@ +module ProjectPathDep + +greet() = print("Hello World!") + +end # module ProjectPathDep diff --git a/test/project/ProjectPath/ProjectPathDep/Project.toml b/test/project/ProjectPath/ProjectPathDep/Project.toml new file mode 100644 index 0000000000000..c69e54e8c9390 --- /dev/null +++ b/test/project/ProjectPath/ProjectPathDep/Project.toml @@ -0,0 +1,4 @@ +name = "ProjectPathDep" +uuid = "f18633fc-8799-43ff-aa06-99ed830dc572" +version = "0.1.0" +entryfile = "CustomPath.jl" diff --git a/test/project/Rot13/Project.toml b/test/project/Rot13/Project.toml new file mode 100644 index 0000000000000..eb03cb84d588e --- /dev/null +++ b/test/project/Rot13/Project.toml @@ -0,0 +1,3 @@ +name = "Rot13" +uuid = "43ef800a-eac4-47f4-949b-25107b932e8f" +version = "0.1.0" diff --git a/test/project/Rot13/src/Rot13.jl b/test/project/Rot13/src/Rot13.jl new file mode 100644 index 0000000000000..0672799d61f24 --- /dev/null +++ b/test/project/Rot13/src/Rot13.jl @@ -0,0 +1,15 @@ +module Rot13 + +function rot13(c::Char) + shft = islowercase(c) ? 'a' : 'A' + isletter(c) ? c = shft + (c - shft + 13) % 26 : c +end + +rot13(str::AbstractString) = map(rot13, str) + +function (@main)(ARGS) + foreach(arg -> print(rot13(arg), " "), ARGS) + return 0 +end + +end # module Rot13 diff --git a/test/project/SubProject/Devved/Project.toml b/test/project/SubProject/Devved/Project.toml new file mode 100644 index 0000000000000..63088a132cb77 --- /dev/null +++ b/test/project/SubProject/Devved/Project.toml @@ -0,0 +1,3 @@ +name = "Devved" +uuid = "cbce3a6e-7a3d-4e84-8e6d-b87208df7599" +version = "0.1.0" diff --git a/test/project/SubProject/Devved/src/Devved.jl b/test/project/SubProject/Devved/src/Devved.jl new file mode 100644 index 0000000000000..f3eb267409ece --- /dev/null +++ b/test/project/SubProject/Devved/src/Devved.jl @@ -0,0 +1,5 @@ +module Devved + +greet() = print("Hello World!") + +end # module Devved diff --git a/test/project/SubProject/Devved2/Project.toml b/test/project/SubProject/Devved2/Project.toml new file mode 100644 index 0000000000000..c761630566116 --- /dev/null +++ b/test/project/SubProject/Devved2/Project.toml @@ -0,0 +1,3 @@ +name = "Devved2" +uuid = "08f74b90-50f5-462f-80b9-a72b1258a17b" +version = "0.1.0" diff --git a/test/project/SubProject/Devved2/src/Devved2.jl b/test/project/SubProject/Devved2/src/Devved2.jl new file mode 100644 index 0000000000000..9bd5df2793671 --- /dev/null +++ b/test/project/SubProject/Devved2/src/Devved2.jl @@ -0,0 +1,5 @@ +module Devved2 + +greet() = print("Hello World!") + +end # module Devved2 diff --git a/test/project/SubProject/Manifest.toml b/test/project/SubProject/Manifest.toml new file mode 100644 index 0000000000000..5d791a74652d4 --- /dev/null +++ b/test/project/SubProject/Manifest.toml @@ -0,0 +1,68 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.12.0-DEV" +manifest_format = "2.0" +project_hash = "620b9377bc807ff657e6618c8ccc24887eb40285" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +version = "1.11.0" + +[[deps.Devved]] +path = "Devved" +uuid = "cbce3a6e-7a3d-4e84-8e6d-b87208df7599" +version = "0.1.0" + +[[deps.Devved2]] +path = "Devved2" +uuid = "08f74b90-50f5-462f-80b9-a72b1258a17b" +version = "0.1.0" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +version = "1.11.0" + +[[deps.Logging]] +deps = ["StyledStrings"] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" +version = "1.11.0" + +[[deps.Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" +version = "1.11.0" + +[[deps.MyPkg]] +deps = ["Devved", "Devved2"] +path = "." +uuid = "0cafdeb2-d7a2-40d0-8d22-4411fcc2c4ee" +version = "0.0.0" + +[[deps.PackageThatIsSub]] +deps = ["Devved2", "MyPkg"] +path = "PackageThatIsSub" +uuid = "1efb588c-9412-4e40-90a4-710420bd84aa" +version = "0.1.0" + +[[deps.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +version = "1.11.0" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" +version = "1.11.0" + +[[deps.StyledStrings]] +uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" +version = "1.11.0" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +version = "1.11.0" diff --git a/test/project/SubProject/PackageThatIsSub/Project.toml b/test/project/SubProject/PackageThatIsSub/Project.toml new file mode 100644 index 0000000000000..e41dd998c5a1c --- /dev/null +++ b/test/project/SubProject/PackageThatIsSub/Project.toml @@ -0,0 +1,14 @@ +name = "PackageThatIsSub" +uuid = "1efb588c-9412-4e40-90a4-710420bd84aa" +version = "0.1.0" + +[workspace] +projects = ["test"] + +[deps] +Devved2 = "08f74b90-50f5-462f-80b9-a72b1258a17b" +MyPkg = "0cafdeb2-d7a2-40d0-8d22-4411fcc2c4ee" + +[preferences] +value = 2 +y = 2 diff --git a/test/project/SubProject/PackageThatIsSub/src/PackageThatIsSub.jl b/test/project/SubProject/PackageThatIsSub/src/PackageThatIsSub.jl new file mode 100644 index 0000000000000..7f9ea94ccb156 --- /dev/null +++ b/test/project/SubProject/PackageThatIsSub/src/PackageThatIsSub.jl @@ -0,0 +1,5 @@ +module PackageThatIsSub + +greet() = print("Hello World!") + +end # module PackageThatIsSub diff --git a/test/project/SubProject/PackageThatIsSub/test/Project.toml b/test/project/SubProject/PackageThatIsSub/test/Project.toml new file mode 100644 index 0000000000000..dc8186e2b735e --- /dev/null +++ b/test/project/SubProject/PackageThatIsSub/test/Project.toml @@ -0,0 +1,8 @@ +[deps] +MyPkg = "0cafdeb2-d7a2-40d0-8d22-4411fcc2c4ee" +PackageThatIsSub = "1efb588c-9412-4e40-90a4-710420bd84aa" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[preferences] +value = 3 +z = 3 diff --git a/test/project/SubProject/Project.toml b/test/project/SubProject/Project.toml new file mode 100644 index 0000000000000..dcb84d865ac85 --- /dev/null +++ b/test/project/SubProject/Project.toml @@ -0,0 +1,13 @@ +name = "MyPkg" +uuid = "0cafdeb2-d7a2-40d0-8d22-4411fcc2c4ee" + +[workspace] +projects = ["sub", "PackageThatIsSub", "test"] + +[deps] +Devved = "cbce3a6e-7a3d-4e84-8e6d-b87208df7599" +Devved2 = "08f74b90-50f5-462f-80b9-a72b1258a17b" + +[preferences] +value = 1 +x = 1 diff --git a/test/project/SubProject/src/MyPkg.jl b/test/project/SubProject/src/MyPkg.jl new file mode 100644 index 0000000000000..6d84954645d55 --- /dev/null +++ b/test/project/SubProject/src/MyPkg.jl @@ -0,0 +1,3 @@ +module MyPkg + +end diff --git a/test/project/SubProject/sub/Project.toml b/test/project/SubProject/sub/Project.toml new file mode 100644 index 0000000000000..50aa238e91d57 --- /dev/null +++ b/test/project/SubProject/sub/Project.toml @@ -0,0 +1,3 @@ +[deps] +Devved = "cbce3a6e-7a3d-4e84-8e6d-b87208df7599" +MyPkg = "0cafdeb2-d7a2-40d0-8d22-4411fcc2c4ee" diff --git a/test/project/SubProject/test/Project.toml b/test/project/SubProject/test/Project.toml new file mode 100644 index 0000000000000..b64312e4b1ee2 --- /dev/null +++ b/test/project/SubProject/test/Project.toml @@ -0,0 +1,4 @@ +[deps] +MyPkg = "0cafdeb2-d7a2-40d0-8d22-4411fcc2c4ee" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +Devved = "cbce3a6e-7a3d-4e84-8e6d-b87208df7599" diff --git a/test/project/deps/CovTest.jl/Project.toml b/test/project/deps/CovTest.jl/Project.toml new file mode 100644 index 0000000000000..97fb2c7d9cfce --- /dev/null +++ b/test/project/deps/CovTest.jl/Project.toml @@ -0,0 +1,3 @@ +name = "CovTest" +uuid = "f1f4390d-b815-473a-b5dd-5af6e1d717cb" +version = "0.1.0" diff --git a/test/project/deps/CovTest.jl/src/CovTest.jl b/test/project/deps/CovTest.jl/src/CovTest.jl new file mode 100644 index 0000000000000..bd172fc3a00f4 --- /dev/null +++ b/test/project/deps/CovTest.jl/src/CovTest.jl @@ -0,0 +1,26 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +module CovTest + +function foo() + x = 1 + y = 2 + z = x * y + return z +end + +function bar() + x = 1 + y = 2 + z = x * y + return z +end + +if Base.generating_output() + # precompile foo but not bar + foo() +end + +export foo, bar + +end #module diff --git a/test/ranges.jl b/test/ranges.jl index 4f88d991181db..9599128739488 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -3,6 +3,9 @@ using Base.Checked: checked_length using InteractiveUtils: code_llvm +isdefined(Main, :OffsetArrays) || @eval Main include("testhelpers/OffsetArrays.jl") +using .Main.OffsetArrays + @testset "range construction" begin @test_throws ArgumentError range(start=1, step=1, stop=2, length=10) @test_throws ArgumentError range(start=1, step=1, stop=10, length=11) @@ -232,9 +235,23 @@ end @test cmp_sn2(Tw(xw/yw), astuple(x/y)..., slopbits) end end + @testset "high precision of varying types" begin + x = Float32(π) + y = Float64(Base.MathConstants.γ) + @test Base.mul12(x, y)[1] ≈ Base.mul12(Float64(π), y)[1] rtol=1e-6 + @test Base.mul12(x, y)[2] ≈ Base.mul12(Float64(π), y)[2] atol=1e-15 + @test Base.div12(x, y)[1] ≈ Base.div12(Float64(π), y)[1] rtol=1e-6 + @test Base.div12(x, y)[2] ≈ Base.div12(Float64(π), y)[2] atol=1e-15 + xtp = Base.TwicePrecision{Float32}(π) + ytp = Base.TwicePrecision{Float64}(Base.MathConstants.γ) + @test Float32(xtp + ytp) ≈ Float32(Base.TwicePrecision{Float64}(π) + ytp) + end x1 = Base.TwicePrecision{Float64}(1) x0 = Base.TwicePrecision{Float64}(0) + @test eltype(x1) == Float64 + @test eltype(typeof(x1)) == Float64 + @test zero(typeof(x1)) === x0 xinf = Base.TwicePrecision{Float64}(Inf) @test Float64(x1+x0) == 1 @test Float64(x1+0) == 1 @@ -297,6 +314,13 @@ end twiceprecision_is_normalized(Base.TwicePrecision{Float64}(rand_twiceprecision(Float32))) end end + + @testset "displaying a complex range (#52713)" begin + r = 1.0*(1:5) .+ im + @test startswith(repr(r), repr(first(r))) + @test endswith(repr(r), repr(last(r))) + @test occursin(repr(step(r)), repr(r)) + end end @testset "ranges" begin @test size(10:1:0) == (0,) @@ -545,6 +569,13 @@ end @test sort(1:10, rev=true) == 10:-1:1 @test sort(-3:3, by=abs) == [0,-1,1,-2,2,-3,3] @test partialsort(1:10, 4) == 4 + + @testset "offset ranges" begin + x = OffsetArrays.IdOffsetRange(values=4:13, indices=4:13) + @test sort(x) === x === sort!(x) + @test sortperm(x) == eachindex(x) + @test issorted(x[sortperm(x)]) + end end @testset "in" begin @test 0 in UInt(0):100:typemax(UInt) @@ -631,11 +662,30 @@ end end end @testset "indexing range with empty range (#4309)" begin - @test (3:6)[5:4] === 7:6 + @test (@inferred (3:6)[5:4]) === 7:6 @test_throws BoundsError (3:6)[5:5] @test_throws BoundsError (3:6)[5] - @test (0:2:10)[7:6] === 12:2:10 + @test (@inferred (0:2:10)[7:6]) === 12:2:11 @test_throws BoundsError (0:2:10)[7:7] + + for start in [true], stop in [true, false] + @test (@inferred (start:stop)[1:0]) === true:false + end + @test (@inferred (true:false)[true:false]) == true:false + + @testset "issue #40760" begin + empty_range = 1:0 + r = range(false, length = 0) + @test r isa UnitRange && first(r) == 0 && last(r) == -1 + r = (true:true)[empty_range] + @test r isa UnitRange && first(r) == true && last(r) == false + @testset for r in Any[true:true, true:true:true, 1:2, 1:1:2] + @test (@inferred r[1:0]) isa AbstractRange + @test r[1:0] == empty_range + @test (@inferred r[1:1:0]) isa AbstractRange + @test r[1:1:0] == empty_range + end + end end # indexing with negative ranges (#8351) for a=AbstractRange[3:6, 0:2:10], b=AbstractRange[0:1, 2:-1:0] @@ -1007,6 +1057,7 @@ end end a = prevfloat(a) end + @test (1:2:3)[StepRangeLen{Bool}(true,-1,2)] == [1] end # issue #20380 @@ -1286,6 +1337,8 @@ end @test sprint(show, UnitRange(1, 2)) == "1:2" @test sprint(show, StepRange(1, 2, 5)) == "1:2:5" + + @test sprint(show, LinRange{Float32}(1.5, 2.5, 10)) == "LinRange{Float32}(1.5, 2.5, 10)" end @testset "Issue 11049, and related" begin @@ -2303,6 +2356,7 @@ end @test_throws BoundsError r[true:true:false] @test_throws BoundsError r[true:true:true] end + @testset "Non-Int64 endpoints that are identical (#39798)" begin for T in DataType[Float16,Float32,Float64,Bool,Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128], r in [ LinRange(1, 1, 10), StepRangeLen(7, 0, 5) ] @@ -2329,13 +2383,46 @@ end @test 0.2 * (-2:2:2) == [-0.4, 0, 0.4] end -@testset "Indexing OneTo with IdentityUnitRange" begin - for endpt in Any[10, big(10), UInt(10)] - r = Base.OneTo(endpt) - inds = Base.IdentityUnitRange(3:5) - rs = r[inds] - @test rs === inds - @test_throws BoundsError r[Base.IdentityUnitRange(-1:100)] +@testset "IdentityUnitRange indexing" begin + @testset "Indexing into an IdentityUnitRange" begin + @testset for r in Any[-1:20, Base.OneTo(20)] + ri = Base.IdentityUnitRange(r) + @test_throws "invalid index" ri[true] + @testset for s in Any[Base.OneTo(6), Base.OneTo{BigInt}(6), 3:6, big(3):big(6), 3:2:7] + @test mapreduce(==, &, ri[s], ri[s[begin]]:step(s):ri[s[end]]) + @test axes(ri[s]) == axes(s) + @test eltype(ri[s]) == eltype(ri) + end + end + @testset "Bool indices" begin + r = 1:1 + @test Base.IdentityUnitRange(r)[true:true] == r[true:true] + @test Base.IdentityUnitRange(r)[true:true:true] == r[true:true:true] + @test_throws BoundsError Base.IdentityUnitRange(1:2)[true:true] + @test_throws BoundsError Base.IdentityUnitRange(1:2)[true:true:true] + end + end + @testset "Indexing with IdentityUnitRange" begin + @testset "OneTo" begin + @testset for endpt in Any[10, big(12), UInt(11)] + r = Base.OneTo(endpt) + inds = Base.IdentityUnitRange(3:5) + rs = r[inds] + @test rs == inds + @test axes(rs) == axes(inds) + @test_throws BoundsError r[Base.IdentityUnitRange(-1:100)] + end + end + @testset "IdentityUnitRange" begin + @testset for r in Any[Base.IdentityUnitRange(1:4), Base.IdentityUnitRange(Base.OneTo(4)), Base.Slice(1:4), Base.Slice(Base.OneTo(4))] + @testset for s in Any[Base.IdentityUnitRange(3:3), Base.IdentityUnitRange(Base.OneTo(2)), Base.Slice(3:3), Base.Slice(Base.OneTo(2))] + rs = r[s] + @test rs == s + @test axes(rs) == axes(s) + end + @test_throws BoundsError r[Base.IdentityUnitRange(first(r):last(r) + 1)] + end + end end end @@ -2535,3 +2622,107 @@ end a = StepRangeLen(1,2,3,2) @test a[UInt(1)] == -1 end + +@testset "StepRangeLen of CartesianIndex-es" begin + CIstart = CartesianIndex(2,3) + CIstep = CartesianIndex(1,1) + r = StepRangeLen(CIstart, CIstep, 4) + @test length(r) == 4 + @test first(r) == CIstart + @test step(r) == CIstep + @test last(r) == CartesianIndex(5,6) + @test r[2] == CartesianIndex(3,4) + + @test repr(r) == "StepRangeLen($CIstart, $CIstep, 4)" + + r = StepRangeLen(CartesianIndex(), CartesianIndex(), 3) + @test all(==(CartesianIndex()), r) + @test length(r) == 3 + @test repr(r) == "StepRangeLen(CartesianIndex(), CartesianIndex(), 3)" + + errmsg = ("deliberately unsupported for CartesianIndex", "StepRangeLen") + @test_throws errmsg range(CartesianIndex(1), step=CartesianIndex(1), length=3) +end + +@testset "logrange" begin + # basic idea + @test logrange(2, 16, 4) ≈ [2, 4, 8, 16] + @test logrange(1/8, 8.0, 7) ≈ [0.125, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0] + @test logrange(1000, 1, 4) ≈ [1000, 100, 10, 1] + @test logrange(1, 10^9, 19)[1:2:end] ≈ 10 .^ (0:9) + + # endpoints + @test logrange(0.1f0, 100, 33)[1] === 0.1f0 + @test logrange(0.789, 123_456, 135_790)[[begin, end]] == [0.789, 123_456] + @test logrange(nextfloat(0f0), floatmax(Float32), typemax(Int))[end] === floatmax(Float32) + @test logrange(nextfloat(Float16(0)), floatmax(Float16), 66_000)[end] === floatmax(Float16) + @test first(logrange(pi, 2pi, 3000)) === logrange(pi, 2pi, 3000)[1] === Float64(pi) + if Int == Int64 + @test logrange(0.1, 1000, 2^54)[end] === 1000.0 + end + + # empty, only, constant + @test first(logrange(1, 2, 0)) === 1.0 + @test last(logrange(1, 2, 0)) === 2.0 + @test collect(logrange(1, 2, 0)) == Float64[] + @test only(logrange(2pi, 2pi, 1)) === logrange(2pi, 2pi, 1)[1] === 2pi + @test logrange(1, 1, 3) == fill(1.0, 3) + + # subnormal Float64 + x = logrange(1e-320, 1e-300, 21) .* 1e300 + @test x ≈ logrange(1e-20, 1, 21) rtol=1e-6 + + # types + @test eltype(logrange(1, 10, 3)) == Float64 + @test eltype(logrange(1, 10, Int32(3))) == Float64 + @test eltype(logrange(1, 10f0, 3)) == Float32 + @test eltype(logrange(1f0, 10, 3)) == Float32 + @test eltype(logrange(1, big(10), 3)) == BigFloat + @test logrange(big"0.3", big(pi), 50)[1] == big"0.3" + @test logrange(big"0.3", big(pi), 50)[end] == big(pi) + + # more constructors + @test logrange(1,2,length=3) === Base.LogRange(1,2,3) == Base.LogRange{Float64}(1,2,3) + @test logrange(1f0, 2f0, length=3) == Base.LogRange{Float32}(1,2,3) + + # errors + @test_throws UndefKeywordError logrange(1, 10) # no default length + @test_throws ArgumentError logrange(1, 10, -1) # negative length + @test_throws ArgumentError logrange(1, 10, 1) # endpoints must not differ + @test_throws DomainError logrange(1, -1, 3) # needs complex numbers + @test_throws DomainError logrange(-1, -2, 3) # not supported, for now + @test_throws MethodError logrange(1, 2+3im, length=4) # not supported, for now + @test_throws ArgumentError logrange(1, 10, 2)[true] # bad index + @test_throws BoundsError logrange(1, 10, 2)[3] + @test_throws ArgumentError Base.LogRange{Int}(1,4,5) # no integer ranges + @test_throws MethodError Base.LogRange(1,4, length=5) # type does not take keyword + # (not sure if these should ideally be DomainError or ArgumentError) + @test_throws DomainError logrange(1, Inf, 3) + @test_throws DomainError logrange(0, 2, 3) + @test_throws DomainError logrange(1, NaN, 3) + @test_throws DomainError logrange(NaN, 2, 3) + + # printing + @test repr(Base.LogRange(1,2,3)) == "LogRange{Float64}(1.0, 2.0, 3)" # like 2-arg show + @test repr("text/plain", Base.LogRange(1,2,3)) == "3-element Base.LogRange{Float64, Base.TwicePrecision{Float64}}:\n 1.0, 1.41421, 2.0" + @test repr("text/plain", Base.LogRange(1,2,0)) == "LogRange{Float64}(1.0, 2.0, 0)" # empty case +end + +@testset "_log_twice64_unchecked" begin + # it roughly works + @test big(Base._log_twice64_unchecked(exp(1))) ≈ 1.0 + @test big(Base._log_twice64_unchecked(exp(123))) ≈ 123.0 + + # it gets high accuracy + @test abs(big(log(4.0)) - log(big(4.0))) < 1e-16 + @test abs(big(Base._log_twice64_unchecked(4.0)) - log(big(4.0))) < 1e-30 + + # it handles subnormals + @test abs(big(Base._log_twice64_unchecked(1e-310)) - log(big(1e-310))) < 1e-20 + + # it accepts negative, NaN, etc without complaint: + @test Base._log_twice64_unchecked(-0.0).lo isa Float64 + @test Base._log_twice64_unchecked(-1.23).lo isa Float64 + @test Base._log_twice64_unchecked(NaN).lo isa Float64 + @test Base._log_twice64_unchecked(Inf).lo isa Float64 +end diff --git a/test/rational.jl b/test/rational.jl index 4b29618bd15e0..b59b25c20656e 100644 --- a/test/rational.jl +++ b/test/rational.jl @@ -28,8 +28,8 @@ using Test @test (1//typemax(Int)) / (1//typemax(Int)) == 1 @test_throws OverflowError (1//2)^63 @test inv((1+typemin(Int))//typemax(Int)) == -1 - @test_throws ArgumentError inv(typemin(Int)//typemax(Int)) - @test_throws ArgumentError Rational(0x1, typemin(Int32)) + @test_throws OverflowError inv(typemin(Int)//typemax(Int)) + @test_throws OverflowError Rational(0x1, typemin(Int32)) @test @inferred(rationalize(Int, 3.0, 0.0)) === 3//1 @test @inferred(rationalize(Int, 3.0, 0)) === 3//1 @@ -43,15 +43,63 @@ using Test # issue 26823 @test_throws InexactError rationalize(Int, NaN) # issue 32569 - @test_throws ArgumentError 1 // typemin(Int) + @test_throws OverflowError 1 // typemin(Int) @test_throws ArgumentError 0 // 0 @test -2 // typemin(Int) == -1 // (typemin(Int) >> 1) @test 2 // typemin(Int) == 1 // (typemin(Int) >> 1) + # issue 32443 + @test Int8(-128)//Int8(1) == -128 + @test_throws OverflowError Int8(-128)//Int8(-1) + @test_throws OverflowError Int8(-1)//Int8(-128) + @test Int8(-128)//Int8(-2) == 64 + # issue 51731 + @test Rational{Int8}(-128) / Rational{Int8}(-128) === Rational{Int8}(1) + # issue 51731 + @test Rational{Int8}(-128) / Rational{Int8}(0) === Rational{Int8}(-1, 0) + @test Rational{Int8}(0) / Rational{Int8}(-128) === Rational{Int8}(0, 1) @test_throws InexactError Rational(UInt(1), typemin(Int32)) @test iszero(Rational{Int}(UInt(0), 1)) @test Rational{BigInt}(UInt(1), Int(-1)) == -1 - @test_broken Rational{Int64}(UInt(1), typemin(Int32)) == Int64(1) // Int64(typemin(Int32)) + @test Rational{Int64}(UInt(1), typemin(Int32)) == Int64(1) // Int64(typemin(Int32)) + + @testset "Rational{T} constructor with concrete T" begin + test_types = [Bool, Int8, Int64, Int128, UInt8, UInt64, UInt128, BigInt] + test_values = Any[ + Any[zero(T) for T in test_types]; + Any[one(T) for T in test_types]; + big(-1); + collect(Iterators.flatten( + (T(j) for T in (Int8, Int64, Int128)) for j in [-3:-1; -128:-126;] + )); + collect(Iterators.flatten( + (T(j) for T in (Int8, Int64, Int128, UInt8, UInt64, UInt128)) for j in [2:3; 126:127;] + )); + Any[typemax(T) for T in (Int64, Int128, UInt8, UInt64, UInt128)]; + Any[typemax(T)-one(T) for T in (Int64, Int128, UInt8, UInt64, UInt128)]; + Any[typemin(T) for T in (Int64, Int128)]; + Any[typemin(T)+one(T) for T in (Int64, Int128)]; + ] + for x in test_values, y in test_values + local big_r = iszero(x) && iszero(y) ? nothing : big(x) // big(y) + for T in test_types + if iszero(x) && iszero(y) + @test_throws Exception Rational{T}(x, y) + elseif Base.hastypemax(T) + local T_range = typemin(T):typemax(T) + if numerator(big_r) ∈ T_range && denominator(big_r) ∈ T_range + @test big_r == Rational{T}(x, y) + @test Rational{T} == typeof(Rational{T}(x, y)) + else + @test_throws Exception Rational{T}(x, y) + end + else + @test big_r == Rational{T}(x, y) + @test Rational{T} == typeof(Rational{T}(x, y)) + end + end + end + end for a = -5:5, b = -5:5 if a == b == 0; continue; end @@ -537,6 +585,7 @@ end 100798//32085 103993//33102 312689//99532 ] + @test rationalize(pi) === rationalize(BigFloat(pi)) end @testset "issue #12536" begin @@ -715,6 +764,19 @@ end @testset "Rational{T} with non-concrete T (issue #41222)" begin @test @inferred(Rational{Integer}(2,3)) isa Rational{Integer} + @test @inferred(Rational{Unsigned}(2,3)) isa Rational{Unsigned} + @test @inferred(Rational{Signed}(2,3)) isa Rational{Signed} + @test_throws InexactError Rational{Unsigned}(-1,1) + @test_throws InexactError Rational{Unsigned}(-1) + @test Rational{Unsigned}(Int8(-128), Int8(-128)) === Rational{Unsigned}(0x01, 0x01) + @test Rational{Unsigned}(Int8(-128), Int8(-1)) === Rational{Unsigned}(0x80, 0x01) + @test Rational{Unsigned}(Int8(0), Int8(-128)) === Rational{Unsigned}(0x00, 0x01) + # Numerator and denominator should have the same type. + @test Rational{Integer}(0x02) === Rational{Integer}(0x02, 0x01) + @test Rational{Integer}(Int16(3)) === Rational{Integer}(Int16(3), Int16(1)) + @test Rational{Integer}(0x01,-1) === Rational{Integer}(-1, 1) + @test Rational{Integer}(-1, 0x01) === Rational{Integer}(-1, 1) + @test_throws InexactError Rational{Integer}(Int8(-1), UInt8(1)) end @testset "issue #41489" begin diff --git a/test/read.jl b/test/read.jl index 3f22dd55a507a..283381668c28a 100644 --- a/test/read.jl +++ b/test/read.jl @@ -720,3 +720,21 @@ end @test isempty(r) && isempty(collect(r)) end end + +@testset "Ref API" begin + io = PipeBuffer() + @test write(io, Ref{Any}(0xabcd_1234)) === 4 + @test read(io, UInt32) === 0xabcd_1234 + @test_throws ErrorException("write cannot copy from a Ptr") invoke(write, Tuple{typeof(io), Ref{Cvoid}}, io, C_NULL) + @test_throws ErrorException("write cannot copy from a Ptr") invoke(write, Tuple{typeof(io), Ref{Int}}, io, Ptr{Int}(0)) + @test_throws ErrorException("write cannot copy from a Ptr") invoke(write, Tuple{typeof(io), Ref{Any}}, io, Ptr{Any}(0)) + @test_throws ErrorException("read! cannot copy into a Ptr") read!(io, C_NULL) + @test_throws ErrorException("read! cannot copy into a Ptr") read!(io, Ptr{Int}(0)) + @test_throws ErrorException("read! cannot copy into a Ptr") read!(io, Ptr{Any}(0)) + @test eof(io) + @test write(io, C_NULL) === sizeof(Int) + @test write(io, Ptr{Int}(4)) === sizeof(Int) + @test write(io, Ptr{Any}(5)) === sizeof(Int) + @test read!(io, Int[1, 2, 3]) == [0, 4, 5] + @test eof(io) +end diff --git a/test/reduce.jl b/test/reduce.jl index 2c852084de37e..f5140c8a34bd9 100644 --- a/test/reduce.jl +++ b/test/reduce.jl @@ -53,8 +53,8 @@ end @test reduce(max, [8 6 7 5 3 0 9]) == 9 @test reduce(+, 1:5; init=1000) == (1000 + 1 + 2 + 3 + 4 + 5) @test reduce(+, 1) == 1 -@test_throws "reducing with * over an empty collection of element type Union{} is not allowed" reduce(*, ()) -@test_throws "reducing with * over an empty collection of element type Union{} is not allowed" reduce(*, Union{}[]) +@test_throws "reducing over an empty collection is not allowed" reduce(*, ()) +@test_throws "reducing over an empty collection is not allowed" reduce(*, Union{}[]) # mapreduce @test mapreduce(-, +, [-10 -9 -3]) == ((10 + 9) + 3) @@ -91,8 +91,7 @@ end @test mapreduce(abs2, *, Float64[]) === 1.0 @test mapreduce(abs2, max, Float64[]) === 0.0 @test mapreduce(abs, max, Float64[]) === 0.0 -@test_throws ["reducing over an empty collection is not allowed", - "consider supplying `init`"] mapreduce(abs2, &, Float64[]) +@test_throws "reducing over an empty collection is not allowed" mapreduce(abs2, &, Float64[]) @test_throws str -> !occursin("Closest candidates are", str) mapreduce(abs2, &, Float64[]) @test_throws "reducing over an empty collection is not allowed" mapreduce(abs2, |, Float64[]) @@ -144,9 +143,8 @@ fz = float(z) @test sum(z) === 136 @test sum(fz) === 136.0 -@test_throws "reducing with add_sum over an empty collection of element type Union{} is not allowed" sum(Union{}[]) -@test_throws ["reducing over an empty collection is not allowed", - "consider supplying `init`"] sum(sin, Int[]) +@test_throws "reducing over an empty collection is not allowed" sum(Union{}[]) +@test_throws "reducing over an empty collection is not allowed" sum(sin, Int[]) @test sum(sin, 3) == sin(3.0) @test sum(sin, [3]) == sin(3.0) a = sum(sin, z) diff --git a/test/reducedim.jl b/test/reducedim.jl index daa0a3fbe1f92..8f629fa83f28d 100644 --- a/test/reducedim.jl +++ b/test/reducedim.jl @@ -124,6 +124,18 @@ fill!(r, -6.3) fill!(r, -1.1) @test sum!(abs2, r, Breduc, init=false) ≈ safe_sumabs2(Breduc, 1) .- 1.1 +# issue #35199 +function issue35199_test(sizes, dims) + M = rand(Float64, sizes) + ax = axes(M) + n1 = @allocations Base.reduced_indices(ax, dims) + return @test n1 == 0 +end +for dims in (1, 2, (1,), (2,), (1,2)) + sizes = (64, 3) + issue35199_test(sizes, dims) +end + # Small arrays with init=false let A = reshape(1:15, 3, 5) R = fill(1, 3) @@ -564,8 +576,8 @@ end @testset "type of sum(::Array{$T}" for T in [UInt8, Int8, Int32, Int64, BigInt] result = sum(T[1 2 3; 4 5 6; 7 8 9], dims=2) @test result == hcat([6, 15, 24]) - @test eltype(result) === (T <: Base.SmallSigned ? Int : - T <: Base.SmallUnsigned ? UInt : + @test eltype(result) === (T <: Base.BitSignedSmall ? Int : + T <: Base.BitUnsignedSmall ? UInt : T) end @@ -608,7 +620,7 @@ end end @testset "NaN/missing test for extrema with dims #43599" begin for sz = (3, 10, 100) - for T in (Int, Float64, BigFloat) + for T in (Int, Float64, BigFloat, BigInt) Aₘ = Matrix{Union{T, Missing}}(rand(-sz:sz, sz, sz)) Aₘ[rand(1:sz*sz, sz)] .= missing unordered_test_for_extrema(Aₘ) @@ -622,9 +634,16 @@ end end end end -@test_broken minimum([missing;BigInt(1)], dims = 1) -@test_broken maximum([missing;BigInt(1)], dims = 1) -@test_broken extrema([missing;BigInt(1)], dims = 1) + +@testset "minimum/maximum over dims with missing (#35308)" begin + for T in (Int, Float64, BigInt, BigFloat) + x = Union{T, Missing}[1 missing; 2 missing] + @test isequal(minimum(x, dims=1), reshape([1, missing], 1, :)) + @test isequal(maximum(x, dims=1), reshape([2, missing], 1, :)) + @test isequal(minimum(x, dims=2), reshape([missing, missing], :, 1)) + @test isequal(maximum(x, dims=2), reshape([missing, missing], :, 1)) + end +end # issue #26709 @testset "dimensional reduce with custom non-bitstype types" begin diff --git a/test/reflection.jl b/test/reflection.jl index c0f32e39805e5..4fd2b7cd52306 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -474,7 +474,7 @@ fLargeTable() = 4 fLargeTable(::Union, ::Union) = "a" @test fLargeTable(Union{Int, Missing}, Union{Int, Missing}) == "a" fLargeTable(::Union, ::Union) = "b" -@test length(methods(fLargeTable)) == 205 +@test length(methods(fLargeTable)) == 206 @test fLargeTable(Union{Int, Missing}, Union{Int, Missing}) == "b" # issue #15280 @@ -614,11 +614,16 @@ end sizeof(Real)) @test sizeof(Union{ComplexF32,ComplexF64}) == 16 @test sizeof(Union{Int8,UInt8}) == 1 -@test_throws ErrorException sizeof(AbstractArray) +@test sizeof(MemoryRef{Int}) == 2 * sizeof(Int) +@test sizeof(GenericMemoryRef{:atomic,Int,Core.CPU}) == 2 * sizeof(Int) +@test sizeof(Array{Int,0}) == 2 * sizeof(Int) +@test sizeof(Array{Int,1}) == 3 * sizeof(Int) +@test sizeof(Array{Int,2}) == 4 * sizeof(Int) +@test sizeof(Array{Int,20}) == 22 * sizeof(Int) @test_throws ErrorException sizeof(Tuple) @test_throws ErrorException sizeof(Tuple{Any,Any}) @test_throws ErrorException sizeof(String) -@test_throws ErrorException sizeof(Vector{Int}) +@test_throws ErrorException sizeof(Memory{false,Int}) @test_throws ErrorException sizeof(Symbol) @test_throws ErrorException sizeof(Core.SimpleVector) @test_throws ErrorException sizeof(Union{}) @@ -923,7 +928,7 @@ end @test nameof(Any) === :Any @test nameof(:) === :Colon @test nameof(Core.Intrinsics.mul_int) === :mul_int -@test nameof(Core.Intrinsics.arraylen) === :arraylen +@test nameof(Core.Intrinsics.cglobal) === :cglobal module TestMod33403 f(x) = 1 @@ -999,6 +1004,15 @@ end @test Base.default_tt(m.f4) == Tuple end +@testset "lookup mi" begin + @test 1+1 == 2 + mi1 = Base.method_instance(+, (Int, Int)) + @test mi1.def.name == :+ + # Note `jl_method_lookup` doesn't returns CNull if not found + mi2 = @ccall jl_method_lookup(Any[+, 1, 1]::Ptr{Any}, 3::Csize_t, Base.get_world_counter()::Csize_t)::Ref{Core.MethodInstance} + @test mi1 == mi2 +end + Base.@assume_effects :terminates_locally function issue41694(x::Int) res = 1 0 ≤ x < 20 || error("bad fact") @@ -1015,7 +1029,22 @@ ambig_effects_test(a::Int, b) = 1 ambig_effects_test(a, b::Int) = 1 ambig_effects_test(a, b) = 1 -@testset "infer_effects" begin +@testset "Base.infer_return_type[s]" begin + # generic function case + @test only(Base.return_types(issue41694, (Int,))) == Base.infer_return_type(issue41694, (Int,)) == Int + # case when it's not fully covered + @test only(Base.return_types(issue41694, (Integer,))) == Base.infer_return_type(issue41694, (Integer,)) == Int + # MethodError case + @test isempty(Base.return_types(issue41694, (Float64,))) + @test Base.infer_return_type(issue41694, (Float64,)) == Union{} + # builtin case + @test only(Base.return_types(typeof, (Any,))) == Base.infer_return_type(typeof, (Any,)) == DataType + @test only(Base.return_types(===, (Any,Any))) == Base.infer_return_type(===, (Any,Any)) == Bool + @test only(Base.return_types(setfield!, ())) == Base.infer_return_type(setfield!, ()) == Union{} + @test only(Base.return_types(Core.Intrinsics.mul_int, ())) == Base.infer_return_type(Core.Intrinsics.mul_int, ()) == Union{} +end + +@testset "Base.infer_effects" begin # generic functions @test Base.infer_effects(issue41694, (Int,)) |> Core.Compiler.is_terminates @test Base.infer_effects((Int,)) do x @@ -1039,7 +1068,34 @@ ambig_effects_test(a, b) = 1 @test Base.infer_effects(typeof, (Any,)) |> Core.Compiler.is_foldable_nothrow @test Base.infer_effects(===, (Any,Any)) |> Core.Compiler.is_foldable_nothrow @test (Base.infer_effects(setfield!, ()); true) # `builtin_effects` shouldn't throw on empty `argtypes` - @test (Base.infer_effects(Core.Intrinsics.arraylen, ()); true) # `intrinsic_effects` shouldn't throw on empty `argtypes` + @test (Base.infer_effects(Core.Intrinsics.mul_int, ()); true) # `intrinsic_effects` shouldn't throw on empty `argtypes` +end + +@testset "Base.infer_exception_type[s]" begin + # generic functions + @test Base.infer_exception_type(issue41694, (Int,)) == only(Base.infer_exception_types(issue41694, (Int,))) == ErrorException + @test Base.infer_exception_type((Int,)) do x + issue41694(x) + end == Base.infer_exception_types((Int,)) do x + issue41694(x) + end |> only == ErrorException + @test Base.infer_exception_type(issue41694) == only(Base.infer_exception_types(issue41694)) == ErrorException # use `default_tt` + let excts = Base.infer_exception_types(maybe_effectful, (Any,)) + @test any(==(Any), excts) + @test any(==(Union{}), excts) + end + @test Base.infer_exception_type(maybe_effectful, (Any,)) == Any + # `infer_exception_type` should account for MethodError + @test Base.infer_exception_type(issue41694, (Float64,)) == MethodError # definitive dispatch error + @test Base.infer_exception_type(issue41694, (Integer,)) == Union{MethodError,ErrorException} # possible dispatch error + @test Base.infer_exception_type(f_no_methods) == MethodError # no possible matching methods + @test Base.infer_exception_type(ambig_effects_test, (Int,Int)) == MethodError # ambiguity error + @test Base.infer_exception_type(ambig_effects_test, (Int,Any)) == MethodError # ambiguity error + # builtins + @test Base.infer_exception_type(typeof, (Any,)) === only(Base.infer_exception_types(typeof, (Any,))) === Union{} + @test Base.infer_exception_type(===, (Any,Any)) === only(Base.infer_exception_types(===, (Any,Any))) === Union{} + @test (Base.infer_exception_type(setfield!, ()); Base.infer_exception_types(setfield!, ()); true) # `infer_exception_type[s]` shouldn't throw on empty `argtypes` + @test (Base.infer_exception_type(Core.Intrinsics.mul_int, ()); Base.infer_exception_types(Core.Intrinsics.mul_int, ()); true) # `infer_exception_type[s]` shouldn't throw on empty `argtypes` end @test Base._methods_by_ftype(Tuple{}, -1, Base.get_world_counter()) == Any[] @@ -1087,7 +1143,9 @@ end return :(x) end end -@test only(Base.return_types(generated_only_simple, (Real,))) == Core.Compiler.return_type(generated_only_simple, Tuple{Real}) == Any +@test only(Base.return_types(generated_only_simple, (Real,))) == + Base.infer_return_type(generated_only_simple, (Real,)) == + Core.Compiler.return_type(generated_only_simple, Tuple{Real}) == Any let (src, rt) = only(code_typed(generated_only_simple, (Real,))) @test src isa Method @test rt == Any @@ -1123,3 +1181,24 @@ let (src, rt) = only(code_typed(sub2ind_gen, (NTuple,Int,Int,); optimize=false)) @test any(iscall((src,sub2ind_gen_fallback)), src.code) @test any(iscall((src,error)), src.code) end + +# marking a symbol as public should not "unexport" it +# https://github.com/JuliaLang/julia/issues/52812 +module Mod52812 +using Test +export a, b +@test_throws ErrorException eval(Expr(:public, :a)) +public c +@test_throws ErrorException eval(Expr(:export, :c)) +export b +public c +end + +@test Base.isexported(Mod52812, :a) +@test Base.isexported(Mod52812, :b) +@test Base.ispublic(Mod52812, :a) +@test Base.ispublic(Mod52812, :b) +@test Base.ispublic(Mod52812, :c) && !Base.isexported(Mod52812, :c) + +@test Base.infer_return_type(code_lowered, (Any,)) == Vector{Core.CodeInfo} +@test Base.infer_return_type(code_lowered, (Any,Any)) == Vector{Core.CodeInfo} diff --git a/test/regex.jl b/test/regex.jl index e5f1428527512..a1d0b1b0ed69a 100644 --- a/test/regex.jl +++ b/test/regex.jl @@ -101,15 +101,34 @@ @test haskey(m, 3) @test !haskey(m, 44) @test (m[1], m[2], m[3]) == ("x", "y", "z") + @test Tuple(m) == ("x", "y", "z") + @test NamedTuple(m) == (var"1"="x", var"2"="y", var"3"="z") + @test Dict(m) == Dict([1=>"x", 2=>"y", 3=>"z"]) @test sprint(show, m) == "RegexMatch(\"xyz\", 1=\"x\", 2=\"y\", 3=\"z\")" end # Named subpatterns + let m = match(r"(?.)(?.)(?.)", "xyz") + @test haskey(m, :a) + @test haskey(m, "b") + @test !haskey(m, "foo") + @test (m[:a], m[:c], m["b"]) == ("x", "y", "z") + @test Tuple(m) == ("x", "y", "z") + @test NamedTuple(m) == (a="x", c="y", b="z") + @test Dict(m) == Dict(["a"=>"x", "c"=>"y", "b"=>"z"]) + @test sprint(show, m) == "RegexMatch(\"xyz\", a=\"x\", c=\"y\", b=\"z\")" + @test keys(m) == ["a", "c", "b"] + end + + # Named and unnamed subpatterns let m = match(r"(?.)(.)(?.)", "xyz") @test haskey(m, :a) @test haskey(m, "b") @test !haskey(m, "foo") @test (m[:a], m[2], m["b"]) == ("x", "y", "z") + @test Tuple(m) == ("x", "y", "z") + @test NamedTuple(m) == (a="x", var"2"="y", b="z") + @test Dict(m) == Dict(["a"=>"x", 2=>"y", "b"=>"z"]) @test sprint(show, m) == "RegexMatch(\"xyz\", a=\"x\", 2=\"y\", b=\"z\")" @test keys(m) == ["a", 2, "b"] end diff --git a/test/reinterpretarray.jl b/test/reinterpretarray.jl index 501e9f4a9b57f..46ecbf6d06723 100644 --- a/test/reinterpretarray.jl +++ b/test/reinterpretarray.jl @@ -6,20 +6,28 @@ using .Main.OffsetArrays isdefined(Main, :TSlow) || @eval Main include("testhelpers/arrayindexingtypes.jl") using .Main: TSlow, WrapperArray +tslow(a::AbstractArray) = TSlow(a) +wrapper(a::AbstractArray) = WrapperArray(a) +fcviews(a::AbstractArray) = view(a, ntuple(Returns(:),ndims(a)-1)..., axes(a)[end]) +fcviews(a::AbstractArray{<:Any, 0}) = view(a) +tslow(t::Tuple) = map(tslow, t) +wrapper(t::Tuple) = map(wrapper, t) +fcviews(t::Tuple) = map(fcviews, t) + +test_many_wrappers(testf, A, wrappers) = foreach(w -> testf(w(A)), wrappers) +test_many_wrappers(testf, A) = test_many_wrappers(testf, A, (identity, tslow, wrapper, fcviews)) + A = Int64[1, 2, 3, 4] -As = TSlow(A) Ars = Int64[1 3; 2 4] -Arss = TSlow(Ars) B = Complex{Int64}[5+6im, 7+8im, 9+10im] -Bs = TSlow(B) Av = [Int32[1,2], Int32[3,4]] -for Ar in (Ars, Arss) +test_many_wrappers(Ars, (identity, tslow)) do Ar @test @inferred(ndims(reinterpret(reshape, Complex{Int64}, Ar))) == 1 @test @inferred(axes(reinterpret(reshape, Complex{Int64}, Ar))) === (Base.OneTo(2),) @test @inferred(size(reinterpret(reshape, Complex{Int64}, Ar))) == (2,) end -for _B in (B, Bs) +test_many_wrappers(B, (identity, tslow)) do _B @test @inferred(ndims(reinterpret(reshape, Int64, _B))) == 2 @test @inferred(axes(reinterpret(reshape, Int64, _B))) === (Base.OneTo(2), Base.OneTo(3)) @test @inferred(size(reinterpret(reshape, Int64, _B))) == (2, 3) @@ -42,24 +50,25 @@ end @test_throws ArgumentError("cannot reinterpret a zero-dimensional `UInt8` array to `UInt16` which is of a larger size") reinterpret(reshape, UInt16, reshape([0x01])) # getindex -for _A in (A, As) +test_many_wrappers(A) do _A @test reinterpret(Complex{Int64}, _A) == [1 + 2im, 3 + 4im] @test reinterpret(Float64, _A) == reinterpret.(Float64, A) @test reinterpret(reshape, Float64, _A) == reinterpret.(Float64, A) end -for Ar in (Ars, Arss) +test_many_wrappers(Ars) do Ar @test reinterpret(reshape, Complex{Int64}, Ar) == [1 + 2im, 3 + 4im] @test reinterpret(reshape, Float64, Ar) == reinterpret.(Float64, Ars) end -for _B in (B, Bs) +test_many_wrappers(B) do _B @test reinterpret(NTuple{3, Int64}, _B) == [(5,6,7),(8,9,10)] @test reinterpret(reshape, Int64, _B) == [5 7 9; 6 8 10] end # setindex -for (_A, Ar, _B) in ((A, Ars, B), (As, Arss, Bs)) - let Ac = copy(_A), Arsc = copy(Ar), Bc = copy(_B) +test_many_wrappers((A, Ars, B)) do (A, Ars, B) + _A, Ar, _B = deepcopy(A), deepcopy(Ars), deepcopy(B) + let Ac = deepcopy(_A), Arsc = deepcopy(Ar), Bc = deepcopy(_B) reinterpret(Complex{Int64}, Ac)[2] = -1 - 2im @test Ac == [1, 2, -1, -2] reinterpret(Complex{Int64}, Arsc)[2] = -1 - 2im @@ -94,50 +103,67 @@ for (_A, Ar, _B) in ((A, Ars, B), (As, Arss, Bs)) end end A3 = collect(reshape(1:18, 2, 3, 3)) -A3r = reinterpret(reshape, Complex{Int}, A3) -@test A3r[4] === A3r[1,2] === A3r[CartesianIndex(1, 2)] === 7+8im -A3r[2,3] = -8-15im -@test A3[1,2,3] == -8 -@test A3[2,2,3] == -15 -A3r[4] = 100+200im -@test A3[1,1,2] == 100 -@test A3[2,1,2] == 200 -A3r[CartesianIndex(1,2)] = 300+400im -@test A3[1,1,2] == 300 -@test A3[2,1,2] == 400 +test_many_wrappers(A3) do A3_ + A3 = deepcopy(A3_) + A3r = reinterpret(reshape, Complex{Int}, A3) + @test A3r[4] === A3r[1,2] === A3r[CartesianIndex(1, 2)] === 7+8im + A3r[2,3] = -8-15im + @test A3[1,2,3] == -8 + @test A3[2,2,3] == -15 + A3r[4] = 100+200im + @test A3[1,1,2] == 100 + @test A3[2,1,2] == 200 + A3r[CartesianIndex(1,2)] = 300+400im + @test A3[1,1,2] == 300 + @test A3[2,1,2] == 400 +end # same-size reinterpret where one of the types is non-primitive -let a = NTuple{4,UInt8}[(0x01,0x02,0x03,0x04)], ra = reinterpret(Float32, a) - @test ra[1] == reinterpret(Float32, 0x04030201) - @test setindex!(ra, 2.0) === ra - @test reinterpret(Float32, a)[1] == 2.0 +let a = NTuple{4,UInt8}[(0x01,0x02,0x03,0x04)] + test_many_wrappers(a, (identity, wrapper, fcviews)) do a_ + a = deepcopy(a_) + ra = reinterpret(Float32, a) + @test ra[1] == reinterpret(Float32, 0x04030201) + @test setindex!(ra, 2.0) === ra + @test reinterpret(Float32, a)[1] == 2.0 + end end -let a = NTuple{4,UInt8}[(0x01,0x02,0x03,0x04)], ra = reinterpret(reshape, Float32, a) - @test ra[1] == reinterpret(Float32, 0x04030201) - @test setindex!(ra, 2.0) === ra - @test reinterpret(reshape, Float32, a)[1] == 2.0 +let a = NTuple{4,UInt8}[(0x01,0x02,0x03,0x04)] + test_many_wrappers(a, (identity, wrapper, fcviews)) do a_ + a = deepcopy(a_) + ra = reinterpret(reshape, Float32, a) + @test ra[1] == reinterpret(Float32, 0x04030201) + @test setindex!(ra, 2.0) === ra + @test reinterpret(reshape, Float32, a)[1] == 2.0 + end end # Pass-through indexing B = Complex{Int64}[5+6im, 7+8im, 9+10im] -Br = reinterpret(reshape, Int64, B) -W = WrapperArray(Br) -for (b, w) in zip(5:10, W) - @test b == w -end -for (i, j) in zip(eachindex(W), 11:16) - W[i] = j +test_many_wrappers(B) do B_ + B = deepcopy(B_) + Br = reinterpret(reshape, Int64, B) + W = WrapperArray(Br) + for (b, w) in zip(5:10, W) + @test b == w + end + for (i, j) in zip(eachindex(W), 11:16) + W[i] = j + end + @test B[1] === Complex{Int64}(11+12im) + @test B[2] === Complex{Int64}(13+14im) + @test B[3] === Complex{Int64}(15+16im) end -@test B[1] === Complex{Int64}(11+12im) -@test B[2] === Complex{Int64}(13+14im) -@test B[3] === Complex{Int64}(15+16im) z3 = (0x00, 0x00, 0x00) Az = [z3 z3; z3 z3] -Azr = reinterpret(reshape, UInt8, Az) -W = WrapperArray(Azr) -copyto!(W, fill(0x01, 3, 2, 2)) -@test all(isequal((0x01, 0x01, 0x01)), Az) -@test eachindex(W, W) == eachindex(W) +test_many_wrappers(Az, (identity, wrapper)) do Az_ + Az = deepcopy(Az_) + Azr = reinterpret(reshape, UInt8, Az) + W = WrapperArray(Azr) + copyto!(W, fill(0x01, 3, 2, 2)) + @test all(isequal((0x01, 0x01, 0x01)), Az) + @test eachindex(W, W) == eachindex(W) +end # ensure that reinterpret arrays aren't erroneously classified as strided let A = reshape(1:20, 5, 4) @@ -169,7 +195,7 @@ function check_strides(A::AbstractArray) end @testset "strides for NonReshapedReinterpretArray" begin - A = Array{Int32}(reshape(1:88, 11, 8)) + A = WrapperArray(Array{Int32}(reshape(1:88, 11, 8))) for viewax2 in (1:8, 1:2:6, 7:-1:1, 5:-2:1, 2:3:8, 7:-6:1, 3:5:11) # dim1 is contiguous for T in (Int16, Float32) @@ -203,7 +229,7 @@ end end @testset "strides for ReshapedReinterpretArray" begin - A = Array{Int32}(reshape(1:192, 3, 8, 8)) + A = WrapperArray(Array{Int32}(reshape(1:192, 3, 8, 8))) for viewax1 in (1:8, 1:2:8, 8:-1:1, 8:-2:1), viewax2 in (1:2, 4:-1:1) for T in (Int16, Float32) @test check_strides(reinterpret(reshape, T, view(A, 1:2, viewax1, viewax2))) @@ -240,7 +266,8 @@ end end # IndexStyle -let a = fill(1.0, 5, 3) +test_many_wrappers(fill(1.0, 5, 3), (identity, wrapper)) do a_ + a = deepcopy(a_) r = reinterpret(Int64, a) @test @inferred(IndexStyle(r)) == IndexLinear() fill!(r, 2) @@ -293,14 +320,13 @@ let a = fill(1.0, 5, 3) @test setindex!(r, -5, goodinds...) === r @test r[goodinds...] == -5 end - - ar = [(1,2), (3,4)] +end +let ar = [(1,2), (3,4)] arr = reinterpret(reshape, Int, ar) @test @inferred(IndexStyle(arr)) == Base.IndexSCartesian2{2}() @test @inferred(eachindex(arr)) == Base.SCartesianIndices2{2}(Base.OneTo(2)) @test @inferred(eachindex(arr, arr)) == Base.SCartesianIndices2{2}(Base.OneTo(2)) end - # Error on reinterprets that would expose padding struct S1 a::Int8 @@ -314,11 +340,14 @@ end A1 = S1[S1(0, 0)] A2 = S2[S2(0, 0)] -@test reinterpret(S1, A2)[1] == S1(0, 0) -@test_throws Base.PaddingError (reinterpret(S1, A2)[1] = S2(1, 2)) -@test_throws Base.PaddingError reinterpret(S2, A1)[1] -reinterpret(S2, A1)[1] = S2(1, 2) -@test A1[1] == S1(1, 2) +test_many_wrappers((A1, A2), (identity, wrapper)) do (A1_, A2_) + A1, A2 = deepcopy(A1_), deepcopy(A2_) + @test reinterpret(S1, A2)[1] == S1(0, 0) + @test_throws Base.PaddingError (reinterpret(S1, A2)[1] = S2(1, 2)) + @test_throws Base.PaddingError reinterpret(S2, A1)[1] + reinterpret(S2, A1)[1] = S2(1, 2) + @test A1[1] == S1(1, 2) +end # Unconventional axes let a = [0.1 0.2; 0.3 0.4], at = reshape([(i,i+1) for i = 1:2:8], 2, 2) @@ -371,50 +400,59 @@ end # Test 0-dimensional Arrays A = zeros(UInt32) -B = reinterpret(Int32, A) -Brs = reinterpret(reshape,Int32, A) -C = reinterpret(Tuple{UInt32}, A) # non-primitive type -Crs = reinterpret(reshape, Tuple{UInt32}, A) # non-primitive type -@test size(B) == size(Brs) == size(C) == size(Crs) == () -@test axes(B) == axes(Brs) == axes(C) == axes(Crs) == () -@test setindex!(B, Int32(5)) === B -@test B[] === Int32(5) -@test Brs[] === Int32(5) -@test C[] === (UInt32(5),) -@test Crs[] === (UInt32(5),) -@test A[] === UInt32(5) -@test setindex!(Brs, Int32(12)) === Brs -@test A[] === UInt32(12) -@test setindex!(C, (UInt32(7),)) === C -@test A[] === UInt32(7) -@test setindex!(Crs, (UInt32(3),)) === Crs -@test A[] === UInt32(3) - - -a = [(1.0,2.0)] -af = @inferred(reinterpret(reshape, Float64, a)) -anew = @inferred(reinterpret(reshape, Tuple{Float64,Float64}, vec(af))) -@test anew[1] == a[1] -@test ndims(anew) == 0 +test_many_wrappers(A, (identity, wrapper)) do A_ + A = deepcopy(A_) + B = reinterpret(Int32, A) + Brs = reinterpret(reshape,Int32, A) + C = reinterpret(Tuple{UInt32}, A) # non-primitive type + Crs = reinterpret(reshape, Tuple{UInt32}, A) # non-primitive type + @test size(B) == size(Brs) == size(C) == size(Crs) == () + @test axes(B) == axes(Brs) == axes(C) == axes(Crs) == () + @test setindex!(B, Int32(5)) === B + @test B[] === Int32(5) + @test Brs[] === Int32(5) + @test C[] === (UInt32(5),) + @test Crs[] === (UInt32(5),) + @test A[] === UInt32(5) + @test setindex!(Brs, Int32(12)) === Brs + @test A[] === UInt32(12) + @test setindex!(C, (UInt32(7),)) === C + @test A[] === UInt32(7) + @test setindex!(Crs, (UInt32(3),)) === Crs + @test A[] === UInt32(3) +end + +test_many_wrappers([(1.0,2.0)], (identity, wrapper)) do a + af = @inferred(reinterpret(reshape, Float64, a)) + anew = @inferred(reinterpret(reshape, Tuple{Float64,Float64}, vec(af))) + @test anew[1] == a[1] + @test ndims(anew) == 0 +end # re-reinterpret a0 = reshape([0x22, 0x44, 0x88, 0xf0, 0x01, 0x02, 0x03, 0x04], 4, 2) -a = reinterpret(reshape, NTuple{4,UInt8}, a0) -@test a == [(0x22, 0x44, 0x88, 0xf0), (0x01, 0x02, 0x03, 0x04)] -@test reinterpret(UInt8, a) == [0x22, 0x44, 0x88, 0xf0, 0x01, 0x02, 0x03, 0x04] -@test reinterpret(reshape, UInt8, a) === a0 +test_many_wrappers(a0, (identity, wrapper)) do a0 + a = reinterpret(reshape, NTuple{4,UInt8}, a0) + @test a == [(0x22, 0x44, 0x88, 0xf0), (0x01, 0x02, 0x03, 0x04)] + @test reinterpret(UInt8, a) == [0x22, 0x44, 0x88, 0xf0, 0x01, 0x02, 0x03, 0x04] + @test reinterpret(reshape, UInt8, a) === a0 +end # reductions a = [(1,2,3), (4,5,6)] -ars = reinterpret(reshape, Int, a) -@test sum(ars) == 21 -@test sum(ars; dims=1) == [6 15] -@test sum(ars; dims=2) == reshape([5,7,9], (3, 1)) -@test sum(ars; dims=(1,2)) == reshape([21], (1, 1)) +test_many_wrappers(a, (identity, wrapper)) do a + ars = reinterpret(reshape, Int, a) + @test sum(ars) == 21 + @test sum(ars; dims=1) == [6 15] + @test sum(ars; dims=2) == reshape([5,7,9], (3, 1)) + @test sum(ars; dims=(1,2)) == reshape([21], (1, 1)) +end # also test large sizes for the pairwise algorithm a = [(k,k+1,k+2) for k = 1:3:4000] -ars = reinterpret(reshape, Int, a) -@test sum(ars) == 8010003 +test_many_wrappers(a, (identity, wrapper)) do a + ars = reinterpret(reshape, Int, a) + @test sum(ars) == 8010003 +end @testset "similar(::ReinterpretArray)" begin a = reinterpret(NTuple{2,Float64}, TSlow(rand(Float64, 4, 4))) @@ -514,6 +552,21 @@ end @test_throws MethodError x[2,4] = nothing end +@testset "pointer for StridedArray" begin + a = rand(Float64, 251) + v = view(a, UInt(2):UInt(251)); + A = reshape(v, 25, 10); + @test A isa StridedArray && pointer(A) === pointer(a, 2) + Av = view(A, 1:20, 1:2) + @test Av isa StridedArray && pointer(Av) === pointer(a, 2) + @test Av * Av' isa Array +end + +@testset "effect of StridedReinterpretArray's getindex" begin + eff = Base.infer_effects(getindex, Base.typesof(reinterpret(Int8, Int[1]), 1)) + @test Core.Compiler.is_effect_free(eff) +end + # reinterpret of arbitrary bitstypes @testset "Reinterpret arbitrary bitstypes" begin struct Bytes15 @@ -535,3 +588,23 @@ end @test_throws ArgumentError reinterpret(Tuple{Int32, Int64}, (Int16(1), Int64(4))) end + +let R = reinterpret(Float32, ComplexF32[1.0f0+2.0f0*im, 4.0f0+3.0f0*im]) + @test !isassigned(R, 0) + @test isassigned(R, 1) + @test isassigned(R, 4) + @test isassigned(R, Int8(2), Int16(1), Int32(1), Int64(1)) + @test !isassigned(R, 1, 2) + @test !isassigned(R, 5) + @test Array(R)::Vector{Float32} == [1.0f0, 2.0f0, 4.0f0, 3.0f0] +end + +let R = reinterpret(reshape, Float32, ComplexF32[1.0f0+2.0f0*im, 4.0f0+3.0f0*im]) + @test !isassigned(R, 0) + @test isassigned(R, 1) + @test isassigned(R, 4) + @test isassigned(R, Int8(2), Int16(2), Int32(1), Int64(1)) + @test !isassigned(R, 1, 1, 2) + @test !isassigned(R, 5) + @test Array(R)::Matrix{Float32} == [1.0f0 4.0f0; 2.0f0 3.0f0] +end diff --git a/test/relocatedepot.jl b/test/relocatedepot.jl new file mode 100644 index 0000000000000..039d422c35e25 --- /dev/null +++ b/test/relocatedepot.jl @@ -0,0 +1,296 @@ +using Test + + +include("testenv.jl") + + +function test_harness(@nospecialize(fn); empty_load_path=true, empty_depot_path=true) + load_path = copy(LOAD_PATH) + depot_path = copy(DEPOT_PATH) + try + empty_load_path && empty!(LOAD_PATH) + empty_depot_path && empty!(DEPOT_PATH) + fn() + finally + copy!(LOAD_PATH, load_path) + copy!(DEPOT_PATH, depot_path) + end +end + +# We test relocation with these dummy pkgs: +# - RelocationTestPkg1 - pkg with no include_dependency +# - RelocationTestPkg2 - pkg with include_dependency tracked by `mtime` +# - RelocationTestPkg3 - pkg with include_dependency tracked by content +# - RelocationTestPkg4 - pkg with no dependencies; will be compiled such that the pkgimage is +# not relocatable, but no repeated recompilation happens upon loading + +if !test_relocated_depot + + @testset "insert @depot tag in path" begin + + test_harness() do + mktempdir() do dir + pushfirst!(DEPOT_PATH, dir) + path = dir*dir + @test Base.replace_depot_path(path) == "@depot"*dir + end + end + + test_harness() do + mktempdir() do dir + pushfirst!(DEPOT_PATH, dir) + path = joinpath(dir, "foo") + if isdirpath(DEPOT_PATH[1]) + DEPOT_PATH[1] = dirname(DEPOT_PATH[1]) # strip trailing pathsep + end + tag = joinpath("@depot", "") # append a pathsep + @test startswith(Base.replace_depot_path(path), tag) + DEPOT_PATH[1] = joinpath(DEPOT_PATH[1], "") # append a pathsep + @test startswith(Base.replace_depot_path(path), tag) + popfirst!(DEPOT_PATH) + @test !startswith(Base.replace_depot_path(path), tag) + end + end + + end + + @testset "restore path from @depot tag" begin + + tmp = tempdir() + + path = joinpath("@depot", "foo", "bar") + tmppath = joinpath(tmp, "foo", "bar") + @test Base.restore_depot_path(path, tmp) == tmppath + + path = joinpath("no@depot", "foo", "bar") + @test Base.restore_depot_path(path, tmp) == path + + path = joinpath("@depot", "foo", "bar\n", "@depot", "foo") + tmppath = joinpath(tmp, "foo", "bar\n", "@depot", "foo") + @test Base.restore_depot_path(path, tmp) == tmppath + + end + + @testset "precompile RelocationTestPkg1" begin + pkgname = "RelocationTestPkg1" + test_harness(empty_depot_path=false) do + push!(LOAD_PATH, @__DIR__) + push!(DEPOT_PATH, @__DIR__) # make src files available for relocation + pkg = Base.identify_package(pkgname) + cachefiles = Base.find_all_in_cache_path(pkg) + rm.(cachefiles, force=true) + @test Base.isprecompiled(pkg) == false + @test Base.isrelocatable(pkg) == false # because not precompiled + Base.require(pkg) + @test Base.isprecompiled(pkg, ignore_loaded=true) == true + @test Base.isrelocatable(pkg) == true + end + end + + @testset "precompile RelocationTestPkg2" begin + pkgname = "RelocationTestPkg2" + test_harness(empty_depot_path=false) do + push!(LOAD_PATH, @__DIR__) + push!(DEPOT_PATH, @__DIR__) # make src files available for relocation + pkg = Base.identify_package(pkgname) + cachefiles = Base.find_all_in_cache_path(pkg) + rm.(cachefiles, force=true) + rm(joinpath(@__DIR__, pkgname, "src", "foodir"), force=true, recursive=true) + @test Base.isprecompiled(pkg) == false + @test Base.isrelocatable(pkg) == false # because not precompiled + touch(joinpath(@__DIR__, pkgname, "src", "foo.txt")) + mkdir(joinpath(@__DIR__, pkgname, "src", "foodir")) + Base.require(pkg) + @test Base.isprecompiled(pkg, ignore_loaded=true) == true + @test Base.isrelocatable(pkg) == false # because tracked by mtime + end + end + + @testset "precompile RelocationTestPkg3" begin + pkgname = "RelocationTestPkg3" + test_harness(empty_depot_path=false) do + push!(LOAD_PATH, @__DIR__) + push!(DEPOT_PATH, @__DIR__) # make src files available for relocation + pkg = Base.identify_package(pkgname) + cachefiles = Base.find_all_in_cache_path(pkg) + rm.(cachefiles, force=true) + rm(joinpath(@__DIR__, pkgname, "src", "bardir"), force=true, recursive=true) + @test Base.isprecompiled(pkg) == false + @test Base.isrelocatable(pkg) == false # because not precompiled + touch(joinpath(@__DIR__, pkgname, "src", "bar.txt")) + mkdir(joinpath(@__DIR__, pkgname, "src", "bardir")) + Base.require(pkg) + @test Base.isprecompiled(pkg, ignore_loaded=true) == true + @test Base.isrelocatable(pkg) == true + end + end + + @testset "precompile RelocationTestPkg4" begin + # test for #52346 and https://github.com/JuliaLang/julia/issues/53859#issuecomment-2027352004 + # If a pkgimage is not relocatable, no repeated precompilation should occur. + pkgname = "RelocationTestPkg4" + test_harness(empty_depot_path=false) do + push!(LOAD_PATH, @__DIR__) + # skip this dir to make the pkgimage not relocatable + filter!(DEPOT_PATH) do depot + !startswith(@__DIR__, depot) + end + pkg = Base.identify_package(pkgname) + cachefiles = Base.find_all_in_cache_path(pkg) + rm.(cachefiles, force=true) + @test Base.isprecompiled(pkg) == false + @test Base.isrelocatable(pkg) == false # because not precompiled + Base.require(pkg) + @test Base.isprecompiled(pkg, ignore_loaded=true) == true + @test Base.isrelocatable(pkg) == false + end + end + + @testset "#52161" begin + # Take the src files from two pkgs Example1 and Example2, + # which are each located in depot1 and depot2, respectively, and + # add them as include_dependency()s to a new pkg Foo, which will be precompiled into depot3. + # After loading the include_dependency()s of Foo should refer to depot1 depot2 each. + test_harness() do + mktempdir() do depot1 + # precompile Example in depot1 + example1_root = joinpath(depot1, "Example1") + mkpath(joinpath(example1_root, "src")) + open(joinpath(example1_root, "src", "Example1.jl"); write=true) do io + println(io, """ + module Example1 + greet() = println("Hello from Example1!") + end + """) + end + open(joinpath(example1_root, "Project.toml"); write=true) do io + println(io, """ + name = "Example1" + uuid = "00000000-0000-0000-0000-000000000001" + version = "1.0.0" + """) + end + pushfirst!(LOAD_PATH, depot1); pushfirst!(DEPOT_PATH, depot1) + pkg = Base.identify_package("Example1"); Base.require(pkg) + mktempdir() do depot2 + # precompile Example in depot2 + example2_root = joinpath(depot2, "Example2") + mkpath(joinpath(example2_root, "src")) + open(joinpath(example2_root, "src", "Example2.jl"); write=true) do io + println(io, """ + module Example2 + greet() = println("Hello from Example2!") + end + """) + end + open(joinpath(example2_root, "Project.toml"); write=true) do io + println(io, """ + name = "Example2" + uuid = "00000000-0000-0000-0000-000000000002" + version = "1.0.0" + """) + end + pushfirst!(LOAD_PATH, depot2); pushfirst!(DEPOT_PATH, depot2) + pkg = Base.identify_package("Example2"); Base.require(pkg) + mktempdir() do depot3 + # precompile Foo in depot3 + open(joinpath(depot3, "Module52161.jl"), write=true) do io + println(io, """ + module Module52161 + using Example1 + using Example2 + srcfile1 = joinpath(pkgdir(Example1), "src", "Example1.jl") + srcfile2 = joinpath(pkgdir(Example2), "src", "Example2.jl") + include_dependency(srcfile1) + include_dependency(srcfile2) + end + """) + end + pushfirst!(LOAD_PATH, depot3); pushfirst!(DEPOT_PATH, depot3) + pkg = Base.identify_package("Module52161"); Base.compilecache(pkg) + cachefile = joinpath(depot3, "compiled", + "v$(VERSION.major).$(VERSION.minor)", "Module52161.ji") + _, (deps, _, _), _... = Base.parse_cache_header(cachefile) + @test map(x -> x.filename, deps) == + [ joinpath(depot3, "Module52161.jl"), + joinpath(depot1, "Example1", "src", "Example1.jl"), + joinpath(depot2, "Example2", "src", "Example2.jl") ] + end + end + end + end + end + + +else + + @testset "load stdlib from test/relocatedepot" begin + test_harness() do + push!(LOAD_PATH, "@stdlib") + push!(DEPOT_PATH, joinpath(@__DIR__, "relocatedepot", "julia")) + # stdlib should be already precompiled + pkg = Base.identify_package("DelimitedFiles") + @test Base.isprecompiled(pkg) == true + @test Base.isrelocatable(pkg) == true + end + end + + @testset "load RelocationTestPkg1 from test/relocatedepot" begin + pkgname = "RelocationTestPkg1" + test_harness() do + push!(LOAD_PATH, joinpath(@__DIR__, "relocatedepot")) + push!(DEPOT_PATH, joinpath(@__DIR__, "relocatedepot")) # required to find src files + push!(DEPOT_PATH, joinpath(@__DIR__, "relocatedepot", "julia")) # contains cache file + pkg = Base.identify_package(pkgname) + @test Base.isprecompiled(pkg) == true + @test Base.isrelocatable(pkg) == true + end + end + + @testset "load RelocationTestPkg2 from test/relocatedepot" begin + pkgname = "RelocationTestPkg2" + test_harness() do + push!(LOAD_PATH, joinpath(@__DIR__, "relocatedepot")) + push!(DEPOT_PATH, joinpath(@__DIR__, "relocatedepot")) # required to find src files + push!(DEPOT_PATH, joinpath(@__DIR__, "relocatedepot", "julia")) # contains cache file + pkg = Base.identify_package(pkgname) + @test Base.isprecompiled(pkg) == false # moving depot changes mtime of include_dependency + @test Base.isrelocatable(pkg) == false # because not precompiled + Base.require(pkg) + @test Base.isprecompiled(pkg) == true + @test Base.isrelocatable(pkg) == false # because tracked by mtime + touch(joinpath(@__DIR__, "relocatedepot", "RelocationTestPkg2", "src", "foodir", "foofoo")) + @test Base.isprecompiled(pkg) == false + @test Base.isrelocatable(pkg) == false # because tracked by mtime + end + end + + @testset "load RelocationTestPkg3 from test/relocatedepot" begin + pkgname = "RelocationTestPkg3" + test_harness() do + push!(LOAD_PATH, joinpath(@__DIR__, "relocatedepot")) + push!(DEPOT_PATH, joinpath(@__DIR__, "relocatedepot")) # required to find src files + push!(DEPOT_PATH, joinpath(@__DIR__, "relocatedepot", "julia")) # contains cache file + pkg = Base.identify_package(pkgname) + @test Base.isprecompiled(pkg) == true + @test Base.isrelocatable(pkg) == true + touch(joinpath(@__DIR__, "relocatedepot", "RelocationTestPkg3", "src", "bardir", "barbar")) + @test Base.isprecompiled(pkg) == false + @test Base.isrelocatable(pkg) == false # because not precompiled + end + end + + @testset "load RelocationTestPkg4 from test/relocatedepot" begin + pkgname = "RelocationTestPkg4" + test_harness() do + push!(LOAD_PATH, @__DIR__, "relocatedepot") + push!(DEPOT_PATH, joinpath(@__DIR__, "relocatedepot")) # required to find src files + push!(DEPOT_PATH, joinpath(@__DIR__, "relocatedepot", "julia")) # contains cache file + pkg = Base.identify_package(pkgname) + # precompiled but not relocatable + @test Base.isprecompiled(pkg) == true + @test Base.isrelocatable(pkg) == false + end + end + +end diff --git a/test/runtests.jl b/test/runtests.jl index 1264acae985b0..c46472ac93fa8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,6 +9,9 @@ using Base: Experimental include("choosetests.jl") include("testenv.jl") +include("buildkitetestjson.jl") + +using .BuildkiteTestJSON (; tests, net_on, exit_on_error, use_revise, seed) = choosetests(ARGS) tests = unique(tests) @@ -84,12 +87,14 @@ move_to_node1("stress") # since it starts a lot of workers and can easily exceed the maximum memory limited_worker_rss && move_to_node1("Distributed") -# Shuffle LinearAlgebra tests to the front, because they take a while, so we might +# Move LinearAlgebra and Pkg tests to the front, because they take a while, so we might # as well get them all started early. -linalg_test_ids = findall(x->occursin("LinearAlgebra", x), tests) -linalg_tests = tests[linalg_test_ids] -deleteat!(tests, linalg_test_ids) -prepend!(tests, linalg_tests) +for prependme in ["LinearAlgebra", "Pkg"] + prependme_test_ids = findall(x->occursin(prependme, x), tests) + prependme_tests = tests[prependme_test_ids] + deleteat!(tests, prependme_test_ids) + prepend!(tests, prependme_tests) +end import LinearAlgebra cd(@__DIR__) do @@ -124,6 +129,7 @@ cd(@__DIR__) do println(""" Running parallel tests with: + getpid() = $(getpid()) nworkers() = $(nworkers()) nthreads() = $(Threads.threadpoolsize()) Sys.CPU_THREADS = $(Sys.CPU_THREADS) @@ -421,6 +427,12 @@ cd(@__DIR__) do Test.record(o_ts, fake) Test.pop_testset() end + + if Base.get_bool_env("CI", false) + @info "Writing test result data to $(@__DIR__)" + write_testset_json_files(@__DIR__, o_ts) + end + Test.TESTSET_PRINT_ENABLE[] = true println() # o_ts.verbose = true # set to true to show all timings when successful diff --git a/test/scopedvalues.jl b/test/scopedvalues.jl index c9d376ab05cbd..61b10c557c455 100644 --- a/test/scopedvalues.jl +++ b/test/scopedvalues.jl @@ -1,16 +1,24 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -import Base: ScopedValues + +using Base.ScopedValues + +include("compiler/irutils.jl") @testset "errors" begin @test ScopedValue{Float64}(1)[] == 1.0 @test_throws InexactError ScopedValue{Int}(1.5) - val = ScopedValue(1) - @test_throws MethodError val[] = 2 - with() do + let val = ScopedValue(1) @test_throws MethodError val[] = 2 + with() do + @test_throws MethodError val[] = 2 + end + end + let val = ScopedValue{String}() + @test_throws KeyError val[] + end + let val = ScopedValue{Int}() + @test_throws KeyError val[] end - val = ScopedValue{Int}() - @test_throws KeyError val[] @test_throws MethodError ScopedValue() end @@ -47,6 +55,16 @@ emptyf() = nothing @testset "conversion" begin with(emptyf, sval_float=>2) @test_throws MethodError with(emptyf, sval_float=>"hello") + a = ScopedValue(1) + with(a => 2.0) do + @test a[] == 2 + @test a[] isa Int + end + a = ScopedValue(1.0) + with(a => 2) do + @test a[] == 2.0 + @test a[] isa Float64 + end end import Base.Threads: @spawn @@ -62,13 +80,13 @@ import Base.Threads: @spawn end @testset "show" begin - @test sprint(show, ScopedValue{Int}()) == "ScopedValue{$Int}(undefined)" - @test sprint(show, sval) == "ScopedValue{$Int}(1)" - @test sprint(show, ScopedValues.current_scope()) == "nothing" + @test sprint(show, ScopedValue{Int}()) == "Base.ScopedValues.ScopedValue{$Int}(undefined)" + @test sprint(show, sval) == "Base.ScopedValues.ScopedValue{$Int}(1)" + @test sprint(show, Core.current_scope()) == "nothing" with(sval => 2.0) do - @test sprint(show, sval) == "ScopedValue{$Int}(2)" + @test sprint(show, sval) == "Base.ScopedValues.ScopedValue{$Int}(2)" objid = sprint(show, Base.objectid(sval)) - @test sprint(show, ScopedValues.current_scope()) == "Base.ScopedValues.Scope(ScopedValue{$Int}@$objid => 2)" + @test sprint(show, Core.current_scope()) == "Base.ScopedValues.Scope(Base.ScopedValues.ScopedValue{$Int}@$objid => 2)" end end @@ -121,3 +139,33 @@ end @test sval_float[] == 1.0 end end + +@testset "isassigned" begin + sv = ScopedValue(1) + @test isassigned(sv) + sv = ScopedValue{Int}() + @test !isassigned(sv) + with(sv => 2) do + @test isassigned(sv) + end +end + +# Test that the `@with` macro doesn't introduce unnecessary PhiC nodes +# (which can be hard for the optimizer to remove). +function with_macro_slot_cross() + a = 1 + @with sval=>1 begin + a = sval_float[] + end + return a +end + +let code = code_typed(with_macro_slot_cross)[1][1].code + @test !any(x->isa(x, Core.PhiCNode), code) +end + +# inline constant scoped values +const inlineable_const_sv = ScopedValue(1) +@test fully_eliminated(; retval=(inlineable_const_sv => 1)) do + inlineable_const_sv => 1 +end diff --git a/test/secretbuffer.jl b/test/secretbuffer.jl index 976c757deea57..29e28ded8da72 100644 --- a/test/secretbuffer.jl +++ b/test/secretbuffer.jl @@ -1,7 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license using Base: SecretBuffer, SecretBuffer!, shred!, isshredded -using Test +using Test, Random @testset "SecretBuffer" begin @testset "original unmodified" begin @@ -129,4 +129,45 @@ using Test @test_throws ArgumentError Base.unsafe_SecretBuffer!(null_ptr) @test_throws ArgumentError Base.unsafe_SecretBuffer!(null_ptr, 0) end + + @testset "copiers" begin + s1 = SecretBuffer() + write(s1, "hello world") + seekstart(s1) + + s2 = copy(s1) + write(s2, 'c') + seekstart(s2) + + @test read(s1) == codeunits("hello world") + @test read(s2) == codeunits("cello world") + + shred!(s1) + @test isshredded(s1) + @test !isshredded(s2) + shred!(s2) + + # Copying into a bigger destination + s3 = SecretBuffer() + s4 = SecretBuffer() + write(s3, "original") + seekstart(s3) + write(s4, randstring(1234)) + s4data = s4.data + copy!(s4, s3) + @test s3.data == s4.data + @test read(s3) == read(s4) == codeunits("original") + @test all(iszero, s4data) + shred!(s3); shred!(s4) + + # Copying into a smaller destination + s5 = SecretBuffer() + s6 = SecretBuffer("sekrit") + str = randstring(321) + write(s5, str) + seekstart(s5) + copy!(s6, s5) + @test read(s5) == read(s6) == codeunits(str) + shred!(s5); shred!(s6) + end end diff --git a/test/sets.jl b/test/sets.jl index e33397e58d2b6..4ab360c9fedd4 100644 --- a/test/sets.jl +++ b/test/sets.jl @@ -124,7 +124,40 @@ end @test isempty(s) @test_throws ArgumentError pop!(s) @test length(Set(['x',120])) == 2 + + # Test that pop! returns the element in the set, not the query + s = Set{Any}(Any[0x01, UInt(2), 3, 4.0]) + @test pop!(s, 1) === 0x01 + @test pop!(s, 2) === UInt(2) + @test pop!(s, 3) === 3 + @test pop!(s, 4) === 4.0 + @test_throws KeyError pop!(s, 5) +end + +@testset "in!" begin + s = Set() + @test !(in!(0x01, s)) + @test !(in!(Int32(2), s)) + @test in!(1, s) + @test in!(2.0, s) + (a, b, c...) = sort!(collect(s)) + @test a === 0x01 + @test b === Int32(2) + @test isempty(c) + + # in! will convert to the right type automatically + s = Set{Int32}() + @test !(in!(1, s)) + @test only(s) === Int32(1) + @test_throws Exception in!("hello", s) + + # Other set types + s = BitSet() + @test !(in!(13, s)) + @test in!(UInt16(13), s) + @test only(s) === 13 end + @testset "copy" begin data_in = (1,2,9,8,4) s = Set(data_in) @@ -164,6 +197,19 @@ end sizehint!(s2, 10) @test s2 == GenericSet(s) end + +@testset "shrinking" begin # Similar test as for the underlying Dict + d = Set(i for i = 1:1000) + filter!(x -> x < 10, d) + sizehint!(d, 10) + @test length(d.dict.slots) < 100 + sizehint!(d, 1000) + sizehint!(d, 1; shrink = false) + @test length(d.dict.slots) >= 1000 + sizehint!(d, 1; shrink = true) + @test length(d.dict.slots) < 1000 +end + @testset "rehash!" begin # Use a pointer type to have defined behavior for uninitialized # array element @@ -364,7 +410,9 @@ end @test issubset(intersect(l,r), r) @test issubset(l, union(l,r)) @test issubset(r, union(l,r)) + @test issubset(union(l,r))(r) @test isdisjoint(l,l) == isempty(l) + @test isdisjoint(l)(l) == isempty(l) @test isdisjoint(l,r) == isempty(intersect(l,r)) if S === Vector @test sort(union(intersect(l,r),symdiff(l,r))) == sort(union(l,r)) @@ -381,6 +429,15 @@ end @test ⊋(S([1,2]), S([1])) @test !⊋(S([1]), S([1])) @test ⊉(S([1]), S([2])) + + @test ⊆(S([1,2]))(S([1])) + @test ⊊(S([1,2]))(S([1])) + @test !⊊(S([1]))(S([1])) + @test ⊈(S([2]))(S([1])) + @test ⊇(S([1]))(S([1,2])) + @test ⊋(S([1]))(S([1,2])) + @test !⊋(S([1]))(S([1])) + @test ⊉(S([2]))(S([1])) end let s1 = S([1,2,3,4]) @test s1 !== symdiff(s1) == s1 @@ -548,6 +605,9 @@ end @test !allunique([1,1,2]) @test !allunique([:a,:b,:c,:a]) @test allunique(unique(randn(100))) # longer than 32 + @test allunique(collect(1:100)) # sorted/unique && longer than 32 + @test allunique(collect(100:-1:1)) # sorted/unique && longer than 32 + @test !allunique(fill(1,100)) # sorted/repeating && longer than 32 @test allunique(collect('A':'z')) # 58-element Vector{Char} @test !allunique(repeat(1:99, 1, 2)) @test !allunique(vcat(pi, randn(1998), pi)) # longer than 1000 @@ -582,21 +642,38 @@ end @test !allunique((1,2,3,4,3)) @test allunique((0.0, -0.0)) @test !allunique((NaN, NaN)) + # Known length 1, need not evaluate: + @test allunique(error(x) for x in [1]) +end + +@testset "allunique(f, xs)" begin + @test allunique(sin, 1:3) + @test !allunique(sin, [1,2,3,1]) + @test allunique(sin, (1, 2, pi, im)) # eltype Any + @test allunique(abs2, 1:100) + @test !allunique(abs, -10:10) + @test allunique(abs2, Vector{Any}(1:100)) + # These cases don't call the function at all: + @test allunique(error, []) + @test allunique(error, [1]) end @testset "allequal" begin + # sets & dictionaries @test allequal(Set()) @test allequal(Set(1)) @test !allequal(Set([1, 2])) @test allequal(Dict()) @test allequal(Dict(:a => 1)) @test !allequal(Dict(:a => 1, :b => 2)) + # vectors @test allequal([]) @test allequal([1]) @test allequal([1, 1]) @test !allequal([1, 1, 2]) @test allequal([:a, :a]) @test !allequal([:a, :b]) + # ranges @test !allequal(1:2) @test allequal(1:1) @test !allequal(4.0:0.3:7.0) @@ -610,6 +687,26 @@ end @test allequal(LinRange(1, 1, 1)) @test allequal(LinRange(1, 1, 2)) @test !allequal(LinRange(1, 2, 2)) + # Known length 1, need not evaluate: + @test allequal(error(x) for x in [1]) + # Empty, but !haslength: + @test allequal(error(x) for x in 1:3 if false) +end + +@testset "allequal(f, xs)" begin + @test allequal(abs2, [3, -3]) + @test allequal(x -> 1, rand(3)) + @test !allequal(x -> rand(), [1,1,1]) + # tuples + @test allequal(abs2, (3, -3)) + @test allequal(x -> 1, Tuple(rand(3))) + @test !allequal(x -> rand(), (1,1,1)) + # These cases don't call the function at all: + @test allequal(error, []) + @test allequal(error, ()) + @test allequal(error, (x for x in 1:3 if false)) + @test allequal(error, [1]) + @test allequal(error, (1,)) end @testset "filter(f, ::$S)" for S = (Set, BitSet) @@ -839,8 +936,8 @@ end b = [2, 3, 1, 3] ua = unique(a) ub = unique(b) - for TA in (Tuple, identity, Set, BitSet, Base.IdSet{Int}), - TB in (Tuple, identity, Set, BitSet, Base.IdSet{Int}), + for TA in (Tuple, identity, Set, BitSet, IdSet{Int}), + TB in (Tuple, identity, Set, BitSet, IdSet{Int}), uA = false:true, uB = false:true A = TA(uA ? ua : a) @@ -859,7 +956,9 @@ end @test !(B ⊉ A) @test !issetequal(A, B) @test !issetequal(B, A) - for T = (Tuple, identity, Set, BitSet, Base.IdSet{Int}) + @test !issetequal(B)(A) + @test !issetequal(A)(B) + for T = (Tuple, identity, Set, BitSet, IdSet{Int}) @test issetequal(A, T(A)) @test issetequal(B, T(B)) end @@ -920,7 +1019,7 @@ end c = [3] d = [4] e = [5] - A = Base.IdSet{Vector{Int}}([a, b, c, d]) + A = IdSet{Vector{Int}}([a, b, c, d]) @test !isempty(A) B = copy(A) @test A ⊆ B @@ -953,4 +1052,6 @@ end end set = TestSet{Any}() @test sizehint!(set, 1) === set + @test sizehint!(set, 1; shrink = true) === set + @test sizehint!(set, 1; shrink = false) === set end diff --git a/test/show.jl b/test/show.jl index f95f943c3c1a4..4bc7540326923 100644 --- a/test/show.jl +++ b/test/show.jl @@ -703,7 +703,7 @@ let oldout = stdout, olderr = stderr redirect_stderr(olderr) close(wrout) close(wrerr) - @test fetch(out) == "Int64 <: Signed\nTESTA\nTESTB\nΑ1Β2\"A\"\nA\n123\"C\"\n" + @test fetch(out) == "primitive type Int64 <: Signed\nTESTA\nTESTB\nΑ1Β2\"A\"\nA\n123\"C\"\n" @test fetch(err) == "TESTA\nTESTB\nΑ1Β2\"A\"\n" finally redirect_stdout(oldout) @@ -1250,70 +1250,73 @@ end @testset "PR 17117: print_array" begin s = IOBuffer(Vector{UInt8}(), read=true, write=true) Base.print_array(s, [1, 2, 3]) - @test String(resize!(s.data, s.size)) == " 1\n 2\n 3" + @test String(take!(s)) == " 1\n 2\n 3" close(s) s2 = IOBuffer(Vector{UInt8}(), read=true, write=true) z = zeros(0,0,0,0,0,0,0,0) Base.print_array(s2, z) - @test String(resize!(s2.data, s2.size)) == "" + @test String(take!(s2)) == "" close(s2) end -let repr = sprint(dump, :(x = 1)) - @test repr == "Expr\n head: Symbol =\n args: Array{Any}((2,))\n 1: Symbol x\n 2: $Int 1\n" -end -let repr = sprint(dump, Pair{String,Int64}) - @test repr == "Pair{String, Int64} <: Any\n first::String\n second::Int64\n" -end -let repr = sprint(dump, Tuple) - @test repr == "Tuple <: Any\n" -end -let repr = sprint(dump, Int64) - @test repr == "Int64 <: Signed\n" -end -let repr = sprint(dump, Any) - @test length(repr) == 4 - @test occursin(r"^Any\n", repr) - @test endswith(repr, '\n') -end -let repr = sprint(dump, Integer) - @test occursin("Integer <: Real", repr) - @test !occursin("Any", repr) -end -let repr = sprint(dump, Union{Integer, Float32}) - @test repr == "Union{Integer, Float32}\n" || repr == "Union{Float32, Integer}\n" -end module M30442 struct T end end -let repr = sprint(show, Union{String, M30442.T}) - @test repr == "Union{$(curmod_prefix)M30442.T, String}" || - repr == "Union{String, $(curmod_prefix)M30442.T}" -end -let repr = sprint(dump, Ptr{UInt8}(UInt(1))) - @test repr == "Ptr{UInt8} @$(Base.repr(UInt(1)))\n" -end -let repr = sprint(dump, Core.svec()) - @test repr == "empty SimpleVector\n" -end -let repr = sprint(dump, sin) - @test repr == "sin (function of type typeof(sin))\n" -end -let repr = sprint(dump, Test) - @test repr == "Module Test\n" -end -let repr = sprint(dump, nothing) - @test repr == "Nothing nothing\n" -end -let a = Vector{Any}(undef, 10000) - a[2] = "elemA" - a[4] = "elemB" - a[11] = "elemC" - repr = sprint(dump, a; context=(:limit => true), sizehint=0) - @test repr == "Array{Any}((10000,))\n 1: #undef\n 2: String \"elemA\"\n 3: #undef\n 4: String \"elemB\"\n 5: #undef\n ...\n 9996: #undef\n 9997: #undef\n 9998: #undef\n 9999: #undef\n 10000: #undef\n" -end -@test occursin("NamedTuple", sprint(dump, NamedTuple)) +@testset "Dump types" begin + let repr = sprint(dump, :(x = 1)) + @test repr == "Expr\n head: Symbol =\n args: Array{Any}((2,))\n 1: Symbol x\n 2: $Int 1\n" + end + let repr = sprint(dump, Pair{String,Int64}) + @test repr == "struct Pair{String, Int64} <: Any\n first::String\n second::Int64\n" + end + let repr = sprint(dump, Tuple) + @test repr == "Tuple <: Any\n" + end + let repr = sprint(dump, Int64) + @test repr == "primitive type Int64 <: Signed\n" + end + let repr = sprint(dump, Any) + @test repr == "abstract type Any\n" + end + let repr = sprint(dump, Integer) + @test occursin("abstract type Integer <: Real", repr) + @test !occursin("Any", repr) + end + let repr = sprint(dump, Union{Integer, Float32}) + @test repr == "Union{Integer, Float32}\n" || repr == "Union{Float32, Integer}\n" + end + + let repr = sprint(show, Union{String, M30442.T}) + @test repr == "Union{$(curmod_prefix)M30442.T, String}" || + repr == "Union{String, $(curmod_prefix)M30442.T}" + end + let repr = sprint(dump, Ptr{UInt8}(UInt(1))) + @test repr == "Ptr{UInt8} @$(Base.repr(UInt(1)))\n" + end + let repr = sprint(dump, Core.svec()) + @test repr == "empty SimpleVector\n" + end + let repr = sprint(dump, sin) + @test repr == "sin (function of type typeof(sin))\n" + end + let repr = sprint(dump, Test) + @test repr == "Module Test\n" + end + let repr = sprint(dump, nothing) + @test repr == "Nothing nothing\n" + end + let a = Vector{Any}(undef, 10000) + a[2] = "elemA" + a[4] = "elemB" + a[11] = "elemC" + repr = sprint(dump, a; context=(:limit => true), sizehint=0) + @test repr == "Array{Any}((10000,))\n 1: #undef\n 2: String \"elemA\"\n 3: #undef\n 4: String \"elemB\"\n 5: #undef\n ...\n 9996: #undef\n 9997: #undef\n 9998: #undef\n 9999: #undef\n 10000: #undef\n" + end + @test occursin("NamedTuple", sprint(dump, NamedTuple)) + # issue 36495, dumping a partial NamedTupled shouldn't error + @test occursin("NamedTuple", sprint(dump, NamedTuple{(:foo,:bar)})) +end # issue #17338 @test repr(Core.svec(1, 2)) == "svec(1, 2)" @@ -1378,6 +1381,7 @@ test_repr("(:).a") @test repr(@NamedTuple{kw::@NamedTuple{kw2::Int64}}) == "@NamedTuple{kw::@NamedTuple{kw2::Int64}}" @test repr(@NamedTuple{kw::NTuple{7, Int64}}) == "@NamedTuple{kw::NTuple{7, Int64}}" @test repr(@NamedTuple{a::Float64, b}) == "@NamedTuple{a::Float64, b}" +@test repr(@NamedTuple{var"#"::Int64}) == "@NamedTuple{var\"#\"::Int64}" # Test general printing of `Base.Pairs` (it should not use the `@Kwargs` macro syntax) @test repr(@Kwargs{init::Int}) == "Base.Pairs{Symbol, $Int, Tuple{Symbol}, @NamedTuple{init::$Int}}" @@ -1459,12 +1463,20 @@ end @test static_shown(:+) == ":+" @test static_shown(://) == "://" @test static_shown(://=) == "://=" -@test static_shown(Symbol("")) == "Symbol(\"\")" -@test static_shown(Symbol("a/b")) == "Symbol(\"a/b\")" -@test static_shown(Symbol("a-b")) == "Symbol(\"a-b\")" +@test static_shown(Symbol("")) == ":var\"\"" +@test static_shown(Symbol("a/b")) == ":var\"a/b\"" +@test static_shown(Symbol("a-b")) == ":var\"a-b\"" @test static_shown(UnionAll) == "UnionAll" - @test static_shown(QuoteNode(:x)) == ":(:x)" +@test static_shown(:!) == ":!" +@test static_shown("\"") == "\"\\\"\"" +@test static_shown("\$") == "\"\\\$\"" +@test static_shown("\\") == "\"\\\\\"" +@test static_shown("a\x80b") == "\"a\\x80b\"" +@test static_shown("a\x80\$\\b") == "\"a\\x80\\\$\\\\b\"" +@test static_shown(GlobalRef(Main, :var"a#b")) == "Main.var\"a#b\"" +@test static_shown(GlobalRef(Main, :+)) == "Main.:(+)" +@test static_shown((a = 3, ! = 4, var"a b" = 5)) == "(a=3, (!)=4, var\"a b\"=5)" # PR #38049 @test static_shown(sum) == "Base.sum" @@ -1881,6 +1893,7 @@ end @test showstr(Pair{Integer,Integer}(1, 2), :typeinfo => Pair{Integer,Integer}) == "1 => 2" @test showstr([Pair{Integer,Integer}(1, 2)]) == "Pair{Integer, Integer}[1 => 2]" + @test showstr([(a=1,)]) == "[(a = 1,)]" @test showstr(Dict{Integer,Integer}(1 => 2)) == "Dict{Integer, Integer}(1 => 2)" @test showstr(Dict(true=>false)) == "Dict{Bool, Bool}(1 => 0)" @test showstr(Dict((1 => 2) => (3 => 4))) == "Dict((1 => 2) => (3 => 4))" @@ -1969,12 +1982,12 @@ end end @testset "Intrinsic printing" begin - @test sprint(show, Core.Intrinsics.arraylen) == "Core.Intrinsics.arraylen" - @test repr(Core.Intrinsics.arraylen) == "Core.Intrinsics.arraylen" + @test sprint(show, Core.Intrinsics.cglobal) == "Core.Intrinsics.cglobal" + @test repr(Core.Intrinsics.cglobal) == "Core.Intrinsics.cglobal" let io = IOBuffer() - show(io, MIME"text/plain"(), Core.Intrinsics.arraylen) + show(io, MIME"text/plain"(), Core.Intrinsics.cglobal) str = String(take!(io)) - @test occursin("arraylen", str) + @test occursin("cglobal", str) @test occursin("(intrinsic function", str) end @test string(Core.Intrinsics.add_int) == "add_int" @@ -2049,6 +2062,7 @@ eval(Meta._parse_string("""function my_fun28173(x) r = 1 s = try r = 2 + Base.inferencebarrier(false) && error() "BYE" catch r = 3 @@ -2062,7 +2076,7 @@ eval(Meta._parse_string("""function my_fun28173(x) end""", "a"^80, 1, 1, :statement)[1]) # use parse to control the line numbers let src = code_typed(my_fun28173, (Int,), debuginfo=:source)[1][1] ir = Core.Compiler.inflate_ir(src) - fill!(src.codelocs, 0) # IRCode printing is only capable of printing partial line info + src.debuginfo = Core.DebugInfo(src.debuginfo.def) # IRCode printing defaults to incomplete line info printing, so turn it off completely for CodeInfo too let source_slotnames = String["my_fun28173", "x"], repr_ir = split(repr(ir, context = :SOURCE_SLOTNAMES=>source_slotnames), '\n'), repr_ir = "CodeInfo(\n" * join((l[4:end] for l in repr_ir), "\n") * ")" # remove line numbers @@ -2085,9 +2099,9 @@ let src = code_typed(my_fun28173, (Int,), debuginfo=:source)[1][1] end @test popfirst!(lines2) == " │ $(QuoteNode(2))" @test pop!(lines2) == " └─── \$(QuoteNode(4))" - @test pop!(lines1) == "17 └─── return %18" - @test pop!(lines2) == " │ return %18" - @test pop!(lines2) == "17 │ \$(QuoteNode(3))" + @test pop!(lines1) == "18 └─── return %21" + @test pop!(lines2) == " │ return %21" + @test pop!(lines2) == "18 │ \$(QuoteNode(3))" @test lines1 == lines2 # verbose linetable @@ -2166,6 +2180,20 @@ replstrcolor(x) = sprint((io, x) -> show(IOContext(io, :limit => true, :color => @test_repr "Bool[1, 0]" end +@testset "Unions with Bool (#39590)" begin + @test repr([missing, false]) == "Union{Missing, Bool}[missing, 0]" + @test_repr "Union{Bool, Nothing}[1, 0, nothing]" +end + +# issue #26847 +@test_repr "Union{Missing, Float32}[1.0]" + +# intersection of #45396 and #48822 +@test_repr "Union{Missing, Rational{Int64}}[missing, 1//2, 2]" + +# Don't go too far with #48822 +@test_repr "Union{String, Bool}[true]" + # issue #30505 @test repr(Union{Tuple{Char}, Tuple{Char, Char}}[('a','b')]) == "Union{Tuple{Char}, Tuple{Char, Char}}[('a', 'b')]" @@ -2648,3 +2676,21 @@ let buf = IOBuffer() Base.show_tuple_as_call(buf, Symbol(""), Tuple{Function,Any}) @test String(take!(buf)) == "(::Function)(::Any)" end + +module Issue49382 + abstract type Type49382 end +end +using .Issue49382 +(::Type{Issue49382.Type49382})() = 1 +@test sprint(show, methods(Issue49382.Type49382)) isa String + +# Showing of bad SlotNumber in Expr(:toplevel) +let lowered = Meta.lower(Main, Expr(:let, Expr(:block), Expr(:block, Expr(:toplevel, :(x = 1)), :(y = 1)))) + ci = lowered.args[1] + @assert isa(ci, Core.CodeInfo) + @test !isempty(ci.slotnames) + @assert ci.code[1].head === :toplevel + ci.code[1].args[1] = :($(Core.SlotNumber(1)) = 1) + # Check that this gets printed as `_1 = 1` not `y = 1` + @test contains(sprint(show, ci), "_1 = 1") +end diff --git a/test/smallarrayshrink.jl b/test/smallarrayshrink.jl index a1a7df5aee5a5..680a882e432d4 100644 --- a/test/smallarrayshrink.jl +++ b/test/smallarrayshrink.jl @@ -1,45 +1,20 @@ @testset "shrink small array" begin - x = [1, 2, 3, 4] - @test x[1] == 1 - @test x[2] == 2 - @test x[3] == 3 - @test x[4] == 4 - @test ccall(:jl_array_size, Int, (Any, UInt), x, 0) == 4 - @test ccall(:jl_array_size, Int, (Any, UInt), x, 1) == 4 - sizehint!(x, 10000) - @test x[1] == 1 - @test x[2] == 2 - @test x[3] == 3 - @test x[4] == 4 - @test ccall(:jl_array_size, Int, (Any, UInt), x, 0) == 4 - @test ccall(:jl_array_size, Int, (Any, UInt), x, 1) == 10000 - sizehint!(x, 4) - @test x[1] == 1 - @test x[2] == 2 - @test x[3] == 3 - @test x[4] == 4 - @test ccall(:jl_array_size, Int, (Any, UInt), x, 0) == 4 - @test ccall(:jl_array_size, Int, (Any, UInt), x, 1) == 4 - - x = [1, 2, 3, 4] - @test x[1] == 1 - @test x[2] == 2 - @test x[3] == 3 - @test x[4] == 4 - @test ccall(:jl_array_size, Int, (Any, UInt), x, 0) == 4 - @test ccall(:jl_array_size, Int, (Any, UInt), x, 1) == 4 - sizehint!(x, 1000000) - @test x[1] == 1 - @test x[2] == 2 - @test x[3] == 3 - @test x[4] == 4 - @test ccall(:jl_array_size, Int, (Any, UInt), x, 0) == 4 - @test ccall(:jl_array_size, Int, (Any, UInt), x, 1) == 1000000 - sizehint!(x, 4) - @test x[1] == 1 - @test x[2] == 2 - @test x[3] == 3 - @test x[4] == 4 - @test ccall(:jl_array_size, Int, (Any, UInt), x, 0) == 4 - @test ccall(:jl_array_size, Int, (Any, UInt), x, 1) == 4 + function check_array(x, size, capacity) + @test x[1] == 1 + @test x[2] == 2 + @test x[3] == 3 + @test x[4] == 4 + @test ccall(:jl_array_size, Int, (Any, UInt), x, 0) == size + @test ccall(:jl_array_size, Int, (Any, UInt), x, 1) == capacity + end + for hint_size = [10000, 1000000] + x = [1, 2, 3, 4] + check_array(x, 4, 4) + sizehint!(x, hint_size) + check_array(x, 4, hint_size) + sizehint!(x, 4; shrink = false) + check_array(x, 4, hint_size) + sizehint!(x, 4) + check_array(x, 4, 4) + end end diff --git a/test/sorting.jl b/test/sorting.jl index f89291c8a685a..2714197f58823 100644 --- a/test/sorting.jl +++ b/test/sorting.jl @@ -9,6 +9,12 @@ using Test isdefined(Main, :OffsetArrays) || @eval Main include("testhelpers/OffsetArrays.jl") using .Main.OffsetArrays +@testset "Base.Sort docstrings" begin + undoc = Docs.undocumented_names(Base.Sort) + @test_broken isempty(undoc) + @test undoc == [:Algorithm, :SMALL_THRESHOLD, :Sort] +end + @testset "Order" begin @test Forward == ForwardOrdering() @test ReverseOrdering(Forward) == ReverseOrdering() == Reverse @@ -88,20 +94,6 @@ end vcat(2000, (x:x+99 for x in 1900:-100:100)..., 1:99) end -function tuple_sort_test(x) - @test issorted(sort(x)) - length(x) > 9 && return # length > 9 uses a vector fallback - @test 0 == @allocated sort(x) -end -@testset "sort(::NTuple)" begin - @test sort((9,8,3,3,6,2,0,8)) == (0,2,3,3,6,8,8,9) - @test sort((9,8,3,3,6,2,0,8), by=x->x÷3) == (2,0,3,3,8,6,8,9) - for i in 1:40 - tuple_sort_test(tuple(rand(i)...)) - end - @test_throws ArgumentError sort((1,2,3.0)) -end - @testset "partialsort" begin @test partialsort([3,6,30,1,9],3) == 6 @test partialsort([3,6,30,1,9],3:4) == [6,9] @@ -544,23 +536,6 @@ end @test isequal(a, [8,6,7,NaN,5,3,0,9]) end -@testset "sort!(iterable)" begin - gen = (x % 7 + 0.1x for x in 1:50) - @test sort(gen) == sort!(collect(gen)) - gen = (x % 7 + 0.1y for x in 1:10, y in 1:5) - @test sort(gen; dims=1) == sort!(collect(gen); dims=1) - @test sort(gen; dims=2) == sort!(collect(gen); dims=2) - - @test_throws ArgumentError("dimension out of range") sort(gen; dims=3) - - @test_throws UndefKeywordError(:dims) sort(gen) - @test_throws UndefKeywordError(:dims) sort(collect(gen)) - @test_throws UndefKeywordError(:dims) sort!(collect(gen)) - - @test_throws ArgumentError sort("string") - @test_throws ArgumentError("1 cannot be sorted") sort(1) -end - @testset "sort!(::AbstractVector{<:Integer}) with short int range" begin a = view([9:-1:0;], :)::SubArray sort!(a) @@ -735,6 +710,7 @@ end safe_algs = [InsertionSort, MergeSort, Base.Sort.ScratchQuickSort(), Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] n = 1000 + Random.seed!(0x3588d23f15e74060); v = rand(1:5, n); s = sort(v); @@ -752,8 +728,9 @@ end for alg in safe_algs @test sort(1:n, alg=alg, lt = (i,j) -> v[i]<=v[j]) == perm end - @test partialsort(1:n, 172, lt = (i,j) -> v[i]<=v[j]) == perm[172] - @test partialsort(1:n, 315:415, lt = (i,j) -> v[i]<=v[j]) == perm[315:415] + # Broken by the introduction of BracketedSort in #52006 which is unstable + # @test_broken partialsort(1:n, 172, lt = (i,j) -> v[i]<=v[j]) == perm[172] (sometimes passes due to RNG) + @test_broken partialsort(1:n, 315:415, lt = (i,j) -> v[i]<=v[j]) == perm[315:415] # lt can be very poorly behaved and sort will still permute its input in some way. for alg in safe_algs @@ -822,9 +799,9 @@ end let requires_uint_mappable = Union{Base.Sort.RadixSort, Base.Sort.ConsiderRadixSort, Base.Sort.CountingSort, Base.Sort.ConsiderCountingSort, - typeof(Base.Sort.DEFAULT_STABLE.next.next.big.next.yes), - typeof(Base.Sort.DEFAULT_STABLE.next.next.big.next.yes.big), - typeof(Base.Sort.DEFAULT_STABLE.next.next.big.next.yes.big.next)} + typeof(Base.Sort.DEFAULT_STABLE.next.next.next.big.next.yes), + typeof(Base.Sort.DEFAULT_STABLE.next.next.next.big.next.yes.big), + typeof(Base.Sort.DEFAULT_STABLE.next.next.next.big.next.yes.big.next)} function test_alg(kw, alg, float=true) for order in [Base.Forward, Base.Reverse, Base.By(x -> x^2)] @@ -972,8 +949,8 @@ end @testset "ScratchQuickSort allocations on non-concrete eltype" begin v = Vector{Union{Nothing, Bool}}(rand(Bool, 10000)) - @test 4 == @allocations sort(v) - @test 4 == @allocations sort(v; alg=Base.Sort.ScratchQuickSort()) + @test 10 > @allocations sort(v) + @test 10 > @allocations sort(v; alg=Base.Sort.ScratchQuickSort()) # it would be nice if these numbers were lower (1 or 2), but these # test that we don't have O(n) allocations due to type instability end @@ -981,15 +958,15 @@ end function test_allocs() v = rand(10) i = randperm(length(v)) - @test 1 == @allocations sort(v) + @test 2 >= @allocations sort(v) @test 0 == @allocations sortperm!(i, v) @test 0 == @allocations sort!(i) @test 0 == @allocations sortperm!(i, v, rev=true) - @test 1 == @allocations sortperm(v, rev=true) - @test 1 == @allocations sortperm(v, rev=false) + @test 2 >= @allocations sortperm(v, rev=true) + @test 2 >= @allocations sortperm(v, rev=false) @test 0 == @allocations sortperm!(i, v, order=Base.Reverse) - @test 1 == @allocations sortperm(v) - @test 1 == @allocations sortperm(i, by=sqrt) + @test 2 >= @allocations sortperm(v) + @test 2 >= @allocations sortperm(i, by=sqrt) @test 0 == @allocations sort!(v, lt=(a, b) -> hash(a) < hash(b)) sort!(Int[], rev=false) # compile @test 0 == @allocations sort!(i, rev=false) @@ -1065,6 +1042,32 @@ end @test issorted(sort!(rand(100), Base.Sort.InitialOptimizations(DispatchLoopTestAlg()), Base.Order.Forward)) end +# Pathologize 0 is a noop, pathologize 3 is fully pathological +function pathologize!(x, level) + Base.require_one_based_indexing(x) + k2 = Int(cbrt(length(x))^2) + seed = hash(length(x), Int === Int64 ? 0x85eb830e0216012d : 0xae6c4e15) + for a in 1:level + seed = hash(a, seed) + x[mod.(hash.(1:k2, seed), range.(1:k2,lastindex(x)))] .= a + end + x +end + +@testset "partialsort tests added for BracketedSort #52006" begin + for x in [pathologize!.(Ref(rand(Int, 1000)), 0:3); pathologize!.(Ref(rand(1000)), 0:3); [pathologize!(rand(Int, 1_000_000), 3)]] + @test partialsort(x, 1) == minimum(x) + @test partialsort(x, lastindex(x)) == maximum(x) + sx = sort(x) + for i in [1, 2, 4, 10, 11, 425, 500, 845, 991, 997, 999, 1000] + @test partialsort(x, i) == sx[i] + end + for i in [1:1, 1:2, 1:5, 1:8, 1:9, 1:11, 1:108, 135:812, 220:586, 363:368, 450:574, 458:597, 469:638, 487:488, 500:501, 584:594, 1000:1000] + @test partialsort(x, i) == sx[i] + end + end +end + # This testset is at the end of the file because it is slow. @testset "searchsorted" begin numTypes = [ Int8, Int16, Int32, Int64, Int128, diff --git a/test/spawn.jl b/test/spawn.jl index 3fdfa794ff39e..831eac493d4aa 100644 --- a/test/spawn.jl +++ b/test/spawn.jl @@ -23,7 +23,7 @@ havebb = false function _tryonce_download_from_cache(desired_url::AbstractString) cache_url = "https://cache.julialang.org/$(desired_url)" - cache_output_filename = joinpath(mktempdir(), "myfile") + cache_output_filename = joinpath(mktempdir(), "busybox") cache_response = Downloads.request( cache_url; output = cache_output_filename, @@ -660,9 +660,21 @@ let p = run(`$sleepcmd 100`, wait=false) kill(p) end -# Second argument of shell_parse +# Second return of shell_parse let s = " \$abc " - @test s[Base.shell_parse(s)[2]] == "abc" + @test Base.shell_parse(s)[2] === findfirst('a', s) + s = "abc def" + @test Base.shell_parse(s)[2] === findfirst('d', s) + s = "abc 'de'f\"\"g" + @test Base.shell_parse(s)[2] === findfirst('\'', s) + s = "abc \$x'de'f\"\"g" + @test Base.shell_parse(s)[2] === findfirst('\'', s) + s = "abc def\$x'g'" + @test Base.shell_parse(s)[2] === findfirst('\'', s) + s = "abc def\$x " + @test Base.shell_parse(s)[2] === findfirst('x', s) + s = "abc \$(d)ef\$(x " + @test Base.shell_parse(s)[2] === findfirst('x', s) - 1 end # Logging macros should not output to finalized streams (#26687) @@ -795,8 +807,9 @@ let text = "input-test-text" out = Base.BufferStream() proc = run(catcmd, IOBuffer(text), out, wait=false) @test proc.out === out - @test read(out, String) == text @test success(proc) + closewrite(out) + @test read(out, String) == text out = PipeBuffer() proc = run(catcmd, IOBuffer(SubString(text)), out) @@ -1003,5 +1016,26 @@ end args = ["ab ^` c", " \" ", "\"", ascii95, ascii95, "\"\\\"\\", "", "|", "&&", ";"]; @test Base.shell_escape_wincmd(Base.escape_microsoft_c_args(args...)) == "\"ab ^` c\" \" \\\" \" \"\\\"\" \" !\\\"#\$%^&'^(^)*+,-./0123456789:;^<=^>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^^_`abcdefghijklmnopqrstuvwxyz{^|}~\" \" ^!\\\"#\$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\" \"\\\"\\\\\\\"\\\\\" \"\" ^| ^&^& ;" +end + +# effects for Cmd construction +for f in (() -> `a b c`, () -> `a a$("bb")a $("c")`) + effects = Base.infer_effects(f) + @test Core.Compiler.is_effect_free(effects) + @test Core.Compiler.is_terminates(effects) + @test Core.Compiler.is_noub(effects) + @test !Core.Compiler.is_consistent(effects) +end +let effects = Base.infer_effects(x -> `a $x`, (Any,)) + @test !Core.Compiler.is_effect_free(effects) + @test !Core.Compiler.is_terminates(effects) + @test !Core.Compiler.is_noub(effects) + @test !Core.Compiler.is_consistent(effects) +end +# Test that Cmd accepts various AbstractStrings +@testset "AbstractStrings" begin + args = split("-l /tmp") + @assert eltype(args) != String + @test Cmd(["ls", args...]) == `ls -l /tmp` end diff --git a/test/specificity.jl b/test/specificity.jl index 9b605444bad42..816a59f63e193 100644 --- a/test/specificity.jl +++ b/test/specificity.jl @@ -1,9 +1,9 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license function args_morespecific(a, b) - sp = (ccall(:jl_type_morespecific, Cint, (Any,Any), a, b) != 0) + sp = Base.morespecific(a, b) if sp # make sure morespecific(a,b) implies !morespecific(b,a) - @test ccall(:jl_type_morespecific, Cint, (Any,Any), b, a) == 0 + @test !Base.morespecific(b, a) end return sp end diff --git a/test/stacktraces.jl b/test/stacktraces.jl index 5b17a0ced4058..69b3b71d03167 100644 --- a/test/stacktraces.jl +++ b/test/stacktraces.jl @@ -92,15 +92,9 @@ can_inline = Bool(Base.JLOptions().can_inline) for (frame, func, inlined) in zip(trace, [g,h,f], (can_inline, can_inline, false)) @test frame.func === typeof(func).name.mt.name # broken until #50082 can be addressed - if inlined - @test frame.linfo.def.module === which(func, (Any,)).module broken=true - @test frame.linfo.def === which(func, (Any,)) broken=true - @test frame.linfo.specTypes === Tuple{typeof(func), Int} broken=true - else - @test frame.linfo.def.module === which(func, (Any,)).module - @test frame.linfo.def === which(func, (Any,)) - @test frame.linfo.specTypes === Tuple{typeof(func), Int} - end + @test frame.linfo.def.module === which(func, (Any,)).module broken=inlined + @test frame.linfo.def === which(func, (Any,)) broken=inlined + @test frame.linfo.specTypes === Tuple{typeof(func), Int} broken=inlined # line @test frame.file === Symbol(@__FILE__) @test !frame.from_c @@ -166,6 +160,22 @@ end @test bt[1].line == topline+4 end +# Accidental incorrect phi block computation in interpreter +global global_false_bool = false +let bt, topline = @__LINE__ + try + let + global read_write_global_bt_test, global_false_bool + if global_false_bool + end + (read_write_global_bt_test, (read_write_global_bt_test=2;)) + end + catch + bt = stacktrace(catch_backtrace()) + end + @test bt[1].line == topline+6 +end + # issue #28990 let bt try @@ -252,3 +262,7 @@ struct F49231{a,b,c,d,e,f,g} end str = sprint(Base.show_backtrace, st, context = (:limit=>true, :stacktrace_types_limited => Ref(false), :color=>true, :displaysize=>(50,132))) @test contains(str, "[2] \e[0m\e[1m(::$F49231{Vector, Val{…}, Vector{…}, NTuple{…}, $Int, $Int, $Int})\e[22m\e[0m\e[1m(\e[22m\e[90ma\e[39m::\e[0m$Int, \e[90mb\e[39m::\e[0m$Int, \e[90mc\e[39m::\e[0m$Int\e[0m\e[1m)\e[22m\n\e[90m") end + +@testset "Base.StackTraces docstrings" begin + @test isempty(Docs.undocumented_names(StackTraces)) +end diff --git a/test/staged.jl b/test/staged.jl index 5204f5f6ca777..6bc3ead35586d 100644 --- a/test/staged.jl +++ b/test/staged.jl @@ -337,12 +337,43 @@ let world = Base.get_world_counter() match = Base._which(Tuple{typeof(sin), Int}; world) mi = Core.Compiler.specialize_method(match) lwr = Core.Compiler.retrieve_code_info(mi, world) - @test all(lin->lin.method === :sin, lwr.linetable) + nstmts = length(lwr.code) + di = Core.DebugInfo(Core.Compiler.DebugInfoStream(mi, lwr.debuginfo, nstmts), nstmts) + lwr.debuginfo = di @eval function sin_generated(a) $(Expr(:meta, :generated, Returns(lwr))) $(Expr(:meta, :generated_only)) end src = only(code_lowered(sin_generated, (Int,))) - @test all(lin->lin.method === :sin, src.linetable) + @test src.debuginfo === di @test sin_generated(42) == sin(42) end + +# Allow passing unreachable insts in generated codeinfo +let + dummy() = return + dummy_m = which(dummy, Tuple{}) + + src = Base.uncompressed_ir(dummy_m) + src.code = Any[ + # block 1 + Core.ReturnNode(nothing), + # block 2 + Core.ReturnNode(), + ] + nstmts = length(src.code) + nslots = 1 + src.ssavaluetypes = nstmts + src.debuginfo = Core.DebugInfo(:f_unreachable_generated) + src.ssaflags = fill(Int32(0), nstmts) + src.slotflags = fill(0, nslots) + src.slottypes = Any[Any] + + @eval function f_unreachable() + $(Expr(:meta, :generated, Returns(src))) + $(Expr(:meta, :generated_only)) + end + + ir, _ = Base.code_ircode(f_unreachable, ()) |> only + @test length(ir.cfg.blocks) == 1 +end diff --git a/test/strings/annotated.jl b/test/strings/annotated.jl new file mode 100644 index 0000000000000..b70a2350757a2 --- /dev/null +++ b/test/strings/annotated.jl @@ -0,0 +1,197 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +@testset "AnnotatedString" begin + str = Base.AnnotatedString("some string") + @test str == Base.AnnotatedString(str.string, Tuple{UnitRange{Int}, Pair{Symbol, Any}}[]) + @test length(str) == 11 + @test ncodeunits(str) == 11 + @test eltype(str) == Base.AnnotatedChar{eltype(str.string)} + @test first(str) == Base.AnnotatedChar(first(str.string), Pair{Symbol, Any}[]) + @test str[1:4] isa SubString{typeof(str)} + @test str[1:4] == Base.AnnotatedString("some") + @test "a" * str == Base.AnnotatedString("asome string") + @test str * "a" == Base.AnnotatedString("some stringa") + @test str * str == Base.AnnotatedString("some stringsome string") + Base.annotate!(str, 1:4, :thing => 0x01) + Base.annotate!(str, 6:11, :other => 0x02) + Base.annotate!(str, 1:11, :all => 0x03) + # :thing :other + # ┌┸─┐ ┌──┸─┐ + # "some string" + # └───┰─────┘ + # :all + @test str[3:4] == SubString(str, 3, 4) + @test Base.AnnotatedString(str[3:4]) == + Base.AnnotatedString("me", [(1:2, :thing => 0x01), (1:2, :all => 0x03)]) + @test Base.AnnotatedString(str[3:6]) == + Base.AnnotatedString("me s", [(1:2, :thing => 0x01), (1:4, :all => 0x03), (4:4, :other => 0x02)]) + @test str == Base.AnnotatedString("some string", [(1:4, :thing => 0x01), (1:11, :all => 0x03), (6:11, :other => 0x02)]) + @test str != Base.AnnotatedString("some string") + @test str != Base.AnnotatedString("some string", [(1:1, :thing => 0x01), (6:6, :other => 0x02), (11:11, :all => 0x03)]) + @test str != Base.AnnotatedString("some string", [(1:4, :thing => 0x11), (1:11, :all => 0x13), (6:11, :other => 0x12)]) + @test str != Base.AnnotatedString("some thingg", [(1:4, :thing => 0x01), (1:11, :all => 0x03), (6:11, :other => 0x02)]) + @test Base.AnnotatedString([Base.AnnotatedChar('a', [:a => 1]), Base.AnnotatedChar('b', [:b => 2])]) == + Base.AnnotatedString("ab", [(1:1, :a => 1), (2:2, :b => 2)]) + let allstrings = + ['a', Base.AnnotatedChar('a'), Base.AnnotatedChar('a', [:aaa => 0x04]), + "a string", Base.AnnotatedString("a string"), + Base.AnnotatedString("a string", [(1:2, :hmm => '%')]), + SubString(Base.AnnotatedString("a string", [(1:2, :hmm => '%')]), 1:1)] + for str1 in repeat(allstrings, 2) + for str2 in repeat(allstrings, 2) + @test String(str1 * str2) == + String(string(str1, str2)) == + String(string(str1)) * String(string(str2)) + @test Base.annotatedstring(str1 * str2) == + Base.annotatedstring(str1, str2) == + Base.annotatedstring(str1) * Base.annotatedstring(str2) + end + end + end + # @test collect(Base.eachstyle(str)) == + # [("some", [:thing => 0x01, :all => 0x03]), + # (" string", [:all => 0x03, :other => 0x02])] + @test ==(Base.annotatedstring_optimize!( + Base.AnnotatedString("abc", [(1:1, :val => 1), + (2:2, :val => 2), + (2:2, :val => 1), + (3:3, :val => 2)])), + Base.AnnotatedString("abc", [(1:2, :val => 1), + (2:3, :val => 2)])) +end + +@testset "AnnotatedChar" begin + chr = Base.AnnotatedChar('c') + @test chr == Base.AnnotatedChar(chr.char, Pair{Symbol, Any}[]) + str = Base.AnnotatedString("hmm", [(1:1, :attr => "h0h0"), + (1:2, :attr => "h0m1"), + (2:3, :attr => "m1m2")]) + @test str[1] == Base.AnnotatedChar('h', Pair{Symbol, Any}[:attr => "h0h0"]) + @test str[2] == Base.AnnotatedChar('m', Pair{Symbol, Any}[:attr => "h0m1", :attr => "m1m2"]) + @test str[3] == Base.AnnotatedChar('m', Pair{Symbol, Any}[:attr => "m1m2"]) +end + +@testset "Styling preservation" begin + str = Base.AnnotatedString("some string", [(1:4, :thing => 0x01), (1:11, :all => 0x03), (6:11, :other => 0x02)]) + @test match(r".e", str).match == str[3:4] + @test match(r"(.e)", str).captures == [str[3:4]] + let m0 = match(r"(.)e", str) + m1 = first(eachmatch(r"(.)e", str)) + for f in fieldnames(RegexMatch) + @test getfield(m0, f) == getfield(m1, f) + end + end + @test lpad(str, 12) == + Base.AnnotatedString(" some string", [(2:5, :thing => 0x01), + (2:12, :all => 0x03), + (7:12, :other => 0x02)]) + @test rpad(str, 12) == + Base.AnnotatedString("some string ", [(1:4, :thing => 0x01), + (1:11, :all => 0x03), + (6:11, :other => 0x02)]) + str1 = Base.AnnotatedString("test", [(1:4, :label => 5)]) + str2 = Base.AnnotatedString("case", [(2:3, :label => "oomph")]) + @test join([str1, str1], ' ') == + Base.AnnotatedString("test test", + [(1:4, :label => 5), + (6:9, :label => 5)]) + @test join([str1, str1], Base.AnnotatedString(" ", [(1:1, :label => 2)])) == + Base.AnnotatedString("test test", + [(1:4, :label => 5), + (5:5, :label => 2), + (6:9, :label => 5)]) + @test repeat(str1, 2) == Base.AnnotatedString("testtest", [(1:8, :label => 5)]) + @test repeat(str2, 2) == Base.AnnotatedString("casecase", [(2:3, :label => "oomph"), + (6:7, :label => "oomph")]) + @test repeat(str1[1], 3) == Base.AnnotatedString("ttt", [(1:3, :label => 5)]) + @test reverse(str1) == Base.AnnotatedString("tset", [(1:4, :label => 5)]) + @test reverse(str2) == Base.AnnotatedString("esac", [(2:3, :label => "oomph")]) +end + +@testset "Unicode" begin + for words in (["ᲃase", "cɦɒnɡeȿ", "can", "CHⱯNGE", "Сodeunıts"], + ["Сodeunıts", "ᲃase", "cɦɒnɡeȿ", "can", "CHⱯNGE"]) + ann_words = [Base.AnnotatedString(w, [(1:ncodeunits(w), :i => i)]) + for (i, w) in enumerate(words)] + ann_str = join(ann_words, '-') + for transform in (lowercase, uppercase, titlecase) + t_words = map(transform, words) + ann_t_words = [Base.AnnotatedString(w, [(1:ncodeunits(w), :i => i)]) + for (i, w) in enumerate(t_words)] + ann_t_str = join(ann_t_words, '-') + t_ann_str = transform(ann_str) + @test String(ann_t_str) == String(t_ann_str) + @test Base.annotations(ann_t_str) == Base.annotations(t_ann_str) + end + for transform in (uppercasefirst, lowercasefirst) + t_words = vcat(transform(first(words)), words[2:end]) + ann_t_words = [Base.AnnotatedString(w, [(1:ncodeunits(w), :i => i)]) + for (i, w) in enumerate(t_words)] + ann_t_str = join(ann_t_words, '-') + t_ann_str = transform(ann_str) + @test String(ann_t_str) == String(t_ann_str) + @test Base.annotations(ann_t_str) == Base.annotations(t_ann_str) + end + end +end + +@testset "AnnotatedIOBuffer" begin + aio = Base.AnnotatedIOBuffer() + # Append-only writing + @test write(aio, Base.AnnotatedString("hello", [(1:5, :tag => 1)])) == 5 + @test write(aio, ' ') == 1 + @test write(aio, Base.AnnotatedString("world", [(1:5, :tag => 2)])) == 5 + @test Base.annotations(aio) == [(1:5, :tag => 1), (7:11, :tag => 2)] + # Check `annotate!`, including region sorting + @test truncate(aio, 0).io.size == 0 + @test write(aio, "hello world") == ncodeunits("hello world") + @test Base.annotate!(aio, 7:11, :tag => 2) === aio + @test Base.annotate!(aio, 1:5, :tag => 1) === aio + @test Base.annotations(aio) == [(1:5, :tag => 1), (7:11, :tag => 2)] + # Reading + @test read(seekstart(deepcopy(aio.io)), String) == "hello world" + @test read(seekstart(deepcopy(aio)), String) == "hello world" + @test read(seek(aio, 0), Base.AnnotatedString) == Base.AnnotatedString("hello world", [(1:5, :tag => 1), (7:11, :tag => 2)]) + @test read(seek(aio, 1), Base.AnnotatedString) == Base.AnnotatedString("ello world", [(1:4, :tag => 1), (6:10, :tag => 2)]) + @test read(seek(aio, 4), Base.AnnotatedString) == Base.AnnotatedString("o world", [(1:1, :tag => 1), (3:7, :tag => 2)]) + @test read(seek(aio, 5), Base.AnnotatedString) == Base.AnnotatedString(" world", [(2:6, :tag => 2)]) + @test read(seekend(aio), Base.AnnotatedString) == Base.AnnotatedString("") + @test read(seekstart(truncate(deepcopy(aio), 5)), Base.AnnotatedString) == Base.AnnotatedString("hello", [(1:5, :tag => 1)]) + @test read(seekstart(truncate(deepcopy(aio), 6)), Base.AnnotatedString) == Base.AnnotatedString("hello ", [(1:5, :tag => 1)]) + @test read(seekstart(truncate(deepcopy(aio), 7)), Base.AnnotatedString) == Base.AnnotatedString("hello w", [(1:5, :tag => 1), (7:7, :tag => 2)]) + @test read(seek(aio, 0), Base.AnnotatedChar) == Base.AnnotatedChar('h', [:tag => 1]) + @test read(seek(aio, 5), Base.AnnotatedChar) == Base.AnnotatedChar(' ', Pair{Symbol, Any}[]) + @test read(seek(aio, 6), Base.AnnotatedChar) == Base.AnnotatedChar('w', [:tag => 2]) + # Check method compatibility with IOBuffer + @test position(aio) == 7 + @test seek(aio, 4) === aio + @test skip(aio, 2) === aio + @test Base.annotations(copy(aio)) == Base.annotations(aio) + @test take!(copy(aio).io) == take!(copy(aio.io)) + # Writing into the middle of the buffer + @test write(seek(aio, 6), "alice") == 5 # Replace 'world' with 'alice' + @test read(seekstart(aio), String) == "hello alice" + @test Base.annotations(aio) == [(1:5, :tag => 1), (7:11, :tag => 2)] # Should be unchanged + @test write(seek(aio, 0), Base.AnnotatedString("hey-o", [(1:5, :hey => 'o')])) == 5 + @test read(seekstart(aio), String) == "hey-o alice" + @test Base.annotations(aio) == [(1:5, :hey => 'o'), (7:11, :tag => 2)] # First annotation should have been entirely replaced + @test write(seek(aio, 7), Base.AnnotatedString("bbi", [(1:3, :hey => 'a')])) == 3 # a[lic => bbi]e ('alice' => 'abbie') + @test read(seekstart(aio), String) == "hey-o abbie" + @test Base.annotations(aio) == [(1:5, :hey => 'o'), (7:7, :tag => 2), (8:10, :hey => 'a'), (11:11, :tag => 2)] + @test write(seek(aio, 0), Base.AnnotatedString("ab")) == 2 # Check first annotation's region is adjusted correctly + @test read(seekstart(aio), String) == "aby-o abbie" + @test Base.annotations(aio) == [(3:5, :hey => 'o'), (7:7, :tag => 2), (8:10, :hey => 'a'), (11:11, :tag => 2)] + @test write(seek(aio, 3), Base.AnnotatedString("ss")) == 2 + @test read(seekstart(aio), String) == "abyss abbie" + @test Base.annotations(aio) == [(3:3, :hey => 'o'), (7:7, :tag => 2), (8:10, :hey => 'a'), (11:11, :tag => 2)] + # Writing one buffer to another + newaio = Base.AnnotatedIOBuffer() + @test write(newaio, seekstart(aio)) == 11 + @test read(seekstart(newaio), String) == "abyss abbie" + @test Base.annotations(newaio) == Base.annotations(aio) + @test write(seek(newaio, 5), seek(aio, 5)) == 6 + @test Base.annotations(newaio) == Base.annotations(aio) + @test write(newaio, seek(aio, 5)) == 6 + @test read(seekstart(newaio), String) == "abyss abbie abbie" + @test Base.annotations(newaio) == vcat(Base.annotations(aio), [(13:13, :tag => 2), (14:16, :hey => 'a'), (17:17, :tag => 2)]) +end diff --git a/test/strings/basic.jl b/test/strings/basic.jl index 13f2f5197187a..87d812c5bf201 100644 --- a/test/strings/basic.jl +++ b/test/strings/basic.jl @@ -203,6 +203,12 @@ end @test (@views (x[3], x[1:2], x[[1,4]])) isa Tuple{Char, SubString, String} @test (@views (x[3], x[1:2], x[[1,4]])) == ('c', "ab", "ad") end + + @testset ":noshift constructor" begin + @test SubString("", 0, 0, Val(:noshift)) == "" + @test SubString("abcd", 0, 1, Val(:noshift)) == "a" + @test SubString("abcd", 0, 4, Val(:noshift)) == "abcd" + end end @@ -1164,7 +1170,7 @@ end code_units = Base.CodeUnits("abc") @test Base.IndexStyle(Base.CodeUnits) == IndexLinear() @test Base.elsize(code_units) == sizeof(UInt8) - @test Base.unsafe_convert(Ptr{Int8}, code_units) == Base.unsafe_convert(Ptr{Int8}, code_units.s) + @test Base.unsafe_convert(Ptr{Int8}, Base.cconvert(Ptr{UInt8}, code_units)) == Base.unsafe_convert(Ptr{Int8}, Base.cconvert(Ptr{Int8}, code_units.s)) end @testset "LazyString" begin diff --git a/test/strings/io.jl b/test/strings/io.jl index aed1f800d4d49..f1fe0c24e8aea 100644 --- a/test/strings/io.jl +++ b/test/strings/io.jl @@ -156,6 +156,15 @@ @test "aaa \\g \\n" == unescape_string(str, ['g', 'n']) end @test Base.escape_raw_string(raw"\"\\\"\\-\\") == "\\\"\\\\\\\"\\\\-\\\\" + @test Base.escape_raw_string(raw"`\`\\-\\") == "\`\\\`\\\\-\\\\" + @test Base.escape_raw_string(raw"\"\\\"\\-\\", '`') == "\"\\\"\\\\-\\\\" + @test Base.escape_raw_string(raw"`\`\\-\\", '`') == "\\\`\\\\\\\`\\\\-\\\\" + @test Base.escape_raw_string(raw"some`string") == "some`string" + @test Base.escape_raw_string(raw"some\"string", '`') == "some\"string" + @test Base.escape_raw_string(raw"some`string\\") == "some`string\\\\" + @test Base.escape_raw_string(raw"some\"string\\", '`') == "some\"string\\\\" + @test Base.escape_raw_string(raw"some\"string") == "some\\\"string" + @test Base.escape_raw_string(raw"some`string", '`') == "some\\`string" end @testset "join()" begin @test join([]) == join([],",") == "" @@ -321,3 +330,12 @@ end # test empty args @test string() == "" end + +module StringsIOStringReturnTypesTestModule + struct S end + Base.joinpath(::S) = S() +end + +@testset "`string` return types" begin + @test all(T -> T <: AbstractString, Base.return_types(string)) +end diff --git a/test/subarray.jl b/test/subarray.jl index 1137c265fa126..106dc6c2afcdd 100644 --- a/test/subarray.jl +++ b/test/subarray.jl @@ -339,6 +339,7 @@ end A = copy(reshape(1:120, 3, 5, 8)) sA = view(A, 2:2, 1:5, :) @test @inferred(strides(sA)) == (1, 3, 15) + @test IndexStyle(sA) == IndexStyle(typeof(sA)) == IndexCartesian() @test parent(sA) == A @test parentindices(sA) == (2:2, 1:5, Base.Slice(1:8)) @test size(sA) == (1, 5, 8) @@ -465,6 +466,113 @@ end @test sA[[1 2 4 4; 6 1 1 4]] == [34 35 38 38; 50 34 34 38] end +@testset "fast linear indexing with AbstractUnitRange or Colon indices" begin + @testset "getindex" begin + @testset "1D" begin + for a1 in Any[1:5, [1:5;]] + b1 = @view a1[:]; # FastContiguousSubArray + c1 = @view a1[eachindex(a1)]; # FastContiguousSubArray + d1 = @view a1[begin:1:end]; # FastSubArray + + ax1 = eachindex(a1); + @test b1[ax1] == c1[ax1] == d1[ax1] == a1[ax1] + @test b1[:] == c1[:] == d1[:] == a1[:] + + # some arbitrary indices + inds1 = 2:4 + c1 = @view a1[inds1] + @test c1[axes(c1,1)] == c1[:] == a1[inds1] + + inds12 = Base.IdentityUnitRange(Base.OneTo(4)) + c1 = @view a1[inds12] + @test c1[axes(c1,1)] == c1[:] == a1[inds12] + + inds2 = 3:2:5 + d1 = @view a1[inds2] + @test d1[axes(d1,1)] == d1[:] == a1[inds2] + end + end + + @testset "2D" begin + a2_ = reshape(1:25, 5, 5) + for a2 in Any[a2_, collect(a2_)] + b2 = @view a2[:, :]; # 2D FastContiguousSubArray + b22 = @view a2[:]; # 1D FastContiguousSubArray + c2 = @view a2[eachindex(a2)]; # 1D FastContiguousSubArray + d2 = @view a2[begin:1:end]; # 1D FastSubArray + + ax2 = eachindex(a2); + @test b2[ax2] == b22[ax2] == c2[ax2] == d2[ax2] == a2[ax2] + @test b2[:] == b22[:] == c2[:] == d2[:] == a2[:] + + # some arbitrary indices + inds1 = 2:4 + c2 = @view a2[inds1] + @test c2[axes(c2,1)] == c2[:] == a2[inds1] + + inds12 = Base.IdentityUnitRange(Base.OneTo(4)) + c2 = @view a2[inds12] + @test c2[axes(c2,1)] == c2[:] == a2[inds12] + + inds2 = 2:2:4 + d2 = @view a2[inds2]; + @test d2[axes(d2,1)] == d2[:] == a2[inds2] + end + end + end + @testset "setindex!" begin + @testset "1D" begin + a1 = rand(10); + a12 = copy(a1); + b1 = @view a1[:]; # 1D FastContiguousSubArray + c1 = @view a1[eachindex(a1)]; # 1D FastContiguousSubArray + d1 = @view a1[begin:1:end]; # 1D FastSubArray + + ax1 = eachindex(a1); + @test (b1[ax1] = a12; b1) == (c1[ax1] = a12; c1) == (d1[ax1] = a12; d1) == (a1[ax1] = a12; a1) + @test (b1[:] = a12; b1) == (c1[:] = a12; c1) == (d1[:] = a12; d1) == (a1[:] = a12; a1) + + # some arbitrary indices + ind1 = 2:4 + c1 = a12[ind1] + @test (c1[axes(c1,1)] = a12[ind1]; c1) == (c1[:] = a12[ind1]; c1) == a12[ind1] + + inds1 = Base.IdentityUnitRange(Base.OneTo(4)) + c1 = @view a1[inds1] + @test (c1[eachindex(c1)] = @view(a12[inds1]); c1) == @view(a12[inds1]) + + ind2 = 2:2:8 + d1 = a12[ind2] + @test (d1[axes(d1,1)] = a12[ind2]; d1) == (d1[:] = a12[ind2]; d1) == a12[ind2] + end + + @testset "2D" begin + a2 = rand(10, 10); + a22 = copy(a2); + a2v = vec(a22); + b2 = @view a2[:, :]; # 2D FastContiguousSubArray + c2 = @view a2[eachindex(a2)]; # 1D FastContiguousSubArray + d2 = @view a2[begin:1:end]; # 1D FastSubArray + + @test (b2[eachindex(b2)] = a2v; vec(b2)) == (c2[eachindex(c2)] = a2v; c2) == a2v + @test (d2[eachindex(d2)] = a2v; d2) == a2v + + # some arbitrary indices + inds1 = 3:9 + c2 = @view a2[inds1] + @test (c2[eachindex(c2)] = @view(a22[inds1]); c2) == @view(a22[inds1]) + + inds1 = Base.IdentityUnitRange(Base.OneTo(4)) + c2 = @view a2[inds1] + @test (c2[eachindex(c2)] = @view(a22[inds1]); c2) == @view(a22[inds1]) + + inds2 = 3:3:9 + d2 = @view a2[inds2] + @test (d2[eachindex(d2)] = @view(a22[inds2]); d2) == @view(a22[inds2]) + end + end +end + @testset "issue #11871" begin a = fill(1., (2,2)) b = view(a, 1:2, 1:2) @@ -530,6 +638,44 @@ end @test foo == [X, X] end + # Test as an assignment's left hand side + let x = [1,2,3,4] + @test Meta.@lower(@view(x[1]) = 1).head == :error + @test Meta.@lower(@view(x[1]) += 1).head == :error + @test Meta.@lower(@view(x[end]) = 1).head == :error + @test Meta.@lower(@view(x[end]) += 1).head == :error + @test Meta.@lower(@view(f(x)[end]) = 1).head == :error + @test Meta.@lower(@view(f(x)[end]) += 1).head == :error + @test (@view(x[1]) .+= 1) == fill(2) + @test x == [2,2,3,4] + @test (@view(reshape(x,2,2)[1,1]) .+= 10) == fill(12) + @test x == [12,2,3,4] + @test (@view(x[end]) .+= 1) == fill(5) + @test x == [12,2,3,5] + @test (@view(reshape(x,2,2)[end]) .+= 10) == fill(15) + @test x == [12,2,3,15] + @test (@view(reshape(x,2,2)[[begin],[begin,end]])::AbstractMatrix{Int} .+= [2]) == [14 5] + @test x == [14,2,5,15] + + x = [1,2,3,4] + @test Meta.@lower(@views(x[[1]]) = 1).head == :error + @test Meta.@lower(@views(x[[1]]) += 1).head == :error + @test Meta.@lower(@views(x[[end]]) = 1).head == :error + @test Meta.@lower(@views(x[[end]]) += 1).head == :error + @test Meta.@lower(@views(f(x)[end]) = 1).head == :error + @test Meta.@lower(@views(f(x)[end]) += 1).head == :error + @test (@views(x[[1]]) .+= 1) == [2] + @test x == [2,2,3,4] + @test (@views(reshape(x,2,2)[[1],1]) .+= 10) == [12] + @test x == [12,2,3,4] + @test (@views(x[[end]]) .+= 1) == [5] + @test x == [12,2,3,5] + @test (@views(reshape(x,2,2)[[end]]) .+= 10) == [15] + @test x == [12,2,3,15] + @test (@views(reshape(x,2,2)[[begin],[begin,end]])::AbstractMatrix{Int} .+= [2]) == [14 5] + @test x == [14,2,5,15] + end + # test @views macro @views let f!(x) = x[begin:end-1] .+= x[begin+1:end].^2 x = [1,2,3,4] @@ -556,6 +702,12 @@ end @test x == [5,8,12,9] && i == [4,3] @. x[3:end] = 0 # make sure @. works with end expressions in @views @test x == [5,8,0,0] + x[begin:end] .+= 1 + @test x == [6,9,1,1] + x[[begin,2,end]] .-= [1,2,3] + @test x == [5,7,1,-2] + @. x[[begin,2,end]] .+= [1,2,3] + @test x == [6,9,1,1] end @views @test isa(X[1:3], SubArray) @test X[begin:end] == @views X[begin:end] @@ -660,8 +812,40 @@ end @testset "unaliascopy trimming; Issue #26263" begin A = rand(5,5,5,5) V = view(A, 2:5, :, 2:5, 1:2:5) - @test @inferred(Base.unaliascopy(V)) == V == A[2:5, :, 2:5, 1:2:5] - @test @inferred(sum(Base.unaliascopy(V))) ≈ sum(V) ≈ sum(A[2:5, :, 2:5, 1:2:5]) + V′ = @inferred(Base.unaliascopy(V)) + @test size(V′.parent) == size(V) + @test V′::typeof(V) == V == A[2:5, :, 2:5, 1:2:5] + @test @inferred(sum(V′)) ≈ sum(V) ≈ sum(A[2:5, :, 2:5, 1:2:5]) + V = view(A, Base.IdentityUnitRange(2:4), :, Base.StepRangeLen(1,1,3), 1:2:5) + V′ = @inferred(Base.unaliascopy(V)) + @test size(V.parent) != size(V′.parent) + @test V′ == V && V′ isa typeof(V) + i1 = collect(CartesianIndices((2:5))) + i2 = [CartesianIndex(), CartesianIndex()] + i3 = collect(CartesianIndices((2:5, 1:2:5))) + V = view(A, i1, 1:5, i2, i3) + @test @inferred(Base.unaliascopy(V))::typeof(V) == V == A[i1, 1:5, i2, i3] + V = view(A, i1, 1:5, i3, i2) + @test @inferred(Base.unaliascopy(V))::typeof(V) == V == A[i1, 1:5, i3, i2] + + @testset "custom ranges" begin + struct MyStepRange{T} <: OrdinalRange{T,T} + r::StepRange{T,T} + end + + for f in (:first, :last, :step, :length, :size) + @eval Base.$f(r::MyStepRange) = $f(r.r) + end + Base.getindex(r::MyStepRange, i::Int) = r.r[i] + + a = rand(6) + V = view(a, MyStepRange(2:2:4)) + @test @inferred(Base.unaliascopy(V))::typeof(V) == V + + # empty range + V = view(a, MyStepRange(2:2:1)) + @test @inferred(Base.unaliascopy(V))::typeof(V) == V + end end @testset "issue #27632" begin @@ -759,9 +943,9 @@ end @testset "issue #41221: view(::Vector, :, 1)" begin v = randn(3) - @test view(v,:,1) == v - @test parent(view(v,:,1)) === v - @test parent(view(v,2:3,1,1)) === v + @test @inferred(view(v,:,1)) == v + @test parent(@inferred(view(v,:,1))) === v + @test parent(@inferred(view(v,2:3,1,1))) === v @test_throws BoundsError view(v,:,2) @test_throws BoundsError view(v,:,1,2) @@ -769,3 +953,186 @@ end @test view(m, 1:2, 3, 1, 1) == m[1:2, 3] @test parent(view(m, 1:2, 3, 1, 1)) === m end + +@testset "issue #53209: avoid invalid elimination of singleton indices" begin + A = randn(4,5) + @test A[CartesianIndices(()), :, 3] == @inferred(view(A, CartesianIndices(()), :, 3)) + @test parent(@inferred(view(A, :, 3, 1, CartesianIndices(()), 1))) === A + @test_throws BoundsError view(A, :, 3, 2, CartesianIndices(()), 1) +end + +@testset "replace_in_print_matrix" begin + struct MyIdentity <: AbstractMatrix{Bool} + n :: Int + end + Base.size(M::MyIdentity) = (M.n, M.n) + function Base.getindex(M::MyIdentity, i::Int, j::Int) + checkbounds(M, i, j) + i == j + end + function Base.replace_in_print_matrix(M::MyIdentity, i::Integer, j::Integer, s::AbstractString) + i == j ? s : Base.replace_with_centered_mark(s) + end + V = view(MyIdentity(3), 1:2, 1:3) + @test sprint(show, "text/plain", V) == "$(summary(V)):\n 1 ⋅ ⋅\n ⋅ 1 ⋅" + + struct OneElVec <: AbstractVector{Bool} + n :: Int + ind :: Int + end + Base.size(M::OneElVec) = (M.n,) + function Base.getindex(M::OneElVec, i::Int) + checkbounds(M, i) + i == M.ind + end + function Base.replace_in_print_matrix(M::OneElVec, i::Integer, j::Integer, s::AbstractString) + i == M.ind ? s : Base.replace_with_centered_mark(s) + end + V = view(OneElVec(6, 2), 1:5) + @test sprint(show, "text/plain", V) == "$(summary(V)):\n ⋅\n 1\n ⋅\n ⋅\n ⋅" + + V = view(1:2, [CartesianIndex(2)]) + @test sprint(show, "text/plain", V) == "$(summary(V)):\n 2" +end + +@testset "Base.first_index for offset indices" begin + a = Vector(1:10) + b = view(a, Base.IdentityUnitRange(4:7)) + @test first(b) == a[Base.first_index(b)] +end + +@testset "StepRangeLen of CartesianIndex-es" begin + v = view(1:2, StepRangeLen(CartesianIndex(1,1), CartesianIndex(1,1), 0)) + @test isempty(v) + r = StepRangeLen(CartesianIndex(1), CartesianIndex(1), 1) + v = view(1:2, r) + @test v == view(1:2, collect(r)) +end + +# https://github.com/JuliaLang/julia/pull/53064 +# `@view(A[idx]) = xxx` should raise syntax error always +@test try + Core.eval(@__MODULE__, :(@view(A[idx]) = 2)) + false +catch err + err isa ErrorException && startswith(err.msg, "syntax:") +end +module Issue53064 +import Base: view +end +@test try + Core.eval(Issue53064, :(@view(A[idx]) = 2)) + false +catch err + err isa ErrorException && startswith(err.msg, "syntax:") +end + + +@testset "avoid allocating in reindex" begin + a = reshape(1:16, 4, 4) + inds = ([2,3], [3,4]) + av = view(a, inds...) + av2 = view(av, 1, 1) + @test parentindices(av2) === (2,3) + av2 = view(av, 2:2, 2:2) + @test parentindices(av2) === (view(inds[1], 2:2), view(inds[2], 2:2)) + + inds = (reshape([eachindex(a);], size(a)),) + av = view(a, inds...) + av2 = view(av, 1, 1) + @test parentindices(av2) === (1,) + av2 = view(av, 2:2, 2:2) + @test parentindices(av2) === (view(inds[1], 2:2, 2:2),) + + inds = (reshape([eachindex(a);], size(a)..., 1),) + av = view(a, inds...) + av2 = view(av, 1, 1, 1) + @test parentindices(av2) === (1,) + av2 = view(av, 2:2, 2:2, 1:1) + @test parentindices(av2) === (view(inds[1], 2:2, 2:2, 1:1),) +end + +@testset "isassigned" begin + a = Vector{BigFloat}(undef, 5) + a[2] = 0 + for v in (view(a, 2:3), # FastContiguousSubArray + view(a, 2:2:4), # FastSubArray + view(a, [2:2:4;]), # SlowSubArray + ) + @test !isassigned(v, 0) # out-of-bounds + @test isassigned(v, 1) # inbounds and assigned + @test !isassigned(v, 2) # inbounds but not assigned + @test !isassigned(v, 4) # out-of-bounds + end + + a = Array{BigFloat}(undef,3,3,3) + a[1,1,1] = 0 + for v in (view(a, :, 1:3, 1), # FastContiguousSubArray + view(a, 1, :, 1:2), # FastSubArray + ) + @test !isassigned(v, 0, 0) # out-of-bounds + @test isassigned(v, 1, 1) # inbounds and assigned + @test !isassigned(v, 1, 2) # inbounds but not assigned + @test !isassigned(v, 3, 3) # out-of-bounds + end + + @testset "_unsetindex!" begin + function test_unsetindex(A, B) + copyto!(A, B) + for i in eachindex(A) + @test !isassigned(A, i) + end + inds = eachindex(A) + @test_throws BoundsError Base._unsetindex!(A, last(inds) + oneunit(eltype(inds))) + end + @testset "dest IndexLinear, src IndexLinear" begin + for p in (fill(BigInt(2)), BigInt[1, 2], BigInt[1 2; 3 4]) + A = view(copy(p), ntuple(_->:, ndims(p))...) + B = view(similar(A), ntuple(_->:, ndims(p))...) + test_unsetindex(A, B) + test_unsetindex(p, B) + end + end + + @testset "dest IndexLinear, src IndexCartesian" begin + for p in (fill(BigInt(2)), BigInt[1, 2], BigInt[1 2; 3 4]) + A = view(copy(p), ntuple(_->:, ndims(p))...) + B = view(similar(A), axes(A)...) + test_unsetindex(A, B) + test_unsetindex(p, B) + end + end + + @testset "dest IndexCartesian, src IndexLinear" begin + for p in (fill(BigInt(2)), BigInt[1, 2], BigInt[1 2; 3 4]) + A = view(p, axes(p)...) + B = similar(A) + test_unsetindex(A, B) + end + end + + @testset "dest IndexCartesian, src IndexCartesian" begin + for p in (fill(BigInt(2)), BigInt[1, 2], BigInt[1 2; 3 4]) + A = view(p, axes(p)...) + B = view(similar(A), axes(A)...) + test_unsetindex(A, B) + end + end + end +end + +@testset "aliasing check with reshaped subarrays" begin + C = rand(2,1) + V1 = @view C[1, :] + V2 = @view C[2, :] + + @test !Base.mightalias(V1, V2) + @test !Base.mightalias(V1, permutedims(V2)) + @test !Base.mightalias(permutedims(V1), V2) + @test !Base.mightalias(permutedims(V1), permutedims(V2)) + + @test Base.mightalias(V1, V1) + @test Base.mightalias(V1, permutedims(V1)) + @test Base.mightalias(permutedims(V1), V1) + @test Base.mightalias(permutedims(V1), permutedims(V1)) +end diff --git a/test/subtype.jl b/test/subtype.jl index 289c31c475792..7805c232bd050 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -146,6 +146,14 @@ function test_diagonal() @test isequal_type(Ref{Tuple{T, T} where Int<:T<:Int}, Ref{Tuple{S, S}} where Int<:S<:Int) + # issue #53021 + @test Tuple{X, X} where {X<:Union{}} <: Tuple{X, X, Vararg{Any}} where {Int<:X<:Int} + @test Tuple{Integer, X, Vararg{X}} where {X<:Int} <: Tuple{Any, Vararg{X}} where {X>:Int} + @test Tuple{Any, X, Vararg{X}} where {X<:Int} <: Tuple{Vararg{X}} where X>:Integer + @test Tuple{Integer, Integer, Any, Vararg{Any}} <: Tuple{Vararg{X}} where X>:Integer + # issue #53019 + @test Tuple{T,T} where {T<:Int} <: Tuple{T,T} where {T>:Int} + let A = Tuple{Int,Int8,Vector{Integer}}, B = Tuple{T,T,Vector{T}} where T>:Integer, C = Tuple{T,T,Vector{Union{Integer,T}}} where T @@ -1260,14 +1268,7 @@ let a = Tuple{Tuple{T2,4},T6} where T2 where T6, end let a = Tuple{T3,Int64,Tuple{T3}} where T3, b = Tuple{S3,S3,S4} where S4 where S3 - I1 = typeintersect(a, b) - I2 = typeintersect(b, a) - @test I1 <: I2 - @test I2 <: I1 - @test_broken I1 <: a - @test I2 <: a - @test I1 <: b - @test I2 <: b + @testintersect(a, b, Tuple{Int64, Int64, Tuple{Int64}}) end let a = Tuple{T1,Val{T2},T2} where T2 where T1, b = Tuple{Float64,S1,S2} where S2 where S1 @@ -1520,7 +1521,7 @@ f26453(x::T,y::T) where {S,T>:S} = 0 @test f26453(1,2) == 0 @test f26453(1,"") == 0 g26453(x::T,y::T) where {S,T>:S} = T -@test_throws UndefVarError(:T) g26453(1,1) +@test_throws UndefVarError(:T, :static_parameter) g26453(1,1) @test issub_strict((Tuple{T,T} where T), (Tuple{T,T} where {S,T>:S})) # issue #27632 @@ -2210,13 +2211,19 @@ let A = Tuple{NTuple{N, Int}, NTuple{N, Int}} where N, Bs = (Tuple{Tuple{Int, Vararg{Any}}, Tuple{Int, Int, Vararg{Any}}}, Tuple{Tuple{Int, Vararg{Any,N1}}, Tuple{Int, Int, Vararg{Any,N2}}} where {N1,N2}, Tuple{Tuple{Int, Vararg{Any,N}} where {N}, Tuple{Int, Int, Vararg{Any,N}} where {N}}) - Cerr = Tuple{Tuple{Int, Vararg{Int, N}}, Tuple{Int, Int, Vararg{Int, N}}} where {N} + C = Tuple{Tuple{Int, Int, Vararg{Int, N}}, Tuple{Int, Int, Vararg{Int, N}}} where {N} + for B in Bs + @testintersect(A, B, C) + end + A = Tuple{NTuple{N, Int}, Tuple{Int, Vararg{Int, N}}} where N + C = Tuple{Tuple{Int, Vararg{Int, N}}, Tuple{Int, Int, Vararg{Int, N}}} where {N} for B in Bs - C = typeintersect(A, B) - @test C == typeintersect(B, A) != Union{} - @test C != Cerr - # TODO: The ideal result is Tuple{Tuple{Int, Int, Vararg{Int, N}}, Tuple{Int, Int, Vararg{Int, N}}} where {N} - @test_broken C != Tuple{Tuple{Int, Vararg{Int}}, Tuple{Int, Int, Vararg{Int}}} + @testintersect(A, B, C) + end + A = Tuple{Tuple{Int, Vararg{Int, N}}, NTuple{N, Int}} where N + C = Tuple{Tuple{Int, Int, Int, Vararg{Int, N}}, Tuple{Int, Int, Vararg{Int, N}}} where {N} + for B in Bs + @testintersect(A, B, C) end end @@ -2229,9 +2236,8 @@ let A = Pair{NTuple{N, Int}, NTuple{N, Int}} where N, Bs = (Pair{<:Tuple{Int, Vararg{Int}}, <:Tuple{Int, Int, Vararg{Int}}}, Pair{Tuple{Int, Vararg{Int,N1}}, Tuple{Int, Int, Vararg{Int,N2}}} where {N1,N2}, Pair{<:Tuple{Int, Vararg{Int,N}} where {N}, <:Tuple{Int, Int, Vararg{Int,N}} where {N}}) - Cs = (Bs[2], Bs[2], Bs[3]) - for (B, C) in zip(Bs, Cs) - # TODO: The ideal result is Pair{Tuple{Int, Int, Vararg{Int, N}}, Tuple{Int, Int, Vararg{Int, N}}} where {N} + C = Pair{Tuple{Int, Int, Vararg{Int, N}}, Tuple{Int, Int, Vararg{Int, N}}} where {N} + for B in Bs @testintersect(A, B, C) end end @@ -2440,7 +2446,7 @@ abstract type P47654{A} end @test_broken typeintersect(Type{Tuple{Array{T,1} where T}}, UnionAll) != Union{} #issue 33137 - @test_broken (Tuple{Q,Int} where Q<:Int) <: Tuple{T,T} where T + @test (Tuple{Q,Int} where Q<:Int) <: Tuple{T,T} where T # issue 24333 @test (Type{Union{Ref,Cvoid}} <: Type{Union{T,Cvoid}} where T) @@ -2565,3 +2571,38 @@ let a = Tuple{Union{Nothing, Type{Pair{T1}} where T1}} b = Tuple{Type{X2} where X2<:(Pair{T2, Y2} where {Src, Z2<:Src, Y2<:Union{Val{Z2}, Z2}})} where T2 @test !Base.has_free_typevars(typeintersect(a, b)) end + +#issue 53366 +let Y = Tuple{Val{T}, Val{Val{T}}} where T + A = Val{Val{T}} where T + T = TypeVar(:T, UnionAll(A.var, Val{A.var})) + B = UnionAll(T, Val{T}) + X = Tuple{A, B} + @testintersect(X, Y, !Union{}) +end + +#issue 53621 (requires assertions enabled) +abstract type A53621{T, R, C, U} <: AbstractSet{Union{C, U}} end +struct T53621{T, R<:Real, C, U} <: A53621{T, R, C, U} end +let + U = TypeVar(:U) + C = TypeVar(:C) + T = TypeVar(:T) + R = TypeVar(:R) + CC = TypeVar(:CC, Union{C, U}) + UU = TypeVar(:UU, Union{C, U}) + S1 = UnionAll(T, UnionAll(R, Type{UnionAll(C, UnionAll(U, T53621{T, R, C, U}))})) + S2 = UnionAll(C, UnionAll(U, UnionAll(CC, UnionAll(UU, UnionAll(T, UnionAll(R, T53621{T, R, CC, UU})))))) + S = Tuple{S1, S2} + T = Tuple{Type{T53621{T, R}}, AbstractSet{T}} where {T, R} + @testintersect(S, T, !Union{}) +end + +#issue 53371 +struct T53371{A,B,C,D,E} end +S53371{A} = Union{Int, <:A} +R53371{A} = Val{V} where V<:(T53371{B,C,D,E,F} where {B<:Val{A}, C<:S53371{B}, D<:S53371{B}, E<:S53371{B}, F<:S53371{B}}) +let S = Type{T53371{A, B, C, D, E}} where {A, B<:R53371{A}, C<:R53371{A}, D<:R53371{A}, E<:R53371{A}}, + T = Type{T53371{A, B, C, D, E} where {A, B<:R53371{A}, C<:R53371{A}, D<:R53371{A}, E<:R53371{A}}} + @test !(S <: T) +end diff --git a/test/syntax.jl b/test/syntax.jl index 17ade72112ec0..985f5c9000fbe 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -553,7 +553,7 @@ for (str, tag) in Dict("" => :none, "\"" => :string, "#=" => :comment, "'" => :c end # meta nodes for optional positional arguments -let src = Meta.lower(Main, :(@inline f(p::Int=2) = 3)).args[1].code[end-1].args[3] +let src = Meta.lower(Main, :(@inline f(p::Int=2) = 3)).args[1].code[end-2].args[3] @test Core.Compiler.is_declared_inline(src) end @@ -713,36 +713,10 @@ m1_exprs = get_expr_list(Meta.lower(@__MODULE__, quote @m1 end)) let low3 = Meta.lower(@__MODULE__, quote @m3 end) m3_exprs = get_expr_list(low3) ci = low3.args[1]::Core.CodeInfo - @test ci.codelocs == [4, 2] + #@test ci.codelocs in ([4, 4, 0], [4, 0]) @test is_return_ssavalue(m3_exprs[end]) end -function f1(a) - b = a + 100 - b -end - -@generated function f2(a) - quote - b = a + 100 - b - end -end - -f1_ci = code_typed(f1, (Int,), debuginfo=:source)[1][1] -f2_ci = code_typed(f2, (Int,), debuginfo=:source)[1][1] - -f1_exprs = get_expr_list(f1_ci) -f2_exprs = get_expr_list(f2_ci) - -if Base.JLOptions().can_inline != 0 - @test length(f1_ci.linetable) == 3 - @test length(f2_ci.linetable) >= 3 -else - @test length(f1_ci.linetable) == 2 - @test length(f2_ci.linetable) >= 3 -end - # Check that string and command literals are parsed to the appropriate macros @test :(x"s") == :(@x_str "s") @test :(x"s"flag) == :(@x_str "s" "flag") @@ -1706,7 +1680,7 @@ end @test Meta.parse("(a...)") == Expr(Symbol("..."), :a) # #19324 -@test_throws UndefVarError(:x) eval(:(module M19324 +@test_throws UndefVarError(:x, :local) eval(:(module M19324 x=1 for i=1:10 x += i @@ -1783,6 +1757,43 @@ end @test B28593.var.name === :S @test C28593.var.name === :S +# issue #51899 +macro struct_macro_51899() + quote + mutable struct Struct51899 + const const_field + const const_field_with_type::Int + $(esc(Expr(:const, :(escaped_const_field::MyType)))) + @atomic atomic_field + @atomic atomic_field_with_type::Int + end + end +end + +let ex = @macroexpand @struct_macro_51899() + const_field, const_field_with_type, escaped_const_field, + atomic_field, atomic_field_with_type = filter(x -> isa(x, Expr), ex.args[end].args[end].args) + @test Meta.isexpr(const_field, :const) + @test const_field.args[1] === :const_field + + @test Meta.isexpr(const_field_with_type, :const) + @test Meta.isexpr(const_field_with_type.args[1], :(::)) + @test const_field_with_type.args[1].args[1] === :const_field_with_type + @test const_field_with_type.args[1].args[2] == GlobalRef(@__MODULE__, :Int) + + @test Meta.isexpr(escaped_const_field, :const) + @test Meta.isexpr(const_field_with_type.args[1], :(::)) + @test escaped_const_field.args[1].args[1] === :escaped_const_field + @test escaped_const_field.args[1].args[2] === :MyType + + @test Meta.isexpr(atomic_field, :atomic) + @test atomic_field.args[1] === :atomic_field + + @test Meta.isexpr(atomic_field_with_type, :atomic) + @test atomic_field_with_type.args[1].args[1] === :atomic_field_with_type + @test atomic_field_with_type.args[1].args[2] == GlobalRef(@__MODULE__, :Int) +end + # issue #25955 macro noeffect25955(e) return e @@ -1886,7 +1897,7 @@ function capture_with_conditional_label() return y->x end let f = capture_with_conditional_label() # should not throw - @test_throws UndefVarError(:x) f(0) + @test_throws UndefVarError(:x, :local) f(0) end # `_` should not create a global (or local) @@ -2178,6 +2189,16 @@ end end @test z28789 == 42 +const warn28789 = "Assignment to `s28789` in soft scope is ambiguous because a global variable by the same name exists: "* + "`s28789` will be treated as a new local. Disambiguate by using `local s28789` to suppress this warning or "* + "`global s28789` to assign to the existing global variable." +@test_logs (:warn, warn28789) @test_throws UndefVarError @eval begin + s28789 = 0 + for i = 1:10 + s28789 += i + end +end + # issue #38650, `struct` should always be a hard scope f38650() = 0 @eval begin @@ -2474,7 +2495,14 @@ end function ncalls_in_lowered(ex, fname) lowered_exprs = Meta.lower(Main, ex).args[1].code return count(lowered_exprs) do ex - Meta.isexpr(ex, :call) && ex.args[1] == fname + if Meta.isexpr(ex, :call) + arg = ex.args[1] + if isa(arg, Core.SSAValue) + arg = lowered_exprs[arg.id] + end + return arg == fname + end + return false end end @@ -3187,6 +3215,22 @@ end end @test err == 5 + 6 @test x == 1 + + x = 0 + try + catch + else + x = 1 + end + @test x == 1 + + try + catch + else + tryelse_in_local_scope = true + end + + @test !@isdefined(tryelse_in_local_scope) end @test_parseerror """ @@ -3536,3 +3580,106 @@ end @test_throws ErrorException("syntax: Attempted to use slot marked unused") @eval function funused50518(::Float64) $(Symbol("#unused#")) end + +@testset "public keyword" begin + p(str) = Base.remove_linenums!(Meta.parse(str)) + # tests ported from JuliaSyntax.jl + @test p("function f(public)\n public + 3\nend") == Expr(:function, Expr(:call, :f, :public), Expr(:block, Expr(:call, :+, :public, 3))) + @test p("public A, B") == Expr(:public, :A, :B) + @test p("if true \n public *= 4 \n end") == Expr(:if, true, Expr(:block, Expr(:*=, :public, 4))) + @test p("module Mod\n public A, B \n end") == Expr(:module, true, :Mod, Expr(:block, Expr(:public, :A, :B))) + @test p("module Mod2\n a = 3; b = 6; public a, b\n end") == Expr(:module, true, :Mod2, Expr(:block, Expr(:(=), :a, 3), Expr(:(=), :b, 6), Expr(:public, :a, :b))) + @test p("a = 3; b = 6; public a, b") == Expr(:toplevel, Expr(:(=), :a, 3), Expr(:(=), :b, 6), Expr(:public, :a, :b)) + @test_throws Meta.ParseError p("begin \n public A, B \n end") + @test_throws Meta.ParseError p("if true \n public A, B \n end") + @test_throws Meta.ParseError p("public export=true foo, bar") + @test_throws Meta.ParseError p("public experimental=true foo, bar") + @test p("public(x::String) = false") == Expr(:(=), Expr(:call, :public, Expr(:(::), :x, :String)), Expr(:block, false)) + @test p("module M; export @a; end") == Expr(:module, true, :M, Expr(:block, Expr(:export, :var"@a"))) + @test p("module M; public @a; end") == Expr(:module, true, :M, Expr(:block, Expr(:public, :var"@a"))) + @test p("module M; export ⤈; end") == Expr(:module, true, :M, Expr(:block, Expr(:export, :⤈))) + @test p("module M; public ⤈; end") == Expr(:module, true, :M, Expr(:block, Expr(:public, :⤈))) + @test p("public = 4") == Expr(:(=), :public, 4) + @test p("public[7] = 5") == Expr(:(=), Expr(:ref, :public, 7), 5) + @test p("public() = 6") == Expr(:(=), Expr(:call, :public), Expr(:block, 6)) +end + +@testset "removing argument sideeffects" begin + # Allow let blocks in broadcasted LHSes, but only evaluate them once: + execs = 0 + array = [1] + let x = array; execs += 1; x; end .+= 2 + @test array == [3] + @test execs == 1 + let; execs += 1; array; end .= 4 + @test array == [4] + @test execs == 2 + let x = array; execs += 1; x; end::Vector{Int} .+= 2 + @test array == [6] + @test execs == 3 + let; execs += 1; array; end::Vector{Int} .= 7 + @test array == [7] + @test execs == 4 +end + +# Allow GlobalRefs in macro definition +module MyMacroModule + macro mymacro end +end +macro MyMacroModule.mymacro() + 1 +end +@eval macro $(GlobalRef(MyMacroModule, :mymacro))(x) + 2 +end +@test (@MyMacroModule.mymacro) == 1 +@test (@MyMacroModule.mymacro(a)) == 2 + +# Issue #53673 - missing macro hygiene for for/generator +baremodule MacroHygieneFor + import ..Base + using Base: esc, Expr, + + macro for1() + :(let a=(for i=10; end; 1); a; end) + end + macro for2() + :(let b=(for j=11, k=12; end; 2); b; end) + end + macro for3() + :(let c=($(Expr(:for, esc(Expr(:block, :(j=11), :(k=12))), :())); 3); c; end) + end + macro for4() + :(begin; local j; let a=(for outer j=10; end; 4); j+a; end; end) + end +end +let nnames = length(names(MacroHygieneFor; all=true)) + @test (@MacroHygieneFor.for1) == 1 + @test (@MacroHygieneFor.for2) == 2 + @test (@MacroHygieneFor.for3) == 3 + @test (@MacroHygieneFor.for4) == 14 + @test length(names(MacroHygieneFor; all=true)) == nnames +end + +baremodule MacroHygieneGenerator + using ..Base: Any, ! + my!(x) = !x + macro gen1() + :(let a=Any[x for x in 1]; a; end) + end + macro gen2() + :(let a=Bool[x for x in (true, false) if my!(x)]; a; end) + end + macro gen3() + :(let a=Bool[x for x in (true, false), y in (true, false) if my!(x) && my!(y)]; a; end) + end +end +let nnames = length(names(MacroHygieneGenerator; all=true)) + @test (MacroHygieneGenerator.@gen1) == Any[x for x in 1] + @test (MacroHygieneGenerator.@gen2) == Bool[false] + @test (MacroHygieneGenerator.@gen3) == Bool[false] + @test length(names(MacroHygieneGenerator; all=true)) == nnames +end + +# Issue #53729 - Lowering recursion into Expr(:toplevel) +@test eval(Expr(:let, Expr(:block), Expr(:block, Expr(:toplevel, :(f53729(x) = x)), :(x=1)))) == 1 +@test f53729(2) == 2 diff --git a/test/sysinfo.jl b/test/sysinfo.jl index cb943cfd38843..f02d8ffe36091 100644 --- a/test/sysinfo.jl +++ b/test/sysinfo.jl @@ -12,6 +12,8 @@ Base.Sys.loadavg() @test length(ccall(:jl_get_cpu_name, String, ())) != 0 @test length(ccall(:jl_get_cpu_features, String, ())) >= 0 +foo_fma() = Core.Intrinsics.have_fma(Int64) +@test ccall(:jl_cpu_has_fma, Bool, (Cint,), 64) == foo_fma() if Sys.isunix() mktempdir() do tempdir @@ -41,3 +43,31 @@ if Sys.isunix() end end end + +@testset "username()" begin + if Sys.isunix() + passwd = Libc.getpwuid(Libc.getuid()) + @test Sys.username() == passwd.username + elseif Sys.iswindows() + @test Sys.username() == ENV["USERNAME"] + else + @test !isempty(Sys.username()) + end +end + +@testset "Base.Sys docstrings" begin + @test isempty(Docs.undocumented_names(Sys)) +end + +@testset "show" begin + example_cpus = [Base.Sys.CPUinfo("Apple M1 Pro", 2400, 0x000000000d913b08, 0x0000000000000000, 0x0000000005f4243c, 0x00000000352a550a, 0x0000000000000000) + Base.Sys.CPUinfo("Apple M1 Pro", 2400, 0x000000000d9040c2, 0x0000000000000000, 0x0000000005d4768c, 0x00000000356b3d22, 0x0000000000000000) + Base.Sys.CPUinfo("Apple M1 Pro", 2400, 0x00000000026784da, 0x0000000000000000, 0x0000000000fda30e, 0x0000000046a731ea, 0x0000000000000000) + Base.Sys.CPUinfo("Apple M1 Pro", 2400, 0x00000000017726c0, 0x0000000000000000, 0x00000000009491de, 0x0000000048134f1e, 0x0000000000000000)] + + Sys.SC_CLK_TCK, save_SC_CLK_TCK = 100, Sys.SC_CLK_TCK # use platform-independent tick units + @test repr(example_cpus[1]) == "Base.Sys.CPUinfo(\"Apple M1 Pro\", 2400, 0x000000000d913b08, 0x0000000000000000, 0x0000000005f4243c, 0x00000000352a550a, 0x0000000000000000)" + @test repr("text/plain", example_cpus[1]) == "Apple M1 Pro: \n speed user nice sys idle irq\n 2400 MHz 2276216 s 0 s 998861 s 8919667 s 0 s" + @test sprint(Sys.cpu_summary, example_cpus) == "Apple M1 Pro: \n speed user nice sys idle irq\n#1 2400 MHz 2276216 s 0 s 998861 s 8919667 s 0 s\n#2 2400 MHz 2275576 s 0 s 978101 s 8962204 s 0 s\n#3 2400 MHz 403386 s 0 s 166224 s 11853624 s 0 s\n#4 2400 MHz 245859 s 0 s 97367 s 12092250 s 0 s\n" + Sys.SC_CLK_TCK = save_SC_CLK_TCK +end diff --git a/test/terminfo.jl b/test/terminfo.jl index cbaab346a617b..07aa21704fef5 100644 --- a/test/terminfo.jl +++ b/test/terminfo.jl @@ -356,14 +356,34 @@ let 0x73, 0x6d, 0x78, 0x78, 0x00, 0x78, 0x6d, 0x00] xterm_extensions = - [:kEND5, :Cs, :kDN5, :Cr, :kDC6, :kPRV6, :kDN7, :kb1, :kpZRO, :kNXT6, - :kLFT5, :kPRV3, :kRIT4, :kDC4, :kc2, :kp5, :kLFT6, :kIC6, :kEND6, :kIC4, - :kRIT7, :rmxx, :kpADD, :xm, :kNXT3, :XT, :kIC7, :kHOM4, :kDC7, :kPRV7, - :ka2, :kUP7, :kDN6, :kIC5, :kNXT4, :kUP5, :AX, :kpSUB, :kb3, :kDN4, - :kHOM5, :kHOM6, :kDN3, :kLFT4, :kRIT5, :kIC3, :kPRV4, :kUP, :kRIT6, :E3, - :kEND3, :kHOM7, :kDC3, :kLFT7, :kNXT5, :Se, :Ss, :kHOM3, :kRIT3, :kNXT7, - :smxx, :kEND4, :kDN, :kUP6, :XM, :kPRV5, :kUP4, :kpDOT, :kpMUL, :kEND7, - :Ms, :kpCMA, :kDC5, :kLFT3, :kpDIV, :kUP3] + [:AX, :E3, :XM, :XT, :enter_strikeout_mode, :exit_strikeout_mode, :ka2, + :kb1, :kb3, :kc2, :key_alt_control_delete_character, + :key_alt_control_down_cursor, :key_alt_control_end, + :key_alt_control_home, :key_alt_control_insert_character, + :key_alt_control_left_cursor, :key_alt_control_next, + :key_alt_control_previous, :key_alt_control_right_cursor, + :key_alt_control_up_cursor, :key_alt_delete_character, + :key_alt_down_cursor, :key_alt_end, :key_alt_home, + :key_alt_insert_character, :key_alt_left_cursor, :key_alt_next, + :key_alt_previous, :key_alt_right_cursor, :key_alt_up_cursor, + :key_control_delete_character, :key_control_down_cursor, + :key_control_end, :key_control_home, :key_control_insert_character, + :key_control_left_cursor, :key_control_next, :key_control_previous, + :key_control_right_cursor, :key_control_up_cursor, + :key_shift_alt_delete_character, :key_shift_alt_down_cursor, + :key_shift_alt_end, :key_shift_alt_home, + :key_shift_alt_insert_character, :key_shift_alt_left_cursor, + :key_shift_alt_next, :key_shift_alt_previous, + :key_shift_alt_right_cursor, :key_shift_alt_up_cursor, + :key_shift_control_delete_character, :key_shift_control_down_cursor, + :key_shift_control_end, :key_shift_control_home, + :key_shift_control_insert_character, :key_shift_control_left_cursor, + :key_shift_control_next, :key_shift_control_previous, + :key_shift_control_right_cursor, :key_shift_control_up_cursor, + :key_shift_down_cursor, :key_shift_up_cursor, :kp5, :kpADD, :kpCMA, + :kpDIV, :kpDOT, :kpMUL, :kpSUB, :kpZRO, :reset_cursor_color, + :reset_cursor_style, :set_cursor_color, :set_cursor_style, + :set_host_clipboard, :xm] xterm_capabilities = Dict{Symbol, Union{Bool, Int, String}}( :AX => true, @@ -889,20 +909,20 @@ let @testset "terminfo" begin dumb = Base.TermInfo(read(IOBuffer(dumb_terminfo), Base.TermInfoRaw)) @test dumb.names == ["dumb", "80-column dumb tty"] - @test dumb.flags == 2 - @test dumb.numbers == [true] - @test dumb.extensions == Symbol[] - @test length(dumb.capabilities) == 14 + @test length(dumb.flags) == 2 + @test length(dumb.numbers) == 1 + @test length(dumb.strings) == 4 + @test isnothing(dumb.extensions) for (key, value) in dumb_capabilities @test dumb[key] == value end xterm = Base.TermInfo(read(IOBuffer(xterm_terminfo), Base.TermInfoRaw)) @test xterm.names == ["xterm", "xterm terminal emulator (X Window System)"] - @test xterm.flags == 38 - @test xterm.numbers == Bool[1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1] - @test sort(xterm.extensions) == sort(xterm_extensions) - @test length(xterm.capabilities) == 519 + @test length(xterm.flags) == 40 + @test length(xterm.numbers) == 15 + @test length(xterm.strings) == 253 + @test sort(xterm.extensions |> collect) == sort(xterm_extensions) for (key, value) in xterm_capabilities @test xterm[key] == value end diff --git a/test/testdefs.jl b/test/testdefs.jl index a1d4107a502bd..b96c95045f2bd 100644 --- a/test/testdefs.jl +++ b/test/testdefs.jl @@ -5,6 +5,9 @@ using Test, Random function runtests(name, path, isolate=true; seed=nothing) old_print_setting = Test.TESTSET_PRINT_ENABLE[] Test.TESTSET_PRINT_ENABLE[] = false + # remove all hint_handlers, so that errorshow tests are not changed by which packages have been loaded on this worker already + # packages that call register_error_hint should also call this again, and then re-add any hooks they want to test + empty!(Base.Experimental._hint_handlers) try if isolate # Simple enough to type and random enough so that no one will hard @@ -79,9 +82,11 @@ function runtests(name, path, isolate=true; seed=nothing) rss = Sys.maxrss() #res_and_time_data[1] is the testset ts = res_and_time_data[1] - passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken = Test.get_test_counts(ts) + tc = Test.get_test_counts(ts) # simplify our stored data to just contain the counts - res_and_time_data = (TestSetException(passes+c_passes, fails+c_fails, errors+c_errors, broken+c_broken, Test.filter_errors(ts)), + res_and_time_data = (TestSetException(tc.passes+tc.cumulative_passes, tc.fails+tc.cumulative_fails, + tc.errors+tc.cumulative_errors, tc.broken+tc.cumulative_broken, + Test.filter_errors(ts)), res_and_time_data[2], res_and_time_data[3], res_and_time_data[4], diff --git a/test/testenv.jl b/test/testenv.jl index 6f99291e01138..3ef1126e0e927 100644 --- a/test/testenv.jl +++ b/test/testenv.jl @@ -35,6 +35,8 @@ if !@isdefined(testenv_defined) const rr_exename = `` end + const test_relocated_depot = haskey(ENV, "RELOCATEDEPOT") + function addprocs_with_testenv(X; rr_allowed=true, kwargs...) exename = rr_allowed ? `$rr_exename $test_exename` : test_exename if X isa Integer diff --git a/test/testhelpers/DualNumbers.jl b/test/testhelpers/DualNumbers.jl index 9f62e3bf0d429..5c481aef47f76 100644 --- a/test/testhelpers/DualNumbers.jl +++ b/test/testhelpers/DualNumbers.jl @@ -41,6 +41,6 @@ Base.sqrt(x::Dual) = Dual(sqrt(x.val), x.eps/(2sqrt(x.val))) Base.isless(x::Dual, y::Dual) = x.val < y.val Base.isless(x::Real, y::Dual) = x < y.val Base.isinf(x::Dual) = isinf(x.val) & isfinite(x.eps) -Base.real(x::Dual) = x # since we curently only consider Dual{<:Real} +Base.real(x::Dual) = x # since we currently only consider Dual{<:Real} end # module diff --git a/test/testhelpers/FillArrays.jl b/test/testhelpers/FillArrays.jl index 1f36a77bf8c12..ee988e0f0aa9c 100644 --- a/test/testhelpers/FillArrays.jl +++ b/test/testhelpers/FillArrays.jl @@ -9,6 +9,8 @@ Fill(v, size::Vararg{Integer}) = Fill(v, size) Base.size(F::Fill) = F.size +Base.copy(F::Fill) = F + @inline getindex_value(F::Fill) = F.value @inline function Base.getindex(F::Fill{<:Any,N}, i::Vararg{Int,N}) where {N} @@ -30,4 +32,31 @@ end Base.show(io::IO, F::Fill) = print(io, "Fill($(F.value), $(F.size))") Base.show(io::IO, ::MIME"text/plain", F::Fill) = show(io, F) +_first_or_one(t::Tuple) = t[1] +_first_or_one(t::Tuple{}) = 1 + +_match_size(sz::Tuple{}, inner::Tuple{}, outer::Tuple{}) = () +function _match_size(sz::Tuple, inner::Tuple, outer::Tuple) + t1 = (_first_or_one(sz), _first_or_one(inner), _first_or_one(outer)) + t2 = _match_size(sz[2:end], inner[2:end], outer[2:end]) + (t1, t2...) +end + +function _repeat_size(sz::Tuple, inner::Tuple, outer::Tuple) + t = _match_size(sz, inner, outer) + map(*, getindex.(t, 1), getindex.(t, 2), getindex.(t, 3)) +end + +function Base.repeat(A::Fill; inner=ntuple(x->1, ndims(A)), outer=ntuple(x->1, ndims(A))) + Base.require_one_based_indexing(A) + length(inner) >= ndims(A) || + throw(ArgumentError("number of inner repetitions $(length(inner)) cannot be "* + "less than number of dimensions of input array $(ndims(A))")) + length(outer) >= ndims(A) || + throw(ArgumentError("number of outer repetitions $(length(outer)) cannot be "* + "less than number of dimensions of input array $(ndims(A))")) + sz = _repeat_size(size(A), Tuple(inner), Tuple(outer)) + Fill(getindex_value(A), sz) +end + end diff --git a/test/testhelpers/Furlongs.jl b/test/testhelpers/Furlongs.jl index f63b5460c7c16..6d52260bb20fd 100644 --- a/test/testhelpers/Furlongs.jl +++ b/test/testhelpers/Furlongs.jl @@ -99,5 +99,6 @@ for op in (:rem, :mod) end end Base.sqrt(x::Furlong) = _div(sqrt(x.val), x, Val(2)) +Base.muladd(x::Furlong, y::Furlong, z::Furlong) = x*y + z end diff --git a/test/testhelpers/ImmutableArrays.jl b/test/testhelpers/ImmutableArrays.jl index df2a78387e07b..8f2d23be3a7a7 100644 --- a/test/testhelpers/ImmutableArrays.jl +++ b/test/testhelpers/ImmutableArrays.jl @@ -25,4 +25,7 @@ Base.getindex(A::ImmutableArray, i...) = getindex(A.data, i...) AbstractArray{T}(A::ImmutableArray) where {T} = ImmutableArray(AbstractArray{T}(A.data)) AbstractArray{T,N}(A::ImmutableArray{S,N}) where {S,T,N} = ImmutableArray(AbstractArray{T,N}(A.data)) +Base.copy(A::ImmutableArray) = ImmutableArray(copy(A.data)) +Base.zero(A::ImmutableArray) = ImmutableArray(zero(A.data)) + end diff --git a/test/testhelpers/OffsetArrays.jl b/test/testhelpers/OffsetArrays.jl index 2402a46c3494e..f8da243da6b63 100644 --- a/test/testhelpers/OffsetArrays.jl +++ b/test/testhelpers/OffsetArrays.jl @@ -642,7 +642,7 @@ Base.copy(A::OffsetArray) = parent_call(copy, A) Base.strides(A::OffsetArray) = strides(parent(A)) Base.elsize(::Type{OffsetArray{T,N,A}}) where {T,N,A} = Base.elsize(A) -@inline Base.unsafe_convert(::Type{Ptr{T}}, A::OffsetArray{T}) where {T} = Base.unsafe_convert(Ptr{T}, parent(A)) +Base.cconvert(::Type{Ptr{T}}, A::OffsetArray{T}) where {T} = Base.cconvert(Ptr{T}, parent(A)) # For fast broadcasting: ref https://discourse.julialang.org/t/why-is-there-a-performance-hit-on-broadcasting-with-offsetarrays/32194 Base.dataids(A::OffsetArray) = Base.dataids(parent(A)) diff --git a/test/testhelpers/Quaternions.jl b/test/testhelpers/Quaternions.jl index 1eddad322ec40..81b7a0c2d0121 100644 --- a/test/testhelpers/Quaternions.jl +++ b/test/testhelpers/Quaternions.jl @@ -20,6 +20,7 @@ Base.abs2(q::Quaternion) = q.s*q.s + q.v1*q.v1 + q.v2*q.v2 + q.v3*q.v3 Base.float(z::Quaternion{T}) where T = Quaternion(float(z.s), float(z.v1), float(z.v2), float(z.v3)) Base.abs(q::Quaternion) = sqrt(abs2(q)) Base.real(::Type{Quaternion{T}}) where {T} = T +Base.real(q::Quaternion) = q.s Base.conj(q::Quaternion) = Quaternion(q.s, -q.v1, -q.v2, -q.v3) Base.isfinite(q::Quaternion) = isfinite(q.s) & isfinite(q.v1) & isfinite(q.v2) & isfinite(q.v3) Base.zero(::Type{Quaternion{T}}) where T = Quaternion{T}(zero(T), zero(T), zero(T), zero(T)) @@ -33,7 +34,9 @@ Base.:(*)(q::Quaternion, w::Quaternion) = Quaternion(q.s*w.s - q.v1*w.v1 - q.v2* q.s*w.v2 - q.v1*w.v3 + q.v2*w.s + q.v3*w.v1, q.s*w.v3 + q.v1*w.v2 - q.v2*w.v1 + q.v3*w.s) Base.:(*)(q::Quaternion, r::Real) = Quaternion(q.s*r, q.v1*r, q.v2*r, q.v3*r) -Base.:(*)(q::Quaternion, b::Bool) = b * q # remove method ambiguity +Base.:(*)(q::Quaternion, r::Bool) = Quaternion(q.s*r, q.v1*r, q.v2*r, q.v3*r) # remove method ambiguity +Base.:(*)(r::Real, q::Quaternion) = q * r +Base.:(*)(r::Bool, q::Quaternion) = q * r # remove method ambiguity Base.:(/)(q::Quaternion, w::Quaternion) = q * conj(w) * (1.0 / abs2(w)) Base.:(\)(q::Quaternion, w::Quaternion) = conj(q) * w * (1.0 / abs2(q)) diff --git a/test/testhelpers/SizedArrays.jl b/test/testhelpers/SizedArrays.jl index dfcc5b79f1387..2d37cead61a08 100644 --- a/test/testhelpers/SizedArrays.jl +++ b/test/testhelpers/SizedArrays.jl @@ -9,8 +9,20 @@ module SizedArrays import Base: +, *, == +using LinearAlgebra +import LinearAlgebra: mul! + export SizedArray +struct SOneTo{N} <: AbstractUnitRange{Int} end +SOneTo(N) = SOneTo{N}() +Base.length(::SOneTo{N}) where {N} = N +Base.size(r::SOneTo) = (length(r),) +Base.axes(r::SOneTo) = (r,) +Base.first(::SOneTo) = 1 +Base.last(r::SOneTo) = length(r) +Base.show(io::IO, r::SOneTo) = print(io, "SOneTo(", length(r), ")") + struct SizedArray{SZ,T,N,A<:AbstractArray} <: AbstractArray{T,N} data::A function SizedArray{SZ}(data::AbstractArray{T,N}) where {SZ,T,N} @@ -22,19 +34,56 @@ struct SizedArray{SZ,T,N,A<:AbstractArray} <: AbstractArray{T,N} new{SZ,T,N,A}(A(data)) end end +SizedMatrix{SZ,T,A<:AbstractArray} = SizedArray{SZ,T,2,A} +SizedVector{SZ,T,A<:AbstractArray} = SizedArray{SZ,T,1,A} Base.convert(::Type{SizedArray{SZ,T,N,A}}, data::AbstractArray) where {SZ,T,N,A} = SizedArray{SZ,T,N,A}(data) # Minimal AbstractArray interface Base.size(a::SizedArray) = size(typeof(a)) Base.size(::Type{<:SizedArray{SZ}}) where {SZ} = SZ +Base.axes(a::SizedArray) = map(SOneTo, size(a)) Base.getindex(A::SizedArray, i...) = getindex(A.data, i...) +Base.setindex!(A::SizedArray, v, i...) = setindex!(A.data, v, i...) Base.zero(::Type{T}) where T <: SizedArray = SizedArray{size(T)}(zeros(eltype(T), size(T))) +Base.parent(S::SizedArray) = S.data +(S1::SizedArray{SZ}, S2::SizedArray{SZ}) where {SZ} = SizedArray{SZ}(S1.data + S2.data) ==(S1::SizedArray{SZ}, S2::SizedArray{SZ}) where {SZ} = S1.data == S2.data -function *(S1::SizedArray, S2::SizedArray) + +homogenize_shape(t::Tuple) = (_homogenize_shape(first(t)), homogenize_shape(Base.tail(t))...) +homogenize_shape(::Tuple{}) = () +_homogenize_shape(x::Integer) = x +_homogenize_shape(x::AbstractUnitRange) = length(x) +const Dims = Union{Integer, Base.OneTo, SOneTo} +function Base.similar(::Type{A}, shape::Tuple{Dims, Vararg{Dims}}) where {A<:AbstractArray} + similar(A, homogenize_shape(shape)) +end +function Base.similar(::Type{A}, shape::Tuple{SOneTo, Vararg{SOneTo}}) where {A<:AbstractArray} + R = similar(A, length.(shape)) + SizedArray{length.(shape)}(R) +end + +const SizedMatrixLike = Union{SizedMatrix, Transpose{<:Any, <:SizedMatrix}, Adjoint{<:Any, <:SizedMatrix}} + +_data(S::SizedArray) = S.data +_data(T::Transpose{<:Any, <:SizedArray}) = transpose(_data(parent(T))) +_data(T::Adjoint{<:Any, <:SizedArray}) = adjoint(_data(parent(T))) + +function *(S1::SizedMatrixLike, S2::SizedMatrixLike) 0 < ndims(S1) < 3 && 0 < ndims(S2) < 3 && size(S1, 2) == size(S2, 1) || throw(ArgumentError("size mismatch!")) - data = S1.data * S2.data + data = _data(S1) * _data(S2) SZ = ndims(data) == 1 ? (size(S1, 1), ) : (size(S1, 1), size(S2, 2)) SizedArray{SZ}(data) end + +# deliberately wide method definitions to test for method ambiguties in LinearAlgebra +*(S1::SizedMatrixLike, M::AbstractMatrix) = _data(S1) * M +mul!(dest::AbstractMatrix, S1::SizedMatrix, M::AbstractMatrix, α::Number, β::Number) = + mul!(dest, _data(S1), M, α, β) +mul!(dest::AbstractMatrix, M::AbstractMatrix, S2::SizedMatrix, α::Number, β::Number) = + mul!(dest, M, _data(S2), α, β) +mul!(dest::AbstractMatrix, S1::SizedMatrix, S2::SizedMatrix, α::Number, β::Number) = + mul!(dest, _data(S1), _data(S2), α, β) +mul!(dest::AbstractVector, M::AbstractMatrix, v::SizedVector, α::Number, β::Number) = + mul!(dest, M, _data(v), α, β) + end diff --git a/test/testhelpers/StructArrays.jl b/test/testhelpers/StructArrays.jl new file mode 100644 index 0000000000000..f03b07f4e60ad --- /dev/null +++ b/test/testhelpers/StructArrays.jl @@ -0,0 +1,39 @@ +module StructArrays + +struct StructArray{T,N,C <: Tuple{Vararg{AbstractArray{<:Any,N}}}} <: AbstractArray{T,N} + components :: C + + function StructArray{T,N,C}(components::C) where {T,N,C} + fieldcount(T) == length(components) || throw(ArgumentError("number of components incompatible with eltype")) + allequal(axes.(components)) || throw(ArgumentError("component arrays must have the same axes")) + new{T,N,C}(components) + end +end + +function StructArray{T}(components::Tuple{Vararg{AbstractArray{<:Any,N}}}) where {T,N} + StructArray{T,N,typeof(components)}(components) +end + +Base.size(S::StructArray) = size(S.components[1]) +Base.axes(S::StructArray) = axes(S.components[1]) +function Base.getindex(S::StructArray{T,N}, inds::Vararg{Int,N}) where {T,N} + vals = map(x -> x[inds...], S.components) + T(vals...) +end +function Base.setindex!(S::StructArray{T,N}, val, inds::Vararg{Int,N}) where {T,N} + vals = getfield.(Ref(convert(T, val)), fieldnames(T)) + for (A,v) in zip(S.components, vals) + A[inds...] = v + end + S +end + +isnonemptystructtype(::Type{T}) where {T} = isstructtype(T) && fieldcount(T) != 0 + +function Base.similar(S::StructArray, ::Type{T}, dims::Tuple{Int, Vararg{Int}}) where {T} + isnonemptystructtype(T) || return similar(S.components[1], T, dims) + arrs = similar.(S.components, fieldtypes(T), Ref(dims)) + StructArray{T}(arrs) +end + +end diff --git a/test/testhelpers/arrayindexingtypes.jl b/test/testhelpers/arrayindexingtypes.jl index 0e956b5216c94..95c1f18e00903 100644 --- a/test/testhelpers/arrayindexingtypes.jl +++ b/test/testhelpers/arrayindexingtypes.jl @@ -66,3 +66,6 @@ Base.axes(A::WrapperArray) = axes(A.parent) Base.getindex(A::WrapperArray, i::Int...) = A.parent[i...] Base.setindex!(A::WrapperArray, v, i::Int...) = A.parent[i...] = v Base.similar(A::WrapperArray, ::Type{T}, dims::Dims) where T = similar(A.parent, T, dims) +Base.cconvert(::Type{Ptr{T}}, A::WrapperArray{T}) where {T} = Base.cconvert(Ptr{T}, A.parent) +Base.strides(A::WrapperArray) = strides(A.parent) +Base.elsize(::Type{WrapperArray{T,N,A}}) where {T,N,A<:AbstractArray{T,N}} = Base.elsize(A) diff --git a/test/testhelpers/coverage_file.info b/test/testhelpers/coverage_file.info index c83e75dee8060..b03b0e07e6977 100644 --- a/test/testhelpers/coverage_file.info +++ b/test/testhelpers/coverage_file.info @@ -10,9 +10,10 @@ DA:11,1 DA:12,1 DA:14,0 DA:17,1 -DA:19,2 +DA:18,1 +DA:19,1 DA:20,1 DA:22,1 -LH:12 -LF:14 +LH:13 +LF:15 end_of_record diff --git a/test/testhelpers/coverage_file.info.bad b/test/testhelpers/coverage_file.info.bad deleted file mode 100644 index 311f6379381ee..0000000000000 --- a/test/testhelpers/coverage_file.info.bad +++ /dev/null @@ -1,20 +0,0 @@ -SF: -DA:3,1 -DA:4,1 -DA:5,0 -DA:7,1 -DA:8,1 -DA:9,3 -DA:10,5 -DA:11,1 -DA:12,1 -DA:14,0 -DA:17,1 -DA:18,0 -DA:19,2 -DA:20,1 -DA:22,1 -DA:1234,0 -LH:12 -LF:16 -end_of_record diff --git a/test/testhelpers/coverage_file.info.bad2 b/test/testhelpers/coverage_file.info.bad2 deleted file mode 100644 index a766597be4c17..0000000000000 --- a/test/testhelpers/coverage_file.info.bad2 +++ /dev/null @@ -1,20 +0,0 @@ -SF: -DA:3,1 -DA:4,1 -DA:5,0 -DA:7,1 -DA:8,1 -DA:9,3 -DA:10,5 -DA:11,0 -DA:12,1 -DA:14,0 -DA:17,1 -DA:18,0 -DA:19,0 -DA:20,0 -DA:22,1 -DA:1234,0 -LH:9 -LF:16 -end_of_record diff --git a/test/testhelpers/coverage_file.jl b/test/testhelpers/coverage_file.jl index e8e0355952d80..577cc6bb5d2ca 100644 --- a/test/testhelpers/coverage_file.jl +++ b/test/testhelpers/coverage_file.jl @@ -24,6 +24,6 @@ end success = code_coverage_test() == [1, 2, 3] && short_form_func_coverage_test(2) == 4 -exit(success ? 0 : 1) +exit(success ? 0 : 1) # end of file diff --git a/test/threads.jl b/test/threads.jl index ad09304bbd80d..172385d1c130e 100644 --- a/test/threads.jl +++ b/test/threads.jl @@ -337,3 +337,20 @@ end # There must be at least two: one for the root test task and one for the async task: @test fetch(@async(@ccall(jl_get_num_stack_mappings()::Cint))) >= 2 end + +@testset "Base.Threads docstrings" begin + @test isempty(Docs.undocumented_names(Threads)) +end + +@testset "wait failed task" begin + @testset "wait without throw keyword" begin + t = Threads.@spawn error("Error") + @test_throws TaskFailedException wait(t) + end + + @testset "wait with throw=false" begin + t = Threads.@spawn error("Error") + wait(t; throw=false) + @test istaskfailed(t) + end +end diff --git a/test/threads_exec.jl b/test/threads_exec.jl index 7953468f9c6f3..7f4775cae0055 100644 --- a/test/threads_exec.jl +++ b/test/threads_exec.jl @@ -803,6 +803,84 @@ function _atthreads_dynamic_with_error(a) end @test_throws "user error in the loop body" _atthreads_dynamic_with_error(zeros(threadpoolsize())) +#### +# :greedy +### + +function _atthreads_greedy_schedule(n) + inc = Threads.Atomic{Int}(0) + flags = zeros(Int, n) + Threads.@threads :greedy for i = 1:n + Threads.atomic_add!(inc, 1) + flags[i] = 1 + end + return inc[], flags +end +@test _atthreads_greedy_schedule(threadpoolsize()) == (threadpoolsize(), ones(threadpoolsize())) +@test _atthreads_greedy_schedule(1) == (1, ones(1)) +@test _atthreads_greedy_schedule(10) == (10, ones(10)) +@test _atthreads_greedy_schedule(threadpoolsize() * 2) == (threadpoolsize() * 2, ones(threadpoolsize() * 2)) + +# nested greedy schedule +function _atthreads_greedy_greedy_schedule() + inc = Threads.Atomic{Int}(0) + Threads.@threads :greedy for _ = 1:threadpoolsize() + Threads.@threads :greedy for _ = 1:threadpoolsize() + Threads.atomic_add!(inc, 1) + end + end + return inc[] +end +@test _atthreads_greedy_greedy_schedule() == threadpoolsize() * threadpoolsize() + +function _atthreads_greedy_dynamic_schedule() + inc = Threads.Atomic{Int}(0) + Threads.@threads :greedy for _ = 1:threadpoolsize() + Threads.@threads :dynamic for _ = 1:threadpoolsize() + Threads.atomic_add!(inc, 1) + end + end + return inc[] +end +@test _atthreads_greedy_dynamic_schedule() == threadpoolsize() * threadpoolsize() + +function _atthreads_dymamic_greedy_schedule() + inc = Threads.Atomic{Int}(0) + Threads.@threads :dynamic for _ = 1:threadpoolsize() + Threads.@threads :greedy for _ = 1:threadpoolsize() + Threads.atomic_add!(inc, 1) + end + end + return inc[] +end +@test _atthreads_dymamic_greedy_schedule() == threadpoolsize() * threadpoolsize() + +function _atthreads_static_greedy_schedule() + ids = zeros(Int, threadpoolsize()) + inc = Threads.Atomic{Int}(0) + Threads.@threads :static for i = 1:threadpoolsize() + ids[i] = Threads.threadid() + Threads.@threads :greedy for _ = 1:threadpoolsize() + Threads.atomic_add!(inc, 1) + end + end + return ids, inc[] +end +@test _atthreads_static_greedy_schedule() == (1:threadpoolsize(), threadpoolsize() * threadpoolsize()) + +# errors inside @threads :greedy +function _atthreads_greedy_with_error(a) + Threads.@threads :greedy for i in eachindex(a) + error("user error in the loop body") + end + a +end +@test_throws "user error in the loop body" _atthreads_greedy_with_error(zeros(threadpoolsize())) + +#### +# multi-argument loop +#### + try @macroexpand @threads(for i = 1:10, j = 1:10; end) catch ex @@ -1108,4 +1186,127 @@ end @testset "threadcall + threads" begin threadcall_threads() #Shouldn't crash! end + +@testset "Wait multiple tasks" begin + convert_tasks(t, x) = x + convert_tasks(::Set{Task}, x::Vector{Task}) = Set{Task}(x) + convert_tasks(::Tuple{Task}, x::Vector{Task}) = tuple(x...) + + function create_tasks() + tasks = Task[] + event = Threads.Event() + push!(tasks, + Threads.@spawn begin + sleep(0.01) + end) + push!(tasks, + Threads.@spawn begin + sleep(0.02) + end) + push!(tasks, + Threads.@spawn begin + wait(event) + end) + return tasks, event + end + + function teardown(tasks, event) + notify(event) + waitall(resize!(tasks, 3), throw=true) + end + + for tasks_type in (Vector{Task}, Set{Task}, Tuple{Task}) + @testset "waitany" begin + @testset "throw=false" begin + tasks, event = create_tasks() + wait(tasks[1]) + wait(tasks[2]) + done, pending = waitany(convert_tasks(tasks_type, tasks); throw=false) + @test length(done) == 2 + @test tasks[1] ∈ done + @test tasks[2] ∈ done + @test length(pending) == 1 + @test tasks[3] ∈ pending + teardown(tasks, event) + end + + @testset "throw=true" begin + tasks, event = create_tasks() + push!(tasks, Threads.@spawn error("Error")) + + @test_throws CompositeException begin + waitany(convert_tasks(tasks_type, tasks); throw=true) + end + + teardown(tasks, event) + end + end + + @testset "waitall" begin + @testset "All tasks succeed" begin + tasks, event = create_tasks() + + wait(tasks[1]) + wait(tasks[2]) + waiter = Threads.@spawn waitall(convert_tasks(tasks_type, tasks)) + @test !istaskdone(waiter) + + notify(event) + done, pending = fetch(waiter) + @test length(done) == 3 + @test tasks[1] ∈ done + @test tasks[2] ∈ done + @test tasks[3] ∈ done + @test length(pending) == 0 + end + + @testset "failfast=true, throw=false" begin + tasks, event = create_tasks() + push!(tasks, Threads.@spawn error("Error")) + + wait(tasks[1]) + wait(tasks[2]) + waiter = Threads.@spawn waitall(convert_tasks(tasks_type, tasks); failfast=true, throw=false) + + done, pending = fetch(waiter) + @test length(done) == 3 + @test tasks[1] ∈ done + @test tasks[2] ∈ done + @test tasks[4] ∈ done + @test length(pending) == 1 + @test tasks[3] ∈ pending + + teardown(tasks, event) + end + + @testset "failfast=false, throw=true" begin + tasks, event = create_tasks() + push!(tasks, Threads.@spawn error("Error")) + + notify(event) + + @test_throws CompositeException begin + waitall(convert_tasks(tasks_type, tasks); failfast=false, throw=true) + end + + @test all(istaskdone.(tasks)) + + teardown(tasks, event) + end + + @testset "failfast=true, throw=true" begin + tasks, event = create_tasks() + push!(tasks, Threads.@spawn error("Error")) + + @test_throws CompositeException begin + waitall(convert_tasks(tasks_type, tasks); failfast=true, throw=true) + end + + @test !istaskdone(tasks[3]) + + teardown(tasks, event) + end + end + end +end end # main testset diff --git a/test/tuple.jl b/test/tuple.jl index b806667fd9d0a..4b0bfbbb3b055 100644 --- a/test/tuple.jl +++ b/test/tuple.jl @@ -212,7 +212,7 @@ end end -@testset "element type" begin +@testset "elelement/value/key types" begin @test eltype((1,2,3)) === Int @test eltype((1.0,2.0,3.0)) <: AbstractFloat @test eltype((true, false)) === Bool @@ -227,6 +227,11 @@ end typejoin(Int, Float64, Bool) @test eltype(Tuple{Int, Missing}) === Union{Missing, Int} @test eltype(Tuple{Int, Nothing}) === Union{Nothing, Int} + + @test valtype((1,2,3)) === eltype((1,2,3)) + @test valtype(Tuple{Int, Missing}) === eltype(Tuple{Int, Missing}) + @test keytype((1,2,3)) === Int + @test keytype(Tuple{Int, Missing}) === Int end @testset "map with Nothing and Missing" begin @@ -807,3 +812,21 @@ namedtup = (;a=1, b=2, c=3) @test Val{Tuple{Vararg{T,4}} where T} === Val{Tuple{T,T,T,T} where T} @test Val{Tuple{Int64, Vararg{Int32,N}} where N} === Val{Tuple{Int64, Vararg{Int32}}} @test Val{Tuple{Int32, Vararg{Int64}}} === Val{Tuple{Int32, Vararg{Int64,N}} where N} + +@testset "from Pair, issue #52636" begin + pair = (1 => "2") + @test (1, "2") == @inferred Tuple(pair) + @test (1, "2") == @inferred Tuple{Int,String}(pair) +end + +@testset "circshift" begin + t1 = (1, 2, 3, 4, 5) + t2 = (1, 'a', -7.0, 3) + t3 = ('a', 'b', 'c', 'd') + @test @inferred(Base.circshift(t1, 2)) == (4, 5, 1, 2, 3) + # The return type of mixed tuples with runtime shift cannot be inferred. + @test Base.circshift(t2, 3) == ('a', -7.0, 3, 1) + @test @inferred(Base.circshift(t3, 7)) == ('b', 'c', 'd', 'a') + @test @inferred(Base.circshift(t3, -1)) == ('b', 'c', 'd', 'a') + @test_throws MethodError circshift(t1, 'a') +end diff --git a/test/version.jl b/test/version.jl index 6986e97e1be91..242b32c47cbdc 100644 --- a/test/version.jl +++ b/test/version.jl @@ -219,16 +219,14 @@ for major=0:3, minor=0:3, patch=0:3 end end -# banner -import Base.banner -io = IOBuffer() -@test banner(io) === nothing -seek(io, 0) -@test countlines(io) == 9 -take!(io) -@test banner(io; short=true) === nothing -seek(io, 0) -@test countlines(io) == 2 +# VersionNumber has the promised fields +let v = v"4.2.1-1.x+a.9" + @test v.major isa Integer + @test v.minor isa Integer + @test v.patch isa Integer + @test v.prerelease isa Tuple{Vararg{Union{Integer, AbstractString}}} + @test v.build isa Tuple{Vararg{Union{Integer, AbstractString}}} +end # julia_version.h version test @test VERSION.major == ccall(:jl_ver_major, Cint, ()) diff --git a/test/worlds.jl b/test/worlds.jl index 8e820bdab88df..945405cee26a4 100644 --- a/test/worlds.jl +++ b/test/worlds.jl @@ -2,8 +2,7 @@ # tests for accurate updating of method tables -using Base: get_world_counter -tls_world_age() = ccall(:jl_get_tls_world_age, UInt, ()) +using Base: get_world_counter, tls_world_age @test typemax(UInt) > get_world_counter() == tls_world_age() > 0 # test simple method replacement @@ -258,15 +257,13 @@ end # avoid adding this to Base function equal(ci1::Core.CodeInfo, ci2::Core.CodeInfo) return ci1.code == ci2.code && - ci1.codelocs == ci2.codelocs && + ci1.debuginfo == ci2.debuginfo && ci1.ssavaluetypes == ci2.ssavaluetypes && ci1.ssaflags == ci2.ssaflags && ci1.method_for_inference_limit_heuristics == ci2.method_for_inference_limit_heuristics && - ci1.linetable == ci2.linetable && ci1.slotnames == ci2.slotnames && ci1.slotflags == ci2.slotflags && - ci1.slottypes == ci2.slottypes && - ci1.rettype == ci2.rettype + ci1.slottypes == ci2.slottypes end equal(p1::Pair, p2::Pair) = p1.second == p2.second && equal(p1.first, p2.first) @@ -456,3 +453,27 @@ let e = ExceptionUnwrapping.X(nothing) @test ExceptionUnwrapping.result == [false, true] empty!(ExceptionUnwrapping.result) end + +fshadow() = 1 +gshadow() = fshadow() +@test fshadow() === 1 +@test gshadow() === 1 +fshadow_m1 = which(fshadow, ()) +fshadow() = 2 +fshadow() = 3 +@test fshadow() === 3 +@test gshadow() === 3 +fshadow_m3 = which(fshadow, ()) +Base.delete_method(fshadow_m1) +@test fshadow() === 3 +@test gshadow() === 3 +Base.delete_method(fshadow_m3) +fshadow_m2 = which(fshadow, ()) +@test fshadow() === 2 +@test gshadow() === 2 +Base.delete_method(fshadow_m2) +@test_throws MethodError(fshadow, (), Base.tls_world_age()) gshadow() +@test Base.morespecific(fshadow_m3, fshadow_m2) +@test Base.morespecific(fshadow_m2, fshadow_m1) +@test Base.morespecific(fshadow_m3, fshadow_m1) +@test !Base.morespecific(fshadow_m2, fshadow_m3) diff --git a/typos.toml b/typos.toml new file mode 100644 index 0000000000000..b9a9311946bc4 --- /dev/null +++ b/typos.toml @@ -0,0 +1,2 @@ +[default] +extend-ignore-words-re = ["^[a-zA-Z]?[a-zA-Z]?[a-zA-Z]?[a-zA-Z]?$"]